microsoft / TypeScript

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

Module resolution: Peers of linked dependencies resolve to devDependencies of the linked package and not to the root ancestor's specifier #57402

Open SteRoy opened 5 months ago

SteRoy commented 5 months ago

Demo Repo

https://github.com/SteRoy/ts-pnpm-resolution-repro

Which of the following problems are you reporting?

The module specifier resolves at runtime, but not at build time

Demonstrate the defect described above with a code sample.

import * as agc from "ag-grid-community"
import { typeComparison } from "lib-with-peers"

// This should be fine, at build time, we'll resolve the peers correctly.
console.log(typeComparison(agc))

Run tsc --showConfig and paste its output here

{
    "compilerOptions": {
        "target": "es2016",
        "module": "commonjs",
        "esModuleInterop": true,
        "forceConsistentCasingInFileNames": true,
        "strict": true,
        "skipLibCheck": true,
        "traceResolution": true
    },
    "files": [
        "./index.ts"
    ]
}

Run tsc --traceResolution and paste its output here

Output is way too long

======== Resolving module 'ag-grid-community' from '/ts-pnpm-resolution-repro/packages/lib-with-peers/index.ts'. ========
Module resolution kind is not specified, using 'Node10'.
Loading module 'ag-grid-community' from 'node_modules' folder, target file types: TypeScript, Declaration.
Searching all ancestor node_modules directories for preferred extensions: TypeScript, Declaration.
Found 'package.json' at '/ts-pnpm-resolution-repro/packages/lib-with-peers/node_modules/ag-grid-community/package.json'.
File '/ts-pnpm-resolution-repro/packages/lib-with-peers/node_modules/ag-grid-community.ts' does not exist.
File '/ts-pnpm-resolution-repro/packages/lib-with-peers/node_modules/ag-grid-community.tsx' does not exist.
File '/ts-pnpm-resolution-repro/packages/lib-with-peers/node_modules/ag-grid-community.d.ts' does not exist.
'package.json' does not have a 'typesVersions' field.
'package.json' does not have a 'typings' field.
'package.json' has 'types' field './main.d.ts' that references '/ts-pnpm-resolution-repro/packages/lib-with-peers/node_modules/ag-grid-community/main.d.ts'.
File '/ts-pnpm-resolution-repro/packages/lib-with-peers/node_modules/ag-grid-community/main.d.ts' exists - use it as a name resolution result.
Resolving real path for '/ts-pnpm-resolution-repro/packages/lib-with-peers/node_modules/ag-grid-community/main.d.ts', result '/ts-pnpm-resolution-repro/node_modules/.pnpm/ag-grid-community@31.0.0/node_modules/ag-grid-community/main.d.ts'.
======== Module name 'ag-grid-community' was successfully resolved to '/ts-pnpm-resolution-repro/node_modules/.pnpm/ag-grid-community@31.0.0/node_modules/ag-grid-community/main.d.ts' with Package ID 'ag-grid-community/main.d.ts@31.0.0'. ========

This is the erroneous section though I believe

Paste the package.json of the importing module, if it exists

{
  "name": "tsc-me",
  "version": "1.0.0",
  "description": "",
  "main": "index.ts",
  "scripts": {
    "tsc": "tsc",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "lib-with-peers": "workspace:*",
    "ag-grid-community": "29.0.0"
  }
}

Paste the package.json of the target module, if it exists

{
  "name": "lib-with-peers",
  "version": "1.0.0",
  "description": "",
  "main": "index.ts",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "peerDependencies": {
    "ag-grid-community": "*"
  },
  "devDependencies": {
    "ag-grid-community": "31.0.0"
  }
}

Any other comments can go here

Hey, apologies if this is out of scope but as far as I can tell this is a homogenous feature across pnpm, npm, and yarn.

The basic premise is that workspaces exist and they allow symlinking from node_modules of some 'consumer' (tsc-me in repro) to some 'producer' (lib-with-peers in repro). This works perfectly fine and most tooling handles this gracefully, the symlinks offer a very pleasant DX and are desirable.

Unfortunately, if our 'producer' has peerDependencies (and thus also devDependencies for those peers to make type checking possible from within the 'producer' itself) then tsc will resolve those peers relative to the symlinked folder and disregard the fact that they are peer dependencies entirely.

Please flag if any detail is missing or if repro is unclear

I believe this has previously been reported without clear repro: https://github.com/microsoft/TypeScript/issues/42441#issue-791319315

andrewbranch commented 2 months ago

most tooling handles this gracefully

What tooling specifically? You'll need to provide a repro that shows a tool resolving differently from tsc. The repro you provided only shows what tsc itself is doing, and doesn't make it easy to see what anything else does for comparison. I made a few small changes to make the resolution visible with Node.js, and tsc is matching it exactly (and you're using --moduleResolution node, so everything is working as expected):

~/Developer/microsoft/eg/ts-pnpm-resolution-repro/packages/tsc-me (main)
❯ node index.js       
From tsc-me: 29.0.0
From lib-with-peers: 31.0.0