Repository navigation
Typescript Watch - Cleaning Target Files on Source Deletion #16057
Description
Activity
For
tsc --watchwe have a list of output files from the previous iteration, we just need to diff that with the current list, and delete the old outputs.Reacted by Özgür Uysal, Trym Skaar, Braden Snell, Hossein Saniei, Salvatore Previti, Sasha Kalmikov, Szymon Witamborski, Zheeeng, Roger Padilla, Joachim Wester and 7 moreIs there a recommended way to work around this today? We have prepended
rimraf lib/to our build scripts, which removes the folder containing the compiled JS files.But this is caused problems with sym-linked modules. The importing project's watch then gets broken with
TS2307: Cannot find module, as it only seem to register the disappearance of thelibfolder, but not the new one put in its place.Reacted by WJH, Matt Mazzola, Zheeeng and Thomas CarlsenCan someone provide some pointers where I can help implement this? Mohamed Hegazy (@mhegazy)?
Things have gotten a bit more complicated with the addition of the Builder the and the WatchProgram (see #20234), there is not really a place to keep this information, since it is always partial, and we are not rebuilding the information after every change as we used to do. I suppose we could investigate restarting the builder if a file was removed. but we will need a way to detect that in the first place..
Hi!
This is constantly biting us during development. A file is removed - but nightwatch / some other library still picks it up and runs tests based on code that should not be there.
Last activity on this was more than 8 months ago, but I hope there is still a possibility for this to be added?
Thanks!
Reacted by André, slaneyrw, Alex, Benno, Danny Guo, Jordán Nikolov, AJ Harvey, Mick Dekkers, Gary Bernhardt, Ludovic Radoux and 53 moreIf you're using jest, you can use ts-jest
Reacted by Adam Sinclair and AAMy 'solution' is obviously not the full thing that is trying to be implemented right now, it's just some very basic behavior I need myself, But I think it might help some people that need something like this right now. So if it's alright, I will just leave a link here for now: https://www.npmjs.com/package/ts-cleaner
Reacted by ArthurKaReacted by Jason Pierce, Cl00e9ment, Andy Pylypchuk and Angelos PikoulasThis functionality would also be helpful on a simple
--clean, this is effecting us in CI and when switching between branches locallyReacted by Bill Donovan, Andrew Wagner, Ignacio Rios Vazquez, Zachary Stall, Dan, Brevon Good, lemon clown, Shannon Monasco, Joe Klos, Richard Moore and 30 moreReacted by Gabriel Lein, Richard Moore, BUON Jean-Guy, Paul-E, Christos Panagiotakopoulos, sourcecodes2 and Anders Kjær Damgaard5 remaining items
Still causing problems...
I would think this feature could use the build info from —incremental to know what needs to be deleted.
Any updates on this?
Also facing this problem, would be a very nice addition
Reacted by Eldar Gabdullin, Ali Yeysideş, Patrick Roza, Yoav Balasiano, Nie, Jason, Arnau Jiménez, Austin Montgomery, Mark Farnum, May Zhong, Gonçalo Tavares and 3 moreBump
Reacted by insidewhy, Holger Jeromin and Simon ReinischReacted by Nie, Jason, Gonçalo Tavares and Ivan SabelnikovJust wanted to add that none of the 3rd party library workarounds mentioned above take care of cleaning up the
.tsbuildinfofile. It keeps info about the deleted source file. Probably not a huge deal, but could lead to bloat with large refactors.Reacted by Anders Kjær Damgaard, Dan Kochetov, Tar and AAFor
tsc --watchwe have a list of output files from the previous iteration, we just need to diff that with the current list, and delete the old outputs.I found this issue after a bit of searching since this is still a very common source of bugs and poses an unfortunate workflow problem with
tsc --watch. In fact, my initial conclusion is that I should be rolling my own watch that invokestsc -pon individual files that are changed.My question here is, why would our approach be to maintain a list of files necessarily? The underlying FS watch should receive events on file deletion or move (typically a "rename" event). On rename, we simply check if a corresponding JS file exists in the
outDirthat matches the file that had moved or been deleted, and delete that JS file. This is a stateless operation and hopefully shouldn't be too hard to implement, but what am I missing? (this is the approach I am going to take when doing this in user space anyways).Reacted by james weber, eklenin, Angelos Pikoulas and Adam SinclairThis a daunting issue, especially if you refactor often, which you should!
And this, is the story about my pursuit to fixing it, the 2023 way! Of course with the help of AI, ChatGTP in particular. So keep reading...
Initially, I googled the issue with
"typescript tsc watch remove deleted files", which is how we used to trouble shoot always. Google responded with current issue first, which was frustratingly still open after 6 years (!).I followed more links but none seemed like solving the problem inherently:
Reading the current issue, I found ts-clean-built to be the best solution & project around at this time. Before even giving it a spin, with only issue, it seemed it would save me hours of wasted effort ;-)
But don't we all have ChatGPT plugins right now, next to the google search? It's just a click away, or as a paid customer you get ALWAYS ChatGPT's answer. Which read as:
ChatGPT When using tsc with the --watch flag, it will recompile the files every time there is a change in the project directory. However, by default, tsc doesn't remove files that have been deleted from the project directory. To remove the deleted files from the output directory, you can use the --removeComments flag. Here's an example command that uses both --watch and --removeComments: tsc --watch --removeComments The --removeComments flag removes all comments from the output files and also removes any files that no longer exist in the source directory. If you want to keep comments in the output files, you can use the --clean flag instead, which removes any files that no longer exist in the source directory without removing comments: tsc --watch --clean Note that if you are using a version of tsc older than 3.8, the --removeComments flag will not work as intended. Instead, you can use the --preserveWatchOutput flag, which will also remove deleted files: tsc --watch --preserveWatchOutput Keep in mind that this flag is only available in TypeScript version 3.8 and newer.Wow! Is that true? Did Chat GPT overwhelmingly solved my ;issue with one
--cleancommmand, but googled failed to tell me?So I immediately added the
--cleanflag to my scripts:npx tsc --watch --clean -p tsconfig.json. Of course it failed:error TS5093: Compiler option '--clean' may only be used with '--build'.ChatGPT let down No 1. For a start,
--cleancan be used only along side--buildfor some reason something ChatGPT could and maybe should have known! Not sure about the dates tho.So, lets try again:
npx tsc --clean --watch --build -p tsconfig.jsonAnd this time I get:
ERROR: "error TS6369: Option '--build' must be the first command line argument.Why so stringent? Another try...
npx tsc --build --clean --watch -p tsconfig.jsonGuess what! Two errors this time!
error TS5072: Unknown build option '-p'. error TS6370: Options 'clean' and 'watch' cannot be combined.Ok, I had enough! In any case,
--build --cleanare useless when watching, its doesnt "mirror"src/todist/amd has a myriad of issues - see https://stackoverflow.com/questions/68144771/whats-the-difference-between-tsc-build-clean-vs-rm-rf-jsSo I tried on last thing, although I was sure its doomed. According to ChatGPT The --removeComments flag removes all comments from the output files and also removes any files that no longer exist in the source directory.. Lets see:
As one would expect, it doesn't, it is just to
Disable emitting comments..So ChatGPT lied and tried to fool me twice in one problem, as many as the solutions! Welcome to the Brave New World of lies, AI powered!
TypeScript team, please, please solve this issue! Till then I'm going for
ts-clean-built!Reacted by J++ and Ivan SabelnikovReacted by Robert KiefferReacted by Simon Reinisch, Weston Thayer, Tino Siegmund, Tar, May Zhong, TeamDman, Anders Kjær Damgaard, Michal Pácal, Jianrong Yu, Jeongho Nam and 5 moreTo anyone looking for a solution, I've completely stopped using
tsc --watchand instead relied on chokidar-cli for watching over my files and triggering thetscbuild command.I don't know if it is as efficient as
tsc --watchcommand but at least it works.{ "name": "myproject", "scripts": { "watch": "chokidar \"src/*\" \"tsconfig.json\" -c \"npm run build\" --initial", "build": "rimraf dist && tsc" }, "devDependencies": { "@types/node": "^20.11.17", "chokidar-cli": "^3.0.0", "rimraf": "^5.0.5", "typescript": "^5.3.3" } }For anyone have this problem like me, i have made a library to solve this, and many more, take a look. typemon
I came up with this solution:
- Add
onchange
yarn add -D onchange
- Configure
package.json
{ "scripts": { "clean": "./scripts/delete_old_files.cjs --dist dist --src src", "watch": "concurrently \"tsc -w\" \"onchange -f unlink 'src/**/*' -- yarn clean\"", "build": "yarn clean && tsc" } }- Add
./scripts/delete_old_files.cjs
#!/usr/bin/env node /** * Deletes files from dist/ that do not exist in src/ */ const fs = require('fs') const path = require('path') const { program } = require('commander') program .requiredOption('--src <source dir>', 'Source directory') .requiredOption('--dist <destination dir>', 'Destination directory') .option('--verbose', 'Print deleted file names') .option('--silent', 'Print nothing') program.parse(process.argv) const options = program.opts() const srcDir = path.resolve(options.src) const distDir = path.resolve(options.dist) let removedFilesCount = 0 const getCorrespondingSrcFile = (distFile) => { const relPath = path.relative(distDir, distFile) const srcFileTs = path.join(srcDir, relPath.replace(/\.js$/, '.ts')) const srcFileJson = path.join(srcDir, relPath) if (fs.existsSync(srcFileTs)) return srcFileTs if (fs.existsSync(srcFileJson)) return srcFileJson return null } const cleanDist = (distPath) => { if (!fs.existsSync(distPath)) { console.error(`Dist directory does not exist: ${distPath}`) process.exit(1) } const filesToCheck = fs.readdirSync(distPath, { withFileTypes: true }) for (const file of filesToCheck) { const distFilePath = path.join(distPath, file.name) if (file.isDirectory()) { cleanDist(distFilePath) if (fs.readdirSync(distFilePath).length === 0) { fs.rmdirSync(distFilePath) } continue } const correspondingSrcFile = getCorrespondingSrcFile(distFilePath) if (!correspondingSrcFile) { removedFilesCount++ if (!options.silent) { if (options.verbose) { console.log(`Removing: ${distFilePath}`) } } fs.unlinkSync(distFilePath) } } } cleanDist(distDir) if (!options.silent) { console.log(`Total old files removed: ${removedFilesCount}`) }
- And of course
chmod +x ./scripts/delete_old_files.cjs
- Add
It is now 2025 😬
Reacted by Maksym Kobieliev, Matt Behrens, escalonn, Sachin Meier and Maxim KiselevReacted by iwatachan and SPGodingReacted by Peter and Sachin MeierReacted by Joshua Jung and Sachin MeierI realize that the advantage of tsc -- watch is that it is not forced to compile everything every time you change a few files, but this feature would be sooo nice if it is not too much of a nightmare to implement.
Reacted by Maksym Kobieliev, François Lamboley and Sachin Meier- addedDomain: Something ElseFallback label when we just can't figure anything else outFallback label when we just can't figure anything else out
on Oct 16, 2025 - added a commit that references this issue
on Feb 7, 2026

Can we add a compiler option to also glob-delete a target file when the source file is removed? We run into this constantly and have to gulp clean frequently - it makes the usage of
tsc -watchpointless as we have to constantly purge the output directory.