diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index 831e04732d3b7c..2c4644b7a2cd4e 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -398,18 +398,24 @@ function finalizeResolution(resolved, base, preserveSymlinks) { resolved.pathname, 'must not include encoded "/" or "\\" characters', fileURLToPath(base)); - const path = fileURLToPath(resolved); + let path = fileURLToPath(resolved); if (getOptionValue('--experimental-specifier-resolution') === 'node') { let file = resolveExtensionsWithTryExactName(resolved); - if (file !== undefined) return file; - if (!StringPrototypeEndsWith(path, '/')) { - file = resolveDirectoryEntry(new URL(`${resolved}/`)); - if (file !== undefined) return file; - } else { - return resolveDirectoryEntry(resolved) || resolved; + + // Directory + if (file === undefined) { + file = StringPrototypeEndsWith(path, '/') ? + (resolveDirectoryEntry(resolved) || resolved) : resolveDirectoryEntry(new URL(`${resolved}/`)); + + if (file === resolved) return file; + + if (file === undefined) { + throw new ERR_MODULE_NOT_FOUND( + resolved.pathname, fileURLToPath(base), 'module'); + } } - throw new ERR_MODULE_NOT_FOUND( - resolved.pathname, fileURLToPath(base), 'module'); + + path = file; } const stats = tryStatSync(StringPrototypeEndsWith(path, '/') ? diff --git a/test/es-module/test-esm-specifiers-symlink.mjs b/test/es-module/test-esm-specifiers-symlink.mjs new file mode 100644 index 00000000000000..9c60c1da89706f --- /dev/null +++ b/test/es-module/test-esm-specifiers-symlink.mjs @@ -0,0 +1,40 @@ +import * as common from '../common/index.mjs'; +import path from 'path'; +import fs from 'fs/promises'; +import tmpdir from '../common/tmpdir.js'; +import { spawn } from 'child_process'; +import assert from 'assert'; + +tmpdir.refresh(); +const tmpDir = tmpdir.path; + +// Create the following file structure: +// ├── index.mjs +// ├── subfolder +// │ ├── index.mjs +// │ └── node_modules +// │ └── package-a +// │ └── index.mjs +// └── symlink.mjs -> ./subfolder/index.mjs +const entry = path.join(tmpDir, 'index.mjs'); +const symlink = path.join(tmpDir, 'symlink.mjs'); +const real = path.join(tmpDir, 'subfolder', 'index.mjs'); +const packageDir = path.join(tmpDir, 'subfolder', 'node_modules', 'package-a'); +const packageEntry = path.join(packageDir, 'index.mjs'); +try { + await fs.symlink(real, symlink); +} catch (err) { + if (err.code !== 'EPERM') throw err; + common.skip('insufficient privileges for symlinks'); +} +await fs.mkdir(packageDir, { recursive: true }); +await Promise.all([ + fs.writeFile(entry, 'import "./symlink.mjs";'), + fs.writeFile(real, 'export { a } from "package-a/index.mjs"'), + fs.writeFile(packageEntry, 'export const a = 1;'), +]); + +spawn(process.execPath, ['--experimental-specifier-resolution=node', entry], + { stdio: 'inherit' }).on('exit', common.mustCall((code) => { + assert.strictEqual(code, 0); +}));