Repository navigation
Project-references type check with --noEmit fails without built files #40431
Description
Activity
- addedDesign LimitationConstraints of the existing architecture prevent this from being fixedConstraints of the existing architecture prevent this from being fixed
on Sep 9, 2020 RyanCavanaugh commented
on Sep 9, 2020 MemberMore actionsA
.d.tscan be subtly different from the.tsthat produced it, so you might not get identical results between a "virtual" build like this and a proper one, so enabling this is a dangerous thing to do. We could produce the .d.ts files in-memory in a serial build, but that's just build mode, and you're not invoking build mode so we can't legally go and inspect the referenced project. I'm not sure what we could do here; this was one of the listed caveats of project references from the get-go.valerybugakov commented
on Sep 10, 2020 AuthorMore actionsRyan Cavanaugh (@RyanCavanaugh) Thanks for the response. This functionality looks like what I want to achieve using
--noEmitflag from the CLI. It doesn't use.d.tsfiles and relies on "virtual" build to provide better DX with type-checking by inspecting source files of the referenced projects.If it's true, it means that the mechanism is already in place. It would be great to expose some flags for developers to use it. Please correct me if I confuse two unrelated features. Maybe Sheetal Nandi (@sheetalkamat) could suggest something regarding use of this functionality via CLI.
RyanCavanaugh commented
on Sep 10, 2020 MemberMore actionsThat PR is implemented by presenting the type checker with a virtual file system that builds .d.ts files on the fly; we can't enable it with a flag from
tscbecause it uses code specific to the language service process.Valery Bugakov (@valerybugakov) Did you ever find a way to solve the problem you've described here?
valerybugakov commented
on Jul 18, 2022 AuthorMore actionsTaylor Kline (@taylorkline), not the root cause. We partially solved the performance issue in our project by caching type check commands per Typescript project with
nx.Reacted by Taylor KlineI think this issue should be reconsidered, and opened again.
It appears that not only do we get
TS6305errors, any files in project references do not appear to be type checked. I could be wrong, and I will create a reproduction in the future.Project references are used in the Vite template for React and TypeScript which is very popular.
Any execution of
tscto check the types of a project in these cases would dangerous, as while Visual Studio Code will check the types a give a false sense of safety any CI/CD or tool such as Vitest will silently fail.In addition, it is not efficient to run
tsc --build, where many of these build with their own bunder.EDIT: Here is a reproduction of this issue which I created for the Vitest team.
Reacted by Jeffrey Lau, Shravan Sunder, Avto.pro, Kepi, Devlin, Simon Siefke, Oliver Schwendener, Daniel Perez, Adam Wróbel, Kyle DeTella and 18 moreThat PR is implemented by presenting the type checker with a virtual file system that builds .d.ts files on the fly; we can't enable it with a flag from tsc because it uses code specific to the language service process.
We could produce the .d.ts files in-memory in a serial build, but that's just build mode, and you're not invoking build mode so we can't legally go and inspect the referenced project.
I'm confused — so is this a technical limitation (as the first quote implies), or a design/correctness limitation?
I hope this topic gets revisited in the future. I just ran into this when I explored the idea of using project references to speed up type checking (via
--noEmit). I have also opted for caching results vianxfor the time being.The thing is that project references put on the table some unique functionality like project boundaries, which can be extra useful in monorepo's workflow,however, it currently overlooks its potential due to the necessity of building files, leading to a performance degradation when compared to the --noEmit check.
In the realm of modern monorepo development, there's a shift towards employing rapid compilers like SWC and esbuild with extracting type checking into a separate task. In such approaches, emitting any files through TypeScript (tsc) becomes unnecessary, further enhancing overall efficiency. I would really like to see someday this feature in your roadmap.
Reacted by rmunch, christopher1986, Arron, Vincent Rubinetti, Kræn Hansen, Haz, Edu and Hidemi YukitaRun into this many times, I believe it should be fixed. There should be no reason to need to emit declaration files if you are just trying to type check and have noEmit set, and the errors are quite confusing.
Reacted by Cefn Hoile, Matheus Michels, Arron, Vincent Rubinetti, tienvudev, Joao Castillo, Gleb, Ben Butterworth, Haz, Alvis Tang and 5 more- added a commit that references this issue
on Oct 24, 2024 This is super annoying!
Without using extra tooling, resorting to
tsc -b || tsc -b --cleandoes the job of type-checking without leaving extra files behind.
Type checking and building are treated as separate tasks by virtually every project, tsc would better serve users by supporting this flow for project references too.
How does VS Code do it? It shows type errors without adding files to the project file tree. Can't that functionality be ported over to tsc?
Reacted by AdrianThe whole point of project references is1 to let one project consume another project's declarations, which is why generating
.d.tsfiles for referenced projects is a requirement.The requirement is enforced by the fact that
noEmitcannot be enabled andcompositemust be enabled for referenced projects1. Enablingcompositein turn automatically enablesdeclaration, and you are not allowed to disable it.You may, however, set
emitDeclarationOnlytotrueto prevent.jsfiles from being emitted, and you may setoutDirordeclarationDirto some directory ignored by version control so as not to have to runtsc -b --cleanafter each build if you don't want your project folders cluttered with.d.tsfiles. A good example for such a directory isnode_modules/.tmpused by Vite's official templates as the output directory for.tsbuildinfofiles generated duringtsc -bruns.I am surprised this solution still hasn't been mentioned in this thread. As the TypeScript team does not seem to be willing to implement on-the-fly
.d.tsfile generation in a virtual file system for type checking purposes, I think it is the best one we have at the moment.Footnotes
-
unless used in a “solution”
tsconfig.jsonwherefilesis set to an empty array, in which case project references are just a mechanism for combining several projects into one ↩ ↩2
Reacted by Edu and Ruchita-
aweebit outputting .d.ts leads to issues if files are moved, renamed, or deleted between builds. The old .d.ts files remain and result in undetectable errors, like importing from files that should no longer exist.
@aweebit outputting .d.ts leads to issues if files are moved, renamed, or deleted between builds. The old .d.ts files remain and result in undetectable errors, like importing from files that should no longer exist.
mkdir leftover-declarations cd leftover-declarations npm init -y npm pkg set type="module" npm i -D typescript@~5.7.3 mkdir referenced
// tsconfig.referenced.json { "include": ["referenced"], "compilerOptions": { "outDir": "node_modules/.tmp", "composite": true, "emitDeclarationOnly": true } }
// tsconfig.json { "exclude": ["referenced"], "compilerOptions": { "outDir": "node_modules/.tmp", "noEmit": true }, "references": [ { "path": "./tsconfig.referenced.json" } ] }
echo "export default 42" > referenced/original.ts echo "import value from './referenced/original'" > index.ts npx tsc -b mv referenced/original.ts referenced/renamed.ts npx tsc -b
This results in
index.ts:1:19 - error TS2307: Cannot find module './referenced/original' or its corresponding type declarations.despite
node_modules/.tmp/referenced/original.d.tsstill existing.So I cannot seem to reproduce the issue you are describing. Could you give a concrete example where leftover declaration files actually lead to problems?
aweebit I mostly only uses project references in monorepo setups which is where I see the problem occur often.
Run
pnpm buildthen renamepackages/a/foo.ts, the import inpackages/b/bar.tswill not error in the editor nor will it error if build is run a second time. After runningpnpm build:cleanthen build will correctly fail because it removes the old.d.tsfile.A solution that turned out to be wrong:
since you are using pnpm anyway, I suggest you try the
"publishConfig"approach described in this article: https://colinhacks.com/essays/live-types-typescript-monorepo. I tried it in your StackBlitz demo and it worked like a charm.Update: I've just realized I only thought it worked because I left no exports other than
"./*.js": "./src/*.ts"in the actual"exports"field (not the one in"publishConfig"), so thefoo.tssource file was the only thing TypeScript could see, and so it reported an error if it couldn't be found. The problem is that Node now also only sees the source file when trying to import"foo.js", which results in a runtime error!
The rest of the original comment:
By the way, the article also mentions that you should always put your custom export conditions such as the
"@ts-ref/source"condition you use in the demo before all other conditions, because the order matters. This doesn't help in our case with file renaming though since TypeScript falls back to thetypescondition when it doesn't findfoo.ts, and so it still discoversfoo.d.tsin the output directory. One possible solution to this would be if TypeScript introduced am option likenoCustomConditionFallbackthat would cause the compiler to only resolve imports using custom conditions if at least one of those specified incustomConditionsis also specified inpackage.jsonfor the module being imported.But it would be even better if there was an option to clean leftover files from the output directory during rebuilds. By that I don't mean something that would have the same effect as
tsc -b --clean && tsc -b, but rather a smart process recognizing and deleting only output files that no longer have a corresponding source file. This way, no performance compromise would have to be made as incremental builds would still be possible.But it would be even better if there was an option to clean leftover files from the output directory during rebuilds. By that I don't mean something that would have the same effect as
tsc -b --clean && tsc -b, but rather a smart process recognizing and deleting only output files that no longer have a corresponding source file. This way, no performance compromise would have to be made as incremental builds would still be possible.It turns out there has been an open issue with a similar feature request for almost 8 years now:
And I've also found an open issue requesting to support
tsc --build --noEmit:For those who received email notifications with my previous comments in their original form: the solution with pnpm's
"publishConfig"turned out to be wrong! See the updated comment.I've just found a different solution on Stack Overflow: https://stackoverflow.com/a/79144361/9861000
It involves an external dependency though, namely Google's Wireit tool. Its About section says:
Wireit upgrades your npm/pnpm/yarn scripts to make them smarter and more efficient.
The docs even have a dedicated TypeScript recipe:
{ "scripts": { "ts": "wireit" }, "wireit": { "ts": { "command": "tsc --build --pretty", "clean": "if-file-deleted", "files": ["src/**/*.ts", "tsconfig.json"], "output": ["lib/**", ".tsbuildinfo"] } } }- Set
"incremental": trueand use--buildto enable incremental compilation, which significantly improves performance. - Include
.tsbuildinfoinoutputso that it is reset on clean builds. Otherwisetscwill get out of sync and produce incorrect output. - Set
"clean": "if-file-deleted"so that you get fast incremental compilation when sources are changed/added, but also stale outputs are cleaned up when a source is deleted (tscdoes not clean up stale outputs by itself). - Include
tsconfig.jsoninfilesso that changing your configuration re-runstsc. - Use
--prettyto get colorful output despite not being attached to a TTY.
I haven't tried it myself, but looks like exactly what I was looking for. Should be a viable solution for monorepos, although of course it is not cool that a third-party tool is required for something like that.
In the answer to the Stack Overflow question, I also found another relevant issue from 2020:
Unfortunately, TypeScript team's stance seems to be that the limitation is by design, so for now, it seems like Wireit running in watch mode during development is as good a solution as it gets.
- Set
Thanks aweebit, the wireit solution looks great. Do you happen to have a monorepo example that uses that setup? I imagine I would have to set up wireit for each internal package then or?
- added a commit that references this issue
on Aug 7, 2026
TypeScript Version: ^4.0.2
Search Terms: "has not been built from source file", "project-references", "typecheck without emit"
Description
In monorepo with project-references it's not possible to use
tscfor type check only without emitting files.Whereas calling
tsc -bworks without any issues. But there's no point in emitting files if we only need to perform type check of all related modules.It seems that is should be possible because of: #32028
Expected behavior:
tscwith--noEmitbuilds all referenced projects without emitting anything:Actual behavior:
It fails because no built files are found for referenced projects:
Playground Link:
repro repo
git clone https://github.com/valerybugakov/project-references-demo cd ./project-references-demo npm i npm run typecheckRelated Issues:
#25613
#25600
#30661 (comment)