You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Monorepo / workspaces imports attempt to reference rootDir / outDir literally instead of package path reference #45964
Monorepo workspaces
import from distribution instead of source
auto-import from package not source
import from source incorrectly
🕗 Version & Regression Information
This is the behavior in every version I tried, from 4.1.* to 4.4.3
💻 Code
When trying to resolve a packages exports the paths do not resolve as a normal packages would, except for the case of the index, where it instead tries to use either the outDir or the rootDir.
// These imports should resolve the generated package structure:import{nameascatName}from"@monorepo-issue/dep-a/cat/cat";import{nameasdogName}from"@monorepo-issue/dep-a/dog/dog";import{isEvil}from"@monorepo-issue/dep-a/eggman/eggman";
Works when referenced via the outDir:
// Importing from generated lib works:import{nameascatName}from"@monorepo-issue/dep-a/lib/cat/cat";import{nameasdogName}from"@monorepo-issue/dep-a/lib/dog/dog";import{isEvil}from"@monorepo-issue/dep-a/lib/eggman/eggman";
Works when referenced via the rootDir:
// Importing from src works:import{nameascatName}from"@monorepo-issue/dep-a/src/cat/cat";import{nameasdogName}from"@monorepo-issue/dep-a/src/dog/dog";import{isEvil}from"@monorepo-issue/dep-a/src/eggman/eggman";
🙁 Actual behavior
When importing from these monorepo/workspaces packages the path should be that of the package import path, opposed to the source:
(Should not have src but instead the package path of @monorepo-issue/dep-a/eggman/eggman)
🙂 Expected behavior
I would expect that as this is imported to main as a package, it would behave like a package and the imports to be valid without requiring the full path including the package root and either the outDir or rootDir.
I'm hoping this is a simple repository configuration issue, from the way workspaces and package references are described I would expect this to work as described above though.
changed the title [-]Monorepo / workspaces attempts to reference original source instead of distribution in packages[/-][+]Monorepo / workspaces attempts to reference rootDir / outDir literally instead of package path reference[/+]on Sep 20, 2021
AFAIK, this has nothing to do with monorepos. It's just that, if the package is meant to be consumed by importing its sub modules, additional configuration for both NodeJS and TypeScript is required.
package.json always allowed you to define the library entry point with main, but importing other files would be relative to the package's root.
Node 12 added support for this exports key, which allows you to map the exports to other locations (and add the file extension so it works with ES modules. This way you can have @monorepo-issue/package/stuff map to {package-root}/outDir/stuff.js instead of {package-root}/stuff. Without this, you would have to import it with @monorepo-issue/package/outDir/stuff.js.
However, TypeScript is not aware of the mapping done by exports, which means it will still respect the main field and try to look for a type definition next to it, but for sub imports, it will look for them starting at the root of the package. But it's possible to work around this by defining a typesVersions property that reproduces the exports property.
Reacted by Andrew Branch, Jarrod Payne and Gabriele Tomberli
The salient point here is we have no idea what directory you plan to npm publish in your dependency—the root, or your outDir? TypeScript can only see the relationship of the files as they are on disk. As Gonçalo Fernandes (@ilogico) points out, export maps would be one way of solving this in the future (TypeScript 4.5 will support them under --moduleResolution node12), since both TypeScript and Node understand them the same way. The intended workflow for workspaces like this right now is to use path mappings in your tsconfig.json like
The paths referencing dep-a/src or dep-a/lib will still resolve, which is not desirable, but auto-imports will deprioritize these in favor of the path-mapped version. See #40253 and its linked issues for more details.
Doesn't #45964 (comment) solve this more cleanly by allowing the package to specify how consumers should resolve these paths? This seems to solve the problem in lieu of TS 4.5, but I'm hesitant to go full bore with this without understanding where the edge cases may be. Is this primarily because auto-imports won't behave as expected?
Doesn't [export maps] solve this more cleanly by allowing the package to specify how consumers should resolve these paths? This [tsconfig path mapping] seems to solve the problem in lieu of TS 4.5, but I'm hesitant to go full bore with this without understanding where the edge cases may be. Is this primarily because auto-imports won't behave as expected?
I think export maps are probably going to be a better long-term solution for most Node projects, but it’s kind of a big “it depends,” especially for the short term when export maps are pretty nascent. When you use path mapping in this case, it feels very kludgy that you can still resolve the relative paths that won’t actually work at runtime. The node12+ module resolution stuff is really nice because the package.json-level config specifies exactly what should happen at runtime, and TypeScript can just follow that. But if you’re not actually writing for node, what then? Path mapping is a more versatile tool, and that has both upsides and downsides. It’s not always the right tool for the job, and it’s not perfect even when it is the right tool for the job. There are so many different JS runtimes and bundlers and ways of organizing projects that TypeScript will never have a tailored solution for each one.
This issue has been marked as 'Question' and has seen no recent activity. It has been automatically closed for house-keeping purposes. If you're still waiting on a response, questions are usually better suited to stackoverflow.
Activity
[-]Monorepo / workspaces attempts to reference original source instead of distribution in packages[/-][+]Monorepo / workspaces attempts to reference rootDir / outDir literally instead of package path reference[/+][-]Monorepo / workspaces attempts to reference rootDir / outDir literally instead of package path reference[/-][+]Monorepo / workspaces imports attempt to reference rootDir / outDir literally instead of package path reference[/+]ilogico commentedon Sep 21, 2021
TypeScript does not support subpath exports yet, which would be the modern way of configuring your package.
Currently, you wan work around this issue by adding a
typesVersionsto yourpackage.json. Something like this worked for me:{ "main": "./dist/lib.js", "exports": { ".": "./dist/lib.js", "./*": "./dist/*.js" }, "types": "./dist/lib.d.ts", "typesVersions": { "*": { "dist/lib.d.ts": ["./dist/lib.d.ts"], "*": ["./dist/*"] } } }Codex- commentedon Sep 22, 2021
Hmmm seems weird, workspace packages do not act like real packages?
ilogico commentedon Sep 22, 2021
AFAIK, this has nothing to do with monorepos. It's just that, if the package is meant to be consumed by importing its sub modules, additional configuration for both NodeJS and TypeScript is required.
package.jsonalways allowed you to define the library entry point withmain, but importing other files would be relative to the package's root.Node 12 added support for this
exportskey, which allows you to map the exports to other locations (and add the file extension so it works with ES modules. This way you can have@monorepo-issue/package/stuffmap to{package-root}/outDir/stuff.jsinstead of{package-root}/stuff. Without this, you would have to import it with@monorepo-issue/package/outDir/stuff.js.However, TypeScript is not aware of the mapping done by
exports, which means it will still respect themainfield and try to look for a type definition next to it, but for sub imports, it will look for them starting at the root of the package. But it's possible to work around this by defining atypesVersionsproperty that reproduces theexportsproperty.andrewbranch commentedon Sep 23, 2021
The salient point here is we have no idea what directory you plan to
npm publishin your dependency—the root, or youroutDir? TypeScript can only see the relationship of the files as they are on disk. As Gonçalo Fernandes (@ilogico) points out, export maps would be one way of solving this in the future (TypeScript 4.5 will support them under--moduleResolution node12), since both TypeScript and Node understand them the same way. The intended workflow for workspaces like this right now is to use path mappings in your tsconfig.json likeThe paths referencing
dep-a/srcordep-a/libwill still resolve, which is not desirable, but auto-imports will deprioritize these in favor of the path-mapped version. See #40253 and its linked issues for more details.jarrodpayne commentedon Sep 24, 2021
Doesn't #45964 (comment) solve this more cleanly by allowing the package to specify how consumers should resolve these paths? This seems to solve the problem in lieu of TS 4.5, but I'm hesitant to go full bore with this without understanding where the edge cases may be. Is this primarily because auto-imports won't behave as expected?
Codex- commentedon Sep 26, 2021
Yeah have gone with #45964 (comment) until a more concrete solution / answer arises. Both current solutions don't feel great.
andrewbranch commentedon Sep 30, 2021
I think export maps are probably going to be a better long-term solution for most Node projects, but it’s kind of a big “it depends,” especially for the short term when export maps are pretty nascent. When you use path mapping in this case, it feels very kludgy that you can still resolve the relative paths that won’t actually work at runtime. The node12+ module resolution stuff is really nice because the package.json-level config specifies exactly what should happen at runtime, and TypeScript can just follow that. But if you’re not actually writing for node, what then? Path mapping is a more versatile tool, and that has both upsides and downsides. It’s not always the right tool for the job, and it’s not perfect even when it is the right tool for the job. There are so many different JS runtimes and bundlers and ways of organizing projects that TypeScript will never have a tailored solution for each one.
typescript-bot commentedon Oct 5, 2021
This issue has been marked as 'Question' and has seen no recent activity. It has been automatically closed for house-keeping purposes. If you're still waiting on a response, questions are usually better suited to stackoverflow.