High-performance library for inlining CSS into HTML 'style' attributes
Project description
css_inline
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-400x faster than alternatives
- Inlines CSS from
style
andlink
tags - Removes
style
andlink
tags - Resolves external stylesheets (including local files)
- Can process multiple documents in parallel
- Works on Linux, Windows, and macOS
- Supports HTML5 & CSS3
- Tested on CPython 3.7, 3.8, 3.9, 3.10, 3.11, 3.12 and PyPy 3.7, 3.8, 3.9, 3.10.
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>
When there is a need to inline multiple HTML documents simultaneously, css_inline
offers the inline_many
function.
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
base_url
. The base URL used to resolve relative URLs. If you'd like to load stylesheets from your filesystem, use thefile://
scheme. Default:None
load_remote_stylesheets
. Specifies whether remote stylesheets should be loaded. Default:True
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
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:
<head>
<!-- Styles below are not removed -->
<style data-css-inline="keep">h1 { color:blue; }</style>
</head>
<body>
<h1>Big Text</h1>
</body>
Such tags will be kept in the resulting HTML even if the keep_style_tags
option is set to false
.
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("...")
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:
Size | css_inline 0.13.0 |
premailer 3.10.0 |
toronado 0.1.0 |
inlinestyler 0.2.5 |
pynliner 0.8.0 |
|
---|---|---|---|---|---|---|
Basic | 230 B | 6.54 µs | 126.95 µs (19.41x) | 653.12 µs (99.85x) | 1.02 ms (157.14x) | 1.17ms (179.91x) |
Realistic-1 | 8.58 KB | 133.87 µs | 1.41 ms (10.60x) | 15.85 ms (118.39x) | 26.48 ms (197.86x) | 51.56 ms (385.18x) |
Realistic-2 | 4.3 KB | 85.41 µs | 2.65 ms (31.09x) | ERROR | 17.68 ms (207.07x) | ERROR |
GitHub page | 1.81 MB | 229.02 ms | 24.73 s (108.02x) | ERROR | ERROR | ERROR |
The "Basic" case was obtained from benchmarking the example from the Usage section.
Note that the toronado
, inlinestyler
and pynliner
libraries both encountered errors when used to inline CSS in the last scenario.
The benchmarking code is available in the benches/bench.py
file. The benchmarks were conducted using the stable rustc 1.75.0
on Python 3.11.7
.
Comparison with other libraries
Besides performance, css-inline
differs from other Python libraries for CSS inlining.
- Generally supports more CSS features than other libraries (for example,
toronado
andpynliner
do not support pseudo-elements); - It has fewer configuration options and not as flexible as
premailer
; - Works on fewer platforms than LXML-based libraries (
premailer
,inlinestyler
,toronado
, and optionallypynliner
); - Does not have debug logs yet;
- Supports only HTML 5.
Further reading
If you want to know how this library was created & how it works internally, you could take a look at these articles:
License
This project is licensed under the terms of the MIT license.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Hashes for css_inline-0.13.0-pp310-pypy310_pp73-manylinux_2_24_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 3814d5060fa874ea88cc7262f0dec79cd83c63d5528fed049434ff4cd725d296 |
|
MD5 | 53498baf03071e74fc8e0ce57892fa26 |
|
BLAKE2b-256 | 6705521e5ae1f0d6725139c5040c53e3ecf400c9b4974fec0c95200479a63c3e |
Hashes for css_inline-0.13.0-pp310-pypy310_pp73-manylinux_2_24_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 00410f5bf5bbc50d6f0aa474750aa2c9ebdc51b37af60e15d1ced7e539608492 |
|
MD5 | 9fccfdc298de5ce789d920b4a85d2808 |
|
BLAKE2b-256 | 475e9aa33705419df86868db7803a9b8949130eacdc9a947f3427b30c4f0df39 |
Hashes for css_inline-0.13.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 80db07d589361f1ed75c297bd984049918693016c81cf288cc3dc7276653469a |
|
MD5 | e774c7bb466480ea8a03786b2e82dcd2 |
|
BLAKE2b-256 | a8a8f3d5cf427cebd63df0eb9718806a29399c182a42c181eefaeca80d30bf90 |
Hashes for css_inline-0.13.0-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 551619187c6c2d58442e5b67917e4254cec552e38750be942a871bc118ee56d6 |
|
MD5 | 48b0759e25bbb43594a722cd679b98fc |
|
BLAKE2b-256 | 663bc92acfe743b2fe389d3514526c8470d4f7b69f6cd86c7d18d2779e7c360a |
Hashes for css_inline-0.13.0-pp39-pypy39_pp73-manylinux_2_24_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 4c9e4d17a801240e3d23d047f159a997f01c52d441a2529dddb18557068dbf5b |
|
MD5 | 4139edf20d20fc928ff64e9be4595799 |
|
BLAKE2b-256 | 0fcaf3397c015b10321a120e96162f99a560fd2d1dd509790d72caa183a9a2e1 |
Hashes for css_inline-0.13.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 46296fefce679d760cd824cefcdd4f372dbc23232599a99e94612fc46bc0bfbe |
|
MD5 | fa6bb1057a9d7248289370069414e353 |
|
BLAKE2b-256 | 3071885a802ccab35fac0dabdf12d3e29a3c6557a2e2a4d84ffa6b098c31b92e |
Hashes for css_inline-0.13.0-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | d7f8399b05d88c4cf48bcea3a9fe4f6cdab9e4def0e608d9c7e9447dd39f2e72 |
|
MD5 | 47757217af3a02f834005e3573e1ee1a |
|
BLAKE2b-256 | 69f94f4cf591307a9f0e9a0c6f9ef57d7d3a6d6dc9256857fa64551e77b7a743 |
Hashes for css_inline-0.13.0-pp38-pypy38_pp73-manylinux_2_24_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 6761771ea8481fdcb00ce25727e08232561a107a8330f012e52276a6fe84e080 |
|
MD5 | 0ee94b07a583505dfb175766b1bfa3af |
|
BLAKE2b-256 | 9ce96416d86658eb50f2b98450c0daf1c4c8e6e8090a22899aafe2bd281f1213 |
Hashes for css_inline-0.13.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 206c1cb0700f00a7164d18ce293bdaa4ca6cdfc4640e04ea5724ef1c4937bd64 |
|
MD5 | 3cd7e75c21c64836c5121619c8c55e83 |
|
BLAKE2b-256 | 9729c1b427d1f42ee751237bcf276050fb64f9fa01d36dfde041b0ddfef37efa |
Hashes for css_inline-0.13.0-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | f481d2dfa116c9f1a163c2478b6c4c7d035740a680d19b6cb780677148c595f6 |
|
MD5 | 13c2b358096536d350bc63de420dafce |
|
BLAKE2b-256 | 2e8d049ea79b87cd00de9b9e1ea1f976e4a9463496a3b29fabf48d342e3ade93 |
Hashes for css_inline-0.13.0-pp37-pypy37_pp73-manylinux_2_24_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 1fa752b54612e38bfc69083fd77de53df2053bd5f1de2a210e37b3347eb9720d |
|
MD5 | fc1a3f88c7a132e92e77a7ecd5d7e6e2 |
|
BLAKE2b-256 | e9b15dde706e0044346ddc870994d7d5ac01582df5692c65957a49cdb9e38227 |
Hashes for css_inline-0.13.0-pp37-pypy37_pp73-macosx_10_12_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 866dba67fc4065981fc60af72f470e58210eaee59b69ba6ac023f948ee5199b5 |
|
MD5 | 78dbca88488c784f06ee733d6487b920 |
|
BLAKE2b-256 | 3968e73bf60486bc116a5a1f05713effb7614d088c743b92a8f65959f4f0bcd9 |
Hashes for css_inline-0.13.0-cp37-abi3-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | f3403165e511935de3fd702b667fac2b018e080d96ac04918041788ec409f04b |
|
MD5 | dcd88288805de75ae1410b2107b4e4e3 |
|
BLAKE2b-256 | 9bcb21e8bb9a7ca0baf3814a66b5b556927fb71d67989b650593d328007cb097 |
Hashes for css_inline-0.13.0-cp37-abi3-win32.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 3963e66e20c554798d58759bb282ef763836b4ee2afa2c6e466b2b31d9475f21 |
|
MD5 | 1bf0cd54424a5dfe72710f60420fd1ca |
|
BLAKE2b-256 | ea42d3fd7ef7d34e67845d1a0ebf5d5b746e613039194c1b4027fd451a7b2590 |
Hashes for css_inline-0.13.0-cp37-abi3-musllinux_1_2_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 2f843e58e8907614c39f65898ebddb62e893bf49a022f26ac1fa1a7f53591521 |
|
MD5 | f76dddc7d1c73e07f763cd64ee7a01e3 |
|
BLAKE2b-256 | 2572b9f674646617ad60cd53775f65d66ccfa8bac50b592f415b6b3b9bf68866 |
Hashes for css_inline-0.13.0-cp37-abi3-musllinux_1_2_armv7l.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | c69ca8e8fca9c4d655de091c126d9d69de82b66e3182abdfb37db54c9532caac |
|
MD5 | 6d97ecb8ea301803e793b58974365222 |
|
BLAKE2b-256 | 7f577d5aa252a8b47a3f8d02ade9f1adea817ecde408d44e038917c562095413 |
Hashes for css_inline-0.13.0-cp37-abi3-musllinux_1_2_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | c1a8cb1323579bc38e2b9a55fe2ebb9f95dae5774521d1e870a6548069774197 |
|
MD5 | 57e19f7360eb255fef1ba35371503b99 |
|
BLAKE2b-256 | d019d99b66c569c02b0c73a432452b17161127815787ab2522d4c284bc758a5d |
Hashes for css_inline-0.13.0-cp37-abi3-manylinux_2_24_armv7l.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 0111e8c300676aa47b99f30667583ec0a70914a63377ceefa008f5ba75853df3 |
|
MD5 | c670f35347aad0862a70199480bec137 |
|
BLAKE2b-256 | cc8d4df02d7b8cdfbb053b90a19a962930db5dd6c10646a5175c4923a15c5c3e |
Hashes for css_inline-0.13.0-cp37-abi3-manylinux_2_24_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 30d7d79b939df4ef14587998ad1be350410b3c19e697d0a445f99c7734fdf15a |
|
MD5 | 17814652b7b1a1eddd689b0a8397a934 |
|
BLAKE2b-256 | 131e19782f53106e365e21be0d7dc8d2918c8326ad104f4c80c1974ba3df1d8c |
Hashes for css_inline-0.13.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 7c4861ed5c62412a7f1db87195cb94cfdb7b2ae1f1b4e10a31df7f6217e49bda |
|
MD5 | f2bb61f010038e8f831d0a8d27d5ff2c |
|
BLAKE2b-256 | b028927abf85de6acd25af961cbf7cd93c15c0636fd69bdeee3b2a7a5ccb1933 |
Hashes for css_inline-0.13.0-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | cbe8e5b1379695183a1700a70ad22877731b22e223ad4b7ddbd38b6e3b18b484 |
|
MD5 | d264f94a34d927b8643ef73788f3fdd8 |
|
BLAKE2b-256 | 4afcb768746d8f59fc2388d604f73429603163ac0ce37926a70235750c8c1cb2 |
Hashes for css_inline-0.13.0-cp37-abi3-macosx_10_12_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | af66e7dc7e762ab24ebe57f98b7c02ac87cc10457a64b545de923a47694f63f7 |
|
MD5 | 557b6acd7e40385649675e53248b2d2c |
|
BLAKE2b-256 | 8d441147584963e869a84d69fefa47d888ead73a1d4b4f27e182f80325164d62 |
Hashes for css_inline-0.13.0-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | c06b1cb6e5638328e9f1a62f8a0b760914852834d950b7f6812653231c806ded |
|
MD5 | 88f96804ace37bcf81d37902a4938687 |
|
BLAKE2b-256 | e411e4b378208c5d9963762e03fc06f299ecf20820ed4bd72d9931b379e86d79 |