Skip to content

Commit 1bdb0d9

Browse files
authored
ESM mode nonrelative imports should assume index.js entrypoints even if no package main is present (microsoft#47854)
1 parent e204acf commit 1bdb0d9

6 files changed

+140
-2
lines changed

src/compiler/moduleNameResolver.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1394,7 +1394,13 @@ namespace ts {
13941394
onlyRecordFailures = true;
13951395
}
13961396
}
1397-
return loadNodeModuleFromDirectory(extensions, candidate, onlyRecordFailures, state, considerPackageJson);
1397+
// esm mode relative imports shouldn't do any directory lookups (either inside `package.json`
1398+
// files or implicit `index.js`es). This is a notable depature from cjs norms, where `./foo/pkg`
1399+
// could have been redirected by `./foo/pkg/package.json` to an arbitrary location!
1400+
if (!(state.features & NodeResolutionFeatures.EsmMode)) {
1401+
return loadNodeModuleFromDirectory(extensions, candidate, onlyRecordFailures, state, considerPackageJson);
1402+
}
1403+
return undefined;
13981404
}
13991405

14001406
/*@internal*/
@@ -2178,7 +2184,7 @@ namespace ts {
21782184
if (packageInfo && packageInfo.packageJsonContent.exports && state.features & NodeResolutionFeatures.Exports) {
21792185
return loadModuleFromExports(packageInfo, extensions, combinePaths(".", rest), state, cache, redirectedReference)?.value;
21802186
}
2181-
const pathAndExtension =
2187+
let pathAndExtension =
21822188
loadModuleFromFile(extensions, candidate, onlyRecordFailures, state) ||
21832189
loadNodeModuleFromDirectoryWorker(
21842190
extensions,
@@ -2188,6 +2194,16 @@ namespace ts {
21882194
packageInfo && packageInfo.packageJsonContent,
21892195
packageInfo && packageInfo.versionPaths
21902196
);
2197+
if (
2198+
!pathAndExtension && packageInfo
2199+
&& packageInfo.packageJsonContent.exports === undefined
2200+
&& packageInfo.packageJsonContent.main === undefined
2201+
&& state.features & NodeResolutionFeatures.EsmMode
2202+
) {
2203+
// EsmMode disables index lookup in `loadNodeModuleFromDirectoryWorker` generally, however non-relative package resolutions still assume
2204+
// a default `index.js` entrypoint if no `main` or `exports` are present
2205+
pathAndExtension = loadModuleFromFile(extensions, combinePaths(candidate, "index.js"), onlyRecordFailures, state);
2206+
}
21912207
return withPackageId(packageInfo, pathAndExtension);
21922208
};
21932209

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
tests/cases/compiler/index.ts(2,31): error TS2834: Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node12' or 'nodenext'. Consider adding an extension to the import path.
2+
tests/cases/compiler/index.ts(3,31): error TS2834: Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node12' or 'nodenext'. Consider adding an extension to the import path.
3+
4+
5+
==== tests/cases/compiler/node_modules/pkg/package.json (0 errors) ====
6+
{
7+
"name": "pkg",
8+
"version": "0.0.1"
9+
}
10+
==== tests/cases/compiler/node_modules/pkg/index.d.ts (0 errors) ====
11+
export const item = 4;
12+
==== tests/cases/compiler/pkg/package.json (0 errors) ====
13+
{
14+
"private": true
15+
}
16+
==== tests/cases/compiler/pkg/index.d.ts (0 errors) ====
17+
export const item = 4;
18+
==== tests/cases/compiler/package.json (0 errors) ====
19+
{
20+
"type": "module",
21+
"private": true
22+
}
23+
==== tests/cases/compiler/index.ts (2 errors) ====
24+
import { item } from "pkg"; // should work (`index.js` is assumed to be the entrypoint for packages found via nonrelative import)
25+
import { item as item2 } from "./pkg"; // shouldn't work (`index.js` is _not_ assumed to be the entrypoint for packages found via relative import)
26+
~~~~~~~
27+
!!! error TS2834: Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node12' or 'nodenext'. Consider adding an extension to the import path.
28+
import { item as item3 } from "./node_modules/pkg" // _even if they're in a node_modules folder_
29+
~~~~~~~~~~~~~~~~~~~~
30+
!!! error TS2834: Relative import paths need explicit file extensions in EcmaScript imports when '--moduleResolution' is 'node12' or 'nodenext'. Consider adding an extension to the import path.
31+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//// [tests/cases/compiler/nodeNextImportModeImplicitIndexResolution.ts] ////
2+
3+
//// [package.json]
4+
{
5+
"name": "pkg",
6+
"version": "0.0.1"
7+
}
8+
//// [index.d.ts]
9+
export const item = 4;
10+
//// [package.json]
11+
{
12+
"private": true
13+
}
14+
//// [index.d.ts]
15+
export const item = 4;
16+
//// [package.json]
17+
{
18+
"type": "module",
19+
"private": true
20+
}
21+
//// [index.ts]
22+
import { item } from "pkg"; // should work (`index.js` is assumed to be the entrypoint for packages found via nonrelative import)
23+
import { item as item2 } from "./pkg"; // shouldn't work (`index.js` is _not_ assumed to be the entrypoint for packages found via relative import)
24+
import { item as item3 } from "./node_modules/pkg" // _even if they're in a node_modules folder_
25+
26+
27+
//// [index.js]
28+
export {};
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
=== tests/cases/compiler/node_modules/pkg/index.d.ts ===
2+
export const item = 4;
3+
>item : Symbol(item, Decl(index.d.ts, 0, 12))
4+
5+
=== tests/cases/compiler/pkg/index.d.ts ===
6+
export const item = 4;
7+
>item : Symbol(item, Decl(index.d.ts, 0, 12))
8+
9+
=== tests/cases/compiler/index.ts ===
10+
import { item } from "pkg"; // should work (`index.js` is assumed to be the entrypoint for packages found via nonrelative import)
11+
>item : Symbol(item, Decl(index.ts, 0, 8))
12+
13+
import { item as item2 } from "./pkg"; // shouldn't work (`index.js` is _not_ assumed to be the entrypoint for packages found via relative import)
14+
>item2 : Symbol(item2, Decl(index.ts, 1, 8))
15+
16+
import { item as item3 } from "./node_modules/pkg" // _even if they're in a node_modules folder_
17+
>item3 : Symbol(item3, Decl(index.ts, 2, 8))
18+
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
=== tests/cases/compiler/node_modules/pkg/index.d.ts ===
2+
export const item = 4;
3+
>item : 4
4+
>4 : 4
5+
6+
=== tests/cases/compiler/pkg/index.d.ts ===
7+
export const item = 4;
8+
>item : 4
9+
>4 : 4
10+
11+
=== tests/cases/compiler/index.ts ===
12+
import { item } from "pkg"; // should work (`index.js` is assumed to be the entrypoint for packages found via nonrelative import)
13+
>item : 4
14+
15+
import { item as item2 } from "./pkg"; // shouldn't work (`index.js` is _not_ assumed to be the entrypoint for packages found via relative import)
16+
>item : any
17+
>item2 : any
18+
19+
import { item as item3 } from "./node_modules/pkg" // _even if they're in a node_modules folder_
20+
>item : any
21+
>item3 : any
22+
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// @module: nodenext
2+
// @filename: node_modules/pkg/package.json
3+
{
4+
"name": "pkg",
5+
"version": "0.0.1"
6+
}
7+
// @filename: node_modules/pkg/index.d.ts
8+
export const item = 4;
9+
// @filename: pkg/package.json
10+
{
11+
"private": true
12+
}
13+
// @filename: pkg/index.d.ts
14+
export const item = 4;
15+
// @filename: package.json
16+
{
17+
"type": "module",
18+
"private": true
19+
}
20+
// @filename: index.ts
21+
import { item } from "pkg"; // should work (`index.js` is assumed to be the entrypoint for packages found via nonrelative import)
22+
import { item as item2 } from "./pkg"; // shouldn't work (`index.js` is _not_ assumed to be the entrypoint for packages found via relative import)
23+
import { item as item3 } from "./node_modules/pkg" // _even if they're in a node_modules folder_

0 commit comments

Comments
 (0)