diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index c0a73a0a5aa068..d354dc47b1be6b 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -34,7 +34,7 @@ const { getOptionValue } = require('internal/options'); const policy = getOptionValue('--experimental-policy') ? require('internal/process/policy') : null; -const { sep, relative } = require('path'); +const { sep, relative, resolve } = require('path'); const preserveSymlinks = getOptionValue('--preserve-symlinks'); const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main'); const typeFlag = getOptionValue('--input-type'); @@ -160,16 +160,18 @@ function getPackageScopeConfig(resolved) { return packageConfig; } -/* +/** * Legacy CommonJS main resolution: * 1. let M = pkg_url + (json main field) * 2. TRY(M, M.js, M.json, M.node) * 3. TRY(M/index.js, M/index.json, M/index.node) * 4. TRY(pkg_url/index.js, pkg_url/index.json, pkg_url/index.node) * 5. NOT_FOUND + * @param {string | URL} url + * @returns {boolean} */ function fileExists(url) { - return tryStatSync(fileURLToPath(url)).isFile(); + return statSync(url, { throwIfNoEntry: false })?.isFile() ?? false; } function legacyMainResolve(packageJSONUrl, packageConfig, base) { @@ -236,7 +238,19 @@ function resolveExtensions(search) { return undefined; } -function resolveIndex(search) { +function resolveDirectoryEntry(search) { + const dirPath = fileURLToPath(search); + const pkgJsonPath = resolve(dirPath, 'package.json'); + if (fileExists(pkgJsonPath)) { + const pkgJson = packageJsonReader.read(pkgJsonPath); + if (pkgJson.containsKeys) { + const { main } = JSONParse(pkgJson.string); + if (main != null) { + const mainUrl = pathToFileURL(resolve(dirPath, main)); + return resolveExtensionsWithTryExactName(mainUrl); + } + } + } return resolveExtensions(new URL('index', search)); } @@ -252,10 +266,10 @@ function finalizeResolution(resolved, base) { let file = resolveExtensionsWithTryExactName(resolved); if (file !== undefined) return file; if (!StringPrototypeEndsWith(path, '/')) { - file = resolveIndex(new URL(`${resolved}/`)); + file = resolveDirectoryEntry(new URL(`${resolved}/`)); if (file !== undefined) return file; } else { - return resolveIndex(resolved) || resolved; + return resolveDirectoryEntry(resolved) || resolved; } throw new ERR_MODULE_NOT_FOUND( resolved.pathname, fileURLToPath(base), 'module'); diff --git a/test/es-module/test-esm-specifiers-legacy-flag.mjs b/test/es-module/test-esm-specifiers-legacy-flag.mjs index fcf0c915b649f0..5351846c6a8a04 100644 --- a/test/es-module/test-esm-specifiers-legacy-flag.mjs +++ b/test/es-module/test-esm-specifiers-legacy-flag.mjs @@ -6,12 +6,15 @@ import assert from 'assert'; import commonjs from '../fixtures/es-module-specifiers/package-type-commonjs'; // esm index.js import module from '../fixtures/es-module-specifiers/package-type-module'; +// Directory entry with main.js +import main from '../fixtures/es-module-specifiers/dir-with-main'; // Notice the trailing slash import success, { explicit, implicit, implicitModule } from '../fixtures/es-module-specifiers/'; assert.strictEqual(commonjs, 'commonjs'); assert.strictEqual(module, 'module'); +assert.strictEqual(main, 'main'); assert.strictEqual(success, 'success'); assert.strictEqual(explicit, 'esm'); assert.strictEqual(implicit, 'cjs'); diff --git a/test/fixtures/es-module-specifiers/dir-with-main/main.js b/test/fixtures/es-module-specifiers/dir-with-main/main.js new file mode 100644 index 00000000000000..dfdd47b877319c --- /dev/null +++ b/test/fixtures/es-module-specifiers/dir-with-main/main.js @@ -0,0 +1 @@ +module.exports = 'main'; diff --git a/test/fixtures/es-module-specifiers/dir-with-main/package.json b/test/fixtures/es-module-specifiers/dir-with-main/package.json new file mode 100644 index 00000000000000..2a4fe3630817ab --- /dev/null +++ b/test/fixtures/es-module-specifiers/dir-with-main/package.json @@ -0,0 +1,3 @@ +{ + "main": "./main.js" +}