@lightning-vue/compiler
v0.0.4
Published
Vue SFC compiler module with a Lightning CSS-backed style compiler
Readme
Compiler SFC Lightning CSS
@lightning-vue/compiler is a compiler-module variant of
@vue/compiler-sfc whose style compiler is implemented with Lightning CSS.
[!WARNING]
@lightning-vue/compileris in early development. Expect breaking changes, incomplete compatibility coverage, and behavior shifts while the style compiler is still being hardened.
The package re-exports the public @vue/compiler-sfc surface so tooling can
swap it in as a compiler-module override, but it owns its own style engine:
compileStyle(...)compileStyleAsync(...)compileStyleWithLightningCss(...)
Everything outside style compilation is still passed through from
@vue/compiler-sfc.
Intended Use
The main target is tooling that already accepts a compiler-module override,
such as @vitejs/plugin-vue:
import vue from "@vitejs/plugin-vue";
import * as compiler from "@lightning-vue/compiler";
export default {
plugins: [vue({ compiler })],
};That is the reason this package re-exports the whole compiler surface instead
of shipping only a standalone compileStyle.
Style Compiler Contract
This package is strict.
It does not emulate the full PostCSS-based style pipeline from
@vue/compiler-sfc. Unsupported style options fail fast. There is no silent
fallback.
Supported:
- ordinary
<style>and<style scoped>compilation :deep(),:slotted(), and:global()selectors- nested CSS via source normalization + Lightning CSS lowering
postcssOptions.map, which is the sourcemap request shape commonly used by@vitejs/plugin-vuecompileStyleAsync(..., { modules: true })for Lightning CSS's built-in CSS Modules support- supported subset:
- default local scoping
- Lightning CSS's default naming, which currently uses
[hash]_[local] modulesOptions.generateScopedNameas a string pattern using only[name],[local], and[hash]modulesOptions.localsConvention
- note:
- this stays async-only to match
@vue/compiler-sfc's public API contract, even though the underlying Lightning CSS modules transform is synchronous
- this stays async-only to match
- supported subset:
Unsupported:
postcssPluginspostcssOptionskeys other thanmapcompileStyle(..., { modules: true })modulescombined withscoped- CSS Modules options outside the supported Lightning CSS subset, including
scopeBehaviour: 'global', functiongenerateScopedName,hashPrefix,exportGlobals, andglobalModulePaths - legacy scoped selector syntax such as
>>>,/deep/,::v-deep(...),::v-slotted(...), and::v-global(...); use:deep(),:slotted(), and:global()instead. These are good future codemod targets. trim: false
When those option shapes are needed, use @vue/compiler-sfc.
Benchmarks
The compare suite measures @lightning-vue/compiler against the current
PostCSS-based @vue/compiler-sfc style compiler on the same scoped CSS input.
Snapshot from pnpm bench:compare on April 24, 2026, using a 5-run median.
Environment: MacBook Air (13-inch, M4, 2025), Apple M4, 10-core CPU
(4 performance + 6 efficiency), 32 GB memory, macOS 26.4.1, Node v24.15.0,
pnpm 10.33.0, darwin/arm64.
node tools/bench-scoped-selector.mjs --bench bench/compileStyle.compare.bench.ts --runs 5Times are median wall time per compileStyle call. Speedup is based on
operations per second, so lower time and higher speedup both favor
@lightning-vue/compiler.
Parity Cases
These cases emit the same CSS after normalization.
| Case | Lightning CSS | PostCSS | Speedup |
| ------------------------------------------------ | ------------: | --------: | ------: |
| Simple selectors | 0.2147 ms | 0.7965 ms | 4.48x |
| :deep() / :slotted() / :global() selectors | 0.5654 ms | 1.6062 ms | 3.27x |
| Nested selectors | 0.3445 ms | 1.4948 ms | 4.92x |
| Animation keyframes | 0.2881 ms | 0.7488 ms | 2.93x |
| Nested at-rules with simple selectors | 0.2584 ms | 0.5807 ms | 2.43x |
| Nested at-rules with compound selectors | 0.3011 ms | 0.7973 ms | 3.06x |
| Nested at-rules with descendant selectors | 0.2998 ms | 0.6953 ms | 2.52x |
| Nested at-rules with realistic selectors | 0.3792 ms | 1.1415 ms | 3.42x |
| Mixed nested selectors and at-rules | 0.4289 ms | 1.8702 ms | 4.94x |
Correctness-Sensitive Cases
These cases exercise documented drift examples. They measure throughput on the same input while the two compilers preserve different selector semantics.
| Case | Lightning CSS | PostCSS | Speedup |
| ------------------------------------------------------- | ------------: | --------: | ------: |
| Selectors that wrap :deep() | 0.3784 ms | 0.9187 ms | 2.81x |
| Mixed realistic styles | 0.6985 ms | 1.7445 ms | 2.74x |
| Nested at-rules with :slotted() and wrapped :deep() | 0.4925 ms | 1.0328 ms | 2.38x |
The Node build is benchmarked here; the browser/WASM build may have different performance characteristics and is not covered by this snapshot.
Intentional Drifts
This package currently has a small set of intentional semantic drifts from the
current PostCSS-based @vue/compiler-sfc implementation.
For concrete examples and side-by-side outputs, see the divergence playground: https://lightning-vue.haoqun.dev/divergence/
- Wildcard selectors stay valid
@lightning-vue/compiler: Keeps valid selectors such as* + :hoverandsvg\|*valid after scoping.- Current PostCSS path: Still breaks some wildcard forms.
- Selectors that wrap
:deep()stay locally scoped@lightning-vue/compiler: In selectors like:not(.foo :deep(.bar))and:has(.foo :deep(.bar)), lowers the deep branch and keeps the wrapper locally anchored.- Current PostCSS path: The inner deep branch now lowers too. The outer
[data-v-*]anchor can still drop off the wrapper, which changes the final selector shape.
- Nested
:slotted(...)keeps its meaning@lightning-vue/compiler: Keeps nested selectors under:slotted(...)targeting slotted content, even inside@media,@supports, and@container.- Current PostCSS path: Can lose that meaning inside nested at-rules and treat the nested rule like an ordinary local selector instead.
- Split mixed slotted and local selector lists
@lightning-vue/compiler: For:slotted(.x), .y { .b {} }, the nested.bis emitted as a local descendant of.y. It does not also get a second slotted interpretation.- Current PostCSS path: Can blur those branches together. A single nested rule cannot safely mean two different things at once, so split the selector list when the slotted branch and the local branch need different output.
- CSS Modules naming differs
@lightning-vue/compiler: Uses Lightning CSS's built-in[hash]_[local]pattern by default.- Current PostCSS path: Uses the
@vue/compiler-sfc/postcss-modulesdefault of_<local>_<hash>_<line>.
- Mixed-case animation declarations still track local keyframes
@lightning-vue/compiler: Tracks renamed local@keyframesthrough declarations such asAnimation: foo 1sandAnimation-Name: foo.- Current PostCSS path: Still uses lowercase-only heuristics, so those valid mixed-case declarations can stop tracking the renamed local
@keyframes.
- Rare ambiguous animation shorthands follow CSS parsing
@lightning-vue/compiler: In ambiguous shorthands such asanimation: paused foo 1s, it renames the actual keyframe namefooand leavespausedalone.- Current PostCSS path: In rare cases, still splits the shorthand into whitespace-separated tokens and rewrites the first token that matches a local
@keyframesname. That can renamepausedinstead offoo, or renameeaseinanimation: ease 1swheneaseis only a timing function.
Related Packages
@vue/compiler-sfcThe default compiler-sfc package with the PostCSS-based style compiler.@lightning-vue/utilsShared selector and source utilities used by this package’s style pipeline.
Dependency
This package requires lightningcss to be available in the dependency tree.
