-
Notifications
You must be signed in to change notification settings - Fork 13.1k
Description
π Search Terms
custom conditions, package.json exports / imports, customConditions, TSConfig
β Viability Checklist
- This wouldn't be a breaking change in existing TypeScript/JavaScript code
- This wouldn't change the runtime behavior of existing JavaScript code
- This could be implemented without emitting different JS based on the types of the expressions
- This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, new syntax sugar for JS, etc.)
- This isn't a request to add a new utility type: https://github.com/microsoft/TypeScript/wiki/No-New-Utility-Types
- This feature would agree with the rest of our Design Goals: https://github.com/Microsoft/TypeScript/wiki/TypeScript-Design-Goals
β Suggestion
TypeScript does not report an error if a custom condition specified in customConditions is encountered, but the file it resolves to does not exist. Instead, it keeps trying to resolve the requested module using other conditions such as types or default (β Demo).
By contrast, Node.js stops trying as soon as the first relevant condition is encountered and throws a runtime error if the file it resolves to does not exist (β Demo).
The motivation behind this fallback mechanism of TypeScript is probably to avoid problems in cases where my package happens to use the same custom condition name as one of its third-party dependencies. For example, both my package and its dependency could use the source condition to resolve to the source .ts files, but since those are normally not published, trying to import from this dependency would result in an error if the fallback mechanism were not in place.
If my idea is right and this is indeed the motivation for the behavior, well, I am not very convinced. I think there is only one right solution to problems that could arise due to using the same custom condition names as third-party dependencies, and that is to make sure you don't! One common pattern to avoid such conflicts are scoped condition names suggested by @colinhacks, the creator of Zod, in Live types in a TypeScript monorepo.
So as not to introduce a breaking change to the behavior of customConditions, I suggest introducing a new TSConfig option for specifying custom conditions for which the fallback mechanism would not be used. I think strictCustomConditions would be a good name for it.
I still haven't explained how it could be useful though, and that's what I am going to do now.
π Motivating Example
What motivated me to post this feature request was the realization there was no good way to use project references in monorepos employing workspaces I came to during a discussion I started with this comment in #40431.
The issue is shown very well in this demo I adapted from one @robbiespeed posted in that thread. There, @ts-ref/source custom conditions are used for source .ts files. If you run pnpm build, then rename packages/a/foo.ts, and then run pnpm build again, you will not see an error despite the foo.ts file that we mean to import from bar.ts no longer being available.
In general, if you delete, move or rename a source .ts file that has already been compiled with "declaration": true (which is how all referenced projects are compiled because they are required to be composite), TypeScript will not report an error when trying to import from it unless you also manually delete the corresponding declaration file. The reason is exactly the fallback mechanism: instead of stopping and reporting an error when the source file doesn't exist, TypeScript continues to inspect the remaining exports conditions and ends up resolving with the types condition that provides the declaration file.
This behavior can cause developers a lot of confusion, e.g. because imports from files that shouldn't exist anymore are suggested in the editor. There have been a couple other proposals that could solve the Issue:
- "tsc --project tsconfig.json --incremental" doesn't clean files from output folder.Β #36648 (my personal favorite, seems like just the right way to make use of the
.tsbuildinfofiles, see my comment) - Support
tsc --build --noEmitΒ #53979
However, it doesn't look like either of these ideas is going to be implemented any time soon, which is exactly why I am opening this issue so as to propose an alternative solution and hopefully have more luck with it :)
π» Use Cases
- What do you want to use this for?
β For βlive typesβ in monorepos based on workspaces. - What shortcomings exist with current approaches?
β The shortcomings ofcustomConditionsare described above. - What workarounds are you using in the meantime?
β Not using workspaces whenever possible, otherwise using Google's Wireit tool to automatically delete declaration files for which the corresponding source files no longer exist (see comment).