css_inline is a high-performance library for inlining CSS into HTML 'style' attributes.
This library is designed for scenarios such as preparing HTML emails or embedding HTML into third-party web pages.
For instance, the crate transforms HTML like this:
<html><head><style>h1 { color:blue; }</style></head><body><h1>Big Text</h1></body></html>into:
<html><head></head><body><h1style="color:blue;">Big Text</h1></body></html>- Extremely fast with a minimal memory footprint
- Uses reliable components from Mozilla's Servo project
- Inlines CSS from
styleandlinktags - Removes
styleandlinktags - Resolves external stylesheets (including local files)
- Optionally caches external stylesheets
- Works on Linux, Windows, and macOS
- Supports HTML5 & CSS3
- Bindings for Python, Ruby, JavaScript, Java, C, PHP, and a WebAssembly module to run in browsers.
- Elixir bindings maintained by Knock
- Command Line Interface
If you'd like to try css-inline, you can check the WebAssembly-powered playground to see the results instantly.
To include it in your project, add the following line to the dependencies section in your project's Cargo.toml file:
[dependencies]
css-inline = "0.20"The Minimum Supported Rust Version is 1.85.
constHTML:&str = r#"<html><head> <style>h1 { color:blue; }</style></head><body> <h1>Big Text</h1></body></html>"#;fnmain() -> css_inline::Result<()>{let inlined = css_inline::inline(HTML)?;// Do something with inlined HTML, e.g. send an emailOk(())}Note that css-inline automatically adds missing html and body tags, so the output is a valid HTML document.
Alternatively, you can inline CSS into an HTML fragment. Structural tags (<html>, <head>, <body>) are stripped from the output; only their contents are preserved. Use inline if you need to keep the full document structure:
constFRAGMENT:&str = r#"<main><h1>Hello</h1><section><p>who am i</p></section></main>"#;constCSS:&str = r#"p { color: red;}h1 { color: blue;}"#;fnmain() -> css_inline::Result<()>{let inlined = css_inline::inline_fragment(FRAGMENT,CSS)?;Ok(())}css-inline can be configured by using CSSInliner::options() that implements the Builder pattern:
constHTML:&str = "...";fnmain() -> css_inline::Result<()>{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 emailOk(())}inline_style_tags. Specifies whether to inline CSS from "style" tags. Default:truekeep_style_tags. Specifies whether to keep "style" tags after inlining. Default:falsekeep_link_tags. Specifies whether to keep "link" tags after inlining. Default:falsekeep_at_rules. Specifies whether to keep "at-rules" (starting with@) after inlining. Default:falseminify_css. Specifies whether to remove trailing semicolons and spaces between properties and values. Default:falsebase_url. The base URL used to resolve relative URLs. If you'd like to load stylesheets from your filesystem, use thefile://scheme. Default:Noneload_remote_stylesheets. Specifies whether remote stylesheets should be loaded. Default:truecache. Specifies cache for external stylesheets. Default:Noneextra_css. Extra CSS to be inlined. Default:Nonepreallocate_node_capacity. Advanced. Preallocates capacity for HTML nodes during parsing. This can improve performance when you have an estimate of the number of nodes in your HTML document. Default:32remove_inlined_selectors. Specifies whether to remove selectors that were successfully inlined from<style>blocks. Default:falseapply_width_attributes. Specifies whether to addwidthHTML attributes from CSSwidthproperties on supported elements (table,td,th,img). Default:falseapply_height_attributes. Specifies whether to addheightHTML attributes from CSSheightproperties on supported elements (table,td,th,img). Default:false
You can also skip CSS inlining for an HTML tag by adding the data-css-inline="ignore" attribute to it:
<head><style>h1 { color:blue; }</style></head><body><!-- The tag below won't receive additional styles --><h1data-css-inline="ignore">Big Text</h1></body>The data-css-inline="ignore" attribute also allows you to skip link and style tags:
<head><!-- Styles below are ignored --><styledata-css-inline="ignore">h1 { color:blue; }</style></head><body><h1>Big Text</h1></body>Alternatively, you may keep style from being removed by using the data-css-inline="keep" attribute.
This is useful if you want to keep @media queries for responsive emails in separate style tags.
Such tags will be kept in the resulting HTML even if the keep_style_tags option is set to false.
<head><!-- Styles below are not removed --><styledata-css-inline="keep">h1 { color:blue; }</style></head><body><h1>Big Text</h1></body>Another possibility is to set keep_at_rules option to true. At-rules cannot be inlined into HTML therefore they
get removed by default. This is useful if you want to keep at-rules, e.g. @media queries for responsive emails in
separate style tags but inline any styles which can be inlined.
Such tags will be kept in the resulting HTML even if the keep_style_tags option is explicitly set to false.
<head><!-- With keep_at_rules=true "color:blue" will get inlined into <h1> but @media will be kept in <style> --><style>h1 { color: blue; } @media (max-width:600px) { h1 { font-size:18px; } }</style></head><body><h1>Big Text</h1></body>If you set the the minify_css option to true, the inlined styles will be minified by removing trailing semicolons
and spaces between properties and values.
<head><!-- With minify_css=true, the <h1> will have `style="color:blue;font-weight:bold"` --><style>h1 { color: blue; font-weight: bold; }</style></head><body><h1>Big Text</h1></body>If you'd like to load stylesheets from your filesystem, use the file:// scheme:
constHTML:&str = "...";fnmain() -> css_inline::Result<()>{let base_url = css_inline::Url::parse("file://styles/email/").expect("Invalid URL");let inliner = css_inline::CSSInliner::options().base_url(Some(base_url)).build();let inlined = inliner.inline(HTML);// Do something with inlined HTML, e.g. send an emailOk(())}For resolving remote stylesheets it is possible to implement a custom resolver:
#[derive(Debug,Default)]pubstructCustomStylesheetResolver;impl css_inline::StylesheetResolverforCustomStylesheetResolver{fnretrieve(&self,location:&str) -> css_inline::Result<String>{Err(self.unsupported("External stylesheets are not supported"))}}fnmain() -> css_inline::Result<()>{let inliner = css_inline::CSSInliner::options().resolver(std::sync::Arc::new(CustomStylesheetResolver)).build();Ok(())}You can also cache external stylesheets to avoid excessive network requests:
use std::num::NonZeroUsize;#[cfg(feature = "stylesheet-cache")]fnmain() -> css_inline::Result<()>{let inliner = css_inline::CSSInliner::options().cache(// This is an LRU cache
css_inline::StylesheetCache::new(NonZeroUsize::new(5).expect("Invalid cache size"))).build();Ok(())}// This block is here for testing purposes#[cfg(not(feature = "stylesheet-cache"))]fnmain() -> css_inline::Result<()>{Ok(())}Caching is disabled by default.
css-inline typically inlines HTML emails within hundreds of microseconds, though results may vary with input complexity.
Benchmarks for css-inline==0.20.0:
- Basic: 4.09 µs, 230 bytes
- Realistic-1: 78.94 µs, 8.58 KB
- Realistic-2: 48.56 µs, 4.3 KB
- GitHub page: 16.78 ms, 1.81 MB
These benchmarks, conducted using rustc 1.91 on Ryzen 9 9950X, can be found in css-inline/benches/inliner.rs.
Install with cargo:
cargo install css-inline
The following command inlines CSS in multiple documents in parallel. The resulting files will be saved
as inlined.email1.html and inlined.email2.html:
css-inline email1.html email2.html
For full details of the options available, you can use the --help flag:
css-inline --help
If you're interested in learning how this library was created and how it works internally, check out these articles:
If you have any questions or discussions related to this library, please join our gitter!
This project is licensed under the terms of the MIT license.