High-performance CSS inlining for HTML documents using a Rust NIF.
This library inlines CSS from <style> tags into element style attributes, which is essential for HTML emails where external stylesheets and <style> tags are often not supported by email clients.
- Fast Rust-based CSS inlining using the css-inline crate
- Precompiled binaries for common platforms (no Rust toolchain required)
- Runs on dirty CPU scheduler to avoid blocking BEAM schedulers
- CSS minification included
- Zero-copy input handling for optimal performance
Add css_inline to your list of dependencies in mix.exs:
def deps do
[
{:css_inline, github: "knocklabs/css_inline"}
]
endhtml = """
<html>
<head>
<style>
p { color: red; font-weight: bold; }
.highlight { background-color: yellow; }
</style>
</head>
<body>
<p>Hello, world!</p>
<p class="highlight">Important message</p>
</body>
</html>
"""
{:ok, inlined} = CSSInline.inline(html)
# => {:ok, "<html><head></head><body><p style=\"color:red;font-weight:bold\">..."}
# Or use the bang version that raises on error
inlined = CSSInline.inline!(html)This package uses rustler_precompiled to provide precompiled NIF binaries for the following platforms:
aarch64-apple-darwin(macOS Apple Silicon)x86_64-apple-darwin(macOS Intel)aarch64-unknown-linux-gnu(Linux ARM64 GNU)aarch64-unknown-linux-musl(Linux ARM64 musl)x86_64-unknown-linux-gnu(Linux x86_64 GNU)x86_64-unknown-linux-musl(Linux x86_64 musl)
If you need to compile from source (e.g., for a platform without precompiled binaries), ensure you have Rust installed and set the environment variable:
export CSS_INLINE_BUILD=true
mix deps.compile css_inline --forceThe Rust NIF is configured to run on a dirty CPU scheduler because CSS inlining can take several milliseconds for large documents. This prevents blocking the main BEAM schedulers which expect NIFs to complete in under 1ms.
Additional performance optimizations include:
- Zero-copy input: Uses
&strto pass references directly to BEAM binary memory - Direct buffer write: Writes directly to a
Vec<u8>avoiding intermediate allocations - Buffer pre-allocation: Pre-allocates 1.5x the input size for typical cases
- CSS minification: Output CSS is automatically minified
Releasing a new version involves building precompiled NIFs and generating a checksum file. Follow these steps:
-
Update the version in
mix.exsand create a PR to merge tomain -
Manually trigger the build workflow:
- Go to Actions → "Build precompiled NIFs" → "Run workflow"
- Enter the version (e.g.,
0.1.0) - must matchmix.exs - This builds all binaries and uploads them to a GitHub release
-
Wait for all builds to complete - all targets must succeed before proceeding
-
Generate the checksum file locally:
mix deps.get CSS_INLINE_BUILD=true mix rustler_precompiled.download CSSInline.Native --all --print
-
Create a PR with the generated
checksum-Elixir.CSSInline.Native.exsfile and merge tomain -
Create the tag pointing to the commit with the checksum:
git checkout main git pull origin main git tag vX.Y.Z git push origin vX.Y.Z
Important: The workflow is manually triggered (not on tag push) to prevent rebuilds when updating the tag. This ensures the checksum file matches the released artifacts.
Consumers can now use the release by pinning to the tag:
{:css_inline, github: "knocklabs/css_inline", tag: "vX.Y.Z"}MIT License - see LICENSE for details.