CSS/JS Bundling and Tree-Shaking
Overview
CSS/JS bundling merges multiple CSS and JS files into single bundled files, reduces HTTP requests, and removes unused CSS selectors via tree-shaking.
Configuration
Add to site.toml:
[build.bundling]
enabled = true
Options
| Option | Default | Description |
|---|---|---|
enabled | false | Master switch (opt-in) |
css | true | Bundle CSS files |
tree_shake_css | true | Remove unused CSS selectors |
js | true | Bundle JS files |
css_output | "css/bundle.css" | Output path for CSS bundle |
js_output | "js/bundle.js" | Output path for JS bundle |
exclude | [] | Glob patterns to exclude from bundling |
Examples
CSS only (no JS bundling):
[build.bundling]
enabled = true
js = false
Exclude vendor files:
[build.bundling]
enabled = true
exclude = ["**/vendor/**", "**/print.css"]
Pipeline Position
Runs as Phase 2.5 in the build pipeline:
- Phase 1: Copy static assets (+ content hash manifest)
- Phase 2: Render all pages (per-page pipeline)
- Phase 2 (cont): Sitemap, post-build plugins
- Phase 2.5: CSS/JS bundling and tree-shaking <-- this feature
- Phase 3: Content hash rewrite
How It Works
CSS Bundling
- Scans all HTML files in
dist/for<link rel="stylesheet">tags - Deduplicates hrefs in first-encounter order (sorted by file path)
- Reads and concatenates CSS files, resolving
@importchains - Tree-shakes: parses all HTML DOMs, keeps selectors matching any page
- Writes bundled CSS to
dist/{css_output}
JS Bundling
- Scans all HTML files for
<script src="...">tags - Skips:
defer,async,type="module", inline scripts - Wraps each file in IIFE:
;(function(){ ... })(); - Writes bundled JS to
dist/{js_output}
HTML Rewriting
- First
<link>/<script>referencing a bundled file: rewritten to bundle - Subsequent references: removed
- Critical CSS preload pattern: both preload
<link>and<noscript>fallback rewritten
Interactions
- Critical CSS: Runs before bundling (Phase 2). Bundling rewrites the preload links.
- Content Hashing: Runs after bundling (Phase 3). Hashes the bundle files.
- Dev Server: Bundling is disabled during dev.
CSS Frameworks (Tailwind, etc.)
Tree-shaking correctly handles CSS selectors with escaped characters, such as Tailwind's responsive breakpoint classes (md\:flex-row, lg\:grid-cols-3), arbitrary value selectors (w-\[200px\]), and fraction utilities (w-1\/2).
Both backslash-character escapes (\:) and hex escapes (\3a ) are preserved during pseudo-class stripping, so selectors inside @media blocks are matched against the DOM correctly.
What Is Skipped
- External URLs (http/https)
<link>withmediaattribute<script>withdefer,async, ortype="module"- Inline
<style>and<script>blocks - Paths matching
excludepatterns
Module Structure
src/build/bundling/
mod.rs -- public API (bundle_assets), orchestration
collect.rs -- HTML scanning, reference collection
css.rs -- CSS merging, tree-shaking
js.rs -- JS concatenation with IIFE wrapping
rewrite.rs -- HTML rewriting