[*] Bug Fix: Surface a clear error when TypeScript (<5.2) can't read the package exports#8628
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
e714096 to
4bb8491
Compare
…kage exports
Lexical publishes its declaration files exclusively through the package.json
"exports" map. A consumer using classic moduleResolution (or TypeScript < 4.7)
ignores "exports", so every subpath import such as
`@lexical/react/ReactExtension` resolved to nothing and reported a misleading
`TS2307: Cannot find module ... or its corresponding type declarations`, which
looks like a missing install.
Point the legacy type-resolution fields at a generated "too old" stub so those
consumers get an actionable upgrade message instead:
- `types` and `typesVersions` (`"*": {"*": [stub]}`) redirect the package root
and every subpath for any TypeScript that consults them (i.e. that is not
resolving types through "exports").
- A `types@<5.2` condition, ordered before `types` in every exports entry,
redirects consumers that do read "exports" but are below the minimum
supported TypeScript version (4.9+ understands it).
Modern resolvers (node16/nodenext/bundler on TypeScript >= 4.9) ignore the
legacy fields because "exports" takes priority, so they continue to resolve
the real types unchanged. The stub is emitted into each package's dist during
the build; update-version maintains the package.json fields.
The 5.2 floor is the empirically measured lower bound: node16/nodenext works
at 4.7 with skipLibCheck, `moduleResolution: bundler` needs 5.0, and
`skipLibCheck: false` needs 5.2 because the public `LexicalEditorWithDispose`
interface extends the global `Disposable` type, which only exists in the
TypeScript 5.2 lib.
Also generate an optional `typescript` peer dependency (`>=5.2`, marked
optional) on every public package so npm/pnpm emit an install-time warning
for an out-of-range TypeScript, complementing the type-check-time guard. The
lockfile is updated to match.
The single-entry exports branch now derives the declaration basename from the
entry source rather than the `types` field (which the stub overwrites), keeping
update-version idempotent.
https://claude.ai/code/session_017CtQcG8G9i6vVTSaMTKj93
4bb8491 to
937f037
Compare
Review: TypeScript Version GateVerdict: Safe to approve ✅ What I verified:
The MIN_TYPESCRIPT_VERSION = 5.2 rationale is well-documented (Disposable interface in lib requires ≥ 5.2 when Review by Navi (potatowagon's AI assistant) |
Description
Lexical publishes its TypeScript declarations exclusively through the package.json
"exports"map. A consumer using classicmoduleResolution(or TypeScript older than 4.7) ignores"exports"entirely, so every subpath import — e.g.@lexical/react/ReactExtension— resolves to nothing and reports:That phrasing looks like a missing install, so it sends people down the wrong path (reinstalling, checking dependencies) instead of fixing the real cause: their TypeScript is too old or misconfigured.
This points the legacy type-resolution fields at a generated "too old" stub so those consumers get an actionable upgrade message instead. It's safe because, since TypeScript 4.9,
"exports"takes priority overtypes/typesVersions— so modern resolvers never see the stub, and only the consumers we want to fail resolve it.Generated by
scripts/updateVersion.mjsfor every public package:types+typesVersions("*": { "*": [stub] }) redirect the package root and every subpath for any TypeScript that consults them (i.e. any TypeScript not resolving types through"exports", at any version).types@<5.2, ordered immediately beforetypesin each exports entry, redirects consumers that do read"exports"but are below the minimum supported version (TypeScript 4.9+ understands thetypes@selector).typescriptpeer dependency (">=5.2",peerDependenciesMeta.typescript.optional) so npm/pnpm emit an install-time warning for an out-of-range TypeScript, complementing the type-check-time guard.The stub (
dist/typescript-too-old.d.ts) is emitted byscripts/build.mjs; it carries an explanatory comment plus a deliberate diagnostic.Why 5.2? It's the empirically measured lower bound across consumer configurations:
moduleResolution: node"exports")node16/nodenext+skipLibCheck: truemoduleResolution: bundlerskipLibCheck: falseThe
skipLibCheck: falsefloor is 5.2 because the publicLexicalEditorWithDisposeinterface extends the globalDisposabletype, which only exists in the TypeScript 5.2 lib. 5.2 is therefore the lowest version that works in every supported configuration.This also fixes a latent idempotency bug surfaced by the change: the single-entry exports branch derived the declaration basename from
packageJson.types, which the stub now overwrites — so a secondupdate-versionrun pointed the realtypescondition at the stub. It now derives the basename from the entry source instead, and the generator is idempotent from a clean checkout.Test plan
Validated on TypeScript 4.4 – 6.0 against the real built packages, plus new per-package audits in
scripts/__tests__/unit/build.test.ts.Before
A classic-resolution consumer (TypeScript 4.4, or any version with
moduleResolution: node) importing a subpath:After
dist/typescript-too-old.d.ts(no more "Cannot find module"); the file documents the fix, andskipLibCheck: falseconsumers also seeLexical requires TypeScript >=5.2 with moduleResolution bundler, node16, or nodenext.pnpm exec vitest --project scripts-unit— 860 passing (includes new guard + peer-dep audits across all public packages)pnpm run tsc-scripts— cleannode ./scripts/updateVersion.mjsis idempotent from a clean checkout (identical output on repeated runs)