feat(css): scope counter names in CSS modules - #21600
Conversation
Localize `counter-reset` / `counter-increment` / `counter-set` names and their `counter()` / `counters()` / `target-counter()` references under `customIdents`, so two modules can no longer collide on a document-global counter. UA counters (`list-item`, `page`, `pages`) stay global. Also rewrite the `<counter-style>` argument of `counter()` / `counters()`: `@counter-style` preludes were already renamed, so a reference there kept naming the original and the custom style silently stopped applying.
The parser hand-listed the keywords of every property a `css/module` reads a scoped name out of — 55 predefined counter styles, the CSS-wide keywords and the animation / container / grid / view-transition / counter tables. Read them off each property's own grammar in the generator instead, so a spec change becomes a reviewable diff rather than a stale list, and emit them into `lib/css/data.js`. What the grammars do not state stays in `SUPPLEMENT`, each entry carrying its reason: the predefined counter styles and UA counters (`mdn-data` models both as a bare `<custom-ident>`), `@view-transition-group` and `masonry` (published grammars lag the spec), and the `grid` shorthand's own `auto-flow` keywords. Deriving the tables also fixes `animation: 3s auto`, where `auto` names the timeline and was localized as a keyframes name.
|
This PR is packaged and the instant preview is available (d6fbda7). Install it locally:
npm i -D webpack@https://pkg.pr.new/webpack@d6fbda7
yarn add -D webpack@https://pkg.pr.new/webpack@d6fbda7
pnpm add -D webpack@https://pkg.pr.new/webpack@d6fbda7 |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #21600 +/- ##
==========================================
- Coverage 94.11% 94.11% -0.01%
==========================================
Files 628 628
Lines 78139 78332 +193
Branches 22944 23047 +103
==========================================
+ Hits 73541 73719 +178
- Misses 4598 4613 +15
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…xture `ConfigCacheTestCases` keeps its own snapshots of the `css-modules` and `pure-css` stylesheets, so the `animation: 3s auto` fixture has to land in both suites.
🦋 Changeset detectedLatest commit: 52e411d The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Merging this PR will improve performance by 22.16%
Performance Changes
Tip Curious why this is faster? Comment Comparing |
Summary
Native CSS already scopes
@keyframes/@counter-style/@container/ view-transition names, but counter names were left global, so two modules that both writecounter-reset: sectioncollide on a document-global counter. Worse,@counter-stylepreludes are renamed whilecounter(x, thumbs)was not, so a scoped custom counter style silently stopped applying — Lightning CSS has the same bug. This scopes counter names and theircounter()/counters()/target-counter()references undercustomIdents, leaving UA counters (list-item,page,pages) global.It also moves the parser's hand-listed keyword tables (55 predefined counter styles, the CSS-wide keywords, and the animation / container / grid / view-transition tables) into
tooling/generate-css-data.js, derived from each property's ownmdn-datagrammar instead of typed out. That drops 316 lines fromlib/css/CssParser.jsand fixesanimation: 3s auto, whereautonames the timeline and was being localized as a keyframes name. Refs #14893.What kind of change does this PR introduce?
feat
Did you add tests for your changes?
Yes —
test/configCases/css/counter-scoping(declaration + reference scoping, counter-style argument,reversed(),@valuealiases, dashed names, reserved keywords,global()/local()escape hatches; dev, production andcustomIdents: false), plus ananimation: 3s autofixture intest/configCases/css/css-modules.Does this PR introduce a breaking change?
No API break, but a behavior change: counter names in CSS modules are now localized by default, like
@keyframes. A file that needs a shared counter should wrap the name in:global(...)/global(...)or setparser: { customIdents: false }.animation: … autois no longer exported as a keyframes name.If relevant, what needs to be documented once your changes are merged or what have you already documented?
The
examples/cssnative-CSS scoping reference now lists counter names. The Native CSS guide on webpack.js.org may want the same note.Use of AI
Yes. Implemented with AI assistance (Claude Code): a cross-bundler audit of CSS Modules support (running
lightningcssandpostcss-modulesdirectly to confirm the gap and the sharedcounter()bug), thelib/css/CssParser.jsscoping changes, the grammar-derived keyword tables intooling/generate-css-data.js, and the tests. The derived tables were diffed entry-by-entry against the previous hand-written ones (499 keywords, 16 intentional differences, each traced) before landing. All changes were verified locally: the CSS config cases, stats and unit suites pass,yarn fix:specialregenerates clean and idempotent, andtsc/ ESLint / Prettier / cspell are clean.Generated by Claude Code
Note
Medium Risk
Behavior change for CSS modules using custom counter names (now scoped by default) and for
animationshorthand parsing; well-covered by tests but affects emitted CSS for existing module stylesheets.Overview
Extends native CSS module scoping so counter names (
counter-reset/-increment/-setandcounter()/counters()/target-counter()/target-counters()) localize undercustomIdents, matching@counter-styleand fixing mismatched@counter-stylevscounter(x, style)renames. UA keywords (list-item,page, predefined styles, etc.) stay global; dashed names andglobal()/local()behave like other custom idents.Parser data refactor: inline keyword tables in
CssParser.jsmove intoCSS_MODULES_KEYWORDS/CSS_MODULES_KEYWORD_OPTIONSinlib/css/data.js(grammar-derived viatooling/generate-css-data.js). Known-property lookup usesMapvalues instead of plain objects.animation: 3s autono longer treatsautoas a keyframes name (timeline keyword).Adds
counter-scopingconfig tests and ananimationTimelinefixture; docs/changeset note counter scoping.Reviewed by Cursor Bugbot for commit 52e411d. Bugbot is set up for automated code reviews on this repo. Configure here.