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
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>
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 "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.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.12.0-pp310-pypy310_pp73-manylinux_2_24_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 9bdcbd880c2cf8a140c7a06e8d335e92066c268158dbf5aadc48ecd78774dc99 |
|
MD5 | 5250e91fad75786b9c5cc00d079f67be |
|
BLAKE2b-256 | cee5fa4670770fa3b997ef288cd798b647983e01817692fa37b02b8ce8025879 |
Hashes for css_inline-0.12.0-pp310-pypy310_pp73-manylinux_2_24_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | e6b56ba51a5e90b217085c6965c7154efbc5be3d699506ba34f00cff1362f027 |
|
MD5 | fea8f75a1b915f4cce74a0101f416be5 |
|
BLAKE2b-256 | e5fc4882eda1f01c1a845ed4a1a69c20f2207c67977335d6a00fbf5f173149c2 |
Hashes for css_inline-0.12.0-pp310-pypy310_pp73-macosx_10_12_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 80530da9e8926dadf532276b4c50db8de6c152268ae5e3484f791ba41303fe46 |
|
MD5 | cfc3b4ce5beb026831a83816abd16bb0 |
|
BLAKE2b-256 | 702e98dc394bd2ae9fe171f99a0ee6688e1cd9b98e2680a25f61a2f32189b34c |
Hashes for css_inline-0.12.0-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | fce03c770a5438a9d004dd3f70346b2b19e9f0489b9424e480c839900054b09b |
|
MD5 | bab54761f630fea3f75325b999804a54 |
|
BLAKE2b-256 | 5cee48377611eff1cf9bcd15ce8c7cadb97e71d2923e1da2c9994b1528602c75 |
Hashes for css_inline-0.12.0-pp39-pypy39_pp73-manylinux_2_24_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | be36f78690072b4bdd875c72f502f79f23616dd8d340b948688ab9d29c033812 |
|
MD5 | b6d4270bbcdbd005da945a246fa4d3d4 |
|
BLAKE2b-256 | 3ebbd44ae2c98bacbaf4a0ffc6b3e5251d2c4bc328a19a34a8a892d3db04841b |
Hashes for css_inline-0.12.0-pp39-pypy39_pp73-macosx_10_12_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 4f681642c6530c98a29289b50958b674a9b86699b5ab0769847e920bd048c456 |
|
MD5 | f0800781ccc81524169bf38ab12e385a |
|
BLAKE2b-256 | cca5e09855a754b896fca12166bd2c5b1d514747c000736ebc47c2039de92afd |
Hashes for css_inline-0.12.0-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 4ea58ebfa84f99d1daa3c11571a7035b85f2e522c89804c40751bdb518799d15 |
|
MD5 | d98d9023c96f3e2e566710ebf0441383 |
|
BLAKE2b-256 | fd246ac96cbadb4cbbc7f1317b3e3ffa44d9b5b7114fcf0e980966e9a666b6bd |
Hashes for css_inline-0.12.0-pp38-pypy38_pp73-manylinux_2_24_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | f5262dab5fe474bb34819967aad08e448cb0552bc5b3766ab53f158551630ed9 |
|
MD5 | 7c8f1a781338d983f5ea9658028662f1 |
|
BLAKE2b-256 | af46babf6a9f169632c96aad451c745d652d473e31157cd44d98334ff1f25556 |
Hashes for css_inline-0.12.0-pp38-pypy38_pp73-macosx_10_12_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | f9925a675980f6b88fb8e6d48be1a2a41f84a6d8de640b00a5f995bd2a1632ac |
|
MD5 | 5138c3a02a9b825871fa15f64af8bb7a |
|
BLAKE2b-256 | f0c10592add3442b4e5604d03c565a640446d82a2ae0bd226249ff926a074344 |
Hashes for css_inline-0.12.0-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | bdb4eb707ec5867565322443216f388d7c148cb4e9d64a54859f8b841ceb4715 |
|
MD5 | 14cc823a31b56bebc23b1c15863cf175 |
|
BLAKE2b-256 | 07dae4b9c37c0eedf0df006a0777cb63d75df2edadac8c52f43d49ec9c2ba462 |
Hashes for css_inline-0.12.0-pp37-pypy37_pp73-manylinux_2_24_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | f6b227d5e4eab4f58b6cddbf2267e69df6c19d933e5b149a06e73df4f50317d2 |
|
MD5 | 80f1df8d67c8e58e99ebafd12cdd76c7 |
|
BLAKE2b-256 | 231b822bc2da8ca9afb435c1d00e2eac46f7511e3ec62de300bc56b3a79ff431 |
Hashes for css_inline-0.12.0-pp37-pypy37_pp73-macosx_10_12_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 4a8b9a2ef51051558661d411e6c5d5342355a74a47a5511c52a49534406de418 |
|
MD5 | 22c8fc99d45c4c7e63a0b3716eacc99e |
|
BLAKE2b-256 | eb1b6222d63c04040dc3bad4adb71944c0fc3c6ccdb8044ed33ac7e80da6f171 |
Hashes for css_inline-0.12.0-cp37-abi3-win_amd64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | a5eddeea1df83ba4d962f5503502dfd3293d8a0c90499a3d2bb2c2c8ffad3042 |
|
MD5 | f3f83ac80f0def46210d2af113beb1f2 |
|
BLAKE2b-256 | 1bd86c15c21fde045e71e951de707d752bce888f1a01303dd99bdd3dcc15ea64 |
Hashes for css_inline-0.12.0-cp37-abi3-win32.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 7df51b6684c8ba3a738709780561ffad5da066ad73e1015f659358f8ed2cd885 |
|
MD5 | 5d998b18cf24ff2542f558e354b99644 |
|
BLAKE2b-256 | 85cb63716083d655ef79eea7dd37051b83f42b28f3202a316659087a1425dc37 |
Hashes for css_inline-0.12.0-cp37-abi3-musllinux_1_2_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 97226ac169dbefce9d16656ae3a6a95571dc21d39c356cf493e9500b9b502c80 |
|
MD5 | 2adbd71ab808b2f2f16ffb3ac69227f6 |
|
BLAKE2b-256 | 80f0aacbc4d60844abdcb406245d5c4881acff439bfbf559f428b3311d3fa92b |
Hashes for css_inline-0.12.0-cp37-abi3-musllinux_1_2_armv7l.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 26f209e1aadc6e74ab8b7549cd700632e24750751a73812eacfdff2ccdcaf330 |
|
MD5 | ef19667a5177b2c9f4f2fe1ad8b33496 |
|
BLAKE2b-256 | 6ef8b878000cc4bab26ad0be07d798fef193c92f85c557ba9a72bfca233de5fb |
Hashes for css_inline-0.12.0-cp37-abi3-musllinux_1_2_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | eb327008d6ce931ee6f3ffe48204b2a80bbe3b35c52bbceb5f496ef0954cefed |
|
MD5 | 4fb62752794c4159ed50ca48f88f956e |
|
BLAKE2b-256 | d2b817247f92ca078b861c395ffa1a402514f06acf2a0a0f892a1cbfdc2027a6 |
Hashes for css_inline-0.12.0-cp37-abi3-manylinux_2_24_armv7l.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 0ad77a48e829510068eff99fd189246ad46024722cb60da8e3c78d9b1c736b64 |
|
MD5 | 3758d3b36e531e688d6b7b361a86975c |
|
BLAKE2b-256 | a970330862548451eedc13b58286564770a6b3cc228b1cbf94739cc5f63ee370 |
Hashes for css_inline-0.12.0-cp37-abi3-manylinux_2_24_aarch64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 9362c728b6c8026e07862650944e789e5607a9039ee04db1cf0aa9a8265003fa |
|
MD5 | 15a7097e33f4c3c87e5078a85bea3bf7 |
|
BLAKE2b-256 | 9ed7b9dcebefc40e26bf0dc502628538d3e422b30d31bf30fbd98cc4f73a0771 |
Hashes for css_inline-0.12.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 5a9125689cfd4882088bdb66063876193a7322e134caffda67537f2fa5ca77c0 |
|
MD5 | add5355813bcfb08330aab2ae844d714 |
|
BLAKE2b-256 | a614c6ca9bb9371b3f5ddeef3a9e0663ec10d8e35320d726640d6e74d06d0c50 |
Hashes for css_inline-0.12.0-cp37-abi3-manylinux_2_12_i686.manylinux2010_i686.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | f73c24581e79faa52670809901c1bcc24232eaeef38c4111a815295923d86191 |
|
MD5 | 67b510e976baeb4876fa65669fd64aba |
|
BLAKE2b-256 | cf044d8f9841c7eb2d620a08833614658efe8f036516a0ca8e776b888e5dab00 |
Hashes for css_inline-0.12.0-cp37-abi3-macosx_10_12_x86_64.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 0b7b8495c48c6c6e54ef3effa604a1e468c0ab4808a58e62a4bd418e1481cb2f |
|
MD5 | d6287dd374ebb25c60122d1c25cd3408 |
|
BLAKE2b-256 | 8eeebb7052de56a97ddeef1593bcab6dc5c549c357017d92e1b9b2edbb5d98e9 |
Hashes for css_inline-0.12.0-cp37-abi3-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Algorithm | Hash digest | |
---|---|---|
SHA256 | 72f127aa9ba934d49567d877a90369b53a78b1adcd3a5e69403ad0a1f1a57680 |
|
MD5 | 56b3e6d17df271b3f2a4a81b91caf90d |
|
BLAKE2b-256 | 73031c32a830258a045168cb45b811b1914397b10855d2f8f4bf9a9b5ed41422 |