Add experimental.lightningCssFeatures config option#90901
Merged
Conversation
Collaborator
Tests Passed |
Merging this PR will not alter performance
Comparing Footnotes
|
Collaborator
Stats from current PR🔴 1 regression
📊 All Metrics📖 Metrics GlossaryDev Server Metrics:
Build Metrics:
Change Thresholds:
⚡ Dev Server
📦 Dev Server (Webpack) (Legacy)📦 Dev Server (Webpack)
⚡ Production Builds
📦 Production Builds (Webpack) (Legacy)📦 Production Builds (Webpack)
📦 Bundle SizesBundle Sizes⚡ TurbopackClient Main Bundles: **402 kB** → **402 kB**
|
| Canary | PR | Change | |
|---|---|---|---|
| middleware-b..fest.js gzip | 766 B | 765 B | ✓ |
| Total | 766 B | 765 B | ✅ -1 B |
Build Details
Build Manifests
| Canary | PR | Change | |
|---|---|---|---|
| _buildManifest.js gzip | 446 B | 450 B | ✓ |
| Total | 446 B | 450 B |
📦 Webpack
Client
Main Bundles
| Canary | PR | Change | |
|---|---|---|---|
| 5528-HASH.js gzip | 5.54 kB | N/A | - |
| 6280-HASH.js gzip | 59.4 kB | N/A | - |
| 6335.HASH.js gzip | 169 B | N/A | - |
| 912-HASH.js gzip | 4.59 kB | N/A | - |
| e8aec2e4-HASH.js gzip | 62.6 kB | N/A | - |
| framework-HASH.js gzip | 59.7 kB | 59.7 kB | ✓ |
| main-app-HASH.js gzip | 254 B | 253 B | ✓ |
| main-HASH.js gzip | 39.1 kB | 39.1 kB | ✓ |
| webpack-HASH.js gzip | 1.68 kB | 1.68 kB | ✓ |
| 262-HASH.js gzip | N/A | 4.59 kB | - |
| 2889.HASH.js gzip | N/A | 169 B | - |
| 5602-HASH.js gzip | N/A | 5.55 kB | - |
| 6948ada0-HASH.js gzip | N/A | 62.6 kB | - |
| 9544-HASH.js gzip | N/A | 60.2 kB | - |
| Total | 233 kB | 234 kB |
Polyfills
| Canary | PR | Change | |
|---|---|---|---|
| polyfills-HASH.js gzip | 39.4 kB | 39.4 kB | ✓ |
| Total | 39.4 kB | 39.4 kB | ✓ |
Pages
| Canary | PR | Change | |
|---|---|---|---|
| _app-HASH.js gzip | 194 B | 194 B | ✓ |
| _error-HASH.js gzip | 183 B | 180 B | 🟢 3 B (-2%) |
| css-HASH.js gzip | 331 B | 330 B | ✓ |
| dynamic-HASH.js gzip | 1.81 kB | 1.81 kB | ✓ |
| edge-ssr-HASH.js gzip | 256 B | 256 B | ✓ |
| head-HASH.js gzip | 351 B | 352 B | ✓ |
| hooks-HASH.js gzip | 384 B | 383 B | ✓ |
| image-HASH.js gzip | 580 B | 581 B | ✓ |
| index-HASH.js gzip | 260 B | 260 B | ✓ |
| link-HASH.js gzip | 2.51 kB | 2.51 kB | ✓ |
| routerDirect..HASH.js gzip | 320 B | 319 B | ✓ |
| script-HASH.js gzip | 386 B | 386 B | ✓ |
| withRouter-HASH.js gzip | 315 B | 315 B | ✓ |
| 1afbb74e6ecf..834.css gzip | 106 B | 106 B | ✓ |
| Total | 7.98 kB | 7.98 kB | ✅ -1 B |
Server
Edge SSR
| Canary | PR | Change | |
|---|---|---|---|
| edge-ssr.js gzip | 125 kB | 125 kB | ✓ |
| page.js gzip | 255 kB | 256 kB | ✓ |
| Total | 380 kB | 381 kB |
Middleware
| Canary | PR | Change | |
|---|---|---|---|
| middleware-b..fest.js gzip | 619 B | 615 B | ✓ |
| middleware-r..fest.js gzip | 156 B | 155 B | ✓ |
| middleware.js gzip | 43.8 kB | 43.9 kB | ✓ |
| edge-runtime..pack.js gzip | 842 B | 842 B | ✓ |
| Total | 45.4 kB | 45.6 kB |
Build Details
Build Manifests
| Canary | PR | Change | |
|---|---|---|---|
| _buildManifest.js gzip | 715 B | 718 B | ✓ |
| Total | 715 B | 718 B |
Build Cache
| Canary | PR | Change | |
|---|---|---|---|
| 0.pack gzip | 4.06 MB | 4.07 MB | 🔴 +7.8 kB (+0%) |
| index.pack gzip | 102 kB | 102 kB | ✓ |
| index.pack.old gzip | 102 kB | 103 kB | 🔴 +1.09 kB (+1%) |
| Total | 4.27 MB | 4.28 MB |
🔄 Shared (bundler-independent)
Runtimes
| Canary | PR | Change | |
|---|---|---|---|
| app-page-exp...dev.js gzip | 322 kB | 322 kB | ✓ |
| app-page-exp..prod.js gzip | 171 kB | 171 kB | ✓ |
| app-page-tur...dev.js gzip | 322 kB | 322 kB | ✓ |
| app-page-tur..prod.js gzip | 171 kB | 171 kB | ✓ |
| app-page-tur...dev.js gzip | 318 kB | 318 kB | ✓ |
| app-page-tur..prod.js gzip | 169 kB | 169 kB | ✓ |
| app-page.run...dev.js gzip | 319 kB | 319 kB | ✓ |
| app-page.run..prod.js gzip | 169 kB | 169 kB | ✓ |
| app-route-ex...dev.js gzip | 70.9 kB | 70.9 kB | ✓ |
| app-route-ex..prod.js gzip | 49.3 kB | 49.3 kB | ✓ |
| app-route-tu...dev.js gzip | 70.9 kB | 70.9 kB | ✓ |
| app-route-tu..prod.js gzip | 49.3 kB | 49.3 kB | ✓ |
| app-route-tu...dev.js gzip | 70.5 kB | 70.5 kB | ✓ |
| app-route-tu..prod.js gzip | 49 kB | 49 kB | ✓ |
| app-route.ru...dev.js gzip | 70.4 kB | 70.4 kB | ✓ |
| app-route.ru..prod.js gzip | 49 kB | 49 kB | ✓ |
| dist_client_...dev.js gzip | 324 B | 324 B | ✓ |
| dist_client_...dev.js gzip | 326 B | 326 B | ✓ |
| dist_client_...dev.js gzip | 318 B | 318 B | ✓ |
| dist_client_...dev.js gzip | 317 B | 317 B | ✓ |
| pages-api-tu...dev.js gzip | 43.2 kB | 43.2 kB | ✓ |
| pages-api-tu..prod.js gzip | 32.9 kB | 32.9 kB | ✓ |
| pages-api.ru...dev.js gzip | 43.2 kB | 43.2 kB | ✓ |
| pages-api.ru..prod.js gzip | 32.9 kB | 32.9 kB | ✓ |
| pages-turbo....dev.js gzip | 52.6 kB | 52.6 kB | ✓ |
| pages-turbo...prod.js gzip | 38.5 kB | 38.5 kB | ✓ |
| pages.runtim...dev.js gzip | 52.6 kB | 52.6 kB | ✓ |
| pages.runtim..prod.js gzip | 38.5 kB | 38.5 kB | ✓ |
| server.runti..prod.js gzip | 62 kB | 62 kB | ✓ |
| Total | 2.84 MB | 2.84 MB |
📝 Changed Files (2 files)
Files with changes:
pages-api.ru..time.prod.jspages.runtime.prod.js
View diffs
pages-api.ru..time.prod.js
Diff too large to display
pages.runtime.prod.js
Diff too large to display
📎 Tarball URL
https://vercel-packages.vercel.app/next/commits/e4dac71c3e18e45a64e90f0f09d3aef2e7efd09e/next
sokra
commented
Mar 5, 2026
sokra
commented
Mar 5, 2026
packages/next/src/build/webpack/loaders/lightningcss-loader/src/features.ts
Outdated
Show resolved
Hide resolved
sokra
commented
Mar 5, 2026
sokra
commented
Mar 6, 2026
a9a140c to
75a5d89
Compare
Contributor
|
Notifying the following users due to files changed in this PR based on this repo's notify modifiers: @timneutkens, @ijjk, @shuding, @huozhi: |
Contributor
|
Offers a solution for #82559 |
mischnic
reviewed
Mar 6, 2026
mischnic
reviewed
Mar 6, 2026
...xperimental-lightningcss-features-exclude/experimental-lightningcss-features-exclude.test.ts
Outdated
Show resolved
Hide resolved
|
感谢反馈!我们看到了你的feature请求。我们会尽快处理。 |
icyJoseph
reviewed
Mar 10, 2026
docs/01-app/03-api-reference/05-config/01-next-config-js/useLightningcss.mdx
Show resolved
Hide resolved
mischnic
approved these changes
Mar 10, 2026
icyJoseph
approved these changes
Mar 10, 2026
Add `lightningCssFeatures: { include?, exclude? }` to experimental config,
allowing users to force-enable or disable specific LightningCSS feature
transformations regardless of browserslist targets. Supports both Turbopack
(Rust) and Webpack (JS loader) paths.
- Extract LIGHTNINGCSS_FEATURE_NAMES const array in config-shared.ts, derive the type from it and reuse in config-schema.ts (eliminates duplicated 24-item enum list) - Extract lightningcss_features_field_mask() helper in Rust to deduplicate include/exclude accessor methods - Hoist feature mask computation out of transform() call in loader.ts, avoiding a duplicated exclude mask calculation and an inline IIFE - Add doc comments to bitmask mapping tables and process.rs parameters - Simplify test by extracting collectPageCss() helper and using matchAll
- Add cross-reference comments linking the triplicated bitmask tables (config-shared.ts, features.ts, next_config.rs) - Warn when lightningCssFeatures is set without useLightningcss - Move test from test/development/ to test/e2e/ for prod-mode coverage - Add comment clarifying CSS link regex handles query strings - Commit tsconfig.json in test fixture (matching sibling test convention)
- Use lightningcss crate `Features` constants instead of hardcoded bitmask values - Expose `featureNamesToMask` from Rust via NAPI, remove duplicated JS `features.ts` - Group `lightningcss_include_features`/`lightningcss_exclude_features` into `LightningCssFeatureFlags` struct
…ng webpack Turbopack always uses lightningcss, so the warning is irrelevant there.
Targets Chrome 100 (which does not support light-dark() natively) with exclude: ['light-dark'], then asserts that the CSS output preserves raw light-dark() calls instead of transpiling them.
Use nextConfig in nextTestSetup to vary config per describe block instead of maintaining separate fixture directories.
821aa81 to
84c68b0
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What?
Adds a new
experimental.lightningCssFeaturesconfig option that lets users control which CSS features lightningcss should always transpile (include) or never transpile (exclude), regardless of browserslist targets.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:
How?
TypeScript config & validation:
config-shared.ts:LIGHTNINGCSS_FEATURE_NAMESconst array (single source of truth) withLightningCssFeaturetype derived from it, andLightningCssFeaturesinterfaceconfig-schema.ts: Zod validation usingz.enum(LIGHTNINGCSS_FEATURE_NAMES)for both include/exclude arraysFeature name → bitmask mapping (Rust, shared via NAPI):
crates/next-core/src/next_config.rs:lightningcss_feature_names_to_mask()maps feature name strings tolightningcss::targets::Featuresbitflag constants and returns aResult<u32>bitmask. Unknown feature names produce an error viabail!.lightningcssFeatureNamesToMaskNapi, callable throughbindings.css.lightning.featureNamesToMask(names)Webpack path:
global.ts/modules.tspass config through to loader optionsloader.tscallsfeatureNamesToMask()via the native bindings to compute include/exclude masks, then passes them to the SWCtransform()calllightningCssFeatureswithoutuseLightningcsswarning only shows when using webpack (Turbopack always uses lightningcss)Turbopack path (Rust):
next_config.rs:LightningCssFeaturesstruct for deserialization, accessor methods converting names → u32 bitmask vialightningcss_feature_names_to_mask()CssOptionsContext→ModuleType::Css→CssModuleAsset(usingLightningCssFeatureFlags { include, exclude }struct) →process.rswhere bitmasks are merged intolightningcss::targets::Targets { include, exclude }u32bitmask representation throughout to avoid adding lightningcss dependency to non-CSS cratesDefaults preserved:
Nesting | MediaRangeSyntaxfor Turbopack,Nestingfor Webpack. Userincludeis OR-ed on top, userexcludemasks bits off.Feature names: 21 individual features (bit 0–20) + 3 composite groups (
selectors,media-queries,colors), all in dash-case.Test
test/e2e/app-dir/experimental-lightningcss-features/) — targets Chrome 123 (which natively supportslight-dark()) withinclude: ['light-dark'], then asserts the CSS output contains lightningcss transpilation markers (--lightningcss-light,--lightningcss-dark) instead of rawlight-dark().test/e2e/app-dir/experimental-lightningcss-features-exclude/) — targets Chrome 100 (which does NOT supportlight-dark()natively) withexclude: ['light-dark'], then asserts the CSS output preserves rawlight-dark()calls and does not contain transpilation markers.