Skip to content

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

Closed

Description

@Codex-

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.

Activity

  1. 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
  2. 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[/+] on Sep 20, 2021
  3. ilogico commented on Sep 21, 2021

    @ilogico

    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/*"]
        }
      }
    }
  4. Codex- commented on Sep 22, 2021

    @Codex-
    Author

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

  5. ilogico commented on Sep 22, 2021

    @ilogico

    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.

  6. andrewbranch commented on Sep 23, 2021

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

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

  7. jarrodpayne commented on Sep 24, 2021

    @jarrodpayne

    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?

  8. Codex- commented on Sep 26, 2021

    @Codex-
    Author

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

  9. andrewbranch commented on Sep 30, 2021

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

  10. typescript-bot commented on Oct 5, 2021

    @typescript-bot
    Contributor

    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.

  11. locked as resolved and limited conversation to collaborators on Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    QuestionAn issue which isn't directly actionable in code

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions