microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.95k stars 12.47k forks source link

Module resolution behaves differently when typeRoots is specified #59329

Closed wjhsf closed 2 weeks ago

wjhsf commented 3 months ago

πŸ”Ž Search Terms

typeRoots, module resolution, moduleResolution, bundler

πŸ•— Version & Regression Information

⏯ Playground Link

No response

πŸ’» Code

No response

πŸ™ Actual behavior

Setting typeRoots to ./node_modules/@types changes how modules are resolved when using the following compiler options:

πŸ™‚ Expected behavior

Setting typeRoots to ./node_modules/@types should not change how modules are resolved.

Additional information about the issue

Clone this gist and execute setup.sh to see a minimal reproduction. It sets up a package with various export types, and tries to compile a file with corresponding imports using various module/moduleResolution combinations. Not all imports are expected to work for all combinations, but the errors are expected to be the same regardless of whether typeRoots is specified. If the errors are different, the script will log the errors along with the compiler options used. (No output from the script means there were no mismatched errors.)

Sample output:

{ module: 'Preserve', moduleResolution: 'Bundler' }
=== no typeRoots ===
index.ts(1,22): error TS2307: Cannot find module 'pkg' or its corresponding type declarations.
index.ts(2,30): error TS2307: Cannot find module 'pkg/index' or its corresponding type declarations.
index.ts(3,32): error TS2307: Cannot find module 'pkg/index.js' or its corresponding type declarations.
index.ts(4,32): error TS2307: Cannot find module 'pkg/internal' or its corresponding type declarations.
index.ts(5,34): error TS2307: Cannot find module 'pkg/internal.js' or its corresponding type declarations.
index.ts(6,32): error TS2307: Cannot find module 'pkg/external' or its corresponding type declarations.
index.ts(7,34): error TS2307: Cannot find module 'pkg/external.js' or its corresponding type declarations.
index.ts(8,31): error TS2307: Cannot find module 'pkg/exports' or its corresponding type declarations.

== with typeRoots ==
index.ts(8,31): error TS2307: Cannot find module 'pkg/exports' or its corresponding type declarations.

{ module: 'Preserve', moduleResolution: 'Node10' }
=== no typeRoots ===
index.ts(1,22): error TS2307: Cannot find module 'pkg' or its corresponding type declarations.
index.ts(2,30): error TS2307: Cannot find module 'pkg/index' or its corresponding type declarations.
index.ts(3,32): error TS2307: Cannot find module 'pkg/index.js' or its corresponding type declarations.
index.ts(4,32): error TS2307: Cannot find module 'pkg/internal' or its corresponding type declarations.
index.ts(5,34): error TS2307: Cannot find module 'pkg/internal.js' or its corresponding type declarations.
index.ts(6,32): error TS2307: Cannot find module 'pkg/external' or its corresponding type declarations.
index.ts(7,34): error TS2307: Cannot find module 'pkg/external.js' or its corresponding type declarations.
index.ts(8,31): error TS2307: Cannot find module 'pkg/exports' or its corresponding type declarations.

== with typeRoots ==
index.ts(8,31): error TS2307: Cannot find module 'pkg/exports' or its corresponding type declarations.
RyanCavanaugh commented 3 months ago

Can you specify exactly one config which you think should behave differently? We're also not super keen on running .sh files; a concrete set of flat files which we can copy locally would be preferred.

wjhsf commented 3 months ago

Here's a proper repo you can clone. It's not quite flat because typeRoots needs a directory structure.

This is the tsconfig.json from the repo:

{
  "files": ["index.ts"],
  "compilerOptions": {
    "noEmit": true,

    // When "typeRoots" is not specified, this project has 8 compiler errors.
    // When "typeRoots" is specified, this project has 1 compiler error.
    // "typeRoots": ["./node_modules/@types"],

    "module": "Node16",
    "moduleResolution": "Node16",
    // Preserve/Node10, Preserve/Bundler, and ES2015+/Bundler all have
    // differences in behavior depending on the presence of "typeRoots".
    // "moduleResolution": "Node10",
    // "moduleResolution": "Bundler"
    // "module": "Preserve",
    // "module": "ES2015",
    // "module": "ES2020",
    // "module": "ES2022",
    // "module": "ESNext",
  }
}

I also noticed that different combinations of "type": "module" or "type": "commonjs" in the root package.json and in typeRoots/pkg/package.json result in different errors being reported when you have typeRoots set in the tsconfig. That's expected behavior. However, if you don't have typeRoots set, then the errors are always the same. Which seems weird.

Root package typeRoots package Errors
cjs cjs TS2307
cjs esm TS1479, TS1479, TS1479, TS1479, TS1479, TS1479, TS1479, TS2307
esm cjs TS2307, TS2307, TS2307, TS2307
esm esm TS2307, TS2307, TS2307, TS2307

When typeRoots is not specified, the set of errors is "TS2307, TS2307, TS2307, TS2307, TS2307, TS2307, TS2307, TS2307" in all four scenarios.


Perhaps unsurprisingly, I've found that it's the presence of "exports" in the package.json that is causing the issue. When that key is removed, then the behavior of tsc is the same regardless of whether typeRoots is specified.

andrewbranch commented 3 months ago

I think this is a duplicate of https://github.com/microsoft/TypeScript/issues/47444. I tried to fix it at https://github.com/microsoft/TypeScript/pull/58638 and the breaks looked untenable. It may be worth experimenting with resolving "exports" first and falling back to resolving "types", but I held off since non-type-reference module resolution does not continue with fallbacks if "exports" resolution fails, and keeping that consistency with the spec is a plus.

andrewbranch commented 2 weeks ago

Marking as a duplicate and scheduling another crack at #47444 for 5.8. (Any fix we can possibly do here is going to be a breaking change, so it’s too late for 5.7.)

typescript-bot commented 2 weeks ago

This issue has been marked as "Duplicate" and has seen no recent activity. It has been automatically closed for house-keeping purposes.