Skip to content

Commit 0af14cc

Browse files
dygaboLinkgoron
authored andcommitted
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: nodejs#40980 Reviewed-By: Bradley Farias <[email protected]> Reviewed-By: Gerhard Stöbich <[email protected]> Reviewed-By: Antoine du Hamel <[email protected]>
1 parent ce958d0 commit 0af14cc

File tree

8 files changed

+372
-50
lines changed

8 files changed

+372
-50
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), parentPath, pkgPath);
460+
pathToFileURL(parentPath), cjsConditions).resolved, 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), null, pkgPath);
484+
cjsConditions).resolved, null, pkgPath);
485485
} catch (e) {
486486
if (e.code === 'ERR_MODULE_NOT_FOUND')
487487
throw createEsmNotFoundErr(request, pkgPath + '/package.json');

lib/internal/modules/esm/resolve.js

+110-48
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,23 @@ const patternRegEx = /\*/g;
463463

464464
function resolvePackageTargetString(
465465
target, subpath, match, packageJSONUrl, base, pattern, internal, conditions) {
466+
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+
466483
if (subpath !== '' && !pattern && target[target.length - 1] !== '/')
467484
throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
468485

@@ -478,7 +495,8 @@ function resolvePackageTargetString(
478495
const exportTarget = pattern ?
479496
RegExpPrototypeSymbolReplace(patternRegEx, target, () => subpath) :
480497
target + subpath;
481-
return packageResolve(exportTarget, packageJSONUrl, conditions);
498+
return packageResolve(
499+
exportTarget, packageJSONUrl, conditions);
482500
}
483501
}
484502
throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
@@ -494,18 +512,21 @@ function resolvePackageTargetString(
494512
if (!StringPrototypeStartsWith(resolvedPath, packagePath))
495513
throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
496514

497-
if (subpath === '') return resolved;
515+
if (subpath === '') return composeResult(resolved);
498516

499517
if (RegExpPrototypeTest(invalidSegmentRegEx, subpath)) {
500518
const request = pattern ?
501519
StringPrototypeReplace(match, '*', () => subpath) : match + subpath;
502520
throwInvalidSubpath(request, packageJSONUrl, internal, base);
503521
}
504522

505-
if (pattern)
506-
return new URL(RegExpPrototypeSymbolReplace(patternRegEx, resolved.href,
507-
() => subpath));
508-
return new URL(subpath, resolved);
523+
if (pattern) {
524+
return composeResult(new URL(RegExpPrototypeSymbolReplace(patternRegEx,
525+
resolved.href,
526+
() => subpath)));
527+
}
528+
529+
return composeResult(new URL(subpath, resolved));
509530
}
510531

511532
/**
@@ -531,9 +552,9 @@ function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath,
531552
let lastException;
532553
for (let i = 0; i < target.length; i++) {
533554
const targetItem = target[i];
534-
let resolved;
555+
let resolveResult;
535556
try {
536-
resolved = resolvePackageTarget(
557+
resolveResult = resolvePackageTarget(
537558
packageJSONUrl, targetItem, subpath, packageSubpath, base, pattern,
538559
internal, conditions);
539560
} catch (e) {
@@ -542,13 +563,13 @@ function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath,
542563
continue;
543564
throw e;
544565
}
545-
if (resolved === undefined)
566+
if (resolveResult === undefined)
546567
continue;
547-
if (resolved === null) {
568+
if (resolveResult === null) {
548569
lastException = null;
549570
continue;
550571
}
551-
return resolved;
572+
return resolveResult;
552573
}
553574
if (lastException === undefined || lastException === null)
554575
return lastException;
@@ -567,12 +588,12 @@ function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath,
567588
const key = keys[i];
568589
if (key === 'default' || conditions.has(key)) {
569590
const conditionalTarget = target[key];
570-
const resolved = resolvePackageTarget(
591+
const resolveResult = resolvePackageTarget(
571592
packageJSONUrl, conditionalTarget, subpath, packageSubpath, base,
572593
pattern, internal, conditions);
573-
if (resolved === undefined)
594+
if (resolveResult === undefined)
574595
continue;
575-
return resolved;
596+
return resolveResult;
576597
}
577598
}
578599
return undefined;
@@ -631,12 +652,15 @@ function packageExportsResolve(
631652
!StringPrototypeIncludes(packageSubpath, '*') &&
632653
!StringPrototypeEndsWith(packageSubpath, '/')) {
633654
const target = exports[packageSubpath];
634-
const resolved = resolvePackageTarget(
655+
const resolveResult = resolvePackageTarget(
635656
packageJSONUrl, target, '', packageSubpath, base, false, false, conditions
636657
);
637-
if (resolved === null || resolved === undefined)
658+
659+
if (resolveResult == null) {
638660
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
639-
return resolved;
661+
}
662+
663+
return resolveResult;
640664
}
641665

642666
let bestMatch = '';
@@ -672,12 +696,20 @@ function packageExportsResolve(
672696

673697
if (bestMatch) {
674698
const target = exports[bestMatch];
675-
const resolved = resolvePackageTarget(packageJSONUrl, target,
676-
bestMatchSubpath, bestMatch, base,
677-
true, false, conditions);
678-
if (resolved === null || resolved === undefined)
699+
const resolveResult = resolvePackageTarget(
700+
packageJSONUrl,
701+
target,
702+
bestMatchSubpath,
703+
bestMatch,
704+
base,
705+
true,
706+
false,
707+
conditions);
708+
709+
if (resolveResult == null) {
679710
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
680-
return resolved;
711+
}
712+
return resolveResult;
681713
}
682714

683715
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
@@ -717,11 +749,12 @@ function packageImportsResolve(name, base, conditions) {
717749
if (imports) {
718750
if (ObjectPrototypeHasOwnProperty(imports, name) &&
719751
!StringPrototypeIncludes(name, '*')) {
720-
const resolved = resolvePackageTarget(
752+
const resolveResult = resolvePackageTarget(
721753
packageJSONUrl, imports[name], '', name, base, false, true, conditions
722754
);
723-
if (resolved !== null && resolved !== undefined)
724-
return resolved;
755+
if (resolveResult != null) {
756+
return resolveResult.resolved;
757+
}
725758
} else {
726759
let bestMatch = '';
727760
let bestMatchSubpath;
@@ -747,11 +780,13 @@ function packageImportsResolve(name, base, conditions) {
747780

748781
if (bestMatch) {
749782
const target = imports[bestMatch];
750-
const resolved = resolvePackageTarget(packageJSONUrl, target,
751-
bestMatchSubpath, bestMatch,
752-
base, true, true, conditions);
753-
if (resolved !== null && resolved !== undefined)
754-
return resolved;
783+
const resolveResult = resolvePackageTarget(packageJSONUrl, target,
784+
bestMatchSubpath,
785+
bestMatch, base, true,
786+
true, conditions);
787+
if (resolveResult != null) {
788+
return resolveResult.resolved;
789+
}
755790
}
756791
}
757792
}
@@ -810,11 +845,11 @@ function parsePackageName(specifier, base) {
810845
* @param {string} specifier
811846
* @param {string | URL | undefined} base
812847
* @param {Set<string>} conditions
813-
* @returns {URL}
848+
* @returns {resolved: URL, format? : string}
814849
*/
815850
function packageResolve(specifier, base, conditions) {
816851
if (NativeModule.canBeRequiredByUsers(specifier))
817-
return new URL('node:' + specifier);
852+
return { resolved: new URL('node:' + specifier) };
818853

819854
const { packageName, packageSubpath, isScoped } =
820855
parsePackageName(specifier, base);
@@ -826,8 +861,7 @@ function packageResolve(specifier, base, conditions) {
826861
if (packageConfig.name === packageName &&
827862
packageConfig.exports !== undefined && packageConfig.exports !== null) {
828863
return packageExportsResolve(
829-
packageJSONUrl, packageSubpath, packageConfig, base, conditions
830-
);
864+
packageJSONUrl, packageSubpath, packageConfig, base, conditions);
831865
}
832866
}
833867

@@ -849,13 +883,24 @@ function packageResolve(specifier, base, conditions) {
849883

850884
// Package match.
851885
const packageConfig = getPackageConfig(packageJSONPath, specifier, base);
852-
if (packageConfig.exports !== undefined && packageConfig.exports !== null)
886+
if (packageConfig.exports !== undefined && packageConfig.exports !== null) {
853887
return packageExportsResolve(
854-
packageJSONUrl, packageSubpath, packageConfig, base, conditions
855-
);
856-
if (packageSubpath === '.')
857-
return legacyMainResolve(packageJSONUrl, packageConfig, base);
858-
return new URL(packageSubpath, packageJSONUrl);
888+
packageJSONUrl, packageSubpath, packageConfig, base, conditions);
889+
}
890+
if (packageSubpath === '.') {
891+
return {
892+
resolved: legacyMainResolve(
893+
packageJSONUrl,
894+
packageConfig,
895+
base),
896+
...(packageConfig.type !== 'none') && { format: packageConfig.type }
897+
};
898+
}
899+
900+
return {
901+
resolved: new URL(packageSubpath, packageJSONUrl),
902+
...(packageConfig.type !== 'none') && { format: packageConfig.type }
903+
};
859904
// Cross-platform root check.
860905
} while (packageJSONPath.length !== lastPath.length);
861906

@@ -893,12 +938,13 @@ function shouldBeTreatedAsRelativeOrAbsolutePath(specifier) {
893938
* @param {string | URL | undefined} base
894939
* @param {Set<string>} conditions
895940
* @param {boolean} preserveSymlinks
896-
* @returns {URL}
941+
* @returns {url: URL, format?: string}
897942
*/
898943
function moduleResolve(specifier, base, conditions, preserveSymlinks) {
899944
// Order swapped from spec for minor perf gain.
900945
// Ok since relative URLs cannot parse as URLs.
901946
let resolved;
947+
let format;
902948
if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
903949
resolved = new URL(specifier, base);
904950
} else if (specifier[0] === '#') {
@@ -907,12 +953,19 @@ function moduleResolve(specifier, base, conditions, preserveSymlinks) {
907953
try {
908954
resolved = new URL(specifier);
909955
} catch {
910-
resolved = packageResolve(specifier, base, conditions);
956+
({ resolved, format } = packageResolve(specifier, base, conditions));
911957
}
912958
}
913-
if (resolved.protocol !== 'file:')
914-
return resolved;
915-
return finalizeResolution(resolved, base, preserveSymlinks);
959+
if (resolved.protocol !== 'file:') {
960+
return {
961+
url: resolved
962+
};
963+
}
964+
965+
return {
966+
url: finalizeResolution(resolved, base, preserveSymlinks),
967+
...(format != null) && { format }
968+
};
916969
}
917970

918971
/**
@@ -1001,9 +1054,15 @@ function defaultResolve(specifier, context = {}, defaultResolveUnused) {
10011054

10021055
conditions = getConditionsSet(conditions);
10031056
let url;
1057+
let format;
10041058
try {
1005-
url = moduleResolve(specifier, parentURL, conditions,
1006-
isMain ? preserveSymlinksMain : preserveSymlinks);
1059+
({ url, format } =
1060+
moduleResolve(
1061+
specifier,
1062+
parentURL,
1063+
conditions,
1064+
isMain ? preserveSymlinksMain : preserveSymlinks
1065+
));
10071066
} catch (error) {
10081067
// Try to give the user a hint of what would have been the
10091068
// resolved CommonJS module
@@ -1031,7 +1090,10 @@ function defaultResolve(specifier, context = {}, defaultResolveUnused) {
10311090
url.protocol !== 'node:')
10321091
throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(url);
10331092

1034-
return { url: `${url}` };
1093+
return {
1094+
url: `${url}`,
1095+
...(format != null) && { format }
1096+
};
10351097
}
10361098

10371099
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)