Files
Tobias KoppersandGitHub 187f35f287 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.
2026-03-11 08:06:52 +01:00

127 lines
6.7 KiB
Plaintext

---
title: useLightningcss
description: Enable experimental support for Lightning CSS.
version: experimental
---
Experimental support for using [Lightning CSS](https://lightningcss.dev) with webpack. Lightning CSS is a fast CSS transformer and minifier, written in Rust.
If this option is not set, Next.js on webpack uses [PostCSS](https://postcss.org/) with [`postcss-preset-env`](https://www.npmjs.com/package/postcss-preset-env) by default.
Turbopack uses Lightning CSS by default since Next 14.2. This configuration option has no effect on Turbopack. Turbopack always uses Lightning CSS.
```ts filename="next.config.ts" switcher
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
experimental: {
useLightningcss: false, // default, ignored on Turbopack
},
}
export default nextConfig
```
```js filename="next.config.js" switcher
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
useLightningcss: true, // disables PostCSS on webpack
},
}
module.exports = nextConfig
```
## `lightningCssFeatures`
By default, Lightning CSS decides which CSS features to transpile based on your [browserslist](https://browsersl.ist/) targets. The `lightningCssFeatures` option lets you override this by forcing specific features to always be transpiled (`include`) or never be transpiled (`exclude`), regardless of browser support.
This applies to both webpack (when `useLightningcss` is enabled) and Turbopack.
```ts filename="next.config.ts" switcher
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
experimental: {
useLightningcss: true,
lightningCssFeatures: {
// Always transpile these features, even if targets support them
include: ['light-dark', 'oklab-colors'],
// Never transpile these features, even if targets don't support them
exclude: ['nesting'],
},
},
}
export default nextConfig
```
```js filename="next.config.js" switcher
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
useLightningcss: true,
lightningCssFeatures: {
// Always transpile these features, even if targets support them
include: ['light-dark', 'oklab-colors'],
// Never transpile these features, even if targets don't support them
exclude: ['nesting'],
},
},
}
module.exports = nextConfig
```
### Options
| Option | Type | Description |
| --------- | ---------- | -------------------------------------------------------------------------- |
| `include` | `string[]` | Features to always transpile, regardless of browser targets. |
| `exclude` | `string[]` | Features to never transpile, even when browser targets would require them. |
### Available features
Individual features:
| Feature name | Description |
| ----------------------------------- | ---------------------------------------------------- |
| `nesting` | [CSS Nesting](https://drafts.csswg.org/css-nesting/) |
| `not-selector-list` | `:not` with multiple selectors |
| `dir-selector` | `:dir()` selector |
| `lang-selector-list` | `:lang()` with multiple languages |
| `is-selector` | `:is()` selector |
| `text-decoration-thickness-percent` | Percentage values in `text-decoration-thickness` |
| `media-interval-syntax` | Media query range interval syntax |
| `media-range-syntax` | Media query range syntax (`width >= 600px`) |
| `custom-media-queries` | `@custom-media` rules |
| `clamp-function` | `clamp()` function |
| `color-function` | `color()` function |
| `oklab-colors` | `oklab()` and `oklch()` colors |
| `lab-colors` | `lab()` and `lch()` colors |
| `p3-colors` | Display P3 colors |
| `hex-alpha-colors` | 4 and 8 digit hex colors with alpha |
| `space-separated-color-notation` | Space-separated color notation (`rgb(0 0 0)`) |
| `font-family-system-ui` | `system-ui` font family |
| `double-position-gradients` | Double-position gradient stops |
| `vendor-prefixes` | Vendor-prefixed properties and values |
| `logical-properties` | Logical properties and values |
| `light-dark` | `light-dark()` color function |
Composite groups (shorthand for enabling multiple features at once):
| Group name | Includes |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `selectors` | `nesting`, `not-selector-list`, `dir-selector`, `lang-selector-list`, `is-selector` |
| `media-queries` | `media-interval-syntax`, `media-range-syntax`, `custom-media-queries` |
| `colors` | `color-function`, `oklab-colors`, `lab-colors`, `p3-colors`, `hex-alpha-colors`, `space-separated-color-notation`, `light-dark` |
## Version History
| Version | Changes |
| -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `16.2.0` | `lightningCssFeatures` added. |
| `15.1.0` | Support for `useSwcCss` was removed from Turbopack. |
| `14.2.0` | Turbopack's default CSS processor was changed from `@swc/css` to Lightning CSS. `useLightningcss` became ignored on Turbopack, and a legacy `experimental.turbo.useSwcCss` option was added. |