Inline CSS into style attributes

Overview

css-inline

ci Crates.io docs.rs gitter

A crate for inlining CSS into HTML documents. It is built with Mozilla's Servo project components.

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.7"

Usage

use css_inline;

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:

use css_inline;

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:

Support

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

Comments
  • Build Python wheels for Apple Silicon M1

    Build Python wheels for Apple Silicon M1

    Links:

    • How it is done in Rust-analyzer - https://github.com/lnicola/rust-analyzer/blob/master/.github/workflows/release.yaml#L185
    • more notes - https://github.com/shepmaster/rust/blob/silicon/silicon/README.md
        - name: Install Rust toolchain
          uses: actions-rs/toolchain@v1
          with:
            toolchain: stable
            target: aarch64-apple-darwin
            profile: minimal
            override: true
    
    Type: Enhancement Area: CI 
    opened by Stranger6667 8
  • Add support for local base url

    Add support for local base url

    Currently, when you supply a local directory for base_url it gives the following error:

    Traceback (most recent call last):
      File "/redacted.py", line 181, in build_html_templates
        inlined = css_inline.inline(f.read(), base_url="/my/css/folder/")
    ValueError: relative URL without a base
    

    With base_url file://my/css/folder/ it doesn't throw an error, but doesn't do any inlining

    Am I doing something wrong or is this not supported at the moment?

    opened by lesleyxyz 7
  • feat: Make loading from an external URL be gated behind a feature.

    feat: Make loading from an external URL be gated behind a feature.

    attohttpc and its transitive dependencies add a large amount of size to the crate (on the order of about ~400 kB, or about 70 kB when compressed). For applications which don't need external URL loading and are concerned with code size (for example, a WASM binary), this is wasted space. Placing this stuff behind a feature gate allows people to opt-out and reduce code size if they wish.

    Note that this doesn't disable loading stylesheets from a local file. It might be worth it to add a feature (something like fs) that also gates that functionality (as this also adds a decent amount of size--about 100 kB compressed and around 500 kB uncompressed). I could pretty easily add this functionality, if desired (I have this functionality on a2aaron:minimal right now).

    opened by a2aaron 6
  • bug: Conditional html comments shouldn't be removed

    bug: Conditional html comments shouldn't be removed

    Conditional comments like <!--[if mso]>Hello outlook<![endif]--> and <!--[if !mso]><!--><h1>Hello not outlook</h1><!--<![endif]--> are used to either hide things from outlook or only show things to outlook, it looks like those are currently being stripped out

    I have a failing test written on this branch)

    Not sure where the problem is; I see code in kuchiki that references comment nodes; this repo seems to be using the newest version, is it possibly a bug on kuchiki?

    opened by cheapsteak 5
  • initial attempt poc at wasm

    initial attempt poc at wasm

    apologies, first time reading/writing rust! XD I referenced mdn's article to get this sort of working

    probably wouldn't want to merge this though since I ripped out the feature to load external stylesheets

    wasm-pack build would fail when trying to build openssl-sys that attohttpc brings in

    Is there a way in rust to conditionally exclude a dependency from the main package?

    opened by cheapsteak 5
  • css-inline does not follow cascade/specificity rules

    css-inline does not follow cascade/specificity rules

    I noticed that for complex html, the css-inliner output often does not look the same as the input on a browser. Digging into it I can see the issue, so here is a simple test case: Given this test.html:

    <html>
      <head>
        <style type='text/css'>
    table, tr {padding: 0;}
    table.content {padding-top: 24px;}
        </style>
      </head>
      <body>
          <table class="content">
            <tr><td>Content</td></tr>
        </table>
      </body>
    </html>
    

    Running css-inline test.html repeatedly will randomly give one of the two results below:

    <html><head>
        <style type="text/css">
    table, tr {padding: 0;}
    table.content {padding-top: 24px;}
        </style>
      </head>
      <body>
          <table class="content" style="padding-top: 24px;padding: 0;">
            <tbody><tr style="padding: 0;"><td>Content</td></tr>
        </tbody></table>
    
    </body></html>
    

    or

    <html><head>
        <style type="text/css">
    table, tr {padding: 0;}
    table.content {padding-top: 24px;}
        </style>
      </head>
      <body>
          <table class="content" style="padding: 0;padding-top: 24px;">
            <tbody><tr style="padding: 0;"><td>Content</td></tr>
        </tbody></table>
    
    </body></html>
    

    These are NOT equivalent, the padding-top will be 0 in the first case, 24 in the latter (which should be the correct one I believe), yet css-inliner apparently considers them the same and outputs one or the other at random.

    rustc 1.59.0, css-inline 0.8.0

    opened by dkechag 4
  • Stylesheet incorrectly overrides `style` attribute values

    Stylesheet incorrectly overrides `style` attribute values

    When an element has both a class attribute and a style attribute, styles from the stylesheet are merged into the destination style attribute. However, existing values in the element's style are overwritten by the values from the stylesheet whereas existing style attribute values should override any values in the stylesheet. This issue results in incorrect styles being applied on elements.

    Sample Jest test

    import { inline } from "css-inline";
    
    describe("css-inline", () => {
      it.only("correctly allows style tag to override stylesheet", () => {
        const document =
    `<html>
      <head><style>p.MsoNormal {margin-left:0in;font-size:14px;font-family:Arial;}</style></head>
      <body><p class=MsoNormal style='margin-left:.25in;font-family:Calibri;'>Some text goes here</p></body>
    </html>`;
    
        const expected =
    `<html><head></head>
      <body><p class="MsoNormal" style="margin-left:.25in;font-family:Calibri;font-size:14px;">Some text goes here</p></body>
    </html>`;
    
        expect(inline(document, { inline_style_tags: true, remove_style_tags: true })).toEqual(expected);
      });
    });
    

    Expected result (margin-left should stay as .25in and font-family should stay as Calibri):

    <html><head></head>
    <body><p class="MsoNormal" style="margin-left:.25in;font-family:Calibri;font-size:14px;">Some text goes here</p></body>
    </html>
    

    Actual result (stylesheet overrides all existing values in style attribute):

    <html><head></head>
    <body><p class="MsoNormal" style="margin-left:0in;font-family:Arial;font-size:14px;">Some text goes here</p>
    </body>
    </html>
    
    opened by tomi-bigpi 4
  • Dedup stylesheet href

    Dedup stylesheet href

    Dearest Maintainer,

    First thank you for your work on this project. I am comparing this to the Roadie gem for ruby. It inlines css into html emails. I have a really fun email that keeps including a script tag. The same dup script tag. I have updated your code to dedup the hrefs you are loading.

    Thanks again. Your command line tool takes 3.1 seconds where the Roadie gem takes about 2 minutes.

    Dictated but not reviewed, Becker

    opened by sbeckeriv 4
  • Optimize WASM package size

    Optimize WASM package size

    The current size: 1492887 bytes

    How much we can cut off:

    • wasm-opt = ['-Os'] - 6629 bytes
    • wasm-opt = ['-Oz'] - 6763 bytes
    • opt-level = "s" - 338471 bytes
    • opt-level = "z" - 442795 bytes

    So, the best I could get with the options above combined is a reduction by 447844 bytes in cost of some performance

    Options I didn't try on my machine yet:

    • Making HTTP loading conditional (e.g. via a feature flag + compile a separate package) should be around 640 Kb
    • Smaller allocator, ~10kb
    Area: Performance Priority: High Type: Enhancement 
    opened by Stranger6667 4
  • Optionally ignore style tags in HTML, accept additional argument for css to inline?

    Optionally ignore style tags in HTML, accept additional argument for css to inline?

    👋 Hi there :)

    Have you considered allowing css to inline be passed as an additional parameter?

    (This might be related to https://github.com/Stranger6667/css-inline/issues/10 , but not quite the same)

    The use case I'm thinking of is to possibly replace juice in mjml (I'm not affiliated, just a lib user), where css specified in <mj-style inline="inline"> is collected as a separate string that's currently passed into juice as extraCss, and inlining of other css is disabled (which makes sense because sometimes you do want to tell clients that respect style sheets to do something different from outlook)

    https://github.com/mjmlio/mjml/blob/246df840f4d0fcd812e51ca55bd6bef6592cb0e6/packages/mjml-core/src/index.js#L317-L319

    opened by cheapsteak 4
  • Bug: Specificity problem with padding / margin and

    Bug: Specificity problem with padding / margin and "constituent" styles like padding-left

    An example:

    import css_inline
    
    html = """<html>
    <head>
        <title>Test</title>
        <style>
            .test { padding-left: 16px; }
            h1 { padding: 0; }
        </style>
    </head>
    <body>
        <h1 class="test">Should have padding: 0 0 0 16px;</h1>
    </body>
    </html>"""
    
    print(css_inline.inline(html))
    
    #<html><head>
    #    <title>Test</title>
    #    <style>
    #        .test { padding-left: 16px; }
    #        h1 { padding: 0; }
    #    </style>
    #</head>
    #<body>
    #    <h1 class="test" style="padding-left: 16px;padding: 0;">Should have padding: 0 0 0 16px;</h1>
    #
    #</body></html>
    

    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:

    <h1 class="test" style="padding: 0;padding-left: 16px;">
    

    ❌ Actual result:

    <h1 class="test" style="padding-left: 16px;padding: 0;">
    

    Version information:

    $ pip show css_inline | grep Version
    Version: 0.8.2
    
    opened by earshinov 3
  • [Python] Distribute typing information

    [Python] Distribute typing information

    It would be useful if Python artifacts will have type stubs. At the moment, not all editors can utilize docstrings (i.e. some PyCharm versions use them) to get the typing info and it is definitely not enough for a pleasant developer experience.

    Type: Enhancement 
    opened by Stranger6667 0
  • Support for converting style attributes to HTML attributes

    Support for converting style attributes to HTML attributes

    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
      <path d="M2,2 v6 h6 v-6 h-6 z" style="stroke:#FF0000;stroke-width:0.5;fill:none;"/>
    </svg>
    

    To

    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
      <path d="M2,2 v6 h6 v-6 h-6 z" stroke="#FF0000" stroke-width="0.5" fill="none"/>
    </svg>
    

    This is useful for font tools, e.g. https://github.com/googlefonts/picosvg/pull/60

    opened by yisibl 1
Releases(rust-v0.8.4)
Owner
Dmitry Dygalo
Building a platform for effortless API testing
Dmitry Dygalo
Lisp-style programming language

Bobbylisp A programming language, syntax are like mal and clojure. This project follow mal guides, Planning to add some more features after finishing

azur 36 Dec 19, 2022
A Zellij plugin to fuzzy find file names and contents in style 🧐

About This Zellij plugin is a fuzzy finder for file names and their contents. It can open results in your $EDITOR (scrolled to the correct line), as f

Aram Drevekenin 11 Jun 22, 2023
A weekly dive into commonly used modules in the Rust ecosystem, with story flavor!

Rust Module of the Week A weekly dive into commonly used modules in the Rust ecosystem, with story flavor! Build status Release Draft The goal The goa

Scott Lyons 20 Aug 26, 2022
📦 Pack hundreds of Garry's Mod Lua files into just a handful

?? gluapack gluapack is a program that can pack hundreds of Garry's Mod Lua files into just a handful. Features Quick, easy and portable - perfect for

null 11 Aug 10, 2021
Rust library for build scripts to compile C/C++ code into a Rust library

A library to compile C/C++/assembly into a Rust library/application.

Alex Crichton 1.3k Dec 21, 2022
Minimal framework to inject the .NET Runtime into a process in Rust

錆の核 sabinokaku Minimal framework to inject the .NET Runtime into a process. Supports Windows and Linux. macOS support is complicated due to SIP, and w

Snowflake Emulator Frontend 8 Mar 6, 2022
Turns running Rust code into a serializable data structure.

WasmBox WasmBox turns running Rust code into a serializable data structure. It does this by compiling it to WebAssembly and running it in a sandbox. T

drifting in space 12 Dec 7, 2022
Moonshine CSS - 🥃 High-proof atomic CSS framework

Moonshine CSS - ?? High-proof atomic CSS framework

Econify 25 Nov 25, 2022
rpsc is a *nix command line tool to quickly search for file systems items matching varied criterions like permissions, extended attributes and much more.

rpsc rpsc is a *nix command line tool to quickly search for file systems items matching varied criterions like permissions, extended attributes and mu

null 3 Dec 15, 2022
A proc macro for creating compile-time checked CSS class sets, in the style of classNames

semester Semester is a declarative CSS conditional class name joiner, in the style of React's classnames. It's intended for use in web frameworks (lik

Nathan West 11 Oct 20, 2022
A small application to convert a 3D model in .obj format into HTML and CSS

obj-to-html is a small application that converts a 3D model in .obj format into HTML and CSS that will display that model in a web browser, spinning a

Andrea 7 Dec 30, 2022
Self-contained template system with Handlebars and inline shell scripts

Handlematters Self-contained template system with Handlebars and inline shell scripts Introduction Handlematters is a template system that combines Ha

Keita Urashima 3 Sep 9, 2022
Extensible inline parser engine, the backend parsing engine for Lavendeux.

Lavendeux Parser - Extensible inline parser engine lavendeux-parser is an exensible parsing engine for mathematical expressions. It supports variable

Richard Carson 10 Nov 3, 2022
LSP inline hints for Lua, intended for use with Neovim.

luahint LSP inline hints for Lua, intended for use with Neovim. Now that inline hints are working in Neovim nightly, I figured I'd attempt to build an

Will Hopkins 15 Jun 15, 2023
Write simple proc-macros inline with other source code.

script-macro An experimental way to write simple proc-macros inline with other source code. Did you ever end up getting frustrated at the boilerplate

Markus Unterwaditzer 17 Jun 10, 2023
An inline SIMD accelerated hashmap designed for small amount of data.

Small-Map An inline SIMD accelerated hashmap designed for small amount of data. Usage use small_map::SmallMap; // Don't worry about the 16 here. // Wh

ihc童鞋@提不起劲 49 Nov 14, 2023
Owned container for dynamically-sized types backed by inline memory

sized-dst This crate provides Dst, an owned container for dynamically-sized types (DSTs) that's backed by inline memory. The main use-case is owned tr

Yuhan Lin 8 Sep 6, 2024
hj is a command line tool to convert HTTP/1-style text into JSON

hj hj is a command line tool to convert HTTP/1-style text into JSON. This command is inspired by yusukebe/rj, which is a standalone HTTP client that s

FUJI Goro 10 Aug 21, 2022
A small tool to use along with i3/Sway to add CSS-powered decorations to your focused windows, for better usability.

glimmer What A tool for decorating i3 windows when they get focused, written in Rust. classic.mp4 Why When using i3-gaps I ran into the following prob

Daniel Acuña 26 Dec 17, 2022
Like jq, but for HTML. Uses CSS selectors to extract bits content from HTML files.

Like jq, but for HTML. Uses CSS selectors to extract bits content from HTML files. Mozilla's MDN has a good reference for CSS selector syntax.

Michael Maclean 6.3k Jan 3, 2023