next.js
187f35f2 - Add experimental.lightningCssFeatures config option (#90901)

Commit
50 days ago
Add experimental.lightningCssFeatures config option (#90901) ### What? Adds a new `experimental.lightningCssFeatures` config option that lets users control which CSS features lightningcss should always transpile (`include`) or never transpile (`exclude`), regardless of browserslist targets. ```js // next.config.js module.exports = { experimental: { useLightningcss: true, lightningCssFeatures: { include: ['light-dark', 'oklab-colors'], exclude: ['nesting'], }, }, } ``` ### Why? Currently, lightningcss feature transpilation is determined solely by browserslist targets. There's no way to force transpilation of a specific feature (e.g., `light-dark()`) when targeting modern browsers that already support it, or to skip transpilation of a feature that the user wants to preserve as-is. This is useful for: - Forcing polyfill-style transpilation for features with incomplete browser support - Testing transpiled output for specific features - Opting out of specific transforms that may interfere with CSS tooling downstream ### How? **TypeScript config & validation:** - `config-shared.ts`: `LIGHTNINGCSS_FEATURE_NAMES` const array (single source of truth) with `LightningCssFeature` type derived from it, and `LightningCssFeatures` interface - `config-schema.ts`: Zod validation using `z.enum(LIGHTNINGCSS_FEATURE_NAMES)` for both include/exclude arrays **Feature name → bitmask mapping (Rust, shared via NAPI):** - `crates/next-core/src/next_config.rs`: `lightningcss_feature_names_to_mask()` maps feature name strings to `lightningcss::targets::Features` bitflag constants and returns a `Result<u32>` bitmask. Unknown feature names produce an error via `bail!`. - Exposed to JS via NAPI as `lightningcssFeatureNamesToMaskNapi`, callable through `bindings.css.lightning.featureNamesToMask(names)` - Single source of truth for feature name resolution — both webpack and Turbopack paths use this Rust function **Webpack path:** - `global.ts` / `modules.ts` pass config through to loader options - `loader.ts` calls `featureNamesToMask()` via the native bindings to compute include/exclude masks, then passes them to the SWC `transform()` call - The `lightningCssFeatures` without `useLightningcss` warning only shows when using webpack (Turbopack always uses lightningcss) **Turbopack path (Rust):** - `next_config.rs`: `LightningCssFeatures` struct for deserialization, accessor methods converting names → u32 bitmask via `lightningcss_feature_names_to_mask()` - Config flows through `CssOptionsContext` → `ModuleType::Css` → `CssModuleAsset` (using `LightningCssFeatureFlags { include, exclude }` struct) → `process.rs` where bitmasks are merged into `lightningcss::targets::Targets { include, exclude }` - Uses `u32` bitmask representation throughout to avoid adding lightningcss dependency to non-CSS crates **Defaults preserved:** `Nesting | MediaRangeSyntax` for Turbopack, `Nesting` for Webpack. User `include` is OR-ed on top, user `exclude` masks bits off. **Feature names:** 21 individual features (bit 0–20) + 3 composite groups (`selectors`, `media-queries`, `colors`), all in dash-case. ### Test - **Include test** (`test/e2e/app-dir/experimental-lightningcss-features/`) — targets Chrome 123 (which natively supports `light-dark()`) with `include: ['light-dark']`, then asserts the CSS output contains lightningcss transpilation markers (`--lightningcss-light`, `--lightningcss-dark`) instead of raw `light-dark()`. - **Exclude test** (`test/e2e/app-dir/experimental-lightningcss-features-exclude/`) — targets Chrome 100 (which does NOT support `light-dark()` natively) with `exclude: ['light-dark']`, then asserts the CSS output preserves raw `light-dark()` calls and does not contain transpilation markers.
Author
Parents
Loading