Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monorepo / workspaces imports attempt to reference rootDir / outDir literally instead of package path reference #45964

Closed
Codex- opened this issue Sep 20, 2021 · 8 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@Codex-
Copy link

Codex- commented Sep 20, 2021

Bug Report

🔎 Search Terms

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.

I have created a minimum reproduction of the issue here: https://github.com/Codex-/monorepo-issue

Does not work:

// These imports should resolve the generated package structure:
import { name as catName } from "@monorepo-issue/dep-a/cat/cat";
import { name as dogName } 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 { name as catName } from "@monorepo-issue/dep-a/lib/cat/cat";
import { name as dogName } 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 { name as catName } from "@monorepo-issue/dep-a/src/cat/cat";
import { name as dogName } 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:
image

(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.

@Codex- Codex- 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 Sep 20, 2021
@Codex- Codex- changed the title 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 Sep 20, 2021
@ilogico
Copy link

ilogico commented 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 typesVersions to your package.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-
Copy link
Author

Codex- commented Sep 22, 2021

Hmmm seems weird, workspace packages do not act like real packages?

@ilogico
Copy link

ilogico commented 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.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.

@andrewbranch
Copy link
Member

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 @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

// packages/main/tsconfig.json
{
  "compilerOptions": {
    "paths": {
      "dep-a/*": ["../dep-a/src/*"]
    }
}

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.

@andrewbranch andrewbranch added the Question An issue which isn't directly actionable in code label Sep 23, 2021
@paynecodes
Copy link

The intended workflow for workspaces like this right now is to use path mappings in your tsconfig.json like

// packages/main/tsconfig.json

{

  "compilerOptions": {

    "paths": {

      "dep-a/*": ["../dep-a/src/*"]

    }

}

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-
Copy link
Author

Codex- commented Sep 26, 2021

Yeah have gone with #45964 (comment) until a more concrete solution / answer arises. Both current solutions don't feel great.

@andrewbranch
Copy link
Member

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.

@typescript-bot
Copy link
Collaborator

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

5 participants