Skip to content

Commit d422e58

Browse files
dygabodanielleadams
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: #41218 Refs: #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]> Backport-PR-URL: #41752 Reviewed-By: Danielle Adams <[email protected]> Reviewed-By: Geoffrey Booth <[email protected]> Reviewed-By: Gerhard Stöbich <[email protected]>
1 parent 694dc12 commit d422e58

File tree

4 files changed

+240
-176
lines changed

4 files changed

+240
-176
lines changed

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

+49-68
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ function emitTrailingSlashPatternDeprecation(match, pjsonUrl, isExports, base) {
131131
* @returns {void}
132132
*/
133133
function emitLegacyIndexDeprecation(url, packageJSONUrl, base, main) {
134-
const format = defaultGetFormat(url);
134+
const format = defaultGetFormatWithoutErrors(url);
135135
if (format !== 'module')
136136
return;
137137
const path = fileURLToPath(url);
@@ -488,22 +488,6 @@ const patternRegEx = /\*/g;
488488
function resolvePackageTargetString(
489489
target, subpath, match, packageJSONUrl, base, pattern, internal, conditions) {
490490

491-
const composeResult = (resolved) => {
492-
let format;
493-
try {
494-
format = getPackageType(resolved);
495-
} catch (err) {
496-
if (err.code === 'ERR_INVALID_FILE_URL_PATH') {
497-
const invalidModuleErr = new ERR_INVALID_MODULE_SPECIFIER(
498-
resolved, 'must not include encoded "/" or "\\" characters', base);
499-
invalidModuleErr.cause = err;
500-
throw invalidModuleErr;
501-
}
502-
throw err;
503-
}
504-
return { resolved, ...(format !== 'none') && { format } };
505-
};
506-
507491
if (subpath !== '' && !pattern && target[target.length - 1] !== '/')
508492
throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
509493

@@ -536,18 +520,22 @@ function resolvePackageTargetString(
536520
if (!StringPrototypeStartsWith(resolvedPath, packagePath))
537521
throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
538522

539-
if (subpath === '') return composeResult(resolved);
523+
if (subpath === '') return resolved;
540524

541525
if (RegExpPrototypeTest(invalidSegmentRegEx, subpath))
542526
throwInvalidSubpath(match + subpath, packageJSONUrl, internal, base);
543527

544528
if (pattern) {
545-
return composeResult(new URL(RegExpPrototypeSymbolReplace(patternRegEx,
546-
resolved.href,
547-
() => subpath)));
529+
return new URL(
530+
RegExpPrototypeSymbolReplace(
531+
patternRegEx,
532+
resolved.href,
533+
() => subpath
534+
)
535+
);
548536
}
549537

550-
return composeResult(new URL(subpath, resolved));
538+
return new URL(subpath, resolved);
551539
}
552540

553541
/**
@@ -673,15 +661,15 @@ function packageExportsResolve(
673661
!StringPrototypeIncludes(packageSubpath, '*') &&
674662
!StringPrototypeEndsWith(packageSubpath, '/')) {
675663
const target = exports[packageSubpath];
676-
const resolveResult = resolvePackageTarget(
664+
const resolved = resolvePackageTarget(
677665
packageJSONUrl, target, '', packageSubpath, base, false, false, conditions
678666
);
679667

680-
if (resolveResult == null) {
668+
if (resolved == null) {
681669
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
682670
}
683671

684-
return { ...resolveResult, exact: true };
672+
return { resolved, exact: true };
685673
}
686674

687675
let bestMatch = '';
@@ -717,7 +705,7 @@ function packageExportsResolve(
717705
if (bestMatch) {
718706
const target = exports[bestMatch];
719707
const pattern = StringPrototypeIncludes(bestMatch, '*');
720-
const resolveResult = resolvePackageTarget(
708+
const resolved = resolvePackageTarget(
721709
packageJSONUrl,
722710
target,
723711
bestMatchSubpath,
@@ -727,15 +715,15 @@ function packageExportsResolve(
727715
false,
728716
conditions);
729717

730-
if (resolveResult == null) {
718+
if (resolved == null) {
731719
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
732720
}
733721

734722
if (!pattern) {
735723
emitFolderMapDeprecation(bestMatch, packageJSONUrl, true, base);
736724
}
737725

738-
return { ...resolveResult, exact: pattern };
726+
return { resolved, exact: pattern };
739727
}
740728

741729
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
@@ -775,11 +763,11 @@ function packageImportsResolve(name, base, conditions) {
775763
if (ObjectPrototypeHasOwnProperty(imports, name) &&
776764
!StringPrototypeIncludes(name, '*') &&
777765
!StringPrototypeEndsWith(name, '/')) {
778-
const resolveResult = resolvePackageTarget(
766+
const resolved = resolvePackageTarget(
779767
packageJSONUrl, imports[name], '', name, base, false, true, conditions
780768
);
781-
if (resolveResult != null) {
782-
return { resolved: resolveResult.resolved, exact: true };
769+
if (resolved != null) {
770+
return { resolved, exact: true };
783771
}
784772
} else {
785773
let bestMatch = '';
@@ -812,15 +800,15 @@ function packageImportsResolve(name, base, conditions) {
812800
if (bestMatch) {
813801
const target = imports[bestMatch];
814802
const pattern = StringPrototypeIncludes(bestMatch, '*');
815-
const resolveResult = resolvePackageTarget(
803+
const resolved = resolvePackageTarget(
816804
packageJSONUrl, target,
817805
bestMatchSubpath, bestMatch,
818806
base, pattern, true,
819807
conditions);
820-
if (resolveResult !== null) {
808+
if (resolved !== null) {
821809
if (!pattern)
822810
emitFolderMapDeprecation(bestMatch, packageJSONUrl, false, base);
823-
return { resolved: resolveResult.resolved, exact: pattern };
811+
return { resolved, exact: pattern };
824812
}
825813
}
826814
}
@@ -880,7 +868,7 @@ function parsePackageName(specifier, base) {
880868
* @param {string} specifier
881869
* @param {string | URL | undefined} base
882870
* @param {Set<string>} conditions
883-
* @returns {resolved: URL, format? : string}
871+
* @returns {URL}
884872
*/
885873
function packageResolve(specifier, base, conditions) {
886874
if (NativeModule.canBeRequiredByUsers(specifier))
@@ -896,7 +884,8 @@ function packageResolve(specifier, base, conditions) {
896884
if (packageConfig.name === packageName &&
897885
packageConfig.exports !== undefined && packageConfig.exports !== null) {
898886
return packageExportsResolve(
899-
packageJSONUrl, packageSubpath, packageConfig, base, conditions);
887+
packageJSONUrl, packageSubpath, packageConfig, base, conditions
888+
).resolved;
900889
}
901890
}
902891

@@ -920,24 +909,19 @@ function packageResolve(specifier, base, conditions) {
920909
const packageConfig = getPackageConfig(packageJSONPath, specifier, base);
921910
if (packageConfig.exports !== undefined && packageConfig.exports !== null) {
922911
return packageExportsResolve(
923-
packageJSONUrl, packageSubpath, packageConfig, base, conditions);
912+
packageJSONUrl, packageSubpath, packageConfig, base, conditions
913+
).resolved;
924914
}
925915

926916
if (packageSubpath === '.') {
927-
return {
928-
resolved: legacyMainResolve(
929-
packageJSONUrl,
930-
packageConfig,
931-
base),
932-
...(packageConfig.type !== 'none') && { format: packageConfig.type }
933-
};
917+
return legacyMainResolve(
918+
packageJSONUrl,
919+
packageConfig,
920+
base
921+
);
934922
}
935923

936-
return {
937-
resolved: new URL(packageSubpath, packageJSONUrl),
938-
...(packageConfig.type !== 'none') && { format: packageConfig.type }
939-
};
940-
924+
return new URL(packageSubpath, packageJSONUrl);
941925
// Cross-platform root check.
942926
} while (packageJSONPath.length !== lastPath.length);
943927

@@ -981,7 +965,6 @@ function moduleResolve(specifier, base, conditions, preserveSymlinks) {
981965
// Order swapped from spec for minor perf gain.
982966
// Ok since relative URLs cannot parse as URLs.
983967
let resolved;
984-
let format;
985968
if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
986969
resolved = new URL(specifier, base);
987970
} else if (specifier[0] === '#') {
@@ -990,15 +973,12 @@ function moduleResolve(specifier, base, conditions, preserveSymlinks) {
990973
try {
991974
resolved = new URL(specifier);
992975
} catch {
993-
({ resolved, format } = packageResolve(specifier, base, conditions));
976+
resolved = packageResolve(specifier, base, conditions);
994977
}
995978
}
996979
if (resolved.protocol !== 'file:')
997980
return resolved;
998-
return {
999-
url: finalizeResolution(resolved, base, preserveSymlinks),
1000-
...(format != null) && { format }
1001-
};
981+
return finalizeResolution(resolved, base, preserveSymlinks);
1002982
}
1003983

1004984
/**
@@ -1047,6 +1027,13 @@ function resolveAsCommonJS(specifier, parentURL) {
10471027
}
10481028
}
10491029

1030+
function throwIfUnsupportedURLProtocol(url) {
1031+
if (url.protocol !== 'file:' && url.protocol !== 'data:' &&
1032+
url.protocol !== 'node:') {
1033+
throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(url);
1034+
}
1035+
}
1036+
10501037
function defaultResolve(specifier, context = {}, defaultResolveUnused) {
10511038
let { parentURL, conditions } = context;
10521039
if (parentURL && policy?.manifest) {
@@ -1087,15 +1074,9 @@ function defaultResolve(specifier, context = {}, defaultResolveUnused) {
10871074

10881075
conditions = getConditionsSet(conditions);
10891076
let url;
1090-
let format;
10911077
try {
1092-
({ url, format } =
1093-
moduleResolve(
1094-
specifier,
1095-
parentURL,
1096-
conditions,
1097-
isMain ? preserveSymlinksMain : preserveSymlinks
1098-
));
1078+
url = moduleResolve(specifier, parentURL, conditions,
1079+
isMain ? preserveSymlinksMain : preserveSymlinks);
10991080
} catch (error) {
11001081
// Try to give the user a hint of what would have been the
11011082
// resolved CommonJS module
@@ -1119,13 +1100,11 @@ function defaultResolve(specifier, context = {}, defaultResolveUnused) {
11191100
throw error;
11201101
}
11211102

1122-
if (url.protocol !== 'file:' && url.protocol !== 'data:' &&
1123-
url.protocol !== 'node:')
1124-
throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(url);
1103+
throwIfUnsupportedURLProtocol(url);
11251104

11261105
return {
11271106
url: `${url}`,
1128-
...(format != null) && { format }
1107+
format: defaultGetFormatWithoutErrors(url),
11291108
};
11301109
}
11311110

@@ -1140,4 +1119,6 @@ module.exports = {
11401119
};
11411120

11421121
// cycle
1143-
const { defaultGetFormat } = require('internal/modules/esm/get_format');
1122+
const {
1123+
defaultGetFormatWithoutErrors,
1124+
} = require('internal/modules/esm/get_format');

0 commit comments

Comments
 (0)