Give AlbumentationsX a star on GitHub — it powers this leaderboard

Star on GitHub

css-inline

High-performance library for inlining CSS into HTML 'style' attributes

Rank: #3265Downloads: 1,447,664 (30 days)

Description

css_inline

<img alt="build status" src="https://img.shields.io/github/actions/workflow/status/Stranger6667/css-inline/build.yml?style=flat-square&labelColor=555555&logo=github" height="20"> <img alt="pypi" src="https://img.shields.io/pypi/v/css_inline.svg?style=flat-square" height="20"> <img alt="versions" src="https://img.shields.io/pypi/pyversions/css_inline.svg?style=flat-square" height="20"> <img alt="license" src="https://img.shields.io/pypi/l/css_inline.svg?style=flat-square" height="20"> <img alt="codecov.io" src="https://img.shields.io/codecov/c/gh/Stranger6667/css-inline?logo=codecov&style=flat-square&token=tOzvV4kDY0" height="20"> <img alt="gitter" src="https://img.shields.io/gitter/room/Stranger6667/css-inline?style=flat-square" height="20">

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
  • 10-500x faster than alternatives
  • Inlines CSS from style and link tags
  • Removes style and link tags
  • Resolves external stylesheets (including local files)
  • Optionally caches external stylesheets
  • Can process multiple documents in parallel
  • Works on Linux, Windows, macOS and in the browser via PyOdide
  • Supports HTML5 & CSS3
  • Tested on CPython 3.9, 3.10, 3.11, 3.12, 3.13, 3.14 and PyPy 3.11.

Playground

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

Installation

Install with pip:

pip install css_inline

Pre-compiled wheels are available for most popular platforms. If not available for your platform, a Rust compiler will be needed to build this package from source. Rust version 1.65 or higher is required.

Usage

import css_inline

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

inlined = css_inline.inline(HTML)
# HTML becomes this:
#
# <html>
# <head>
#    <style>h1 { color:blue; }</style>
# </head>
# <body>
#     <h1 style="color:blue;">Big Text</h1>
# </body>
# </html>

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, preserving the original structure:

FRAGMENT = """<main>
<h1>Hello</h1>
<section>
<p>who am i</p>
</section>
</main>"""

CSS = """
p {
    color: red;
}

h1 {
    color: blue;
}
"""

inlined = css_inline.inline_fragment(FRAGMENT, CSS)
# HTML becomes this:
# <main>
# <h1 style="color: blue;">Hello</h1>
# <section>
# <p style="color: red;">who am i</p>
# </section>
# </main>

When there is a need to inline multiple HTML documents simultaneously, css_inline offers inline_many and inline_many_fragments functions. This feature allows for concurrent processing of several inputs, significantly improving performance when dealing with a large number of documents.

import css_inline

css_inline.inline_many(["<...>", "<...>"])

Under the hood, inline_many, spawns threads at the Rust layer to handle the parallel processing of inputs. This results in faster execution times compared to employing parallel processing techniques at the Python level.

Note: To fully benefit from inline_many, you should run your application on a multicore machine.

Configuration

For configuration options use the CSSInliner class:

import css_inline

inliner = css_inline.CSSInliner(keep_style_tags=True)
inliner.inline("...")
  • inline_style_tags. Specifies whether to inline CSS from "style" tags. Default: True
  • keep_style_tags. Specifies whether to keep "style" tags after inlining. Default: False
  • keep_link_tags. Specifies whether to keep "link" tags after inlining. Default: False
  • keep_at_rules. Specifies whether to keep "at-rules" (starting with @) after inlining. Default: False
  • minify_css. Specifies whether to remove trailing semicolons and spaces between properties and values. Default: False
  • base_url. The base URL used to resolve relative URLs. If you'd like to load stylesheets from your filesystem, use the file:// scheme. Default: None
  • load_remote_stylesheets. Specifies whether remote stylesheets should be loaded. Default: True
  • cache. Specifies caching options for external stylesheets (for example, StylesheetCache(size=5)). Default: None
  • extra_css. Extra CSS to be inlined. Default: None
  • preallocate_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: 32
  • remove_inlined_selectors. Specifies whether to remove selectors that were successfully inlined from <style> blocks. Default: False
  • apply_width_attributes. Specifies whether to add width HTML attributes from CSS width properties on supported elements (table, td, th, img). Default: False
  • apply_height_attributes. 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>

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>

If you'd like to load stylesheets from your filesystem, use the file:// scheme:

import css_inline

# styles/email is relative to the current directory
inliner = css_inline.CSSInliner(base_url="file://styles/email/")
inliner.inline("...")

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

import css_inline

inliner = css_inline.CSSInliner(
    cache=css_inline.StylesheetCache(size=5)
)
inliner.inline("...")

Caching is disabled by default.

XHTML compatibility

If you'd like to work around some XHTML compatibility issues like closing empty tags (<hr> vs. <hr/>), you can use the following snippet that involves lxml:

import css_inline
from lxml import html, etree

document = "..."  # Your HTML document
inlined = css_inline.inline(document)
tree = html.fromstring(inlined)
inlined = etree.tostring(tree).decode(encoding="utf-8")

Performance

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

Here is the performance comparison:

Sizecss_inline 0.19.0premailer 3.10.0toronado 0.1.0inlinestyler 0.2.5pynliner 0.8.0
Basic230 B4.27 µs85.05 µs (19.93x)495.30 µs (116.05x)1.02 ms (238.87x)867.79 µs (203.32x)
Realistic-18.58 KB80.59 µs1.03 ms (12.76x)11.55 ms (143.29x)26.37 ms (327.21x)11.71 ms (145.36x)
Realistic-24.3 KB46.88 µs1.44 ms (30.73x)ERROR17.71 ms (377.77x)ERROR
GitHub page1.81 MB17.57 ms10.78 s (**613