Repository navigation
Conversation
Widen the @babel/core peer range to ^7.0.0 || ^8.0.0. Babel 8 removed the t.jSX* builder aliases, so the css prop transform now uses t.jsx*. Inline the JSX parser option instead of inheriting @babel/plugin-syntax-jsx, whose 7.x peer range rejects Babel 8 and made npm warn ERESOLVE. Pin preset-env modules to commonjs for the build so lib/ stays CommonJS once it is built with Babel 8. Babel 8 is ESM-only and cannot load inside Jest, so scripts/babel8.js runs the built plugin over the fixtures on Babel 8 and CI runs it. Closes styled-components#425
🦋 Changeset detectedLatest commit: 8bed53b 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 |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Widened
@babel/corepeer —^7.0.0 || ^8.0.0, and moved@babel/plugin-syntax-jsxfromdependenciestodevDependenciessince its^7.0.0-0peer was the install blocker. - Inlined the JSX syntax plugin — dropped
inherits: @babel/plugin-syntax-jsx;src/index.jsnow pushes thejsxparser plugin frommanipulateOptions, guarded on atypescriptparser plugin being present. This is exactly the body of@babel/plugin-syntax-jsx@7.28.6(confirmed innode_modules), so the parser behavior is unchanged. - Babel 8
cssprop fix —src/visitors/transpileCssProp.jsswaps the removedt.jSXIdentifier/jSXAttribute/jSXExpressionContainerbuilders for the lowercasejsx*names, which exist on both majors and emit an identical AST. - CommonJS build —
.babelrcsetsmodules: "commonjs"; builtlib/stays CJS. - Babel 8 test harness —
scripts/babel8.jsruns the builtlib/over the fixture suite on Babel 8 outside Jest, plus atest:babel8script, CI step, and AGENTS.md entry. Skips fixtures that also run preset-env/other transforms and one TS fixture Babel 8's parser rejects. - Regression coverage — new
typescript-parser-plugin-with-options-keeps-angle-bracket-assertionsfixture and aruntime dependenciestest that would have failed while@babel/plugin-syntax-jsxwas a runtime dependency. - Docs/metadata — README tested-against line and a
minorchangeset.
Verified locally: pnpm test (129 passed), pnpm build && pnpm test:babel8 (88 passed / 0 failed / 11 skipped on Babel 8.0.9), node scripts/compat.js (v5/v6/v7 all match base), and Prettier clean. One non-blocking note: the Babel 8 harness skips the headline transpile-css-prop*/add-display-names fixtures because they run preset-env, so their exact snapshots aren't exercised on Babel 8 — other css-prop fixtures do run and cover the jsxIdentifier change. The skip reasoning is documented in the script.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
Babel 8 validates t.identifier, so building an expression for a hyphenated tag threw even though the tag path never uses it. Build it only in the branch that needs it. scripts/babel8.js skipped the fixtures that cover this. Fixtures that use Babel's own presets or plugins now run with this plugin alone on both majors and must match.
There was a problem hiding this comment.
Important
The css-prop crash on Babel 8 is fixed for single-hyphen tag names, but custom-element names the tag regexp rejects still throw under Babel 8.
Reviewed changes
- Lazy
getNameExpression—src/visitors/transpileCssProp.jsnow builds the name expression inline inside the non-tag branch instead of unconditionally. Babel 8'st.identifiervalidates its argument, so the old unconditionalt.identifier("button-ghost")threw"button-ghost" is not a valid identifier name; Babel 7 does not validate. - Expanded Babel 8 harness —
scripts/babel8.jsno longer skips fixtures that also run preset-env or other transforms. It now runs the plugin alone (presets stripped, only this plugin kept) on Babel 7 and Babel 8 and compares the two outputs, so the skipped count drops from 11 to 1 (only the TS fixture Babel 8's parser rejects). - New fixture —
css-prop-on-hyphenated-elementlocks in the single-hyphen<button-ghost>case end to end.
Verified locally: pnpm test (130 passed) and pnpm build && pnpm test:babel8 (99 passed, 0 failed, 1 skipped on Babel 8.0.9).
The tag regexp is narrower than the set of valid custom-element names; the inline comment has the repro and the class of names it misses.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
| } else { | ||
| styled = t.callExpression(importName, [nameExpression]) | ||
| styled = t.callExpression(importName, [ | ||
| getNameExpression(elem.node.name, t, path), |
There was a problem hiding this comment.
TAG_NAME_REGEXP (/^[a-z][a-z\d]*(\-[a-z][a-z\d]*)?$/) matches only names with a single hyphen followed by a letter, so valid custom-element names like <my-foo-bar> or <foo-1> fall into this branch and getNameExpression builds t.identifier("my-foo-bar"), which Babel 8 rejects with "my-foo-bar" is not a valid identifier name. On Babel 7 these didn't throw but emitted broken _styled(my-foo-bar) (invalid JS), so this PR makes the single-hyphen case work while leaving the multi-hyphen / digit-segment class crashing. Treating any lowercase or hyphenated name as a string tag (Babel's own JSX heuristic) closes it.
Technical details
# Custom-element names that miss TAG_NAME_REGEXP throw on Babel 8
## Affected sites
- `src/visitors/transpileCssProp.js:11` — `TAG_NAME_REGEXP` only allows one optional `-segment` and requires a letter right after each hyphen
- `src/visitors/transpileCssProp.js:108-110` — non-tag branch calls `getNameExpression`, whose `t.identifier(node.name)` throws on Babel 8 for invalid identifiers
## Required outcome
- A `css` prop on any valid custom element (`<my-foo-bar>`, `<foo-1>`, `<my-element>`) must produce `styled("name")` on both Babel 7 and Babel 8, without throwing.
## Repro (built `lib/`, Babel 8.0.9)
- `<my-element css="color: red" />` -> OK, `styled("my-element")`
- `<my-foo-bar css="color: red" />` -> throws `"my-foo-bar" is not a valid identifier name`
- `<foo-1 css="color: red" />` -> throws `"foo-1" is not a valid identifier name`
## Suggested approach (optional)
- Broaden the tag test, e.g. treat the name as a string tag when it contains a hyphen or starts lowercase (`/^[a-z]/-ish`), matching how `@babel/plugin-transform-react-jsx` classifies intrinsic elements. Keep the `t.identifier` path for actual component references.
- Add a multi-hyphen fixture (`<my-foo-bar>`) next to the new single-hyphen one.
## Open questions for the human (optional)
- Is treating all hyphenated lowercase names as string tags acceptable, given Babel 7 previously emitted `_styled(my-foo-bar)` for these?Babel 8's TypeScript preset no longer removes the jsx parser plugin, so adding it broke angle-bracket assertions and generic arrow functions in .ts files with "Unterminated JSX contents". That preset also no longer adds jsx to .tsx files, so decide by file extension. Run .ts and .tsx through the real Babel 8 preset in scripts/babel8.js.
There was a problem hiding this comment.
ℹ️ No new issues in this delta. The prior review's custom-element tag-detection thread (src/visitors/transpileCssProp.js) is unchanged by these commits and remains open.
Reviewed changes
- Extension-aware
jsxparser guard —src/index.jsmanipulateOptionsnow also skips pushingjsxwhenopts.filenameends in.ts/.mts/.cts(/\.[cm]?ts$/), so Babel 8's TypeScript preset, which runs after this plugin and no longer stripsjsx, cannot turn<T>valueassertions into JSX parse errors..tsxstill getsjsx. - Babel 8 TypeScript harness —
scripts/babel8.jsnow runsTYPESCRIPT_CASES(a.ts,a.tsx) through@babel/preset-typescript@8against the builtlib/, backed by the newbabel8-preset-typescriptdevDependency alias. - Parser-option unit tests —
test/index.test.jsadds ajsx parser optionblock asserting.ts/.mts/.ctsstay off and.tsx/.jsturn on, using atypescript-plugin stub that runs after the plugin.
Verified locally: pnpm test (135 passed), pnpm build && pnpm test:babel8 (101 passed, 0 failed, 1 skipped on Babel 8.0.9), and the built plugin against the real @babel/preset-typescript@8 on Babel 8 for .ts and .tsx.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Closes #425
Babel 8 is out, and installing this plugin next to
@babel/core@8failed with a peer dependency error. This PR makes the plugin work on Babel 8 while keeping Babel 7 output exactly the same.What changed
@babel/core: ^7.0.0 || ^8.0.0.cssprop crash fixed. Babel 8 removed thet.jSXIdentifier-style builder aliases, so anycssprop threwt.jSXIdentifier is not a function. The plugin now usest.jsxIdentifier, which Babel 7 has too.@babel/plugin-syntax-jsxdependency. Its 7.x peer range only accepts Babel 7, so even with the wider range npm still warnedERESOLVE overriding peer dependencyand strict installs failed. The plugin now sets thejsxparser option itself, the same few lines that package ran..babelrcnow saysmodules: "commonjs". The builtlib/is byte-identical on Babel 7.How it was tested
pnpm testpasses, including a new test that every runtime dependency's@babel/corepeer accepts both majors, and a new fixture for the TypeScript parser case.pnpm build && pnpm test:babel8runs the built plugin over the same fixture files with Babel 8. CI runs it afterpnpm test.@babel/preset-envor other Babel transforms. Their snapshots are mostly Babel's own helpers and output defaults, which differ between majors. I diffed them by hand and the plugin's parts match.importinsidedeclare namespace) Babel 8's parser now rejects, as TypeScript does.npm installnext to@babel/core@8, and next to@babel/core@7. No warnings,npm lsis clean, and a small styled-components file transforms identically on both, includingcomponentId.node scripts/compat.js(styled-components v5, v6, v7) andpnpm benchshow no differences.This was developed with AI assistance, and I've reviewed and tested every change.
This is part of my Hacktoberfest contribution. If you're happy with it, would you mind adding the
hacktoberfest-acceptedlabel? Thank you for maintaining this plugin!