stranger6667/css-inline

A crate for inlining CSS into HTML documents

It is built with Mozilla's Servo project components

css-inline

. .

When you send HTML emails, you need to use "style" attributes instead of "style" tags. For example, this HTML:

<html>
    <head>
        <title>Test</title>
        <style>h1 { color:blue; }</style>
    </head>
    <body>
        <h1>Big Text</h1>
    </body>
</html>

Will be turned into this:

<html>
    <head><title>Test</title></head>
    <body>
        <h1 style="color:blue;">Big Text</h1>
    </body>
</html>

To use it in your project add the following line to your dependencies section in the project's Cargo.toml file:

css-inline = "0.8"

Minimum Supported Rust Version is 1.54.

Usage

const HTML: &str = r#"<html>
<head>
    <title>Test</title>
    <style>h1 { color:blue; }</style>
</head>
<body>
    <h1>Big Text</h1>
</body>
</html>"#;

fn main() -> Result<(), css_inline::InlineError> {
    let inlined = css_inline::inline(HTML)?;
    // Do something with inlined HTML, e.g. send an email
    Ok(())
}

Features & Configuration

css-inline can be configured by using CSSInliner::options() that implements the Builder pattern:

const HTML: &str = "...";

fn main() -> Result<(), css_inline::InlineError> {
    let inliner = css_inline::CSSInliner::options()
        .load_remote_stylesheets(false)
        .build();
    let inlined = inliner.inline(HTML);
    // Do something with inlined HTML, e.g. send an email
    Ok(())
}
  • inline_style_tags. Whether to inline CSS from "style" tags. Default: true
  • remove_style_tags. Remove "style" tags after inlining. Default: false
  • base_url. Base URL to resolve relative URLs. Default: None
  • load_remote_stylesheets. Whether remote stylesheets should be loaded or not. Default: true
  • extra_css. Additional CSS to inline. Default: None

Bindings

There are bindings for Python and WebAssembly in the bindings directory.

Command Line Interface

css-inline provides a command-line interface:

css-inline --help

css-inline inlines CSS into HTML documents.

USAGE:
   css-inline [OPTIONS] [PATH ...]
   command | css-inline [OPTIONS]

ARGS:
    <PATH>...
        An HTML document to process. In each specified document
        "css-inline" will look for all relevant "style" and "link"
        tags, will load CSS from them and then inline it to the
        HTML tags, according to the corresponding CSS selectors.
        When multiple documents are specified, they will be
        processed in parallel, and each inlined file will be saved
        with "inlined." prefix. E.g., for "example.html", there
        will be "inlined.example.html".

OPTIONS:
    --inline-style-tags
        Whether to inline CSS from "style" tags. The default
        value is `true`. To disable inlining from "style" tags
        use `--inline-style-tags=false`.

    --remove-style-tags
        Remove "style" tags after inlining.

    --base-url
        Used for loading external stylesheets via relative URLs.

    --load-remote-stylesheets
        Whether remote stylesheets should be loaded or not.

    --extra-css
        Additional CSS to inline.

Extra materials

If you want to know how this library was created & how it works internally, you could take a look at these articles:

  • Rust crate
  • Python bindings

Support

If you have anything to discuss regarding this library, please, join our gitter!

Issues

Collection of the latest Issues

earshinov

earshinov

Comment Icon1

An example:

As specificity rules state, the styles bound to .test selector should have priority over those bound to h1. Generally css-inline handles specificity well, but in the example given above, styles inlining should result in padding: 0;padding-left: 16px; (equivalent to padding: 0 0 0 16px;) instead of padding-left: 16px;padding:0; (equivalent to padding: 0;).

Expected result:

❌ Actual result:

Version information:

dkechag

dkechag

Comment Icon0

I've been using an old Perl css-inliner, which is quite good, but very, very slow (yes, I mean very slow even for pure Perl). I was happy to see this Rust-powered solution, and it is indeed over 100x faster, but has a "feature" that sort of breaks it for me and I don't get the reason. So, given input:

I get:

Which comes up as Here’s Johnny on the browser. Now, one could say why use the right single quote (although it is very common for typography reasons in place of apostrophe), but that was just an example, even things like &pound; get translated to 0xC2 0xA3, which looks quite bad unless your charset is UTF-8. Which is not great if you are trying to inline various things you did not create with that limitation in mind. I looked in the python wrapper code as I went through that, and I see it is not doing anything special apart from calling the Rust package, and looking into the Rust doc I don't see any control (or mention) of this behaviour, so the issue might be with the Servo components and not with css-inline technically, but I thought I'd ask in case I might be missing something.

xorduna

xorduna

Comment Icon0

Hi! I just compiled css-inline python binding for mac osx arm64 without any problem and it works perfetly. Is there any way I cap help uploading this build as a wheel for other users?

I tried to execute python3 -m build but I got this error

error: failed to get css-inlineas a dependency of packagecss-inline-python v0.8.1 (/private/var/folders/cn/qmc8lb9j5ddclqxj073wdky40000gn/T/build-via-sdist-gffl6klk/css_inline-0.8.1)`

Thanks!

matthiaskoenig

matthiaskoenig

Comment Icon2

The CSS inlining is removing the closing characters of single tags. E.g.

  • <hr /> is replaced with <hr>
  • <img ... /> is replaced with <img ...>

This makes the library unusable in contexts were valid XML is required. There is no need to remove the closing / and is most likely a bug in the transformation.

Thanks for the great library. This would solve my use cases if it would keep the HTML valid XML.

Stranger6667

Stranger6667

Area: Performance
Comment Icon0

Inlining itself could be also optimized if we could just join all loaded CSS into a single string. Worth researching, however, I am not sure how common it is to have emails with multiple style / link tags

Stranger6667

Stranger6667

Area: Performance
Comment Icon0

Now we have some extra spaces that are not needed. ; in the end, is also optional

Versions

Find the latest versions by id

rust-v0.7.0 - Jun 09, 2021

Fixed

  • Ignored selectors specificity. #108

rust-v0.6.1 - Dec 07, 2020

Fixed

  • Compatibility with the new cssparser crate version.

Performance

  • Avoid string allocations during converting ParseError to InlineError.

rust-v0.6.0 - Nov 01, 2020

Changed

  • Links to remote stylesheets are deduplicated now.

Fixed

  • Wrong inlined file prefixes handling in CLI. #89

Performance

  • Use Formatter.write_str instead of write! macro in the Display trait implementation for InlineError. #85
  • Use Cow for error messages. #87

rust-v0.5.0 - Aug 09, 2020

Added

  • CSSInliner::options() that implements the Builder pattern. #71

Changed

  • Restrict visibility of items in parser.rs

Performance

  • Avoid string allocation in get_full_url

python-v0.5.0 - Aug 09, 2020

Performance

  • Avoid string allocation in get_full_url

rust-v0.4.0 - Jul 17, 2020

0.3.2 - Jun 27, 2020

0.3.1 - Jun 25, 2020

0.3.0 - Jun 25, 2020

0.2.0 - Jun 25, 2020

0.1.0 - Jun 22, 2020

Information - Updated Jul 24, 2022

Stars: 98
Forks: 12
Issues: 15

This is an experimental serializer for Rust's serde ecosystem, which can convert Rust objects to...

This is an experimental serializer for Rust's serde ecosystem, which can convert Rust objects to Python values and back

This is an experimental serializer for Rust's serde ecosystem, which can convert Rust objects to...

Robust and Fast tokenizations alignment library for Rust and Python

Blog post: maturin to build the wheel

Robust and Fast tokenizations alignment library for Rust and Python

Rust Python Integration Made Easy

PIME is a Rust crate, which allows PyO3, neotasker Python module

Rust Python Integration Made Easy

A Rust clone of Python's click, built on the structopt crate

A Rust clone of Python's structopt crate

A Rust clone of Python's click, built on the structopt crate

A simple safe Rust library to cache functions (or sections of code) in memory, mainly...

A simple safe Rust library to cache functions (or sections of code) in memory, mainly ported from my Python module Rust

A simple safe Rust library to cache functions (or sections of code) in memory, mainly...

Orbit System is a Command &amp; Control system for Windows written both in the Rust...

Orbit System is a Command &amp; Control system for Windows written both in the Rust and Python programming languages

Orbit System is a Command &amp; Control system for Windows written both in the Rust...

Quick demo of getting python and rust to communicate with graphql

The main point of this is to show how we can validate a graphql query

Quick demo of getting python and rust to communicate with graphql

A simple programming language inspired by Rust and Python

The most important type in Kuru is a Set

A simple programming language inspired by Rust and Python

Python/Rust module for mol2 format (de)serialization

Default level of compression here is 3

Python/Rust module for mol2 format (de)serialization

PYO3 Python Classes in Rust

Calling Rust from Python using PyO3

PYO3 Python Classes in Rust
Facebook Instagram Twitter GitHub Dribbble
Privacy