26.2 Incremental Compilation: tsbuildinfo Files
Right, let’s talk about one of the few things TypeScript got genuinely, unambiguously right for performance: incremental compilation. If you’ve ever sat there, drumming your fingers, waiting for a full project rebuild after changing a single comma, this is the feature that stops that particular brand of madness.
At its core, incremental compilation is a simple concept: instead of rebuilding the entire world from scratch every time you run tsc, the compiler saves a little file of its own homework—a .tsbuildinfo file—that tells it exactly what work it doesn’t need to redo. It’s the compiler’s version of “I’ll just leave these parts out and only fix the bit I messed up.”
What’s Actually in That tsbuildinfo File?
Think of the .tsbuildinfo file as a detailed cache manifest. It’s not code; it’s a JSON-like structure (stored in a binary format for efficiency) that records the state of your last successful build. The juicy bits inside include:
- File Signatures: A hash (like a digital fingerprint) for every source file and its dependencies. If the hash hasn’t changed, the compiler knows the file’s API hasn’t changed, so any code that depended on it last time is still valid.
- Dependency Graph: A map of which files depend on which other files. This lets the compiler quickly see the ripple effect of a change. If you change
utils.ts, it knows it needs to checkcomponent-a.tsandcomponent-b.tsbut can safely ignoreunrelated-module.ts. - Compiler Options: The exact settings used for the build. This is crucial. If you change your
tsconfig.jsonfromtarget: es2015totarget: es2022, the compiler sees the options in the.tsbuildinfofile no longer match, says “well, that’s a whole new ball game,” and triggers a full rebuild. As it should.
You enable it in your tsconfig.json like so:
{
"compilerOptions": {
"incremental": true,
// OutDir is highly recommended to keep things tidy
"outDir": "./dist"
}
}
That’s it. Now, when you run tsc, you’ll see a new tsconfig.tsbuildinfo file appear next to your compiled JS (or in your outDir if you specified one). The next time you run tsc, it will read this file and only recompile what’s necessary.
The –incremental Flag vs. The Config Option
You can also enable this from the command line with tsc --incremental. So, which should you use? The config file, every time. The command-line flag is useful for a quick one-off, but it dumps the .tsbuildinfo file in your root directory, which is messy. Setting it in tsconfig.json gives you control and keeps your project structure clean. It’s the difference between leaving your tools on the kitchen counter and putting them back in the toolbox.
The Critical Pitfall: Cleaning Your Build
Here’s the part the manual often glosses over, and it’s a footgun waiting to go off. The entire premise of incremental compilation is that the .tsbuildinfo file and your outDir are in perfect sync. They are a matched set.
If you go and manually delete your dist folder but leave the tsconfig.tsbuildinfo file lying around, you’ve just created a paradox. The compiler’s homework says “I already built all these files,” but you’ve just vaporized the evidence. The result? On the next build, it will likely only compile the files you’ve changed since the last build, leaving huge gaps in your output directory. You’ll be left with a broken, partial build, wondering what went wrong.
The solution is simple: always clean both the output directory and the .tsbuildinfo file together. If you’re using a script, it should look like this:
# A safe clean script for an incremental project
rm -rf dist tsconfig.tsbuildinfo
Better yet, use the tsc --clean command, which is specifically designed to handle this for you. It knows what needs to be removed to ensure a clean slate.
Composite Projects and References: Where It Gets Superpowers
Incremental compilation is nice, but its true purpose is to enable TypeScript’s project references. When you break a large codebase into smaller, referenced projects, the .tsbuildinfo file becomes the contract between them.
When you build a dependent project, the compiler doesn’t re-check the upstream project it depends on; it just reads the upstream’s .tsbuildinfo file to understand its API. This is why building with project references (tsc -b) is so much faster than a monolithic build. It’s not magic; it’s just the compiler ruthlessly avoiding work it knows it doesn’t need to do, thanks to these little manifest files.
So, enable it. There’s virtually no downside. It’s one of the few free lunches in software engineering—a simple setting that buys you back precious seconds of your life, every single time you save a file. And in this line of work, you take those wins where you can get them.