You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The current readPackage hook only acts when a package declares @types/x in its devDependencies. Many popular packages reference React types in their published .d.ts but declare @types/reactnowhere — not even in devDependencies (e.g. framer-motion, @emotion/react, @dnd-kit/*, recoil). For these, the plugin is a no-op, and TypeScript falls back to the hidden hoist directory (node_modules/.pnpm/node_modules/), picking an arbitrary @types/react version.
In a 138-workspace monorepo where @types/react 17/18/19 coexist, we measured that typecheck results depend on the install path (empty→full install passes; an install tree previously touched by --prod --filter fails with 609 TS2786 errors), because the hoist fallback content differs per install history. After adopting this plugin, we still had to hand-maintain 22 packageExtensions entries solely for packages with zero @types declarations — and that list rots as dependencies change.
Proposal
For every runtime peer dependency foo (skipping @types/* itself), if the manifest has no @types/foo in dependencies, peerDependencies, or peerDependenciesMeta, add:
Peer-depending on foo means "I use the consumer's copy of foo" — so the package's types should resolve to the consumer's @types/foo as well. This rule restores that correspondence at the declaration level.
Why optional peers are the right shape
pnpm decides link creation by declaration presence; optional vs required only gates the missing-peer warning. An optional peer is sufficient to make the package resolve the consumer's @types/react directly instead of the hoist fallback.
autoInstallPeers does not install optional peers, so JS-only consumers get no extra installs and no warnings — the rule is inert unless the consumer already provides the types.
Peers materialize as resolution suffixes in pnpm, so this rule:
changes resolution keys for every package that peer-depends on a typed runtime package (one-time lockfile churn on adoption), and
splits instances where multiple @types/x versions coexist — though in that scenario per-subtree types are arguably the correct outcome (it's what we reproduced manually with packageExtensions + '*').
Given that, I'd expect this to fit better as an opt-in flag on the plugin (or a sibling config dependency) rather than a default.
Has this direction been considered? And are there costs or failure modes I'm missing that led to the current devDependencies-signal-only design? Curious to hear your thoughts.
Problem
The current
readPackagehook only acts when a package declares@types/xin itsdevDependencies. Many popular packages reference React types in their published.d.tsbut declare@types/reactnowhere — not even indevDependencies(e.g.framer-motion,@emotion/react,@dnd-kit/*,recoil). For these, the plugin is a no-op, and TypeScript falls back to the hidden hoist directory (node_modules/.pnpm/node_modules/), picking an arbitrary@types/reactversion.In a 138-workspace monorepo where
@types/react17/18/19 coexist, we measured that typecheck results depend on the install path (empty→full install passes; an install tree previously touched by--prod --filterfails with 609TS2786errors), because the hoist fallback content differs per install history. After adopting this plugin, we still had to hand-maintain 22packageExtensionsentries solely for packages with zero@typesdeclarations — and that list rots as dependencies change.Proposal
For every runtime peer dependency
foo(skipping@types/*itself), if the manifest has no@types/fooindependencies,peerDependencies, orpeerDependenciesMeta, add:Peer-depending on
foomeans "I use the consumer's copy offoo" — so the package's types should resolve to the consumer's@types/fooas well. This rule restores that correspondence at the declaration level.Why optional peers are the right shape
@types/reactdirectly instead of the hoist fallback.autoInstallPeersdoes not install optional peers, so JS-only consumers get no extra installs and no warnings — the rule is inert unless the consumer already provides the types.peerDependencies, so it is unaffected by @pnpm/plugin-types-fixer does not work when packages are already cached in the store (bundledManifest strips devDependencies) pnpm#10698 (cached bundled manifests strippingdevDependenciesmakes the current rule non-deterministic).Known cost / open question
Peers materialize as resolution suffixes in pnpm, so this rule:
@types/xversions coexist — though in that scenario per-subtree types are arguably the correct outcome (it's what we reproduced manually withpackageExtensions+'*').Given that, I'd expect this to fit better as an opt-in flag on the plugin (or a sibling config dependency) rather than a default.
Has this direction been considered? And are there costs or failure modes I'm missing that led to the current devDependencies-signal-only design? Curious to hear your thoughts.