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.2 |
premailer 3.10.0 |
toronado 0.1.0 |
inlinestyler 0.2.5 |
pynliner 0.8.0 |
|
---|---|---|---|---|---|---|
Basic | 230 B | 6.75 µs | 131.50 µs (19.48x) | 666.20 µs (98.70x) | 1.05 ms (155.82x) | 1.20 ms (178.63x) |
Realistic-1 | 8.58 KB | 138.79 µs | 1.43 ms (10.34x) | 16.52 ms (119.07x) | 27.59 ms (198.85x) | 51.74 ms (372.84x) |
Realistic-2 | 4.3 KB | 86.95 µs | 2.69 ms (31.03x) | ERROR | 18.06 ms (207.76x) | 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.1
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.2-pp310-pypy310_pp73-manylinux_2_24_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 14b65d129dbb6ab966dce4939931a4285e0293c23e88b34d7c98cb128895a10f |
|
MD5 | e40e1418fa840966b8747ae4e8fc0e38 |
|
BLAKE2b-256 | 429b603aef86c629e1ae733791cab16196e945c79e2609a24c1f73588ac4db0e |
Hashes for css_inline-0.11.2-pp310-pypy310_pp73-manylinux_2_24_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | ff6be8c1b028149c7cbe80a8ec0c617458b7c1b35bcf98506b6053554c3fac87 |
|
MD5 | 8b1f5d41b34de8fe17bb11abad1a97a4 |
|
BLAKE2b-256 | f1933e28b73a9b3a0bfa57eb2b0b29c1de643297a217517745dc280a066fa0a6 |
Hashes for css_inline-0.11.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 915248e3895920372230f3031f5d383f8ecb8e6d24e7cd7e1e59e4ccd8f779d2 |
|
MD5 | b42a4d73ca63642d2f307e01a2d2c960 |
|
BLAKE2b-256 | 74a74bfdbdaba9e286cd7072845a68b47d29fdf77bdd74c6b3b053ac0ff7dbc8 |
Hashes for css_inline-0.11.2-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | e7113c483ec45d24c5d5b4f4035d10acce3e2986667b952644332bf5080f3198 |
|
MD5 | ba7c60e6eac838b06f7d1479b0c10a2d |
|
BLAKE2b-256 | ee20c89073e0b371beb71c0b91cadce234f929cc3749245468e5857d194af671 |
Hashes for css_inline-0.11.2-pp39-pypy39_pp73-manylinux_2_24_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 6ca6459284832439f096c4d0e88f07deb1218e7781c6f4139bc56361466cadf6 |
|
MD5 | 1a551e152cb0108b5c53f62b1b6e9f59 |
|
BLAKE2b-256 | 0086e268dc1c108da783346e540d0c64abd7940e2ca6d72f0e19d204ddb0f700 |
Hashes for css_inline-0.11.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 067d28588ebbbaff185a73f72771bcf8589ae8eb267f0ef3a36907612cede76c |
|
MD5 | 5691419ac396bb5f3f85afa91bd94ed2 |
|
BLAKE2b-256 | f48c11be95bd29898322860f09216ac7ee83933792baa283e593fa7eedf7e2d3 |
Hashes for css_inline-0.11.2-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 25e22bf5dc65120c7b9d520b64c2c5a202a5a0eda0caf5b5b9f9fa3ec616c001 |
|
MD5 | b69090727cebc69300d17cc470af4530 |
|
BLAKE2b-256 | 4a87403bb75c30e29ede6752efbdfc10e4acceacfe5434b83c45bc2eba020c81 |
Hashes for css_inline-0.11.2-pp38-pypy38_pp73-manylinux_2_24_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 8083d9a16058eda0d4828ca86ccc6079db538c2b07a24ea231d503819a62c621 |
|
MD5 | f2d21d72708e6cf7dd5a310dc5ad2d84 |
|
BLAKE2b-256 | 46296c25885068873de0e10e4378f01394b9a975c516fa150ec67c202923e40b |
Hashes for css_inline-0.11.2-pp38-pypy38_pp73-macosx_10_12_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | a8ac8be0c397893db8858942269ca1388bf824b650b4578b10b6e34f55901d47 |
|
MD5 | b371a95797d0f645a8b9d658b209515e |
|
BLAKE2b-256 | 2bfe8d64f6a842c7fdc3327768fad5ff850901ed8f1d4e3ba5124c431858beec |
Hashes for css_inline-0.11.2-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 62615ce6ec5f311f9f4e119aa69fc52bc3c31b051929d0c67568c5cd229bab08 |
|
MD5 | cf98a633de2f948d952b0f47c62f3ef9 |
|
BLAKE2b-256 | 7c7cfb9013705fdff15312b6f921a41fa26cc14e26ea48180b4ba18ac7b68d77 |
Hashes for css_inline-0.11.2-pp37-pypy37_pp73-manylinux_2_24_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 486e3c11d36915877099d639de34a2e5ebf454e94dd45150b41b50570685848c |
|
MD5 | 5f96aa9d393e44b1e395a11f41d1eef1 |
|
BLAKE2b-256 | 9df1b8128cdcf28500b8460d4d0dcb5439bb74530cc58fe97481d67c1237e19a |
Hashes for css_inline-0.11.2-pp37-pypy37_pp73-macosx_10_12_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 7e528037e4d92937b4142a28f3bdb4186c7e4293e769a414912bf28cf24b1ef4 |
|
MD5 | 542c88e9380f2006436e4e5e508b6a98 |
|
BLAKE2b-256 | 91f87018e7881f27c07c1b95f97aae2f356d37e4ed7dace877495e748401b65e |
Hashes for css_inline-0.11.2-cp37-abi3-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 5f1c514a6a8f45e3be03c9644a253288f2096190333d11a24a053115473a4640 |
|
MD5 | e9fd47b68d928830c7b4e0a9a02da044 |
|
BLAKE2b-256 | 46c1b8e8dfea13021f3dff6ca4da1dfa04ba443a17dc7a3c426d1e9bc5946a2f |
Hashes for css_inline-0.11.2-cp37-abi3-win32.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 9f5a472ba739bcb75a2a49f88ec02bee3e57781b1944e964a0ca5cbaca6b671b |
|
MD5 | 65d8ad68e9a91bc9e02ecfbe34f9ce57 |
|
BLAKE2b-256 | 583cbc7e1b7d09777aa543a377136e6d6fde30239147cefec2b298c7243ef9aa |
Hashes for css_inline-0.11.2-cp37-abi3-musllinux_1_2_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 439ea710b76e0bf586e8fbc90496d4a3e2133a0fd7c61ab3d8c68ace4760cef6 |
|
MD5 | d7dc1ca1caf8f2963eacf1e857e5985c |
|
BLAKE2b-256 | 5da3bc535a6eebb48f96a60f5b8a550f26a8ee8179f7245880f920ac52945157 |
Hashes for css_inline-0.11.2-cp37-abi3-musllinux_1_2_armv7l.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 9620dbb36a3325937bf51b75884f844cdee79a924904975807eddfca3b66de46 |
|
MD5 | 7850cbaa63ef833126e4c9247e2d7f7a |
|
BLAKE2b-256 | f9ccf9a34a1a957667e287e2486804abd191e1e5ea8f2391888cb3951e51fbb5 |
Hashes for css_inline-0.11.2-cp37-abi3-musllinux_1_2_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | e6f0b8d1cb99b5102ec4d2ff45fe46a2e01c273143010966e52f75e7e91f175d |
|
MD5 | f44a1f7a524630cfe80f969c7bfb32ed |
|
BLAKE2b-256 | c6cfd03f11448e8a7fb4268d1e5fdebeb14b045c6554c903101f06a0b997c05e |
Hashes for css_inline-0.11.2-cp37-abi3-manylinux_2_24_armv7l.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 61a1723f27d759ab2de4452add35cb9070f518401ecfa62212ad077243d4e5ee |
|
MD5 | 8585186910591c40d0d3842c666adfe7 |
|
BLAKE2b-256 | 5ce435191a4cfff42ba43110b3a9d896a39b42a2a6d2310c35bc5cb888066637 |
Hashes for css_inline-0.11.2-cp37-abi3-manylinux_2_24_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | d1aa7f6e1930f7a90e3e6815fc7bfe97d1eb564173d247197cee524da9bfeaa3 |
|
MD5 | 84da25b3ceedeba5599a5b6a4edf7353 |
|
BLAKE2b-256 | 5609bd346fdb8563462b3254ef1511150ec5e9a183250e09e04ddbf45f846c79 |
Hashes for css_inline-0.11.2-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 0c4299200900f34fa07272fdbc7e9c6499844714753834c0d6d07618e2fd1a63 |
|
MD5 | 35ea35388b4a130fd1c4a355c90e7478 |
|
BLAKE2b-256 | 266ab4e3afe1425b07074de39503447f542ed00b91c8921b637012a075536fde |
Hashes for css_inline-0.11.2-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | ad2990fabee2322b722a4958b9f77affc3d9eab22b694fd9004bb384a75e5141 |
|
MD5 | 45e6193f430faf03b342b504439eefce |
|
BLAKE2b-256 | 18cd05556c598f7065a629b4371f1b15d094b85610f884e6660ddb9e34e26af9 |
Hashes for css_inline-0.11.2-cp37-abi3-macosx_10_12_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | e2199ddecbe45f751135d46d645875d5d90966d5ee49d449cc0788b8d4d0ba13 |
|
MD5 | 1c5d93ab5028ff3dc684f9d231c16673 |
|
BLAKE2b-256 | 4ca672a7466f93e1d47da385b895327c8f08f1f061857fe7358203fb0860b8d5 |
Hashes for css_inline-0.11.2-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | bff40b07bb10802aadefeddfe5b17e93aad74feb0df6a2650dbbb2b767415894 |
|
MD5 | 3cdbe138e5070e131ee5e7eba6981408 |
|
BLAKE2b-256 | 89a955f1af95dc3588acaf58070cc70bd1cec909b53d4e1e6ed9934bc337cbf0 |