Skip to content

Commit 52ac441

Browse files
dygaboLinkgoron
authored andcommitted
loader: fix package resolution for edge case
this commit solves a regression introduced with PR-40980. if a resolve call results in a script with .mjs extension the is automatically set to . This avoids the case where an additional in the same directory as the .mjs file would declare the to commonjs PR-URL: nodejs#41218 Refs: nodejs#40980 Refs: yargs/yargs#2068 Reviewed-By: Guy Bedford <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent b428af2 commit 52ac441

File tree

5 files changed

+233
-169
lines changed

5 files changed

+233
-169
lines changed

lib/internal/modules/cjs/loader.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ function trySelf(parentPath, request) {
457457
try {
458458
return finalizeEsmResolution(packageExportsResolve(
459459
pathToFileURL(pkgPath + '/package.json'), expansion, pkg,
460-
pathToFileURL(parentPath), cjsConditions).resolved, parentPath, pkgPath);
460+
pathToFileURL(parentPath), cjsConditions), parentPath, pkgPath);
461461
} catch (e) {
462462
if (e.code === 'ERR_MODULE_NOT_FOUND')
463463
throw createEsmNotFoundErr(request, pkgPath + '/package.json');
@@ -481,7 +481,7 @@ function resolveExports(nmPath, request) {
481481
try {
482482
return finalizeEsmResolution(packageExportsResolve(
483483
pathToFileURL(pkgPath + '/package.json'), '.' + expansion, pkg, null,
484-
cjsConditions).resolved, null, pkgPath);
484+
cjsConditions), null, pkgPath);
485485
} catch (e) {
486486
if (e.code === 'ERR_MODULE_NOT_FOUND')
487487
throw createEsmNotFoundErr(request, pkgPath + '/package.json');

lib/internal/modules/esm/get_format.js

+42-24
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ const legacyExtensionFormatMap = {
3232
'.node': 'commonjs'
3333
};
3434

35+
let experimentalSpecifierResolutionWarned = false;
36+
3537
if (experimentalWasmModules)
3638
extensionFormatMap['.wasm'] = legacyExtensionFormatMap['.wasm'] = 'wasm';
3739

@@ -53,41 +55,57 @@ const protocolHandlers = ObjectAssign(ObjectCreate(null), {
5355

5456
return format;
5557
},
56-
'file:'(parsed, url) {
57-
const ext = extname(parsed.pathname);
58-
let format;
59-
60-
if (ext === '.js') {
61-
format = getPackageType(parsed.href) === 'module' ? 'module' : 'commonjs';
62-
} else {
63-
format = extensionFormatMap[ext];
64-
}
65-
if (!format) {
66-
if (experimentalSpecifierResolution === 'node') {
67-
process.emitWarning(
68-
'The Node.js specifier resolution in ESM is experimental.',
69-
'ExperimentalWarning');
70-
format = legacyExtensionFormatMap[ext];
71-
} else {
72-
throw new ERR_UNKNOWN_FILE_EXTENSION(ext, fileURLToPath(url));
73-
}
74-
}
75-
76-
return format || null;
77-
},
58+
'file:': getFileProtocolModuleFormat,
7859
'node:'() { return 'builtin'; },
7960
});
8061

81-
function defaultGetFormat(url, context) {
62+
function getLegacyExtensionFormat(ext) {
63+
if (
64+
experimentalSpecifierResolution === 'node' &&
65+
!experimentalSpecifierResolutionWarned
66+
) {
67+
process.emitWarning(
68+
'The Node.js specifier resolution in ESM is experimental.',
69+
'ExperimentalWarning');
70+
experimentalSpecifierResolutionWarned = true;
71+
}
72+
return legacyExtensionFormatMap[ext];
73+
}
74+
75+
function getFileProtocolModuleFormat(url, ignoreErrors) {
76+
const ext = extname(url.pathname);
77+
if (ext === '.js') {
78+
return getPackageType(url) === 'module' ? 'module' : 'commonjs';
79+
}
80+
81+
const format = extensionFormatMap[ext];
82+
if (format) return format;
83+
if (experimentalSpecifierResolution !== 'node') {
84+
// Explicit undefined return indicates load hook should rerun format check
85+
if (ignoreErrors)
86+
return undefined;
87+
throw new ERR_UNKNOWN_FILE_EXTENSION(ext, fileURLToPath(url));
88+
}
89+
return getLegacyExtensionFormat(ext) ?? null;
90+
}
91+
92+
function defaultGetFormatWithoutErrors(url, context) {
8293
const parsed = new URL(url);
94+
if (!ObjectPrototypeHasOwnProperty(protocolHandlers, parsed.protocol))
95+
return null;
96+
return protocolHandlers[parsed.protocol](parsed, true);
97+
}
8398

99+
function defaultGetFormat(url, context) {
100+
const parsed = new URL(url);
84101
return ObjectPrototypeHasOwnProperty(protocolHandlers, parsed.protocol) ?
85-
protocolHandlers[parsed.protocol](parsed, url) :
102+
protocolHandlers[parsed.protocol](parsed, false) :
86103
null;
87104
}
88105

89106
module.exports = {
90107
defaultGetFormat,
108+
defaultGetFormatWithoutErrors,
91109
extensionFormatMap,
92110
legacyExtensionFormatMap,
93111
};

lib/internal/modules/esm/load.js

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
const { defaultGetFormat } = require('internal/modules/esm/get_format');
44
const { defaultGetSource } = require('internal/modules/esm/get_source');
5-
const { translators } = require('internal/modules/esm/translators');
65
const { validateAssertions } = require('internal/modules/esm/assert');
76

87
/**
@@ -18,7 +17,7 @@ async function defaultLoad(url, context) {
1817
} = context;
1918
const { importAssertions } = context;
2019

21-
if (!format || !translators.has(format)) {
20+
if (format == null) {
2221
format = defaultGetFormat(url);
2322
}
2423

lib/internal/modules/esm/resolve.js

+40-59
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ function emitTrailingSlashPatternDeprecation(match, pjsonUrl, base) {
107107
* @returns {void}
108108
*/
109109
function emitLegacyIndexDeprecation(url, packageJSONUrl, base, main) {
110-
const format = defaultGetFormat(url);
110+
const format = defaultGetFormatWithoutErrors(url);
111111
if (format !== 'module')
112112
return;
113113
const path = fileURLToPath(url);
@@ -464,22 +464,6 @@ const patternRegEx = /\*/g;
464464
function resolvePackageTargetString(
465465
target, subpath, match, packageJSONUrl, base, pattern, internal, conditions) {
466466

467-
const composeResult = (resolved) => {
468-
let format;
469-
try {
470-
format = getPackageType(resolved);
471-
} catch (err) {
472-
if (err.code === 'ERR_INVALID_FILE_URL_PATH') {
473-
const invalidModuleErr = new ERR_INVALID_MODULE_SPECIFIER(
474-
resolved, 'must not include encoded "/" or "\\" characters', base);
475-
invalidModuleErr.cause = err;
476-
throw invalidModuleErr;
477-
}
478-
throw err;
479-
}
480-
return { resolved, ...(format !== 'none') && { format } };
481-
};
482-
483467
if (subpath !== '' && !pattern && target[target.length - 1] !== '/')
484468
throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
485469

@@ -512,7 +496,7 @@ function resolvePackageTargetString(
512496
if (!StringPrototypeStartsWith(resolvedPath, packagePath))
513497
throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
514498

515-
if (subpath === '') return composeResult(resolved);
499+
if (subpath === '') return resolved;
516500

517501
if (RegExpPrototypeTest(invalidSegmentRegEx, subpath)) {
518502
const request = pattern ?
@@ -521,12 +505,16 @@ function resolvePackageTargetString(
521505
}
522506

523507
if (pattern) {
524-
return composeResult(new URL(RegExpPrototypeSymbolReplace(patternRegEx,
525-
resolved.href,
526-
() => subpath)));
508+
return new URL(
509+
RegExpPrototypeSymbolReplace(
510+
patternRegEx,
511+
resolved.href,
512+
() => subpath
513+
)
514+
);
527515
}
528516

529-
return composeResult(new URL(subpath, resolved));
517+
return new URL(subpath, resolved);
530518
}
531519

532520
/**
@@ -753,7 +741,7 @@ function packageImportsResolve(name, base, conditions) {
753741
packageJSONUrl, imports[name], '', name, base, false, true, conditions
754742
);
755743
if (resolveResult != null) {
756-
return resolveResult.resolved;
744+
return resolveResult;
757745
}
758746
} else {
759747
let bestMatch = '';
@@ -785,7 +773,7 @@ function packageImportsResolve(name, base, conditions) {
785773
bestMatch, base, true,
786774
true, conditions);
787775
if (resolveResult != null) {
788-
return resolveResult.resolved;
776+
return resolveResult;
789777
}
790778
}
791779
}
@@ -849,7 +837,7 @@ function parsePackageName(specifier, base) {
849837
*/
850838
function packageResolve(specifier, base, conditions) {
851839
if (NativeModule.canBeRequiredByUsers(specifier))
852-
return { resolved: new URL('node:' + specifier) };
840+
return new URL('node:' + specifier);
853841

854842
const { packageName, packageSubpath, isScoped } =
855843
parsePackageName(specifier, base);
@@ -888,19 +876,14 @@ function packageResolve(specifier, base, conditions) {
888876
packageJSONUrl, packageSubpath, packageConfig, base, conditions);
889877
}
890878
if (packageSubpath === '.') {
891-
return {
892-
resolved: legacyMainResolve(
893-
packageJSONUrl,
894-
packageConfig,
895-
base),
896-
...(packageConfig.type !== 'none') && { format: packageConfig.type }
897-
};
879+
return legacyMainResolve(
880+
packageJSONUrl,
881+
packageConfig,
882+
base
883+
);
898884
}
899885

900-
return {
901-
resolved: new URL(packageSubpath, packageJSONUrl),
902-
...(packageConfig.type !== 'none') && { format: packageConfig.type }
903-
};
886+
return new URL(packageSubpath, packageJSONUrl);
904887
// Cross-platform root check.
905888
} while (packageJSONPath.length !== lastPath.length);
906889

@@ -944,7 +927,6 @@ function moduleResolve(specifier, base, conditions, preserveSymlinks) {
944927
// Order swapped from spec for minor perf gain.
945928
// Ok since relative URLs cannot parse as URLs.
946929
let resolved;
947-
let format;
948930
if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
949931
resolved = new URL(specifier, base);
950932
} else if (specifier[0] === '#') {
@@ -953,19 +935,13 @@ function moduleResolve(specifier, base, conditions, preserveSymlinks) {
953935
try {
954936
resolved = new URL(specifier);
955937
} catch {
956-
({ resolved, format } = packageResolve(specifier, base, conditions));
938+
resolved = packageResolve(specifier, base, conditions);
957939
}
958940
}
959941
if (resolved.protocol !== 'file:') {
960-
return {
961-
url: resolved
962-
};
942+
return resolved;
963943
}
964-
965-
return {
966-
url: finalizeResolution(resolved, base, preserveSymlinks),
967-
...(format != null) && { format }
968-
};
944+
return finalizeResolution(resolved, base, preserveSymlinks);
969945
}
970946

971947
/**
@@ -1014,6 +990,13 @@ function resolveAsCommonJS(specifier, parentURL) {
1014990
}
1015991
}
1016992

993+
function throwIfUnsupportedURLProtocol(url) {
994+
if (url.protocol !== 'file:' && url.protocol !== 'data:' &&
995+
url.protocol !== 'node:') {
996+
throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(url);
997+
}
998+
}
999+
10171000
function defaultResolve(specifier, context = {}, defaultResolveUnused) {
10181001
let { parentURL, conditions } = context;
10191002
if (parentURL && policy?.manifest) {
@@ -1054,15 +1037,13 @@ function defaultResolve(specifier, context = {}, defaultResolveUnused) {
10541037

10551038
conditions = getConditionsSet(conditions);
10561039
let url;
1057-
let format;
10581040
try {
1059-
({ url, format } =
1060-
moduleResolve(
1061-
specifier,
1062-
parentURL,
1063-
conditions,
1064-
isMain ? preserveSymlinksMain : preserveSymlinks
1065-
));
1041+
url = moduleResolve(
1042+
specifier,
1043+
parentURL,
1044+
conditions,
1045+
isMain ? preserveSymlinksMain : preserveSymlinks
1046+
);
10661047
} catch (error) {
10671048
// Try to give the user a hint of what would have been the
10681049
// resolved CommonJS module
@@ -1086,13 +1067,11 @@ function defaultResolve(specifier, context = {}, defaultResolveUnused) {
10861067
throw error;
10871068
}
10881069

1089-
if (url.protocol !== 'file:' && url.protocol !== 'data:' &&
1090-
url.protocol !== 'node:')
1091-
throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(url);
1070+
throwIfUnsupportedURLProtocol(url);
10921071

10931072
return {
10941073
url: `${url}`,
1095-
...(format != null) && { format }
1074+
format: defaultGetFormatWithoutErrors(url),
10961075
};
10971076
}
10981077

@@ -1107,4 +1086,6 @@ module.exports = {
11071086
};
11081087

11091088
// cycle
1110-
const { defaultGetFormat } = require('internal/modules/esm/get_format');
1089+
const {
1090+
defaultGetFormatWithoutErrors,
1091+
} = require('internal/modules/esm/get_format');

0 commit comments

Comments
 (0)