Commit a40e3f7
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
- .changeset
- .github/workflows
- .yarn
- plugins/@yarnpkg
- releases
- src
- minify
- utils
- visitors
- test/fixtures
- add-display-names
- css-prop-mixed-with-transpile-template-literals-disabled
- css-prop-object-computed-key-with-nonprimitive-value
- css-prop-with-existing-styled-import
- css-prop-with-namespace-import
- minify-css-to-use-with-transpilation
- namespace-default-tag-form
- pre-transpiled
- transformed-imports-with-jsx-member-expressions
- transpile-css-prop-add-import
- transpile-css-prop-add-require
- transpile-css-prop-all-options-on
- transpile-css-prop
- transpile-template-literals-with-config
- transpile-template-literals-without-config
- withconfig-spread-and-css-prop
- withconfig-spread-with-transpile
- withconfig-string-literal-displayname
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
39 | 39 | | |
40 | 40 | | |
41 | 41 | | |
42 | | - | |
| 42 | + | |
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
46 | | - | |
| 46 | + | |
47 | 47 | | |
48 | 48 | | |
49 | 49 | | |
| |||
54 | 54 | | |
55 | 55 | | |
56 | 56 | | |
57 | | - | |
| 57 | + | |
58 | 58 | | |
59 | 59 | | |
60 | 60 | | |
| |||
68 | 68 | | |
69 | 69 | | |
70 | 70 | | |
71 | | - | |
| 71 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
10 | 10 | | |
11 | 11 | | |
12 | 12 | | |
13 | | - | |
| 13 | + | |
14 | 14 | | |
15 | | - | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
16 | 18 | | |
17 | 19 | | |
18 | | - | |
| 20 | + | |
19 | 21 | | |
20 | | - | |
| 22 | + | |
21 | 23 | | |
22 | 24 | | |
23 | 25 | | |
24 | | - | |
| 26 | + | |
25 | 27 | | |
26 | 28 | | |
27 | | - | |
| 29 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
5 | | - | |
6 | | - | |
7 | | - | |
8 | | - | |
9 | | - | |
10 | | - | |
11 | | - | |
12 | | - | |
13 | | - | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | | - | |
| 1 | + | |
This file was deleted.
This file was deleted.
0 commit comments