Skip to content

feat(css): emit named exports for JS keyword class names in CSS modules#22393

Open
semimikoh wants to merge 3 commits into
vitejs:mainfrom
semimikoh:fix/css-modules-keyword-named-exports
Open

feat(css): emit named exports for JS keyword class names in CSS modules#22393
semimikoh wants to merge 3 commits into
vitejs:mainfrom
semimikoh:fix/css-modules-keyword-named-exports

Conversation

@semimikoh

@semimikoh semimikoh commented May 5, 2026

Copy link
Copy Markdown
Contributor

CSS modules containing class names that match JavaScript reserved keywords
(e.g. switch, if, for, class) were not emitted as named exports,
making them accessible only via the default export object.

Summary

  • dataToEsm skips named export generation when a key fails the
    key === makeLegalIdentifier(key) check — reserved words like switch
    get prefixed to _switch, so they never match
  • Added includeArbitraryNames: true option to the dataToEsm call in
    cssPostPlugin, which emits string-named exports for such keys:
    export { _arbitrary0 as "switch" }
  • Reserved keyword class names are now accessible via namespace import
    (styles.switch) or string literal import (import { "switch" as s })

Related

Fixes #14050

Test plan

Added playground test case with a CSS module containing a .switch class,
verifying it is correctly applied via import * as keywordMod and
keywordMod.switch

@sapphi-red sapphi-red left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will break existing apps when the app targets browsers that does not support the arbitrary names feature.
I guess we need to conditionally enable this when the target supports them. or maybe we can transpile it somehow.

@sapphi-red sapphi-red changed the title fix(css): emit named exports for JS keyword class names in CSS modules feat(css): emit named exports for JS keyword class names in CSS modules May 21, 2026
@sapphi-red sapphi-red added feat: css p2-nice-to-have Not breaking anything but nice to have (priority) labels May 21, 2026
@semimikoh semimikoh force-pushed the fix/css-modules-keyword-named-exports branch from f216993 to cd583a2 Compare May 26, 2026 01:10
@semimikoh

Copy link
Copy Markdown
Contributor Author

Thanks for the review, good catch! @sapphi-red

I've updated this to conditionally enable arbitrary named exports based on build.target:

  • When the target supports arbitrary module namespace identifier names, the output is unchanged (export { _x as "foo-bar" }).
  • When it doesn't, reserved-word class names (e.g. switch, default) are still emitted as named exports via the ES2015-safe export { _x as switch } form (valid since ES2015). Only class names with illegal identifier characters (e.g. foo-bar) fall back to default-export-only access, matching the previous behavior.

The support boundaries match esbuild's compat table for ArbitraryModuleNamespaceNames (Chrome/Edge 90, Firefox 87, Safari 14.1, iOS 14.5, Node 16, ES2022).

Added tests for both supported (es2022) and unsupported (es2021) targets, plus the version-boundary detection.

@sapphi-red

Copy link
Copy Markdown
Member
  • reserved-word class names (e.g. switch, default) are still emitted as named exports via the ES2015-safe export { _x as switch } form (valid since ES2015).

Would you send this change to @rollup/pluginutils? I think it's better to live there instead of having it here directly. We can patch it until it's merged.

@semimikoh

Copy link
Copy Markdown
Contributor Author

@sapphi-red
Moved the handling into @rollup/pluginutils as requested — rollup/plugins#2002.

dataToEsm now emits the ES2015-safe export { _x as switch } (unquoted) form for keys that are valid IdentifierNames (reserved words / globals like switch, await), while the quoted arbitrary-namespace form (export { _x as "foo-bar" }, ES2022+) stays gated behind includeArbitraryNames. So Vite no longer needs to assemble those exports by hand.

While there I also fixed a latent bug: a default key produced a duplicate default export (SyntaxError: Duplicate export of 'default'). Filed as rollup/plugins#2001 and resolved by the same PR.

As you suggested, I've patched @rollup/pluginutils locally until that's released and simplified css.ts to delegate to dataToEsm (the manual export assembly is gone). I'll drop the patch and bump the dependency once #2002 ships.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat: css p2-nice-to-have Not breaking anything but nice to have (priority)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Reserved JS keywords are not allowed as CSS modules class names

2 participants