Skip to content

Commit c75d953

Browse files
committed
loader: return package format from defaultResolve if known
This is a proposed modification of defaultResolve to return the package format in case it has been found during package resolution. The format will be returned as described in the documentation: https://nodejs.org/api/esm.html#resolvespecifier-context-defaultresolve There is one new unit test as well: test/es-module/test-esm-resolve-type.js PR-URL: #40980 Reviewed-By: Bradley Farias <[email protected]> Reviewed-By: Gerhard Stöbich <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]>
1 parent ab28dc5 commit c75d953

File tree

7 files changed

+368
-46
lines changed

7 files changed

+368
-46
lines changed

lib/internal/modules/esm/resolve.js

+108-46
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,23 @@ const patternRegEx = /\*/g;
473473

474474
function resolvePackageTargetString(
475475
target, subpath, match, packageJSONUrl, base, pattern, internal, conditions) {
476+
477+
const composeResult = (resolved) => {
478+
let format;
479+
try {
480+
format = getPackageType(resolved);
481+
} catch (err) {
482+
if (err.code === 'ERR_INVALID_FILE_URL_PATH') {
483+
const invalidModuleErr = new ERR_INVALID_MODULE_SPECIFIER(
484+
resolved, 'must not include encoded "/" or "\\" characters', base);
485+
invalidModuleErr.cause = err;
486+
throw invalidModuleErr;
487+
}
488+
throw err;
489+
}
490+
return { resolved, ...(format !== 'none') && { format } };
491+
};
492+
476493
if (subpath !== '' && !pattern && target[target.length - 1] !== '/')
477494
throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
478495

@@ -488,7 +505,8 @@ function resolvePackageTargetString(
488505
const exportTarget = pattern ?
489506
RegExpPrototypeSymbolReplace(patternRegEx, target, () => subpath) :
490507
target + subpath;
491-
return packageResolve(exportTarget, packageJSONUrl, conditions);
508+
return packageResolve(
509+
exportTarget, packageJSONUrl, conditions);
492510
}
493511
}
494512
throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
@@ -504,15 +522,18 @@ function resolvePackageTargetString(
504522
if (!StringPrototypeStartsWith(resolvedPath, packagePath))
505523
throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
506524

507-
if (subpath === '') return resolved;
525+
if (subpath === '') return composeResult(resolved);
508526

509527
if (RegExpPrototypeTest(invalidSegmentRegEx, subpath))
510528
throwInvalidSubpath(match + subpath, packageJSONUrl, internal, base);
511529

512-
if (pattern)
513-
return new URL(RegExpPrototypeSymbolReplace(patternRegEx, resolved.href,
514-
() => subpath));
515-
return new URL(subpath, resolved);
530+
if (pattern) {
531+
return composeResult(new URL(RegExpPrototypeSymbolReplace(patternRegEx,
532+
resolved.href,
533+
() => subpath)));
534+
}
535+
536+
return composeResult(new URL(subpath, resolved));
516537
}
517538

518539
/**
@@ -538,9 +559,9 @@ function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath,
538559
let lastException;
539560
for (let i = 0; i < target.length; i++) {
540561
const targetItem = target[i];
541-
let resolved;
562+
let resolveResult;
542563
try {
543-
resolved = resolvePackageTarget(
564+
resolveResult = resolvePackageTarget(
544565
packageJSONUrl, targetItem, subpath, packageSubpath, base, pattern,
545566
internal, conditions);
546567
} catch (e) {
@@ -549,13 +570,13 @@ function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath,
549570
continue;
550571
throw e;
551572
}
552-
if (resolved === undefined)
573+
if (resolveResult === undefined)
553574
continue;
554-
if (resolved === null) {
575+
if (resolveResult === null) {
555576
lastException = null;
556577
continue;
557578
}
558-
return resolved;
579+
return resolveResult;
559580
}
560581
if (lastException === undefined || lastException === null)
561582
return lastException;
@@ -574,12 +595,12 @@ function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath,
574595
const key = keys[i];
575596
if (key === 'default' || conditions.has(key)) {
576597
const conditionalTarget = target[key];
577-
const resolved = resolvePackageTarget(
598+
const resolveResult = resolvePackageTarget(
578599
packageJSONUrl, conditionalTarget, subpath, packageSubpath, base,
579600
pattern, internal, conditions);
580-
if (resolved === undefined)
601+
if (resolveResult === undefined)
581602
continue;
582-
return resolved;
603+
return resolveResult;
583604
}
584605
}
585606
return undefined;
@@ -638,12 +659,15 @@ function packageExportsResolve(
638659
!StringPrototypeIncludes(packageSubpath, '*') &&
639660
!StringPrototypeEndsWith(packageSubpath, '/')) {
640661
const target = exports[packageSubpath];
641-
const resolved = resolvePackageTarget(
662+
const resolveResult = resolvePackageTarget(
642663
packageJSONUrl, target, '', packageSubpath, base, false, false, conditions
643664
);
644-
if (resolved === null || resolved === undefined)
665+
666+
if (resolveResult == null) {
645667
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
646-
return { resolved, exact: true };
668+
}
669+
670+
return { ...resolveResult, exact: true };
647671
}
648672

649673
let bestMatch = '';
@@ -679,14 +703,25 @@ function packageExportsResolve(
679703
if (bestMatch) {
680704
const target = exports[bestMatch];
681705
const pattern = StringPrototypeIncludes(bestMatch, '*');
682-
const resolved = resolvePackageTarget(packageJSONUrl, target,
683-
bestMatchSubpath, bestMatch, base,
684-
pattern, false, conditions);
685-
if (resolved === null || resolved === undefined)
706+
const resolveResult = resolvePackageTarget(
707+
packageJSONUrl,
708+
target,
709+
bestMatchSubpath,
710+
bestMatch,
711+
base,
712+
pattern,
713+
false,
714+
conditions);
715+
716+
if (resolveResult == null) {
686717
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
687-
if (!pattern)
718+
}
719+
720+
if (!pattern) {
688721
emitFolderMapDeprecation(bestMatch, packageJSONUrl, true, base);
689-
return { resolved, exact: pattern };
722+
}
723+
724+
return { ...resolveResult, exact: pattern };
690725
}
691726

692727
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
@@ -726,11 +761,12 @@ function packageImportsResolve(name, base, conditions) {
726761
if (ObjectPrototypeHasOwnProperty(imports, name) &&
727762
!StringPrototypeIncludes(name, '*') &&
728763
!StringPrototypeEndsWith(name, '/')) {
729-
const resolved = resolvePackageTarget(
764+
const resolveResult = resolvePackageTarget(
730765
packageJSONUrl, imports[name], '', name, base, false, true, conditions
731766
);
732-
if (resolved !== null)
733-
return { resolved, exact: true };
767+
if (resolveResult != null) {
768+
return { resolved: resolveResult.resolved, exact: true };
769+
}
734770
} else {
735771
let bestMatch = '';
736772
let bestMatchSubpath;
@@ -762,14 +798,15 @@ function packageImportsResolve(name, base, conditions) {
762798
if (bestMatch) {
763799
const target = imports[bestMatch];
764800
const pattern = StringPrototypeIncludes(bestMatch, '*');
765-
const resolved = resolvePackageTarget(packageJSONUrl, target,
766-
bestMatchSubpath, bestMatch,
767-
base, pattern, true,
768-
conditions);
769-
if (resolved !== null) {
801+
const resolveResult = resolvePackageTarget(
802+
packageJSONUrl, target,
803+
bestMatchSubpath, bestMatch,
804+
base, pattern, true,
805+
conditions);
806+
if (resolveResult !== null) {
770807
if (!pattern)
771808
emitFolderMapDeprecation(bestMatch, packageJSONUrl, false, base);
772-
return { resolved, exact: pattern };
809+
return { resolved: resolveResult.resolved, exact: pattern };
773810
}
774811
}
775812
}
@@ -833,7 +870,7 @@ function parsePackageName(specifier, base) {
833870
* @param {string} specifier
834871
* @param {string | URL | undefined} base
835872
* @param {Set<string>} conditions
836-
* @returns {URL}
873+
* @returns {resolved: URL, format? : string}
837874
*/
838875
function packageResolve(specifier, base, conditions) {
839876
const { packageName, packageSubpath, isScoped } =
@@ -846,8 +883,7 @@ function packageResolve(specifier, base, conditions) {
846883
if (packageConfig.name === packageName &&
847884
packageConfig.exports !== undefined && packageConfig.exports !== null) {
848885
return packageExportsResolve(
849-
packageJSONUrl, packageSubpath, packageConfig, base, conditions
850-
).resolved;
886+
packageJSONUrl, packageSubpath, packageConfig, base, conditions);
851887
}
852888
}
853889

@@ -869,13 +905,26 @@ function packageResolve(specifier, base, conditions) {
869905

870906
// Package match.
871907
const packageConfig = getPackageConfig(packageJSONPath, specifier, base);
872-
if (packageConfig.exports !== undefined && packageConfig.exports !== null)
908+
if (packageConfig.exports !== undefined && packageConfig.exports !== null) {
873909
return packageExportsResolve(
874-
packageJSONUrl, packageSubpath, packageConfig, base, conditions
875-
).resolved;
876-
if (packageSubpath === '.')
877-
return legacyMainResolve(packageJSONUrl, packageConfig, base);
878-
return new URL(packageSubpath, packageJSONUrl);
910+
packageJSONUrl, packageSubpath, packageConfig, base, conditions);
911+
}
912+
913+
if (packageSubpath === '.') {
914+
return {
915+
resolved: legacyMainResolve(
916+
packageJSONUrl,
917+
packageConfig,
918+
base),
919+
...(packageConfig.type !== 'none') && { format: packageConfig.type }
920+
};
921+
}
922+
923+
return {
924+
resolved: new URL(packageSubpath, packageJSONUrl),
925+
...(packageConfig.type !== 'none') && { format: packageConfig.type }
926+
};
927+
879928
// Cross-platform root check.
880929
} while (packageJSONPath.length !== lastPath.length);
881930

@@ -912,12 +961,13 @@ function shouldBeTreatedAsRelativeOrAbsolutePath(specifier) {
912961
* @param {string} specifier
913962
* @param {string | URL | undefined} base
914963
* @param {Set<string>} conditions
915-
* @returns {URL}
964+
* @returns {url: URL, format?: string}
916965
*/
917966
function moduleResolve(specifier, base, conditions) {
918967
// Order swapped from spec for minor perf gain.
919968
// Ok since relative URLs cannot parse as URLs.
920969
let resolved;
970+
let format;
921971
if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
922972
resolved = new URL(specifier, base);
923973
} else if (specifier[0] === '#') {
@@ -926,10 +976,13 @@ function moduleResolve(specifier, base, conditions) {
926976
try {
927977
resolved = new URL(specifier);
928978
} catch {
929-
resolved = packageResolve(specifier, base, conditions);
979+
({ resolved, format } = packageResolve(specifier, base, conditions));
930980
}
931981
}
932-
return finalizeResolution(resolved, base);
982+
return {
983+
url: finalizeResolution(resolved, base),
984+
...(format != null) && { format }
985+
};
933986
}
934987

935988
/**
@@ -1040,8 +1093,14 @@ function defaultResolve(specifier, context = {}, defaultResolveUnused) {
10401093

10411094
conditions = getConditionsSet(conditions);
10421095
let url;
1096+
let format;
10431097
try {
1044-
url = moduleResolve(specifier, parentURL, conditions);
1098+
({ url, format } =
1099+
moduleResolve(
1100+
specifier,
1101+
parentURL,
1102+
conditions
1103+
));
10451104
} catch (error) {
10461105
// Try to give the user a hint of what would have been the
10471106
// resolved CommonJS module
@@ -1077,7 +1136,10 @@ function defaultResolve(specifier, context = {}, defaultResolveUnused) {
10771136
url.hash = old.hash;
10781137
}
10791138

1080-
return { url: `${url}` };
1139+
return {
1140+
url: `${url}`,
1141+
...(format != null) && { format }
1142+
};
10811143
}
10821144

10831145
module.exports = {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Flags: --loader ./test/fixtures/es-module-loaders/hook-resolve-type.mjs
2+
import { allowGlobals } from '../common/index.mjs';
3+
import * as fixtures from '../common/fixtures.mjs';
4+
import { strict as assert } from 'assert';
5+
import * as fs from 'fs';
6+
7+
allowGlobals(global.getModuleTypeStats);
8+
9+
const basePath =
10+
new URL('./node_modules/', import.meta.url);
11+
12+
const rel = (file) => new URL(file, basePath);
13+
const createDir = (path) => {
14+
if (!fs.existsSync(path)) {
15+
fs.mkdirSync(path);
16+
}
17+
};
18+
19+
const moduleName = 'module-counter-by-type';
20+
21+
const moduleDir = rel(`${moduleName}`);
22+
createDir(basePath);
23+
createDir(moduleDir);
24+
fs.cpSync(
25+
fixtures.path('es-modules', moduleName),
26+
moduleDir,
27+
{ recursive: true }
28+
);
29+
30+
const { importedESM: importedESMBefore,
31+
importedCJS: importedCJSBefore } = global.getModuleTypeStats();
32+
33+
import(`${moduleName}`).finally(() => {
34+
fs.rmSync(basePath, { recursive: true, force: true });
35+
});
36+
37+
const { importedESM: importedESMAfter,
38+
importedCJS: importedCJSAfter } = global.getModuleTypeStats();
39+
40+
assert.strictEqual(importedESMBefore + 1, importedESMAfter);
41+
assert.strictEqual(importedCJSBefore, importedCJSAfter);

0 commit comments

Comments
 (0)