Repository navigation
feat: support Babel 8 - #431
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: 4e65dec 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) | 𝕏
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) | 𝕏
A name like my-foo-bar or foo-1 missed the tag pattern and was emitted as styled(my-foo-bar), which Babel 7 printed as a subtraction and Babel 8 rejects as an invalid identifier.
Names starting lowercase or that are not valid identifiers (clipPath, foo_bar, my-foo-bar) are elements, matching the JSX transform. A hyphenated member such as Icons.arrow-up becomes Icons["arrow-up"] instead of a subtraction.
There was a problem hiding this comment.
✅ No new issues found. The prior review's custom-element tag-detection finding is fixed by
ca1fff7/1d9bc52, and its thread is resolved.
Reviewed changes
- Replaced regex tag detection with JSX's own rule —
src/visitors/transpileCssProp.jsnow treats aJSXIdentifieras an intrinsic string tag whent.react.isCompatTag(name) || !t.isValidIdentifier(name), so<my-foo-bar>,<foo-1>,<clipPath>, and<foo_bar>compile tostyled("...")instead of a broken variable reference. This closes the prior review's open finding. - Computed member access for hyphenated members —
getNameExpressionemitsIcons["arrow-up"]for a member whose property is not a valid identifier, instead of the invalidIcons.arrow-up. - Expanded fixture —
css-prop-on-intrinsic-element-namescovers multi-hyphen, digit-segment, SVG camelCase, underscore, and hyphenated-member names end to end. - Harness/config cleanup —
scripts/babel8.jsmoves the skip check before reading.babelrc, guardsconfig.plugins, and renamespluginOnly->matchesBabel7; the changesets split into aminor(Babel 8) and apatch(css-prop names), dropping the now-inaccurate "Babel 7 output is unchanged" line.
Verified locally: pnpm test (135 passed) and pnpm build && pnpm test:babel8 (101 passed, 0 failed, 1 skipped on Babel 8.0.9). Confirmed t.react.isCompatTag and t.isValidIdentifier exist and behave identically on @babel/types 7.29.0 and 8.0.8, that the new rule matches @babel/plugin-transform-react-jsx's effective behavior, and that the new fixture actually runs on Babel 8 (not skipped).
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
|
@VibhuV1 thank you! |
<myButton css> after const myButton = styled.button styled the variable before this branch; the JSX element rule turned it into the unknown DOM element "myButton". A lowercase-first name that is not plain lowercase now becomes a string tag only when nothing in scope defines it.
quantizor
left a comment
There was a problem hiding this comment.
Thank you for the Babel 8 work and the thorough testing behind it!
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes
- Binding-aware tag detection —
src/visitors/transpileCssProp.jsnow classifies a lowercase-starting element name as a string tag unless it is a valid identifier with no in-scope binding. This restores the pre-PR resolution for camelCase / underscore names like<myButton>and<my_card>(which the prior commit'sisCompatTag-only rule had turned intostyled("myButton")), while unbound<clipPath>/<foo_bar>/ hyphenated names still become string tags. Pure-lowercase names stay string tags, matchingmain. - Regression fixture —
css-prop-on-intrinsic-element-namesadds boundmyButton/my_cardcases assertingstyled(myButton)/styled(my_card), and the changeset text is corrected to describe that bound-name behavior.
Verified locally: pnpm test (135 passed) and pnpm build && pnpm test:babel8 (101 passed, 0 failed, 1 skipped on Babel 8.0.9). Independently brute-forced the new rule against main's TAG_NAME_REGEXP over all lowercase-starting names: no case flips a main string tag into an identifier, no invalid JS is emitted on the non-tag path, and every difference is a main bug being fixed. t.react.isCompatTag behaves identically on @babel/types 7.29.0 and 8.0.9, and the fixture round-trips.
deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏
|
Thank you so much, @quantizor! This was my first contribution here and I learned a lot. Thanks too for handling the custom-element names on top of it. Happy to help with anything else Babel 8 related. |

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!