Repository navigation
fix: declare the @types packages the published types import - #5821
unrevised6419 wants to merge 1 commit into
Conversation
🦋 Changeset detectedLatest commit: 5f34da4 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 |
4af2244 to
f1af2f6
Compare
There was a problem hiding this comment.
ℹ️ No blocking issues. One ordering nit and a couple of test-quality notes inline.
Reviewed changes
- Optional
@types/*peers —@types/node,@types/react,@types/react-domare declared as optional peer dependencies so TypeScript resolves the packages the published.d.tsfiles import from wherestyled-componentsphysically lives, not only under a hoisted tree. packaging-types.test.ts— new suite locking that every dependency/peer shipping no types has an@typessibling declared, that@types/nodeis present while sources nameNodeJS., and that every@typespeer is marked optional.- Changeset — patch entry describing the consumer-visible fix.
I verified the peer additions do not require a lockfile change (pnpm install --frozen-lockfile reports "Lockfile is up to date", since the @types/* are already devDependencies and pnpm dedups them), confirmed each dependency/peer's type-shipping status, and checked there is no stylis type import and no other NodeJS. use outside ServerStyleSheet.tsx.
ℹ️ The new test guards the manifest, not the resolution behavior
The regression only surfaces under an isolated linker with skipLibCheck: false; the jest suite runs hoisted, so packaging-types.test.ts can assert the package.json contract but not that tsc actually resolves react, react-dom/server and NodeJS from the package location. That is an acceptable scope given the manual packed-tarball verification in the PR description — flagging it only so the guard is not mistaken for behavioral coverage of the consumer failure mode.
ℹ️ Nitpicks
- The test docblock says the assertions lock "anything the public types reach for is named in
package.json". The firstitonly reaches packages already listed independencies/peerDependencies, so it would not catch a brand-new external import that is undeclared entirely. Narrowing that wording would keep the comment honest. shipsOwnTypescallsrequire.resolve(\${name}/package.json`)` for every dependency and peer. If an optional peer is ever absent from a given install layout, the test throws instead of reporting the offending package. A try/catch that fails with the package name would make the failure legible.
DeepSeek Flash (free via Pullfrog for OSS) | 𝕏
The shipped .d.ts files import React's types, PipeableStream from react-dom/server and the NodeJS stream globals without declaring any of them. A hoisted node_modules resolves those from the consuming app by accident; an isolated layout does not, and every styled component's props degrade to any.
f1af2f6 to
5f34da4
Compare
There was a problem hiding this comment.
ℹ️ No critical issues — one observation below.
Reviewed changes
- Raised the
@types/reactand@types/react-dompeer floors from*to>= 19.0.0, matching thereact/react-domruntime peers. - Removed
packages/styled-components/src/test/packaging-types.test.ts, the manifest-contract suite that guarded the@types/*declarations. - Moved the three
@types/*keys to the head ofpeerDependenciesandpeerDependenciesMeta, so the two objects agree and sort first (addresses the prior review's ordering nit).
I verified the floor change does not re-break resolution: installing the package into a scratch pnpm 10 consumer under an isolated virtual store, a @types/react@18 consumer with the >= 19 peer still has its copy linked into the package's virtual-store node_modules, so TypeScript resolves it. An absent optional peer emits no warning, so the JS-only consumer claim holds.
ℹ️ The manifest contract is no longer guarded
Deleting packaging-types.test.ts leaves nothing in the suite that fails if the @types/* peers are dropped, reordered, or stripped of peerDependenciesMeta.optional. treeshake.test.ts already asserts package.json shape (sideEffects, the browser field, subpath entries) and is the natural home. The caveat from the prior review still stands — jest runs hoisted, so a manifest test cannot prove isolated-linker resolution — but it would pin the declaration this fix depends on, which the repo's strict-TDD mandate otherwise expects.
Technical details
# Restore a manifest guard for the `@types/*` peers
## Affected sites
- `packages/styled-components/src/test/packaging-types.test.ts` — deleted; was the only test asserting the `@types/*` peers and their optional flags.
- `packages/styled-components/src/test/treeshake.test.ts:642` — existing precedent for `package.json`-shape assertions.
## Required outcome
- A suite-level test that fails if any of `@types/react`, `@types/react-dom`, `@types/node` is removed from `peerDependencies`, or loses its `peerDependenciesMeta.optional: true`, or if any untyped dependency lacks a declared `@types` sibling.
## Open questions for the human
- Is the manifest guard wanted at all, or is the packed-tarball manual verification in the PR body considered sufficient for a metadata-only change?DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Problem
The shipped
.d.tsfiles reference types the package never declares:react→ needs@types/reactreact-dom/server(PipeableStreaminServerStyleSheet) → needs@types/react-domNodeJS.ReadableStream/NodeJS.ReadWriteStreamglobals in the same public signature → needs@types/nodeUnder a hoisted
node_modulesthese resolve by accident, from the consuming app's own tree. Under an isolated layout they do not — pnpm's default linker with a global virtual store links each package from a store path outside the consuming repo, and TypeScript resolves a package's imports from where that package physically lives.reactthen lands on the untypedreact/index.js, every styled component's props collapse toany, andnoImplicitAnyconsumers get errors thatskipLibCheck: truehad been hiding inside the.d.ts.Reported from a consumer app that hit it on 6.5.3 and worked around it with a
packageExtensionsentry.Fix
@types/react,@types/react-domand@types/nodeare added as optional peer dependencies. Thereact/react-domranges are unchanged. Optional matters: JavaScript-only consumers must not get an unmet-peer warning, and a fresh install of a JS-only consumer confirms none is emitted.The two React types floors mirror the runtime ones at
>= 19.0.0, which is also what the public types need: they nameReact.JSX.ElementandReact.JSX.IntrinsicElements, and@types/react-domconstrains@types/reactitself.@types/nodestays*on purpose — the only thing the types reach for is theNodeJSstream globals, present in every major, so a floor there would assert a constraint the package does not have and would age into a false warning.No
@types/stylis— v7 emits CSS through the in-house parser and has no stylis dependency;dependenciesiscsstypealone.Verification
Packed the tarball and installed it into two scratch consumers under pnpm with the virtual store outside the project (
enableGlobalVirtualStore), typechecking withskipLibCheck: falseandnoImplicitAny:Without the declarations:
With them,
tscexits 0 and--traceResolutionshowsreact,react-dom/serverandcsstypeeach resolving to a.d.ts.The
@ts-expect-errorform is deliberate — a positive assertion keeps passing once the types widen toany, so it would not catch a regression.Full suites pass on the change:
typecheck,test:types, 1473 web tests, 2527 native tests.No automated test ships with this. A manifest-only assertion would restate
package.jsonwithout proving the types resolve, and the check that does prove it — pack, install into an isolated-store consumer, typecheck withskipLibCheck: false— needs a real install per run. Happy to add it to the build suite if you want that cost.Out of scope, noted while sweeping
publintflags ESM files served as CJS and the absentexportsmap;attwflags CJS default-export interop on thenativesubpaths. Both predate this change and are breaking to fix, so they are left alone.