microsoft/TypeScript
Watch and builder
How tsc --watch, tsserver, and tsc -b keep a long-lived compilation alive without re-doing work after every keystroke.
Source
| File | Lines | Role |
|---|---|---|
src/compiler/watch.ts |
1,300 | Watch-mode shared utilities; pretty-printed status messages |
src/compiler/watchPublic.ts |
1,800 | createWatchProgram, the public watch entry |
src/compiler/watchUtilities.ts |
1,000+ | Shared host helpers (cached file system, polling) |
src/compiler/builder.ts |
4,000+ effective | Incremental file/affecting-files tracker, .tsbuildinfo |
src/compiler/builderPublic.ts |
small | createBuilderProgram factories |
src/compiler/builderState.ts |
800+ | Per-file signature computation |
Purpose
A Program is immutable and constructing one is expensive. Watch mode and tsserver need to:
- Detect changes — files added, modified, deleted. Both file watchers and polling are supported.
- Decide what to re-do — usually a tiny subset of the program (the changed file plus its dependents).
- Reuse old state — share parsed/bound source files with the next program.
- Persist incrementality —
.tsbuildinfofiles store enough state for a subsequent process to resume incrementality after a restart.
Key abstractions
| Symbol | Role |
|---|---|
createWatchProgram(host) |
The watch loop |
createWatchCompilerHost(...) / createWatchCompilerHostOfFilesAndCompilerOptions(...) / createWatchCompilerHostOfConfigFile(...) |
Host factories — the host owns file system, watchers, error reporting |
WatchOptions |
Per-tsconfig watch tuning (watchFile, watchDirectory, fallbackPolling, synchronousWatchDirectory, excludeDirectories, excludeFiles) |
BuilderProgram |
A Program wrapper that tracks which files need re-emit |
EmitAndSemanticDiagnosticsBuilderProgram |
The default — does both emit and semantic diagnostics incrementally |
SemanticDiagnosticsBuilderProgram |
Type-check only, no emit (used by language service) |
getSemanticDiagnosticsOfNextAffectedFile |
Pulls one affected file at a time so editors can stream results |
.tsbuildinfo |
On-disk cache of file signatures, options, and reference graph |
Watch loop
graph TD
Start["createWatchProgram"] --> Build["build initial Program"]
Build --> Emit["emit (or skip in --noEmit)"]
Emit --> Wait["host.watchFile / watchDirectory"]
Wait -->|change| Plan["plan rebuild"]
Plan --> Reuse["pass oldProgram into createProgram"]
Reuse --> Build
Wait -->|tsconfig change| Reload["reload tsconfig"]
Reload --> BuildTwo layers of caching keep this fast:
- The host's
cachedDirectoryStructureHostmemoises directory listings soreadDirectorydoesn't hit disk on each rebuild. - The
BuilderProgramrecords, for each file, a signature (a hash of its emit-affecting shape). When file A's signature changes, only files that imported A's affected exports are flagged for rebuild.
Incremental signatures
builderState.ts computes per-file signatures — hashes derived from the emit output (.js text + .d.ts text) of a file. When a file changes:
- Re-parse, re-bind, re-check the file.
- Recompute its signature. If unchanged, no downstream file needs work — only the file itself was a leaf change.
- If the signature changed, walk the import graph and invalidate every file that depended on this one's exports.
This lets watchers cheaply distinguish between "edited a comment in a deeply-imported library file" (signature stable, no rebuild) and "added a new export" (rebuild every consumer).
.tsbuildinfo
When --incremental (or --composite) is set, the builder serialises its state to a .tsbuildinfo file at the end of compilation. The file contains:
- The exact
CompilerOptionsused. - Per-file signatures.
- The reference graph.
- Open referenced project paths and their states (in solution-builder mode).
A subsequent tsc invocation reads .tsbuildinfo, validates that options haven't changed, and resumes incrementality. Even though the V8 process has died and no in-memory Program exists, the next run can avoid re-checking unchanged files.
Watch options
watchOptions in tsconfig.json (top-level, not under compilerOptions) lets users tune:
watchFile—useFsEvents,useFsEventsOnParentDirectory,priorityPollingInterval,dynamicPriorityPolling,fixedPollingInterval.watchDirectory— recursive watching strategies.fallbackPolling— when native watchers fail.synchronousWatchDirectory— debugging aid.excludeDirectories,excludeFiles— paths the watcher should ignore (e.g.,dist/).
These exist because Node's native watchers behave differently across operating systems, network filesystems, virtualised drives, and very large directories. The WatchOptions enum is the team's accumulated knowledge of which strategies work where.
Integration points
tsc --watchcallscreateWatchProgramdirectly (viaexecuteCommandLine).tsserverbuilds its own per-project watch host on top of these primitives insrc/server/editorServices.ts.tsc -b(build mode) wraps multipleBuilderPrograms — see systems/build-mode.- The
EmitResultof an incremental emit is consumed by editors that watch the on-disk output.
Entry points for modification
- Watcher behaviour —
watchUtilities.tsandts.sys.watchFile/watchDirectoryinsys.ts. - Status messages —
formatDiagnosticsForWriterinwatch.ts. - Signature computation —
builderState.ts. .tsbuildinfoshape —builder.ts(be very careful about backward compatibility).
See systems/build-mode for the multi-project orchestration.
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.