lightningcss-build
v1.0.1
Published
Opinionated CSS build tool for libraries, powered by lightningcss
Maintainers
Readme
lightningcss-build
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.
Install
npm install --save-dev lightningcss-buildRequires Node 22+.
Usage
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.
Examples
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. Deleting an entry removes its output; recreating the same
file later (e.g. via git checkout) re-registers it and rebuilds.
Browserslist
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.
Exit codes
| 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.
Non-goals
- 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.
License
MIT © 2026 Igor Danchenko
