Search code examples
typescripttscincremental-build

tsc incremental build clean output file on source file deletion


I'm experimenting with incremental build to speed up generating .js from .ts in a large project for local and CI processes. But, I'm facing an issue where deleting a source file doesn't delete the corresponding .js file during incremental build. This is causing problems for me because If I have to clean the folder before every build, there would be no point in using the incremental build feature.

Example:

.
├── bar.ts
├── foo.ts
├── index.ts
├── package.json
└── tsconfig.json

After build npx tsc --build --incremental ./

.
├── bar.js
├── bar.ts
├── foo.js
├── foo.ts
├── index.js
├── index.ts
├── package.json
├── tsconfig.json
└── tsconfig.tsbuildinfo

Now delete bar.ts and run npx tsc --build --incremental ./

.
├── bar.js
├── foo.js
├── foo.ts
├── index.js
├── index.ts
├── package.json
├── tsconfig.json
└── tsconfig.tsbuildinfo

bar.js won't be deleted.

Is there anything missing in the CLI argument? There is no information about it in the documentation.


Solution

  • To my understanding this is intended behaviour from the TS compiler. The question was posted a long time before this answer and I don't know if the docs have changed in the meantime but the current as of writing documentation on the incremental flag states the following:

    incremental
    Tells TypeScript to save information about the project graph from the last compilation to files stored on disk. This creates a series of .tsbuildinfo files in the same folder as your compilation output. They are not used by your JavaScript at runtime and can be safely deleted.

    There's no mention of automatic deletion (cleaning) of emitted .js files so there's no reason expect such behaviour.

    It's difficult to say what TSC does in your exact project without seeing the tsconfig.json but most probably the .js files aren't being tracked by TSC. So if you move or delete the source file (bar.ts in your example), the emitted bar.js file is "just another file" to TSC and it'd be incredibly rude of it to delete it :)

    There is a clean flag that makes sure the output is done to new files, removing the old ones from the way if necessary but that still won't track bar.js unless a corresponding bar.ts exists. If you move or delete bar.ts, TSC still doesn't know that bar.js is anything special; definitely not that it can be safely deleted.

    See a similar discussion at microsoft/TypeScript#36648.


    Have you determined that writing the files is the actual bottleneck and not the type check? The incremental flag's main sell is the tsbuildinfo datastructure(s); it can be that deleting all the .js output files isn't as big of a performance hit as you think.

    If the file write time actually is a problem, my next suggestion is to avoid doing it with noEmit until absolutely necessary. And if you really do need incremental file writes, I don't see other ways than using e.g. noEmit and dry to (i) get a list of the output files of the project as it currently is and then (ii) remove all the .js files that aren't in that list. If you're using e.g. a build script in package.json, it's easy to just add this as a step before/after a build.

    If none of those suggestions are satisfactory, I think you'll have to suggest a feature for this.