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
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>
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.11.1 |
premailer 3.10.0 |
toronado 0.1.0 |
inlinestyler 0.2.5 |
pynliner 0.8.0 |
|
---|---|---|---|---|---|---|
Basic | 230 B | 6.62 µs | 127.54 µs (19.25x) | 661.58 µs (99.86x) | 1.04 ms (157.91x) | 1.13 ms (180.99x) |
Realistic-1 | 8.58 KB | 145.58 µs | 1.47 ms (10.10x) | 17.07 ms (117.26x) | 28.22 ms (193.88x) | 52.96 ms (363.81x) |
Realistic-2 | 4.3 KB | 94.20 µs | 2.67 ms (28.36x) | ERROR | 17.95 ms (190.59x) | ERROR |
GitHub page | 1.81 MB | 338.88 ms | 25.58 s (75.49x) | ERROR | ERROR | ERROR |
The above data was obtained from benchmarking the inlining of CSS in HTML, as described in 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.74
on Python 3.11.6
.
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.
Python support
css_inline
supports CPython 3.7, 3.8, 3.9, 3.10, 3.11, 3.12 and PyPy 3.7, 3.8, 3.9, 3.10.
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.11.1-pp310-pypy310_pp73-manylinux_2_24_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 446df3edea9e5ae953d8a7eab578bedfff70269aab1565bc225fa3040f07e2c2 |
|
MD5 | 6c291e4fd0a60cea31bb4091c880c6ea |
|
BLAKE2b-256 | 4f7d05aa9b08a4276b7ce251a029f0a5900869617c140ab8326fc021ec887b54 |
Hashes for css_inline-0.11.1-pp310-pypy310_pp73-manylinux_2_24_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | ce003f94ba7dad9c3f5ef1b0f401281f79b4874cb5807eaf8062093b22a16711 |
|
MD5 | 7e71fe09ec4bdd792b4b9bcb6c257f4f |
|
BLAKE2b-256 | 08a32b63ca9f56aea77e10a45e89da1e5c57e8e0f3ff4ec535efb2392c1101f0 |
Hashes for css_inline-0.11.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 0a2dfdb46c6275d06156cb7ead17a035c571fcf6224c4c2a3b20e1eb43be6c93 |
|
MD5 | a811d5b09a52fce6252e1b1b81c6e80e |
|
BLAKE2b-256 | e48b6779a26b5610253e943d2714fadf9d111ebad9c94eea0e9068f8d95a15fc |
Hashes for css_inline-0.11.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 84ad0e44203f00bff0b38eaae14fd5c21afb64a81ded72dae1e58ba97ecd2a01 |
|
MD5 | e5fda07152b3b2aa37a73f34c6527ed8 |
|
BLAKE2b-256 | 0cbbfb4280e730a6129c944be13f78ca274b8aa29a13818a836d63deb2614112 |
Hashes for css_inline-0.11.1-pp39-pypy39_pp73-manylinux_2_24_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | f49e7069146db2a1e732513a6058d046a2ab69dc05117954e3c011ae9ce0ced5 |
|
MD5 | bc136065090b186977aab6978a12d4d0 |
|
BLAKE2b-256 | f3b6c8c7c2c479275ea97db0118fd52a07c28c4d2f465db3eee6b2a41673522b |
Hashes for css_inline-0.11.1-pp39-pypy39_pp73-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 8303f8c2bb0f2730134a02709e0b470bce143f7810f18bad2ae54f23ad4ef21d |
|
MD5 | 451ed9b90a79a6e1ea478712574e185e |
|
BLAKE2b-256 | cf1d28a26d9a17a62b2d050f5c9c732e09e963f03f138c64b06f156e77d177e9 |
Hashes for css_inline-0.11.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 7d2fb36d8512d3b90b534cdb41b6f10de112ed82690b55062809d14b932f39f6 |
|
MD5 | edf234947ce55b8ae588228454c7cf63 |
|
BLAKE2b-256 | bec643c18a860f30fbf6f8ca6225e5f6effbee96a8f5636d29cc9b81fc6560fb |
Hashes for css_inline-0.11.1-pp38-pypy38_pp73-manylinux_2_24_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | a67792ab891d33765722f20c9bd41b102af266d8fae9e364b964a95e6ddee7a6 |
|
MD5 | 9a604884cc2e94c1aa1ad4f6177d0552 |
|
BLAKE2b-256 | 18597dc75c8d60a48038405d1ee769e069a01068f24826378bf7072c541cc10a |
Hashes for css_inline-0.11.1-pp38-pypy38_pp73-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 878912030f8f1ed52a7df052a23b36af1a15fd511a175eb312ffe9f2b22b9d1b |
|
MD5 | d5c7eb3f1eb78af2c91c087e185dbe43 |
|
BLAKE2b-256 | 4d916282b95a0e41597fda666892c172a4bc1aab92f5da670afd9a6a0eec8b21 |
Hashes for css_inline-0.11.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 5dc26c0eca2d464c5091f5514d906ce1bb36f2ed72d814fee58f1ca50e10034b |
|
MD5 | 36e044662199dfbb5cd2471b49974b4d |
|
BLAKE2b-256 | b79f88e8fe757d83b38e6e11739cd4f085f730dabf3a6846e71ec3a8c1226eca |
Hashes for css_inline-0.11.1-pp37-pypy37_pp73-manylinux_2_24_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | ae6c8241faa1731dc971056776b5e5918b1a2eb267c09813d9f17d63a715a03c |
|
MD5 | f77c02e155c0c4fb999e4ae97006f1b9 |
|
BLAKE2b-256 | c9c53addafb142f57046d64b51cdaca5145ee3b57883279d0ed5e8d39d3504aa |
Hashes for css_inline-0.11.1-pp37-pypy37_pp73-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | a315f78fd25f2ef22f3b579efdaea5ca6e1374036df9df3e06be06cdf3b1c1ff |
|
MD5 | c025eed72fe59b83c824f48f7925227c |
|
BLAKE2b-256 | 79343e2c2b17aa9bdc5b42acd64e8b50e00644094a5a458a2bf00b52a399079e |
Hashes for css_inline-0.11.1-cp37-abi3-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | cd9b58ecb79617bc3f01664faafa1096672fcc32807702b10d7364690f51820c |
|
MD5 | 1b2e39685bd2a3f77bdd8bb5f28f1418 |
|
BLAKE2b-256 | b4009cfd196c90cc7c85fd562adc9a69b5d6741ce9bdfde79e1f8c3e6bc5dc8c |
Hashes for css_inline-0.11.1-cp37-abi3-win32.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | b15142d4ab71936221096f32772df8eae4248f617ca464dcf4a19cefe3df7aeb |
|
MD5 | bd16d27c08ed8728183480f9ff3df780 |
|
BLAKE2b-256 | dfd980f05e4e608171c12bf7d55eb5608ed91d5cdf4f08aedfa7d9ddb23a30bd |
Hashes for css_inline-0.11.1-cp37-abi3-musllinux_1_2_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | bade059c5a79f12b146f718b0091a14d3b6b2293ab1964415bd8aee604291d4d |
|
MD5 | 793cb9fe7ad13f58ec77ff67f9fccd29 |
|
BLAKE2b-256 | fec27db16a35d5d1d7dc417952875983fc08bb22ce840c4b96070b30cff317c0 |
Hashes for css_inline-0.11.1-cp37-abi3-musllinux_1_2_armv7l.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 0ebbcf5e18ef9ca539ccc7ebb152533ced029aeb00d957c914722a3ebf92db91 |
|
MD5 | f9f524f72ee0cb71c11231f38917860d |
|
BLAKE2b-256 | 0ae276af1130e9dfddc2cc8e0777e735121af68fb176679c1086afd209df90fb |
Hashes for css_inline-0.11.1-cp37-abi3-musllinux_1_2_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | d8a95cc8b1993407f3337d27f6d7ad15b88fce1d16405c99fce0de35018a8fec |
|
MD5 | 589696c5b4911862e72869bfdd9f1af8 |
|
BLAKE2b-256 | 2172ffa61690207550ee5ca437e3539c84a82453fad10a0b7a380f74d63fb7ea |
Hashes for css_inline-0.11.1-cp37-abi3-manylinux_2_24_armv7l.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | a2b56d7a6f0cae059dd512f3bf893ddeee798f259ad371c28c8fea13b26b1427 |
|
MD5 | 13e93f2240ededdebabda20109092b6b |
|
BLAKE2b-256 | 768ef6e0afd9b8e5a949a4025566ca6fffa46ddaa8d04e65898f0f79585cd6ab |
Hashes for css_inline-0.11.1-cp37-abi3-manylinux_2_24_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 9b7151559ba62f67bb6611de96ed9a9becc10ac577638ed2af48e4213cd6925e |
|
MD5 | 8743539e47b3e560c9c9cbe554ebe59f |
|
BLAKE2b-256 | efc75968f4f3fb477410f4e53d05531febe54eaf87f2884cbb6456a6a89e2d5a |
Hashes for css_inline-0.11.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | df002f227f02019a9fb23541c44a64f841fc0c6c9ab3d700a4c8685e73593815 |
|
MD5 | 9f9370641069837d4e7fc66c58783d54 |
|
BLAKE2b-256 | 947c240447f490b7963159c9f1a16658cf478c17fd8005be5ba1f2560d4778a4 |
Hashes for css_inline-0.11.1-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 4246404c0706a4243599a514b4e4c030174a733d42dd48f682cb054d005c934f |
|
MD5 | c0c4676b0c1623001d8dabf773360749 |
|
BLAKE2b-256 | 7e5ac4a6639409ffe243b0a959cf1b95bc2661343631e81447b1eee2d0d67d0a |
Hashes for css_inline-0.11.1-cp37-abi3-macosx_10_12_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | e643e388e50fb97a26976126a00ce8ca758e1b559de8a38e199e22c5ca28fd74 |
|
MD5 | 331264e33f4607c4f2d3782910cef9b0 |
|
BLAKE2b-256 | 41dbc16bc0535f3a07eb1b3199263982936745addcb84c5b6727951bba615452 |
Hashes for css_inline-0.11.1-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | b37d287d68681613d5e9fa52bcd682d2c009e32245e92c25a124525e725038cf |
|
MD5 | 2050145208066178994589c1810a81b8 |
|
BLAKE2b-256 | d39681c77e604fca36a257fd5252f7894905896c6792f1648fd6898b8d3716f0 |