incremental

Caches compilation state in .tsbuildinfo so later runs only re-check what changed.

Signature
"incremental": boolean  // default: false (true when composite is enabled)
  • Added in TypeScript 3.4

Parameters

ParameterTypeDescription
$incrementaloptionalboolean= false, or true when composite is enabledSave information about the previous compilation to disk so subsequent runs only re-check and re-emit files that changed.
$tsBuildInfoFileoptionalstring= .tsbuildinfo, written next to the emitted JavaScriptCustom path for the incremental state file. Added in TypeScript 3.4.

Return value

booleanA .tsbuildinfo file holding the previous compilation's state, which the next run uses to skip unchanged work.

Errors

The compiler errors if it cannot determine where to write the build info — with no outDir and no output file, tsBuildInfoFile must be specified explicitly.

Changelog

  • PHP 3.4Introduced along with tsBuildInfoFile.
  • PHP 3.0composite (added earlier) implies incremental, so project-reference builds always keep build info.

incremental makes tsc write a .tsbuildinfo file recording the state of the compilation: the file set, their versions, the resolved module graph and the diagnostics. The next run reads it, works out which files actually changed and which files depend on them, and re-checks only that subset. On a large project the second build is often several times faster than the first.

When it pays

Any repeated compilation of the same project: local rebuilds, tsc --watch startup, and CI when the .tsbuildinfo is restored from a cache between jobs. That last case is the biggest single win available to most pipelines and costs one cache directive.

What trips people up

The file is build output, not source. Commit it and every machine fights over it; the correct move is .gitignore plus a CI cache. It is also invalidated wholesale by a change to compilerOptions or a TypeScript version bump — a build that got mysteriously slow after an upgrade is usually just one cold rebuild.

With no outDir, the compiler has nowhere obvious to put the file and asks you to set tsBuildInfoFile explicitly. And composite turns incremental on for you, so referenced projects always carry build info.

Example

Example · typescript
// A sketch of the question .tsbuildinfo lets tsc answer without
// re-reading and re-checking the whole program.
type BuildInfo = { version: string; hashes: Record<string, string> };

const previous: BuildInfo = {
  version: "5.6.2",
  hashes: { "src/a.ts": "a1", "src/b.ts": "b1", "src/c.ts": "c1" },
};
const current: Record<string, string> = {
  "src/a.ts": "a1", "src/b.ts": "b2", "src/c.ts": "c1",
};

const changed = Object.keys(current)
  .filter((f) => previous.hashes[f] !== current[f])
  .sort();

console.log({ reusedFrom: previous.version, changed, skipped: 2 });

When to use it

  • Cutting rebuild time on a large codebase where most files are untouched between runs.
  • Caching .tsbuildinfo between CI jobs so the type-check step reuses the previous run's work.
  • Speeding up tsc --watch startup after a restart.
  • Redirecting build info out of dist with tsBuildInfoFile so the published artefact stays clean.

Frequently asked questions

Should I commit .tsbuildinfo to git?

No. It is generated build output, tied to your machine's file timestamps and to the exact compiler options and TypeScript version used. Add it to .gitignore. If you want the speedup in CI, cache the file between runs instead, keyed on tsconfig.json and your lockfile.

Why did my incremental build get slow again?

The build info was invalidated. Changing anything in compilerOptions, upgrading TypeScript, or deleting the outDir all force a full rebuild on the next run. That first run is cold and slow; the run after it should be fast again. If every run is slow, check that the .tsbuildinfo file is actually being written where you expect and survives between runs.

What is the difference between incremental and composite?

incremental caches one project's compilation state; composite additionally makes a project referenceable by other projects and forces declaration output. composite implies incremental, so enabling it gives you the cache automatically. Use incremental alone for a single project, and composite when the project is part of a project-references graph built with tsc --build.

More examples

Keeping build info out of the published output

By default the file lands beside the emitted JavaScript, so dist/ ships a cache artefact you did not intend to publish. Pointing tsBuildInfoFile at node_modules/.cache keeps dist clean and puts the file somewhere already ignored by git and already cached by most CI setups.

Example · json
{
  "compilerOptions": {
    "incremental": true,
    "tsBuildInfoFile": "node_modules/.cache/tsc/tsconfig.tsbuildinfo",
    "outDir": "dist",
    "rootDir": "src"
  }
}

Reusing the cache in CI

The cache key includes tsconfig.json and the lockfile because a change to compilerOptions or to the TypeScript version invalidates the build info entirely. restore-keys lets a near-miss still restore the previous cache, so the type-check re-uses most of the prior run instead of starting cold.

Example · yaml
# .github/workflows/ci.yml (excerpt)
- uses: actions/cache@v4
  with:
    path: node_modules/.cache/tsc
    key: tsc-${{ runner.os }}-${{ hashFiles('tsconfig.json', 'package-lock.json') }}
    restore-keys: tsc-${{ runner.os }}-

- run: npx tsc --noEmit

Discussion

  • Be the first to comment on this lesson.