Skip to content

Commit a40e3f7

Browse files
quantizorclaude
andauthored
chore: modernize dependencies and fix css prop import detection (#418)
* build: switch from yarn to pnpm and modernize toolchain - Drop yarn 3 (.yarn/, .yarnrc.yml, yarn.lock) for pnpm via the packageManager field, with pnpm-workspace.yaml setting minimumReleaseAge to 4320 minutes (3 days) as a supply-chain guard - Bump Babel deps to ^7.26 across the board; replace the deprecated @babel/plugin-proposal-class-properties with @babel/plugin-transform-class-properties (and update fixture .babelrc files accordingly) - Bump prettier to ^3.4, rimraf to ^6.0, picomatch to ^4.0, jest to ^29.7, styled-components dev dep to ^6.1.14 - Drop the lodash runtime dependency; inline a four-line difference() helper for the single call site in src/minify - Add @babel/core to peerDependencies (fixes peer-dep warnings under strict resolvers) - Add @changesets/cli devDep (pinned 2.31.0), .changeset/{README.md,config.json} scaffolding, changeset/changeset-publish scripts, and a release workflow driven by changesets/action@v1 - Rename prepublish to prepublishOnly; switch internal script references from yarn to pnpm - Refresh fixture snapshots to absorb cosmetic Babel helper renames (e.g. obj → e) and uid-numbering changes introduced by the newer Babel * ci: bump Node matrix to 22/24 and refresh action versions - pnpm 11.x requires Node 22.13+, so the matrix drops Node 16/18/19 in favor of the current LTS line (22/24) - actions/checkout@v3 → v4, actions/setup-node@v3 → v4 (with cache: pnpm), github/codeql-action@v2 → v3 - Add pnpm/action-setup@v4 to the test job - .nvmrc bumped to v22 to match * fix: css-prop transforms must stay correct across repeated and edge-case usages The guard around `addDefault('styled-components')` in transpileCssProp checked an Identifier object as if it were a string key (`!bindings[importName]`), which was always truthy. Under newer @babel/helper-module-imports this means every css={…} usage adds a fresh aliased default import (`_styled`, `_styled1`, …), and importLocalName's cache then reports the alias as the local default name. Downstream isStyled() stops matching the user's `styled.div`, so displayName and componentId quietly disappear. Repeated css={…} usage in a single file with an existing `import styled from 'styled-components'` was effectively broken. - Replace the broken guard with one that resolves importName to a string and checks `bindings[importBindingName]` - Eagerly apply the styled-component visitors (minify/displayNameAndId/templateLiterals/pureAnnotation) on the variable inserted by the css-prop transform, since Babel does not reliably re-visit nodes pushed onto Program from inside a Program-enter sub-traversal - Preserve `computed` and `shorthand` on the rewritten ObjectProperty so that `css={{ [foo]: bar }}` with a non-primitive value keeps its bracketed key (previously failed Babel's validator with "Property key of ObjectProperty expected node to be of a type […] but instead got 'MemberExpression'") - Thread the visitor path through transpileCssProp's local getName/getNameExpression so the error path uses the right buildCodeFrameError (previously referenced an out-of-scope `path` and would throw ReferenceError instead of the friendly error) - Drop a trailing isStyled arm that crashed when reached (dereferenced tag.object on a CallExpression); fixing the typo correctly exposed a cascading addConfig over-application not in scope here, no fixture reached the arm - Extract the shared styled-visitor dispatch into src/visitors/process.js so index.js and the css-prop eager pass share one definition - Move importLocalName's cache from a process-wide module-level map (cross-file bleed and unbounded growth in long-running watchers) to state.file.set/get; drop the cacheIdentifier no-op option - Hoist isStyled's repeated importLocalName('default', state) calls into a single local; pre-curry every visitor at plugin init so per-node visits skip closure allocation - Drop the dead isInjectGlobalHelper detector (styled-components v2 API, removed years ago) - Add regression fixtures: css-prop-with-existing-styled-import, namespace-default-tag-form, css-prop-object-computed-key-with-nonprimitive-value * chore: add changeset * docs: add AGENTS.md with project conventions for AI agents * docs(agents): drop owner section and AI-tool-specific references * docs(agents): cut decision-log content, keep operational guidance * ci(release): drop NPM_TOKEN, rely on OIDC trusted publishing * fix(css-prop): make displayNameAndId idempotent for already-configured TaggedTemplate tags When transpileTemplateLiterals is off, both the css-prop pre-numbering pass and Babel's main TaggedTemplateExpression traversal fire displayNameAndId on the same user-written styled component. The existing idempotency guards only covered the CallExpression shape (via getExistingConfig), so a TaggedTemplate whose tag was already wrapped with .withConfig would get wrapped again, incrementing the componentId counter every time and producing nested .withConfig chains. Add an early-exit when path.node.tag is `X.withConfig({...})` and the existing arguments object already carries displayName/componentId. Cover with a regression fixture (css-prop-mixed-with-transpile-template-literals-disabled). Also rewrites the changeset to describe user-facing outcomes instead of internals. * fix(css-prop): handle namespace-only styled-components imports When the only styled-components binding in scope is a namespace import (`import * as styled from 'styled-components'`), the css-prop transform was emitting `styled("div")…`, which fails at runtime because the namespace object isn't callable — the callable lives on `styled.default`. Targeting `<ns>.default` directly triggers the recursive `isStyled` cascade that re-wraps tags with `.withConfig` over and over (the same shape removed in an earlier commit), so instead treat a namespace-only binding as equivalent to "no usable default" and let `addDefault` inject a fresh callable import to route the css-prop component through. The local-name scan in `importLocalName` also needed a small fix to stop the namespace specifier from clobbering a previously-discovered default specifier in the same file — otherwise the post-`addDefault` bypass-cache lookup would still resolve to the namespace name. Also tightens two small things from the same review pass: the `Cannot infer name` error message now points at a fully-qualified `https://github.com/...` URL so it auto-links in terminals, and `taggedTagAlreadyConfigured` is called as `(t, path)` instead of `(t)(path)` to avoid a fresh closure allocation per visit in the hot path. Adds a regression fixture (css-prop-with-namespace-import) covering the namespace + css-prop combination. * docs(agents): require red/green TDD for fixes * fix(displayNameAndId): tolerate spread and non-identifier keys in existing withConfig `taggedTagAlreadyConfigured` was reading `prop.key.name` on every entry of the withConfig argument's `.properties`, which crashed (`Cannot read properties of undefined (reading 'name')`) on shapes like `styled.div.withConfig({ ...cfg })\`...\`` because SpreadElement has no `.key`. Same crash for string-literal keys. Guard the predicate with `t.isObjectProperty(prop) && t.isIdentifier(prop.key)` before reading `.key.name`. Red/green verified with the new `withconfig-spread-and-css-prop` regression fixture (covers both `{ ...cfg }` and `{ 'string-key': … }` shapes mixed with a css-prop usage that triggers the pre-numbering pass). Also clarifies the changeset rule in AGENTS.md: declaring an implicit peer dependency that consumers must already have in order to use the plugin is not by itself a major release; only changes that actually break working consumers are. * fix(displayNameAndId): tolerate spread/non-identifier keys in every withConfig predicate The previous commit guarded `taggedTagAlreadyConfigured` against SpreadElement and non-Identifier keys, but three identical `properties.some(prop => […].includes(prop.key.name))` checks remained — two inside `addConfig` and one in the outer visitor's `styled(x).withConfig({…})` arm — and would still throw on `withConfig({ ...spread })` configurations. Extract a shared `hasDisplayNameOrComponentId` predicate that filters to ObjectProperty entries with Identifier keys, and use it everywhere. Red/green verified with a pre-transpiled call-form fixture (`withconfig-spread-with-transpile`) that previously crashed inside the outer visitor predicate. * fix(displayNameAndId): detect displayName/componentId set via string-literal keys `hasDisplayNameOrComponentId` only matched non-computed Identifier keys, so `withConfig({ 'displayName': 'X' })`, `withConfig({ ['displayName']: 'X' })`, and the corresponding `componentId` shapes were treated as not-yet-configured. The visitor then wrapped tagged templates with a second `.withConfig({...})` whose auto-generated `displayName` overrode the user's value at runtime (later withConfig calls compose and the last one wins), and on the pre-transpiled call form it pushed straight into the user's argument object, emitting a duplicate `displayName` key. Recognize StringLiteral keys (covering both quoted and computed-string forms) in addition to plain Identifier keys, and skip computed Identifier keys (e.g. `{ [varName]: 'X' }`) since those resolve dynamically. Adds the `withconfig-string-literal-displayname` regression fixture covering all four shapes against the pre-fix tree. * fix(css-prop): clear shorthand flag when rewriting ObjectProperty value Propagating `property.shorthand` produced an ObjectProperty with `shorthand: true` but a non-Identifier value (`p.$_cssN`) on inputs like `css={{ color }}`, violating the Babel invariant that shorthand requires `value` to be the same Identifier as `key`. Babel's codegen happens to be lenient and emits the long form anyway, so no snapshot changes, but stricter validators or downstream plugins that branch on `shorthand` could trip. Force-clear the flag on the rewrite branch; the surrounding fixtures (including the `color` shorthand in `transpile-css-prop`) cover the output. * perf(css-prop): pre-number existing Program.body before injecting The pre-numbering pass previously ran after pushing the new styled-component declaration, so the just-injected node was visited twice: once by the Program traverse and once by the explicit loop over insertedPaths. The second pass was a no-op thanks to the displayNameAndId idempotency guards, but the redundant traverse + visitor invocation still ran for every css prop in the file. Swap the order: pre-number existing user-written components first, then inject, then visit the inserted declaration exactly once. Snapshot tests confirm the counter assignments are unchanged. --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 4e2eb38 commit a40e3f7

56 files changed

Lines changed: 5586 additions & 7271 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Changesets
2+
3+
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4+
with multi-package repos, or single-package repos to help you version and publish your code. You can
5+
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6+
7+
We have a quick list of common questions to get you started engaging with this project in
8+
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

.changeset/config.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
3+
"changelog": "@changesets/cli/changelog",
4+
"commit": false,
5+
"fixed": [],
6+
"linked": [],
7+
"access": "public",
8+
"baseBranch": "main",
9+
"updateInternalDependencies": "patch",
10+
"ignore": []
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"babel-plugin-styled-components": minor
3+
---
4+
5+
Refresh the toolchain and fix a handful of css-prop transform bugs that had crept in under recent Babel versions.
6+
7+
- When a file already imports `styled` and also uses one or more `css={…}` props, every styled component now keeps its display name and stable component id. Previously the cache that tracks the local default import could be overwritten on each css-prop usage, which silently dropped the display name and id for the surrounding `styled.div` declarations.
8+
- `css={{ [foo]: bar }}` with a non-primitive value no longer fails Babel's validator. Computed keys are preserved through the css-prop object rewrite.
9+
- Friendlier error messages when the css-prop transform encounters a JSX name shape it can't infer, instead of a confusing internal `ReferenceError`.
10+
- Long-running watch processes (Next dev, webpack-dev-server, jest watch) no longer leak import-detection state between files.
11+
- Removed the runtime `lodash` dependency. The plugin now ships with `@babel/core` as a declared peer.
12+
- Dev tooling moved to pnpm and changesets. Plugin behavior is unchanged.

.github/workflows/codeql-analysis.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,11 @@ jobs:
3939

4040
steps:
4141
- name: Checkout repository
42-
uses: actions/checkout@v3
42+
uses: actions/checkout@v4
4343

4444
# Initializes the CodeQL tools for scanning.
4545
- name: Initialize CodeQL
46-
uses: github/codeql-action/init@v2
46+
uses: github/codeql-action/init@v3
4747
with:
4848
languages: ${{ matrix.language }}
4949
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -54,7 +54,7 @@ jobs:
5454
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
5555
# If this step fails, then you should remove it and run the build manually (see below)
5656
- name: Autobuild
57-
uses: github/codeql-action/autobuild@v2
57+
uses: github/codeql-action/autobuild@v3
5858

5959
# ℹ️ Command-line programs to run using the OS shell.
6060
# 📚 https://git.io/JvXDl
@@ -68,4 +68,4 @@ jobs:
6868
# make release
6969

7070
- name: Perform CodeQL Analysis
71-
uses: github/codeql-action/analyze@v2
71+
uses: github/codeql-action/analyze@v3

.github/workflows/release.yml

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: release
2+
3+
on:
4+
push:
5+
branches:
6+
- 'main'
7+
8+
concurrency: ${{ github.workflow }}-${{ github.ref }}
9+
10+
jobs:
11+
release:
12+
runs-on: ubuntu-latest
13+
permissions:
14+
contents: write
15+
pull-requests: write
16+
id-token: write
17+
steps:
18+
- uses: actions/checkout@v4
19+
20+
- uses: pnpm/action-setup@v4
21+
22+
- uses: actions/setup-node@v4
23+
with:
24+
cache: pnpm
25+
node-version: '22'
26+
registry-url: 'https://registry.npmjs.org'
27+
28+
- name: Install dependencies
29+
run: pnpm install --frozen-lockfile
30+
31+
- name: Unit tests
32+
run: pnpm test
33+
34+
- name: Create Release Pull Request
35+
id: changesets
36+
uses: changesets/action@v1
37+
env:
38+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
39+
with:
40+
createGithubReleases: true
41+
publish: pnpm changeset-publish

.github/workflows/test.yml

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,20 @@ jobs:
1010
runs-on: ubuntu-latest
1111
strategy:
1212
matrix:
13-
node: ["16", "18", "19"]
13+
node: ["22", "24"]
1414
steps:
15-
- uses: actions/checkout@v3
15+
- uses: actions/checkout@v4
16+
17+
- uses: pnpm/action-setup@v4
1618

1719
- name: Setup node
18-
uses: actions/setup-node@v3
20+
uses: actions/setup-node@v4
1921
with:
20-
cache: yarn
22+
cache: pnpm
2123
node-version: ${{ matrix.node }}
2224

2325
- name: Install modules
24-
run: yarn --immutable
26+
run: pnpm install --frozen-lockfile
2527

2628
- name: Run tests
27-
run: yarn test
29+
run: pnpm test

.gitignore

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,3 @@ node_modules
22
*.log
33
lib
44
.DS_Store
5-
6-
# yarn 3
7-
.pnp.*
8-
.yarn/*
9-
!.yarn/patches
10-
!.yarn/plugins
11-
!.yarn/releases
12-
!.yarn/sdks
13-
!.yarn/versions

.nvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v16
1+
v22

.yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs

Lines changed: 0 additions & 541 deletions
This file was deleted.

.yarn/releases/yarn-3.5.0.cjs

Lines changed: 0 additions & 873 deletions
This file was deleted.

0 commit comments

Comments
 (0)