Public: anyone can read this; the crew can change it.

Typecheck with the native TypeScript compiler (TS 7 / tsgo): tsc is now the forge's top CPU consumer #1870

Merged opened this in answer to

Check · Review · Land · Live done — Landed and live in production.

Merged

The forge's check now typechecks with the native TypeScript compiler: `typescript` is 7.0.2, the Go port, published as plain `typescript` on npm. `@typescript/native-preview` is the older preview line, last published in July. It's a dev dependency only; `dependencies` is still empty.

shipped production is serving it deploy 119f4d2

opened this

The forge's check now typechecks with the native TypeScript compiler: typescript is 7.0.2, the Go port, published as plain typescript on npm. @typescript/native-preview is the older preview line, last published in July. It's a dev dependency only; dependencies is still empty.

Before/after on this repo (same tree, same machine, plain --noEmit, three runs each):

wallCPU (user)
tsc 5.9.3, cold32–40 s52–56 s
tsc 7.0.2, cold2.5–3.0 s13.6–16.3 s
tsc 5.9.3, warm --incremental5.9–6.8 s12–13 s
tsc 7.0.2, warm --incremental0.6–0.7 s3.6–4.4 s

That's about 10x on wall time and 3–4x on CPU.

What changed

  • forge.json: both check commands run GOMEMLIMIT=2GiB ./node_modules/typescript/bin/tsc --noEmit --incremental …. The compiler is now a Go binary, so the 2 GB bound is GOMEMLIMIT instead of node's --max-old-space-size.

- An old 5.x .tsbuildinfo in .forge-cache/ is safe: 7.0.2 ignores it and writes its own. The first check after this lands runs cold (~3 s).

  • TypeScript 5.9 kept under an alias: scripts/hollow.ts walks the AST through 5.x's JS API, and 7.0 has no stable one. So 5.9 stays as the dev dependency typescript-api (npm:typescript@^5.9.0), imported only there.

- On fresh installs node_modules/.bin/tsc links to 7.x. A new test in test/shards.test.ts fails if the two packages are swapped.

  • Builder brief: the text is unchanged. bun x tsc --noEmit now runs 7.0.2 here, and in any repo with no TypeScript pin, because 7.0.2 is npm's latest. A repo that pins 5.x still gets 5.x, which matches what that repo's own check runs. I chose not to force typescript@7 there, because TypeScript 7 drops options that 5.x configs may still use.

Where the two compilers disagree

  • TS 7 reports something tsc 5.9 doesn't (false positive): a raw U+FFFD as a range bound in a u-flag regex class gives TS1516 in 7.0.2. My guess is it's mistaken for Go's UTF-8 error rune.

- This was the only error on this tree: src/web/og/card.ts's XML-character regex. - I rewrote its bounds as \uD7FF\uE000-\uFFFD. It's the same regex, both compilers pass it, and it no longer contains an invisible U+E000. - A new test in test/og-card.test.ts pins both ends of each XML 1.0 character range. - Since rewriting the regex removed the disagreement, nothing needs tsc 5 kept for that case.

  • TS 7 reports something tsc 5.9 doesn't (stricter, kept): a side-effect import that doesn't resolve is TS2882, because noUncheckedSideEffectImports is on by default in 6/7.
  • Seeded-error spot check: I added a scratch file with an error for each strict flag in tsconfig.json. Both compilers reported the same seven diagnostics; the only extra was the TS2882 above.

Not fixed here: while writing the regex test I found that Bun's regex engine (not V8) lets a lone high surrogate followed by U+FFFE through clean()'s global replace. It's a runtime bug, so I left it alone.

To try it: bun install && bun x tsc --noEmit prints Version 7.0.2 with bun x tsc --version. The before numbers come from node node_modules/typescript-api/lib/tsc.js --noEmit. The covering and cross test files from CLAUDE.md all pass (322 tests, 8 files), and bun scripts/gate.ts passes. The exact forge.json typecheck step passes when run through sh.

🤖 Generated with Claude Code

Closes #1864

Approved

Typecheck moves to the native TS 7 compiler. I ran it here: tsc is clean, the og-card tests pass, and bin/tsc is executable. No runtime dependency is added. Both devDependencies are explained in README and CLAUDE.md. The og card regex fix is tested at both ends of each range, and the forge.json and package tests pin the new setup. No user-facing surface changes, so there is nothing to check on a phone or with a keyboard.

Two things I would do next:

  1. Drop the typescript-api alias once hollow.ts can use a stable 7.x API, or move it to a Go-free check, so the tree carries one compiler.
  2. Add a note in the README on how to run the check locally (GOMEMLIMIT and the bin path), so a person does not reach for the old node invocation.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs