Skip to content

Latest commit

 

History

History

README.md

css-inline

build status npm codecov.io gitter

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 library transforms HTML like this:

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

into:

<html>
  <head></head>
  <body>
    <h1 style="color:blue;">Big Text</h1>
  </body>
</html>
  • Uses reliable components from Mozilla's Servo project
  • Inlines CSS from style and link tags
  • Removes style and link tags
  • Resolves external stylesheets (including local files)
  • Optionally caches external stylesheets
  • Works on Linux, Windows, and macOS
  • Supports HTML5 & CSS3
  • Tested on Node.js 20 & 22.

Playground

If you'd like to try css-inline, you can check the WebAssembly-powered playground to see the results instantly.

Installation

Node.js

Install with npm:

npm i @css-inline/css-inline

Usage

import { inline } from "@css-inline/css-inline";

var inlined = inline(
  `
  <html>
    <head>
      <style>h1 { color:red }</style>
    </head>
    <body>
      <h1>Test</h1>
    </body>
  </html>
  `,
);
// Do something with the inlined HTML, e.g. send an email

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:

import { inlineFragment } from "@css-inline/css-inline";

var inlined = inlineFragment(
  `
  <main>
    <h1>Hello</h1>
    <section>
      <p>who am i</p>
    </section>
  </main>
  `,
  `
  p {
      color: red;
  }

  h1 {
      color: blue;
  }
  `
);
// HTML becomes this:
// <main>
// <h1 style="color: blue;">Hello</h1>
// <section>
// <p style="color: red;">who am i</p>
// </section>
// </main>

Configuration

  • inlineStyleTags. Specifies whether to inline CSS from "style" tags. Default: true
  • keepStyleTags. Specifies whether to keep "style" tags after inlining. Default: false
  • keepLinkTags. Specifies whether to keep "link" tags after inlining. Default: false
  • keepAtRules. Specifies whether to keep "at-rules" (starting with @) after inlining. Default: false
  • minifyCss. Specifies whether to remove trailing semicolons and spaces between properties and values. Default: false
  • baseUrl. The base URL used to resolve relative URLs. If you'd like to load stylesheets from your filesystem, use the file:// scheme. Default: null
  • loadRemoteStylesheets. Specifies whether remote stylesheets should be loaded. Default: true
  • cache. Specifies caching options for external stylesheets (for example, {size: 5}). Default: null
  • extraCss. Extra CSS to be inlined. Default: null
  • preallocateNodeCapacity. 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: 32
  • removeInlinedSelectors. Specifies whether to remove selectors that were successfully inlined from <style> blocks. Default: false
  • applyWidthAttributes. Specifies whether to add width HTML attributes from CSS width properties on supported elements (table, td, th, img). Default: false
  • applyHeightAttributes. Specifies whether to add height HTML attributes from CSS height properties 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 -->
    <h1 data-css-inline="ignore">Big Text</h1>
</body>
</html>

The data-css-inline="ignore" attribute also allows you to skip link and style tags:

<head>
  <!-- Styles below are ignored -->
  <style data-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 -->
  <style data-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>

You can also cache external stylesheets to avoid excessive network requests:

import { inline } from "@css-inline/css-inline";

var inlined = inline(
  `
  <html>
    <head>
      <link href="http://127.0.0.1:1234/external.css" rel="stylesheet">
      <style>h1 { color:red }</style>
    </head>
    <body>
      <h1>Test</h1>
    </body>
  </html>
  `,
  { cache: { size: 5 } },
);

Caching is disabled by default.

WebAssembly

css-inline also ships a WebAssembly module built with wasm-bindgen to run in browsers.

<script src="https://unpkg.com/@css-inline/css-inline-wasm"></script>
<script>
    // Initialize the WASM module first
    cssInline.initWasm(fetch('https://unpkg.com/@css-inline/css-inline-wasm/index_bg.wasm'));

    const inlinedHtml = cssInline.inline(`<html>
  <head>
    <style>h1 { color:blue; }</style>
  </head>
  <body>
    <h1>Big Text</h1>
  </body>
</html>`);

    document.getElementById('output').src = inlinedHtml
</script>

NOTE: WASM module currently lacks support for fetching stylesheets from network or filesystem and caching.

Performance

css-inline is powered by efficient tooling from Mozilla's Servo project and significantly outperforms other JavaScript alternatives in terms of speed. Most of the time it achieves over a 3x speed advantage compared to the next fastest alternative.

Here is the performance comparison:

Size css-inline css-inline-wasm juice inline-css
Basic 230 B 8.37 µs 15.96 µs (1.91x) 42.32 µs (5.06x) 80.85 µs (9.66x)
Realistic-1 8.58 KB 168.95 µs 344.59 µs (2.04x) 524.38 µs (3.10x) 1.15 ms (6.78x)
Realistic-2 4.3 KB 90.36 µs 180.31 µs (2.00x) 634.52 µs (7.02x) 898.47 µs (9.94x)
GitHub page 1.81 MB 32.73 ms 119.60 ms (3.65x) 1.60 s (48.90x) 326.41 ms (9.97x)

The "Basic" case was obtained from benchmarking the example from the Usage section.

The benchmarking code is available in the benches/bench.ts file. The benchmarks were conducted using the stable rustc 1.91 on Node.js v22.21.1.

License

This project is licensed under the terms of the MIT license.