Fast CSS inlining written in Rust
Project description
css_inline
Blazing-fast CSS inlining for Python implemented with Mozilla's Servo project components.
Features:
- Removing
style
tags after inlining; - Resolving external stylesheets (including local files);
- Control if
style
tags should be processed; - Out-of-document CSS to inline;
- Inlining multiple documents in parallel (via Rust-level threads)
The project supports CSS Syntax Level 3.
Installation
To install css_inline
via pip
run the following command:
pip install css_inline
Pre-compiled wheels for most popular platforms are provided. If your platform is not in the support table below, you will need a Rust compiler to build this package from source. The minimum supported Rust version is 1.60.
Usage
To inline CSS in a HTML document:
import css_inline
HTML = """<html>
<head>
<title>Test</title>
<style>h1 { color:blue; }</style>
</head>
<body>
<h1>Big Text</h1>
</body>
</html>"""
inlined = css_inline.inline(HTML)
# HTML becomes this:
#
# <html>
# <head>
# <title>Test</title>
# <style>h1 { color:blue; }</style>
# </head>
# <body>
# <h1 style="color:blue;">Big Text</h1>
# </body>
# </html>
If you want to inline many HTML documents, you can utilize inline_many
that processes the input in parallel.
import css_inline
css_inline.inline_many(["<...>", "<...>"])
inline_many
will spawn threads on the Rust level; thus, you can expect it's running faster than css_inline.inline
via Python's multiprocessing
or threading
modules.
For customization options use the CSSInliner
class:
import css_inline
inliner = css_inline.CSSInliner(remove_style_tags=True)
inliner.inline("...")
If you'd like to skip CSS inlining for an HTML tag, add data-css-inline="ignore"
attribute to it:
<head>
<title>Test</title>
<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>
This attribute also allows you to skip link
and style
tags:
<head>
<title>Test</title>
<!-- Styles below are ignored -->
<style data-css-inline="ignore">h1 { color:blue; }</style>
</head>
<body>
<h1>Big Text</h1>
</body>
</html>
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("...")
Standards support & restrictions
css-inline
is built on top of kuchiki and cssparser and relies on their behavior for HTML / CSS parsing and serialization.
Notably:
- Only HTML 5, XHTML is not supported;
- Only CSS 3;
- Only UTF-8 for string representation. Other document encodings are not yet supported.
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
Due to the usage of efficient tooling from Mozilla's Servo project (html5ever
, rust-cssparser
and others) this
library has excellent performance characteristics. In comparison with other Python projects, it is usually >10x faster than the nearest alternative.
For inlining CSS in the html document from the Usage
section above there is the following breakdown in the benchmarks:
css_inline 0.8.2
- 22.42 uspremailer 3.10.0
- 332.02 us (x14.81)toronado 0.1.0
- 1.59 ms (x71.17)inlinestyler 0.2.5
- 2.35 ms (x105.07)pynliner 0.8.0
- 2.79 ms (x124.80)
Realistic email 1:
css_inline 0.8.2
- 487.75 uspremailer 3.10.0
- 3.92 ms (x8.05)toronado 0.1.0
- 52.09 ms (x106.81)inlinestyler 0.2.5
- 81.17 ms (x166.43)pynliner 0.8.0
- 128.81 ms (x264.1)
Realistic email 2:
css_inline 0.8.2
- 386.64 uspremailer 3.10.0
- 4.82 ms (x12.47)toronado 0.1.0
-Error: Pseudo-elements are not supported
inlinestyler 0.2.5
- 40.80 ms (x105.54)pynliner 0.8.0
-Error: No match was found
You can take a look at the benchmarks' code at benches/bench.py
file.
The results above were measured with stable rustc 1.64.0
, Python 3.10.4
, Linux x86_64
on i8700K, and 32GB RAM.
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 and PyPy 3.7, 3.8, 3.9.
Extra materials
If you want to know how this library was created & how it works internally, you could take a look at these articles:
License
The code in this project is licensed under MIT license.
By contributing to css_inline
, you agree that your contributions
will be licensed under its 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.8.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 2bf6053352d053d32a2294510416c704e5b62325a8214dca5c764455e318948c |
|
MD5 | c56d52896da53d284ccdc94bec773cbf |
|
BLAKE2b-256 | dd29382074c486c84302b06d8e432a4ca806c40d5b653a113f904455a1b56ff7 |
Hashes for css_inline-0.8.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | f3b5dc09f6de78fa9f7152e701fdfa2ed827a7c0543a6746ba9879e8731c6da5 |
|
MD5 | 6a2a317f9cab15c694e7efe78ab4256d |
|
BLAKE2b-256 | 3a80f9e7032c93e23846cae2cbf8975969e55097ff4cbd1e45b1f84d2000805e |
Hashes for css_inline-0.8.7-pp39-pypy39_pp73-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 7e5d5aebf87bcacc6bd3f548e0648f4e1b26d4b50e7239df234a42cb1374c039 |
|
MD5 | 3c52977e7544b16a886a80713f5294a5 |
|
BLAKE2b-256 | eceec43cdad2c5120921341c6f54fc5e26067b1a601f5f5ba4ca50f66c4a39e9 |
Hashes for css_inline-0.8.7-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | ea7d4313eed1de7def6d2e9c4780f11e1faed3800f2e2ad4de47dc15ba7a40da |
|
MD5 | 1ea368cdc113b90495cfcc2e7405d374 |
|
BLAKE2b-256 | d473373cd54c17d08a3bf1ad21b68cd95e1dea641887976a1761b1da8ab8ba53 |
Hashes for css_inline-0.8.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 0e87293717865bca00bbc2046864e33a421468129a8a8fe938abf2397c4a8e26 |
|
MD5 | 797ed80dee788a6bf6a708828f0d3bcf |
|
BLAKE2b-256 | 8f4d92519ef75fada878db35d3b5b81f8c30a04048b8f8697c9b4e5ec51eb681 |
Hashes for css_inline-0.8.7-pp38-pypy38_pp73-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 819bf4c331e59da07d824529027c00ec618c2f0b1d498f43e5281988339ac082 |
|
MD5 | d635c1c86cfd2d30ad6bf37271e8b081 |
|
BLAKE2b-256 | ab3b6712029e64cf7015d44e73cffa2a2e4be4c4e49fa13f0f8f0ad8054838ea |
Hashes for css_inline-0.8.7-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 6579c9ea5e288e644bd78dbfd3d2bae9c33eb11e3f02d2c61cf4b5683c8f97da |
|
MD5 | f8d3dc594bf51f68d8dc5d8115237ee4 |
|
BLAKE2b-256 | c238d857cd12288861cad72d749adc79b1e19758978f360fdbf9c4347c9a1ddd |
Hashes for css_inline-0.8.7-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | f0757b71ae23d4131d7ca274bb35d41f5faa41b88eed9090df3ab409689d055d |
|
MD5 | 20a73e816afe89aabb6293945aefc1da |
|
BLAKE2b-256 | d86211068fe8259171480ce8d3dcab3bd6aeffc78bc51ee47136818c8a174241 |
Hashes for css_inline-0.8.7-pp37-pypy37_pp73-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 36a392ed87f840621838e63937e151d236a62cfdba2760f503273330691ea3d8 |
|
MD5 | 6f3d52f8a135c6033823994ce9e2e8df |
|
BLAKE2b-256 | 6648edfa9d2de674b089a042df0a4875a8a44a8f031283c9f62aacf8dee01948 |
Hashes for css_inline-0.8.7-cp37-abi3-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 6803c2fb2064330de1761a44ae86ab6c9b78d1761c36ebdd8919346e595d702d |
|
MD5 | cd30b20c74efda63afd2a96e5e5332a2 |
|
BLAKE2b-256 | 80ad36c49e066fb8c8c3ccf3f1b21bbd45c083b225812492aa2c9fb1236c5d1f |
Hashes for css_inline-0.8.7-cp37-abi3-win32.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | c5910202e7583f0d1b6aea34d63cd378e28808f8bcf210a9c961cb26ccc5ed25 |
|
MD5 | 91f06882a41a1e9292156659f4725ef6 |
|
BLAKE2b-256 | 0a20bd1f043f5e81f6593236c5cb5c93ba7fc2f0ff75044c2cb851303c5323cf |
Hashes for css_inline-0.8.7-cp37-abi3-musllinux_1_2_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 51cc996cfce5fd10aa5e41569c664cded198f30f4706e699e97942893aa9e7f9 |
|
MD5 | a6a932edde865543d240e20e1cc86bf4 |
|
BLAKE2b-256 | 212e35ccd9b0a7a5dd71e0f53bbe9c2e5ada2df78915300d08cdc74332301070 |
Hashes for css_inline-0.8.7-cp37-abi3-musllinux_1_2_armv7l.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 6c0f02ddc5b694520d0fb8db7961e703120a373e516a74cfa6c8303b1c131e42 |
|
MD5 | 1dcd1ac059d561ef2b727e9c56a46222 |
|
BLAKE2b-256 | 99d805c401978f3883346dd7053742d553c9513f2da7131a3fc939fd3ea762bf |
Hashes for css_inline-0.8.7-cp37-abi3-musllinux_1_2_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 5b08837acd1fe60a8f8cbd44dfce88dda1676aca47eeb51bf512c02e90803b77 |
|
MD5 | 9cbc5a7d67c67a5c96ba9898139a4e89 |
|
BLAKE2b-256 | bf6fc2ea9e1176cc5033d83e50e10e761e687a8e0466450cf10340819b9cfc59 |
Hashes for css_inline-0.8.7-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 20ee0894e7a72434750799fced0c7404fed19f3d0538c7fb3ff61d4efacd473d |
|
MD5 | c8f122551165166170aee3b7c0fda3fa |
|
BLAKE2b-256 | 7a47fc4ee90afbf08b1e8f2abbf8e754677f94557b8e3c6497b557a70601405c |
Hashes for css_inline-0.8.7-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 39a90bd53272ee68a4d7909dc9b03240b296c50249b964cb253faf361c90e9dd |
|
MD5 | 6135c77ec7144f187faa0b125c811948 |
|
BLAKE2b-256 | e682d4cd820c5adadb9a64fb28a8d416bd801e8db16fe424a9824a26dd2152a4 |
Hashes for css_inline-0.8.7-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 773a150ba085d73ea8a4f27f562dcf90a7bc8dcecf0d1867b660f22d036c6a6a |
|
MD5 | 202a20d7626def5f872ff541a1d905c8 |
|
BLAKE2b-256 | f1fd48e24a4ad4e863238ed0c13106868b08c68dfec0a9eb0bb1a1cb74effc8c |
Hashes for css_inline-0.8.7-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 84e2b2c5c7c16b5ff546f9fae53e7f0d24bc63e9de6a2a655809c3698ad9f9e2 |
|
MD5 | da0312cb5d700d96627339f063d53cff |
|
BLAKE2b-256 | 2c436cb7c54014cad705b13e9cb6b14548adfb93929e00960e5ba3e6a6dcc6b2 |
Hashes for css_inline-0.8.7-cp37-abi3-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 6e0afb35e17888b2ddd8efee738f1f68ae569615cb32b66427381372cab2d6b2 |
|
MD5 | da14ea80cc99c25c804360bec730f85c |
|
BLAKE2b-256 | 7f04e833249b4ab6bf0d220e4a1ba24b6fb3dfe1f0ccd8a1333345d50398f5b9 |
Hashes for css_inline-0.8.7-cp37-abi3-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 3604d7af3df90681a5a31d2a1438bd237ae1ba171f2ea1cb62824f4909238a63 |
|
MD5 | 92e82008d3c84d7191af48e1e8f57c11 |
|
BLAKE2b-256 | 068fbb8e9ff91bed628d8d4d6b800a03b6238b1d53347440e49eec53ed5e5629 |