An opinionated CSS bundler for libraries, built on the
lightningcss Node API.
It adds the three things lightningcss-cli does not support — glob entry
patterns, watch mode, and preserving your source layout in the output
— on top of always-bundle, always-minify, and always-target-browserslist
defaults. One command replaces ad hoc combinations of lightningcss,
npm-run-all, and custom watcher scripts.
It always:
- Minifies the output.
- Bundles
@imports into each entry. - Targets the project's
browserslistconfig (or browserslist's defaults if none is present). - Preserves source directory structure in the output.
- Expands glob patterns consistently across shells (macOS, Linux, and Windows), so quoting your patterns behaves the same everywhere.
In watch mode, it rebuilds only the entries affected by a change — editing a
shared @import partial rebuilds only the entries that import it.
npm install --save-dev lightningcss-buildRequires Node 22+.
lightningcss-build [options] [entries...]Each entry is a file path or a glob pattern (resolved relative to the current
working directory). If no entries are specified, it defaults to
<input-dir>/*.css — top-level .css files in the source root, which is a
common case for libraries.
| Option | Default | Description |
|---|---|---|
-i, --input-dir <dir> |
src |
Source root |
-o, --output-dir <dir> |
dist |
Output directory |
-w, --watch |
— | Rebuild on file changes |
-s, --silent |
— | Suppress non-error output |
-v, --version |
— | Show version |
-h, --help |
— | Show help |
Every resolved entry must live under --input-dir — anything outside is a hard
error. Output mirrors the layout beneath it: each entry (or each file matched by
an entry glob) produces its own file at
<output-dir>/<relative-path-from-input-dir>.
Entries are never combined — N entries resolve to N outputs. Bundling refers
to inlining each entry's @imports into that entry's output, not to merging
entries with each other.
Files reached only via @import (not listed as entries) are inlined into the
importer and never emitted as standalone outputs. A file that is both listed as
an entry and @imported by another entry is emitted standalone and inlined
into its importer.
Default. Top-level stylesheets in src/:
{
"scripts": {
"build:css": "lightningcss-build"
}
}→ each src/*.css writes to dist/.
Glob over a tree. Multiple stylesheets in a nested source tree:
{
"scripts": {
"build:css": "lightningcss-build \"src/**/*.css\""
}
}→ writes one output per matched file, preserving the directory layout.
Quote your globs. Without quotes, some shells expand globs themselves (and inconsistently —
shdoes not understand**). Quoting delegates expansion tolightningcss-build, which behaves identically across macOS, Linux, and Windows.
Custom input and output directories. Stylesheets under styles/, output to
build/css/:
{
"scripts": {
"build:css": "lightningcss-build -i styles -o build/css \"styles/**/*.css\""
}
}→ styles/index.css writes to build/css/index.css;
styles/components/button.css writes to build/css/components/button.css.
Watch mode. Re-run on change, scoped to the affected entries:
{
"scripts": {
"watch:css": "lightningcss-build -w \"src/**/*.css\""
}
}Ctrl+C (SIGINT) or SIGTERM shuts the watcher down cleanly.
Only files under --input-dir are watched. @imports may still reference files
outside it (they are bundled normally), but changes to those external files do
not trigger a rebuild — re-save the importing entry to pick them up.
The set of entries is fixed at startup. Creating a new file that matches an
entry glob does not add it to the watch set; restart lightningcss-build to
pick up new entries.
Browserslist targets come from your project's browserslist config. The tool
looks in the standard locations, in the usual order of precedence:
browserslistfield inpackage.json.browserslistrc- Browserslist's built-in
defaults(> 0.5%, last 2 versions, Firefox ESR, not dead)
Example package.json:
{
"browserslist": ["> 0.25%", "last 2 versions", "not dead"]
}There is no --targets flag. If you need a different query per build, set the
BROWSERSLIST env var inline or switch configs with BROWSERSLIST_ENV.
| Code | Meaning |
|---|---|
0 |
Build succeeded |
1 |
Build error (parse error, missing @import, etc.) |
2 |
Usage error (no entries, unknown flag, or entry outside --input-dir) |
In watch mode, build errors stay scoped to the failing entry — previous good
outputs are preserved, the watcher keeps running, and the error is logged as
file:line:col.
- Not a
lightningcss-clireplacement for generic use cases. The opinions (always bundle, always minify, always respect Browserslist, and always preserve structure) cannot be turned off. - Not a CSS Modules / custom-media / custom-syntax configurator. If you need
those
lightningcssfeatures, uselightningcss-clior the API directly. - Not a config-file tool. All options are passed via CLI
argvor resolved from standard project files. - Not a general bundler. No JS, no assets, no CSS Modules JSON, no HMR.
MIT © 2026 Igor Danchenko