Skip to content

chore(glyph): drop the @types/three patch and unship typescript - #83

Merged
thejustinwalsh merged 1 commit into
mainfrom
chore/drop-types-three-patch
Aug 20, 2026
Merged

chore(glyph): drop the @types/three patch and unship typescript#83
thejustinwalsh merged 1 commit into
mainfrom
chore/drop-types-three-patch

Conversation

@thejustinwalsh

@thejustinwalsh thejustinwalsh commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

Two dependency problems that both landed on consumers. Found while auditing canary readiness; fixed rather than filed.

TypeScript was a hard runtime dependency

Every consumer downloaded typescript@7.0.2 — a 3.5 MB alpha — to render text.

The module graph was already correct. The built dist/node/bake.js reaches discovery only through a dynamic import, and nothing outside compiler-adapter references the compiler:

dist/node/bake.js:102   await import('../discovery.js')   ← only runtime edge

Discovery is reachable only from the Node-only ./bake subpath, and nothing in the browser graph touches the compiler. So this was a manifest bug, not an architecture problem, and needed no restructuring.

It becomes a devDependency, not a peer. An exact 7.0.2 peer would demand every consumer's project sit on a TypeScript alpha and warn at all of them — and it only mirrored an assertion the code already makes:

// compiler-adapter.ts:22
if (compilerVersion !== supportedTypeScriptVersion) {
  throw new Error(`@pmndrs/glyph discovery supports TypeScript ${supportedTypeScriptVersion}; received ${compilerVersion}`);
}

That runtime check is the real contract. Project-discovery bake resolves the compiler from wherever the consumer has it; everyone else never sees it.

For reference, @react-three/fiber@10.0.0-alpha.2 declares typescript in no field at all and its shipped dist/ contains zero references to it — TypeScript is purely a build tool at their monorepo root.

The @types/three patch is deleted, because the fix is upstream

The 171-line patch existed for a real reason: Node<T> was a 20-deep nested conditional chain that made ordinary TSL expressions expand pathologically. And because Node<T> is a type alias, module augmentation could never have replaced it — TypeScript can't augment type aliases. That's why it was a patch and not a .d.ts in our tree.

That fix landed upstream in @types/three@0.185.4:

0.185.1 (our pin) 0.185.4
interface NodeExtras absent present
NodeExtras[TNodeType] indexed access absent present
old nested conditional chain present removed

We were one patch release behind a fix we were carrying locally. All four workspace pins move to 0.185.4 and the patch is deleted.

Two residuals, handled internally

Not upstream yet:

  • modelViewProjection is still declared as an unparameterized Node. Narrowed once at the boundary in tsl/bitmap-shader.ts rather than cast at each use.
  • The Loop hunk turned out to be unnecessary — removing it produced no errors.

The TSL fixture keeps a @ts-expect-error tripwire rather than a silent workaround: it fails when DefinitelyTyped narrows the export, so the local narrowing gets deleted rather than forgotten.

Why this mattered beyond tidiness

The published .d.ts import from three/webgpu nine times and were only ever type-checked against the patched types. Consumers resolve stock ones. With the patch gone and the pin bumped, what we ship and what they resolve are finally the same declarations.

Verification

  • pnpm check — exit 0, OKF 0 errors / 0 warnings
  • tsc -p tsconfig.build.json — 0 errors with the patch removed
  • Package-size evidence regenerated. three-runtime-js raw 376,899 against the 377,000 ceiling (101 bytes headroom); minified is 6 bytes smaller than baseline, since the narrowing collapsed a repeated reference. gzip and brotli unchanged or better.
  • Four package concept digests regenerated. Neither concept doc asserts anything about the dependency set or the patch, so only the pins moved.

Not in this PR

@pmndrs/glyph still has no README, so the npm page is blank — including no note that TypeScript consumers supply their own @types/three, which is the ecosystem convention rather than a peer declaration. That belongs with the other canary-readiness doc fixes.

@github-actions

Copy link
Copy Markdown

size-limit report 📦

Path Size
Core JS (gzip) 18.75 KB (0%)
Shaper Wasm (gzip) 426.1 KB (0%)
Three.js adapter JS (gzip) 61.36 KB (+0.02% 🔺)
Inter font · Bitmap (gzip) 545.27 KB (0%)
Inter font · MTSDF (gzip) 6.48 MB (0%)
Inter font · Slug (gzip) 604.06 KB (0%)
Font Awesome icons · Bitmap (gzip) 439.53 KB (0%)
Font Awesome icons · MTSDF (gzip) 6.89 MB (0%)
Font Awesome icons · Slug (gzip) 642.64 KB (0%)
Font validator JS (gzip) 134.24 KB (0%)
Runtime bake host JS (gzip) 5.4 KB (0%)
Runtime bake Worker JS (gzip) 142.47 KB (0%)
Font baker JS (gzip) 2.41 KB (0%)
Font baker Wasm (gzip) 379.63 KB (0%)
Bitmap baker JS (gzip) 4.66 KB (0%)
Bitmap baker Wasm (gzip) 228.34 KB (0%)
MTSDF baker JS (gzip) 5.39 KB (0%)
MTSDF baker Wasm (gzip) 209.22 KB (0%)
Slug baker JS (gzip) 4.01 KB (0%)
Slug baker Wasm (gzip) 181.26 KB (0%)

Two dependency problems that both landed on consumers.

TypeScript was a hard runtime dependency, so every consumer downloaded a
3.5 MB alpha to render text. The module graph was already lazy: the built
`dist/node/bake.js` reaches discovery only through `await import()`,
discovery is reachable only from the Node-only `./bake` subpath, and
nothing in the browser graph touches the compiler. It was a manifest bug,
not an architecture problem.

It becomes a devDependency rather than a peer. An exact `7.0.2` peer would
demand every consumer's project sit on a TypeScript alpha and warn at all
of them, and it only mirrored an assertion the code already makes:
`compiler-adapter` version-checks at runtime and throws a message naming
the required version. That assertion is the real contract. Project-
discovery bake resolves the compiler from wherever the consumer has it;
everyone else never sees it. For reference, r3f ships no typescript
dependency in any field and no runtime reference to it.

The 171-line `@types/three` patch existed because `Node<T>` was a 20-deep
conditional chain that made ordinary TSL expressions expand pathologically,
and because `Node<T>` is a type alias, module augmentation could never have
replaced it. That fix is upstream in `@types/three@0.185.4` -- `NodeExtras`
lookup table present, conditional chain gone -- so the pin moves up across
all four workspace manifests and the patch is deleted.

Two residuals are not upstream. `modelViewProjection` is still declared as
an unparameterized `Node`, which is narrowed once at the boundary in
`bitmap-shader.ts` rather than cast per use; the TSL fixture keeps a
`@ts-expect-error` tripwire that fails when DefinitelyTyped narrows it, so
the local narrowing gets deleted rather than forgotten. The patch's `Loop`
hunk turned out to be unnecessary and is simply gone.

This matters beyond tidiness: the published declarations import from
`three/webgpu` nine times and were only ever type-checked against the
patched types, while consumers resolve stock ones.

Package-size evidence and the package concept digests are regenerated.
Neither concept doc asserts anything about the dependency set or the patch,
so only the pins moved.
@thejustinwalsh
thejustinwalsh force-pushed the chore/drop-types-three-patch branch from a12ee09 to 797a523 Compare August 20, 2026 14:24
@thejustinwalsh
thejustinwalsh merged commit fbfb13e into main Aug 20, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant