TypeScript: Incremental --build, then delete generated js file, then another incremental --build does not recreate js file
TypeScript Version: 3.4.0-dev.20190326
Search Terms:
incremental
Code
tsconfig.json:
{
"compilerOptions": {
"incremental": true
}
}
x.ts:
console.log("hello");
Expected behavior:
$ tsc --buildx.jshas been generated$ rm x.js$ tsc --build
Expected: x.js has been regenerated
Actual behavior:
x.js still does not exist
Related Issues:
Did not find any.
About this issue
- Original URL
- State: open
- Created 5 years ago
- Reactions: 54
- Comments: 40 (6 by maintainers)
Commits related to this issue
- Clean typescript build outputs when cleaning dist/ * See https://github.com/microsoft/TypeScript/issues/30602 — committed to paritytech/substrate-connect by raoulmillais 3 years ago
A separate, but possibly related issue: deleting source
.tsfiles does not remove the related.jsfiles. Right now, to address this, I clean the output directory before every compilation - but this makes it impossible for me to use the--incrementalflag.Yes, yes please. In our use case we compile migration files to a directory and all files in this directory are evaluated when the migrations run. We have had issues where checking out to feature branch A, testing the feature, reverting the migrations and checking out to feature B, preserves the migrations of feature A. This harms developer experience because our developers need to keep track of any compiled files that might linger from other branches.
IMO an incremental build should take the output folder from the current state to the desired state while using
.tsbuildinfoto speed up the process and only build when necessary. Running an incremental build should produce the same result as removing the output folder and running a normal buildThanks for the clarification, @RyanCavanaugh. Here’s my thoughts:
In the general case, I’m fine with this, even if it breaks the cache transparency, I think. But it’s pretty common to delete the entire output directory, and it’s an unnecessary small cut to have to remember to delete the build cache too compared to other tools. Perhaps checking for just the output dir leads to false confidence?
Substantially, perhaps, but the standard use case is the pack up everything in the out directory. For someone that’s not paying attention, this means they are bloating their package size.
Eh, depends on your style. moving source files is fairly common. The repo I’m in at the moment has 43 renames or deletes in the last 100 commits.
Not sure why there are scare quotes here: what’s wrong with
lastBuildOutput.filter(f => !newBuildOutput.includes(f))?@RyanCavanaugh
I’ve had this happen when an unrelated
cleanscript deleted output directories including, but not necessarily limited to, Typescript output. I mean, normally I assume that if I delete output and re-run a build tool, the output will be recreated; that’s true of all build tools I’m used to, for web development or otherwise, except, in this case, Typescript.I agree, but since this issue has been open 2 years, I was hoping to suggest something that might be accepted, so those of us who care can use the safer option.
Ideally, the
"incremental" : trueshould guarantee correct output, and we can have more advanced options to adjust the build speed / safety if desired, like:The default values were chosen because they are safest:
checkMissingOutputFiles=>truecheckMissingSourceFiles=>falsecheckModifiedOutputFiles=>trueI’ve noticed also
tsc -b --cleanonly cleans the output files without rebuilding them, is this the desired behavior? Since the command istsc -b --cleanI assumed it willbuildalso.This is a bit confusing.
--build, then thetscprogram completes very fast, with no clues as to why, as if everything is fine.tscshould give a helpful message. Or, even better, it should run the project in--buildmode.tsconfig.jsonthat can make the--buildoption not required.tsc --clean, it fails, without a helpful message. There’s no obvious way to know that it should betsc --build --cleanIt’s just not intuitive. I think many people spend many hours (if not days) fiddling around with this stuff. It is too time consuming.
This is reasonable, but I think the 90% case is worth covering here: something like if
outDirdoesn’t exist at all (or maybe the first output file?) does not exist when starting a newtsc --incremental, assume it’s been externally deleted and ignore.tsbuildinfo?Another workaround / fix for people who need these external tools is to simply place the
.tsbuildinfoin theoutDirso it gets cleaned too.I strongly agree with this statement!
At the moment, incremental builds provide maximum performance but with the possibility of “incorrect” or unexpected output (i.e., it does not build a file we expected it to build).
I’d rather the incremental build always be 100% correct. It should still be faster than a full clean and build, since it can skip the compilation of some modules.
Maybe we need a different flag that guarantees correctness, but with a slight performance penalty?
Experience report:
We are running into this in the context of converting one project in a monorepo from JS to TS. So we used to have an
index.jsin one of our libraries. I converted it toindex.tsand added abuildscript in the package.json. All contributors install all dependencies and run allbuildscripts anyway as part of the monorepo tooling, so I assumed that they would not see any difference. But when contributors switch back and forth between a branch that has my changes and a branch that does not have my changes,gitends up deleting theindex.jsbut not thetsconfig.tsbuildinfo. I think because the other branch hasindex.jstracked bygitbut nottsconfig.tsbuildinfo. So the end result of switching branches back and forth is that the build is broken and can only be repaired by cleaning.I saw a couple of colleagues complaining in Slack and was affected myself, so I guess that more colleagues were affected but didn’t complain. I estimate we lost about 10 person hours because of this, plus some confidence in typescript. (Personally I’m still confident in typescript, but some colleagues had a first bad interaction now).
There are multiple workarounds for this issue.
Its not clear why only .js file is deleted
I just got hit by an issue from a renamed source file, where the old output JS file ended up getting executed instead, and had a runtime error.
I think my case was specifically the
Should a subsequent build delete files that were generated by a prior build but no longer have a source?issue. But it sounds like the general issue at hand is from trying to keep the source and output files in sync (kind of similar to cache invalidation?)I’m a fan of having configuration options for correctness / speed tradeoffs if possible. In my opinion, it should guarantee correctness by default, but configuration options to toggle would be awesome.
It sounds like there’s multiple ways for the compiled output directory to get out of sync with the source, where incremental builds won’t re-sync. This makes incremental builds less valuable for me, by not being able to trust whether the output directory is in the expected state or requires some manual inspection / fixing.
My understanding of some current workaround options are: 1. Manually delete the outDir &
.tsbuildinfowhen I run into an error (my current approach, but might be non-obvious if an error is related or not) 2. Write my own custom logic to identify if the outDir has gotten out of sync (seems fairly complicated to me) 3. Always delete the outDir &.tsbuildinfobefore incremental compilation (removes most of the benefit of incremental)I’m curious if there’s any other good workarounds!
Regarding precedents for deletion, I’m familiar with some related config flags in a couple other tools. But they’re specifically for source to target directory sync, rather than incremental compilation:
rsync --delete:delete extraneous files from dest dirsaws s3 sync --delete:Files that exist in the destination but not in the source are deleted during sync.I would have assumed that
--incrementalstores and checks the signatures of both the definitions and the generated sources.In the current state there’s basically a secret contract saying that nobody but tsc can touch the build output. It’s not what I would assume from something simply advertised as an incremental build. And apart from subtle phrasing in the 3.4 release notes I don’t think it’s clear from the docs either that only types definitions are checked.
I too would be for making
--incrementalcheck the generated sources by default ; if this comes to an unreasonable cost to certain projects, having an additional, clearly labelled setting (eg.--incrementalTypeChecking) to restore current behavior would probably be enough to make everyone happy.I so much agree with @simonbuchan
I just lost 1 hour with that.
Adopted new “project” strategy (for a src + test simple project); built; cleaned the whole dist; wondered half an hour why subsequent
tsc -bdidn’t produce any output at all; spent more half an hour tried to digg and removeincrementaltag (which I hate in general, stateless in mind); got “incrementaloption may not be disabled for composite projects”; got stuck.@simonbuchan
killing the whole purpose here
I’m seeing similar behavior when running a build doesn’t output a new build (I use incremental as well). Deleting the tsbuildinfo does fix this. However I found that the issue I had was using “emitDecoratorMetadata” in the tsconfig for our decorators. This for some reason prevents ‘build’ from re-generating the build output correctly.
We are having problems similar to @Toxaris Sometimes,
tscstop recompiling some files, assuming that they are unchanged. The only way to recover is to force a change in the affected file or delete the.tsbuildinfofile. In our case too, it seems to happen when switching branches or rebasing.I have also tried looking into the
.tsbuildinfocontents, but I don’t know how to interpret them. Is there a strategy we can use to pin down the problem?The
--incrementalfeature is really new and I haven’t found a lot of information on how it works internally.Is it fair to assume that the TS compiler will/could generate the output faster when it has a
.tsbuildinfofile even if there isn’t any output files?If the build time is going to be the same as a fully clean one with no
.tsbuildinfo, then storing them in git does not make any sense. On the other side, if the build time should be indeed faster then I think the scenario is compelling enough to investigate how much of cost it will have and if it is worth fixing this. Any project that runs CI or a developer doing a fresh checkout will benefit from this and save precious time.@matthiasg See #31118.
tsbuildinfo also does not get invalidated when tsconfig.json changes. Thus code generated e.g with
target: es2017does not get rebuilt when the target gets changed.Your expectations are fine, we’re just trying to figure out if the scenario is common enough that e.g. people who have 1,200 build outputs should be paying the cost on every single operation for TS to check if all those outputs still exist. Can the “unrelated clean script” just delete the
.tsbuildinfoas well?any plans on fixing this? cc @sheetalkamat
it kills the point of caching build info in CI