diff --git a/src/compiler/moduleNameResolver.ts b/src/compiler/moduleNameResolver.ts index be8573b075db3..31c4daeb45936 100644 --- a/src/compiler/moduleNameResolver.ts +++ b/src/compiler/moduleNameResolver.ts @@ -2756,27 +2756,31 @@ function loadModuleFromImmediateNodeModulesDirectory(extensions: Extensions, mod function loadModuleFromSpecificNodeModulesDirectory(extensions: Extensions, moduleName: string, nodeModulesDirectory: string, nodeModulesDirectoryExists: boolean, state: ModuleResolutionState, cache: ModuleResolutionCache | undefined, redirectedReference: ResolvedProjectReference | undefined): Resolved | undefined { const candidate = normalizePath(combinePaths(nodeModulesDirectory, moduleName)); + const { packageName, rest } = parsePackageName(moduleName); + const packageDirectory = combinePaths(nodeModulesDirectory, packageName); + let rootPackageInfo: PackageJsonInfo | undefined; // First look for a nested package.json, as in `node_modules/foo/bar/package.json`. let packageInfo = getPackageJsonInfo(candidate, !nodeModulesDirectoryExists, state); // But only if we're not respecting export maps (if we are, we might redirect around this location) - if (!(state.features & NodeResolutionFeatures.Exports)) { - if (packageInfo) { - const fromFile = loadModuleFromFile(extensions, candidate, !nodeModulesDirectoryExists, state); - if (fromFile) { - return noPackageId(fromFile); - } - - const fromDirectory = loadNodeModuleFromDirectoryWorker( - extensions, - candidate, - !nodeModulesDirectoryExists, - state, - packageInfo.contents.packageJsonContent, - getVersionPathsOfPackageJsonInfo(packageInfo, state), - ); - return withPackageId(packageInfo, fromDirectory); + if (rest !== "" && packageInfo && ( + !(state.features & NodeResolutionFeatures.Exports) || + !hasProperty((rootPackageInfo = getPackageJsonInfo(packageDirectory, !nodeModulesDirectoryExists, state))?.contents.packageJsonContent ?? emptyArray, "exports") + )) { + const fromFile = loadModuleFromFile(extensions, candidate, !nodeModulesDirectoryExists, state); + if (fromFile) { + return noPackageId(fromFile); } + + const fromDirectory = loadNodeModuleFromDirectoryWorker( + extensions, + candidate, + !nodeModulesDirectoryExists, + state, + packageInfo.contents.packageJsonContent, + getVersionPathsOfPackageJsonInfo(packageInfo, state), + ); + return withPackageId(packageInfo, fromDirectory); } const loader: ResolutionKindSpecificLoader = (extensions, candidate, onlyRecordFailures, state) => { @@ -2803,11 +2807,9 @@ function loadModuleFromSpecificNodeModulesDirectory(extensions: Extensions, modu return withPackageId(packageInfo, pathAndExtension); }; - const { packageName, rest } = parsePackageName(moduleName); - const packageDirectory = combinePaths(nodeModulesDirectory, packageName); if (rest !== "") { // Previous `packageInfo` may have been from a nested package.json; ensure we have the one from the package root now. - packageInfo = getPackageJsonInfo(packageDirectory, !nodeModulesDirectoryExists, state); + packageInfo = rootPackageInfo ?? getPackageJsonInfo(packageDirectory, !nodeModulesDirectoryExists, state); } // package exports are higher priority than file/directory/typesVersions lookups and (and, if there's exports present, blocks them) if (packageInfo && packageInfo.contents.packageJsonContent.exports && state.features & NodeResolutionFeatures.Exports) { diff --git a/tests/baselines/reference/nestedPackageJsonRedirect(moduleresolution=bundler).trace.json b/tests/baselines/reference/nestedPackageJsonRedirect(moduleresolution=bundler).trace.json new file mode 100644 index 0000000000000..b6dd002185a28 --- /dev/null +++ b/tests/baselines/reference/nestedPackageJsonRedirect(moduleresolution=bundler).trace.json @@ -0,0 +1,17 @@ +[ + "======== Resolving module '@restart/hooks/useMergedRefs' from '/main.ts'. ========", + "Explicitly specified module resolution kind: 'Bundler'.", + "File '/package.json' does not exist.", + "Loading module '@restart/hooks/useMergedRefs' from 'node_modules' folder, target file types: TypeScript, JavaScript, Declaration, JSON.", + "Found 'package.json' at '/node_modules/@restart/hooks/useMergedRefs/package.json'.", + "Found 'package.json' at '/node_modules/@restart/hooks/package.json'.", + "File '/node_modules/@restart/hooks/useMergedRefs.ts' does not exist.", + "File '/node_modules/@restart/hooks/useMergedRefs.tsx' does not exist.", + "File '/node_modules/@restart/hooks/useMergedRefs.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 '../esm/useMergedRefs.d.ts' that references '/node_modules/@restart/hooks/esm/useMergedRefs.d.ts'.", + "File '/node_modules/@restart/hooks/esm/useMergedRefs.d.ts' exist - use it as a name resolution result.", + "Resolving real path for '/node_modules/@restart/hooks/esm/useMergedRefs.d.ts', result '/node_modules/@restart/hooks/esm/useMergedRefs.d.ts'.", + "======== Module name '@restart/hooks/useMergedRefs' was successfully resolved to '/node_modules/@restart/hooks/esm/useMergedRefs.d.ts'. ========" +] \ No newline at end of file diff --git a/tests/baselines/reference/nestedPackageJsonRedirect(moduleresolution=node16).trace.json b/tests/baselines/reference/nestedPackageJsonRedirect(moduleresolution=node16).trace.json new file mode 100644 index 0000000000000..4d8ad876095fe --- /dev/null +++ b/tests/baselines/reference/nestedPackageJsonRedirect(moduleresolution=node16).trace.json @@ -0,0 +1,31 @@ +[ + "File '/node_modules/@restart/hooks/esm/package.json' does not exist.", + "Found 'package.json' at '/node_modules/@restart/hooks/package.json'.", + "File '/package.json' does not exist.", + "======== Resolving module '@restart/hooks/useMergedRefs' from '/main.ts'. ========", + "Explicitly specified module resolution kind: 'Node16'.", + "Resolving in CJS mode with conditions 'node', 'require', 'types'.", + "File '/package.json' does not exist according to earlier cached lookups.", + "Loading module '@restart/hooks/useMergedRefs' from 'node_modules' folder, target file types: TypeScript, JavaScript, Declaration.", + "Found 'package.json' at '/node_modules/@restart/hooks/useMergedRefs/package.json'.", + "File '/node_modules/@restart/hooks/package.json' exists according to earlier cached lookups.", + "File '/node_modules/@restart/hooks/useMergedRefs.ts' does not exist.", + "File '/node_modules/@restart/hooks/useMergedRefs.tsx' does not exist.", + "File '/node_modules/@restart/hooks/useMergedRefs.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 '../esm/useMergedRefs.d.ts' that references '/node_modules/@restart/hooks/esm/useMergedRefs.d.ts'.", + "File '/node_modules/@restart/hooks/esm/useMergedRefs.d.ts' exist - use it as a name resolution result.", + "Resolving real path for '/node_modules/@restart/hooks/esm/useMergedRefs.d.ts', result '/node_modules/@restart/hooks/esm/useMergedRefs.d.ts'.", + "======== Module name '@restart/hooks/useMergedRefs' was successfully resolved to '/node_modules/@restart/hooks/esm/useMergedRefs.d.ts'. ========", + "File 'package.json' does not exist.", + "File '/package.json' does not exist according to earlier cached lookups.", + "File 'package.json' does not exist according to earlier cached lookups.", + "File '/package.json' does not exist according to earlier cached lookups.", + "File 'package.json' does not exist according to earlier cached lookups.", + "File '/package.json' does not exist according to earlier cached lookups.", + "File 'package.json' does not exist according to earlier cached lookups.", + "File '/package.json' does not exist according to earlier cached lookups.", + "File 'package.json' does not exist according to earlier cached lookups.", + "File '/package.json' does not exist according to earlier cached lookups." +] \ No newline at end of file diff --git a/tests/baselines/reference/nestedPackageJsonRedirect(moduleresolution=nodenext).trace.json b/tests/baselines/reference/nestedPackageJsonRedirect(moduleresolution=nodenext).trace.json new file mode 100644 index 0000000000000..77e7411b6622e --- /dev/null +++ b/tests/baselines/reference/nestedPackageJsonRedirect(moduleresolution=nodenext).trace.json @@ -0,0 +1,31 @@ +[ + "File '/node_modules/@restart/hooks/esm/package.json' does not exist.", + "Found 'package.json' at '/node_modules/@restart/hooks/package.json'.", + "File '/package.json' does not exist.", + "======== Resolving module '@restart/hooks/useMergedRefs' from '/main.ts'. ========", + "Explicitly specified module resolution kind: 'NodeNext'.", + "Resolving in CJS mode with conditions 'node', 'require', 'types'.", + "File '/package.json' does not exist according to earlier cached lookups.", + "Loading module '@restart/hooks/useMergedRefs' from 'node_modules' folder, target file types: TypeScript, JavaScript, Declaration.", + "Found 'package.json' at '/node_modules/@restart/hooks/useMergedRefs/package.json'.", + "File '/node_modules/@restart/hooks/package.json' exists according to earlier cached lookups.", + "File '/node_modules/@restart/hooks/useMergedRefs.ts' does not exist.", + "File '/node_modules/@restart/hooks/useMergedRefs.tsx' does not exist.", + "File '/node_modules/@restart/hooks/useMergedRefs.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 '../esm/useMergedRefs.d.ts' that references '/node_modules/@restart/hooks/esm/useMergedRefs.d.ts'.", + "File '/node_modules/@restart/hooks/esm/useMergedRefs.d.ts' exist - use it as a name resolution result.", + "Resolving real path for '/node_modules/@restart/hooks/esm/useMergedRefs.d.ts', result '/node_modules/@restart/hooks/esm/useMergedRefs.d.ts'.", + "======== Module name '@restart/hooks/useMergedRefs' was successfully resolved to '/node_modules/@restart/hooks/esm/useMergedRefs.d.ts'. ========", + "File 'package.json' does not exist.", + "File '/package.json' does not exist according to earlier cached lookups.", + "File 'package.json' does not exist according to earlier cached lookups.", + "File '/package.json' does not exist according to earlier cached lookups.", + "File 'package.json' does not exist according to earlier cached lookups.", + "File '/package.json' does not exist according to earlier cached lookups.", + "File 'package.json' does not exist according to earlier cached lookups.", + "File '/package.json' does not exist according to earlier cached lookups.", + "File 'package.json' does not exist according to earlier cached lookups.", + "File '/package.json' does not exist according to earlier cached lookups." +] \ No newline at end of file diff --git a/tests/cases/conformance/moduleResolution/nestedPackageJsonRedirect.ts b/tests/cases/conformance/moduleResolution/nestedPackageJsonRedirect.ts new file mode 100644 index 0000000000000..8d5804c1fd010 --- /dev/null +++ b/tests/cases/conformance/moduleResolution/nestedPackageJsonRedirect.ts @@ -0,0 +1,29 @@ +// @moduleResolution: node16,nodenext,bundler +// @noEmit: true +// @noTypesAndSymbols: true +// @traceResolution: true +// @strict: true + +// @Filename: /node_modules/@restart/hooks/package.json +{ + "name": "@restart/hooks", + "version": "0.3.25", + "main": "cjs/index.js", + "types": "cjs/index.d.ts", + "module": "esm/index.js" +} + +// @Filename: /node_modules/@restart/hooks/useMergedRefs/package.json +{ + "name": "@restart/hooks/useMergedRefs", + "private": true, + "main": "../cjs/useMergedRefs.js", + "module": "../esm/useMergedRefs.js", + "types": "../esm/useMergedRefs.d.ts" + } + +// @Filename: /node_modules/@restart/hooks/esm/useMergedRefs.d.ts +export {}; + +// @Filename: /main.ts +import {} from "@restart/hooks/useMergedRefs";