Skip to content

perf: keep the typescript check's program between rebuilds - #332

Merged
alexander-akait merged 1 commit into
mainfrom
perf/reuse-the-typescript-program
Sep 13, 2026
Merged

alexander-akait merged 1 commit into
mainfrom
perf/reuse-the-typescript-program

Conversation

@alexander-akait

Copy link
Copy Markdown
Member

Summary

Every compilation built a program from nothing and asked it for every diagnostic the project has, so a watch rebuild paid for the whole project however little had changed. The program is now kept for the compiler that built it and handed to the one after it — a semantic-diagnostics builder over a host that answers with the source file it read last time for as long as the file on disk is untouched, which is what lets TypeScript reuse its work and check only what the change reaches.

Measured in a real webpack watch build over three hundred TypeScript files, three runs each:

first build rebuilds
before ~930 ms 359, 320, 312, 340, 329 ms
after ~920 ms 148, 31, 33, 28, 37 ms

It costs the memory the program holds on to: 108.6 MB retained against 59.4 MB, measured after three rebuilds with --expose-gc. That is the same trade fork-ts-checker-webpack-plugin and vite-plugin-checker make, and it is only paid while watching — a one-shot build releases the program with the compiler.

A change to the config file, or to the compiler options given here, starts a new program: nothing the last one was built from survives it. What is reported is what getPreEmitDiagnostics asks a program for, asked of the builder instead so that a file it has already checked is not checked again.

What kind of change does this PR introduce?

perf

Did you add tests for your changes?

Yes — a case in test/typescript/watch.test.js for the thing incremental checking can get wrong: a file that is itself fine, whose changed exports break a different file, and which must be reported against the dependent. It also asserts the second program was handed the first, through a recorder wrapping the real TypeScript (test/typescript/mock/typescript-recorder). Before writing any of this I checked the incremental result against a full getPreEmitDiagnostics over eleven mutations — a comment, a type break, a shape change breaking a dependent, a fix, a deletion, a new bad file, and strict off and back on — and they agreed at every step.

Does this PR introduce a breaking change?

No. The set of diagnostics reported is unchanged.

If relevant, what needs to be documented once your changes are merged or what have you already documented?

Documented in this PR: the TypeScript section said the program was rebuilt whenever a checked file changed, which is no longer true, and now says what a rebuild costs and what starts a new program.

Use of AI

Claude Code wrote this change, guided and reviewed by me. Both the speedup and the memory it costs were measured rather than asserted, and the incremental result was checked against a full one before the mechanism was ported.

🤖 Generated with Claude Code

https://claude.ai/code/session_01GzZci4NQeiqwdrVfd7dGXy


Generated by Claude Code

Every compilation built a program from nothing and asked it for every
diagnostic the project has, so a watch rebuild paid for the whole project
however little had changed. The program is kept for the compiler that built
it and handed to the one after it, as a semantic-diagnostics builder over a
host that answers with the source file it read last time for as long as the
file on disk is untouched — which is what lets TypeScript reuse its work and
check only what the change reaches. Over three hundred files in a real watch
build: rebuilds of 240-360 ms become 145 ms for the first and about 30 ms
after, for the 49 MB the program holds on to.

A change to the config file or to the compiler options given here starts a
new program, since nothing the last one was built from survives it. What is
reported is what `getPreEmitDiagnostics` asks a program for, asked of the
builder instead so that a file it has already checked is not checked again.

Claude-Session: https://claude.ai/code/session_01GzZci4NQeiqwdrVfd7dGXy

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@alexander-akait
alexander-akait merged commit 8f231cd into main Sep 13, 2026
13 checks passed
@alexander-akait
alexander-akait deleted the perf/reuse-the-typescript-program branch September 13, 2026 16:28
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