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.54.
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 use Rust-level threads; 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.6-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | f82e9d7b8938953f408fb1e183efb00d5cd972410c889fa2ab347f9829bc4891 |
|
MD5 | 347105cceafc16a90335460814ba9da9 |
|
BLAKE2b-256 | b80c76f637296f4771a2383dbed5baa969b2656865aedb1a316933035ef50b68 |
Hashes for css_inline-0.8.6-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 252d85719a51ff4bdcdc043885c3f02c9b0de9c982891e16ae9a3bce7358ef8a |
|
MD5 | d4b7bf03c4c03bc618ad07d5e893a498 |
|
BLAKE2b-256 | 6e3dc265750bc1592aefdb35c7c281f9afa727330c22911dec3c6392c946f53a |
Hashes for css_inline-0.8.6-pp39-pypy39_pp73-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 41b120d9f2f912f8d02560c86876f661351069a7cd501a1e68a08bb6635ceb86 |
|
MD5 | 1d2cc718cef69bc1fa1eb68c3c87d57f |
|
BLAKE2b-256 | 6e75fd788b54d6439914f37a97a482281668b0e6529d826063c7436897724a5f |
Hashes for css_inline-0.8.6-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | e47a8688d4fe30c5da6346a26d882d433c86e9362fb1c45a476daa92ef0a4dca |
|
MD5 | aeeb9c7c6116f376d114d36ec474b9a9 |
|
BLAKE2b-256 | ed32bc69f38c66443ecd58244aad279df6d9c1d0106e3a2af6143219d1c9d7e5 |
Hashes for css_inline-0.8.6-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 3a040444c1f48d6bd1e426ddda7041a5142d00b8594861a30e9ba4e1e28b3b74 |
|
MD5 | d0a3d58fd5837a497284de8077ce1478 |
|
BLAKE2b-256 | 7b3d566a88475b0e6ebd998a09be14faabdad35e8b45ea2a00cc4c89862aafeb |
Hashes for css_inline-0.8.6-pp38-pypy38_pp73-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | fb792716ec45b58b0ec223fcbc4b34e31a35f07f8ef99de1e782bed1d48cacd9 |
|
MD5 | eba3b0b51d8e75e5b7093447c0c38548 |
|
BLAKE2b-256 | 76d89ffbc5c84d5428e544fb3333d8b9956c671fac21bab4cc4165bfcbe152fe |
Hashes for css_inline-0.8.6-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | f50633854f8c540027d13959e4379f24233543ebb70535d08a266f5ab5fed1fb |
|
MD5 | fdabff97b2f55808d99822df312ee5d1 |
|
BLAKE2b-256 | 9e4349c7229ab4a9cb85f81fe8b21ab7027b3ae0a61695453ec16a145a7f7f41 |
Hashes for css_inline-0.8.6-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 94cc8a7022fac3877b59fd4fc6524de067c98914a951d109fd31a9a4212d4879 |
|
MD5 | 0dc57e1c8fbc7581970699b619532a3f |
|
BLAKE2b-256 | 9d6ee01ae97082bcd361a2ad79bd9c65a9f0468b160d070e892db835a89a27e1 |
Hashes for css_inline-0.8.6-pp37-pypy37_pp73-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 0fae5d7aef700a396a8ceb3fa4d940e3992f35791568a76b74cd4e5dfa75e7ff |
|
MD5 | b23c8fdbf1960cf69f68bbccf7389b7e |
|
BLAKE2b-256 | 881d99f9d64e017cd9943d50e55e58a9d0c8482e348535468b83dbbf6b48c953 |
Hashes for css_inline-0.8.6-cp37-abi3-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 0cc4dc8dea4426ab2f37a87f4292bca08e63d118edb8646c51a0203386fab97e |
|
MD5 | 8e8ac3037d5b23498c01424d060711be |
|
BLAKE2b-256 | d2b1b20578aa51dc10e757466a714ec7f546a7f95736a44ca6ae2e199faccdde |
Hashes for css_inline-0.8.6-cp37-abi3-win32.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | efdd82c01bfeb250b50f86121ef8a8f2560abc628b746aecb4a39e98e6414105 |
|
MD5 | 9d069e16ebcf7ff0eddab5740831b474 |
|
BLAKE2b-256 | 348e2c2e87c68fecf46690010d08195971f187c5ab7784583b995683dc23fd71 |
Hashes for css_inline-0.8.6-cp37-abi3-musllinux_1_2_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 41d16597067fe3af979d64740dc8e4d05d331faf2f146d410a4c2d37aad93026 |
|
MD5 | 9ffdb2e30c4d04e62b8444039b5dfaf8 |
|
BLAKE2b-256 | 09bdd99d0512bc03e8509b46559ca9be6ad25f2219adf32a33250919d2bcb5be |
Hashes for css_inline-0.8.6-cp37-abi3-musllinux_1_2_armv7l.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 0024b7d38b7c402270158a0e55a2680cc73fe66b827a3f4055cb67958d6d87e2 |
|
MD5 | cfd40fedf55368544b8c6a98d0ebd697 |
|
BLAKE2b-256 | dc4a1423c9291262d1eedf3f8aff80aaa42f08abad4ac0d0d5db42882c6ede5c |
Hashes for css_inline-0.8.6-cp37-abi3-musllinux_1_2_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 98cf19a9189a075532205c4b57bfc4324e8815761198f58ac032e171863053a7 |
|
MD5 | ac10b509dd303419664c3d939ac9eaf6 |
|
BLAKE2b-256 | 6b7e17260982631f1dfc076ca755afd40b3750d5e677e5637bc49f38f73bb65b |
Hashes for css_inline-0.8.6-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 3939c9abb50f8e02e2a174ec6732019eda236f5671be0112198ea4e84e865b4c |
|
MD5 | 62280a13bdea4d9192f093771428b032 |
|
BLAKE2b-256 | 5dfb9fbda4840b92b09fa4ecc4b514dec1a77ff6e627ca71b0044d2d0e390ce5 |
Hashes for css_inline-0.8.6-cp37-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 94af1cfa2e707a75992d8f5af6e2ebf593c8e28c89eba1bfe591c15ac42cfb7d |
|
MD5 | 3c9dd97c280896c09ba6f6c13e5ab9eb |
|
BLAKE2b-256 | a82116fd7603d9e8bfbda62781cfee3ffc2b356395ece1e004edd32360e50123 |
Hashes for css_inline-0.8.6-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | fa7cdcd29b5a946eb8671ec5028f24ee4383c8caebf22f90fca35886ca0b165b |
|
MD5 | 72ec0fea8d52a73340ca991793f19b25 |
|
BLAKE2b-256 | 5009c6b4bd360da79b00b43419c4137de7d5810761540fb760ac8375e1248ed1 |
Hashes for css_inline-0.8.6-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | a0db101ec9ace5f0297d1f7146f8b0ee1bfa3db17672e26d753f568f6962bc9a |
|
MD5 | d3b9a3ff76a9be20883fc240a009e645 |
|
BLAKE2b-256 | 8639c3ccf3cf7e7d05b3b392bc0d0cf3719ec0890701f75dbbf70240850c1667 |
Hashes for css_inline-0.8.6-cp37-abi3-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 6ae494043f29cd16636c21df1b3983659a3e4a3d9953ce989e20bfa12127b746 |
|
MD5 | 898d22abd220319cd73137f346198d0f |
|
BLAKE2b-256 | 2ca42d5b673122a0bfdde319f394c6339e8302896ee5240e1b5155c2ff866ad4 |
Hashes for css_inline-0.8.6-cp37-abi3-macosx_10_7_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 0fcdbd126e40fa25d624b244b9825ee32860620c4a6e35fa3498901c5a41b646 |
|
MD5 | 338abb7e05be58d320ada51ea7fb1540 |
|
BLAKE2b-256 | 55625051a4067f7dda49c98a0a1d3741e8525394f31e24c17b0495cd0431af9a |