Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
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 <bradley.meck@gmail.com>
Reviewed-By: Gerhard Stöbich <deb2001-github@yahoo.de>
Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>

Backport-PR-URL: #41752
  • Loading branch information
dygabo committed Feb 1, 2022
commit 3e60925dbcf837d4cea0e5cc4c26bac8c976bd7d
154 changes: 108 additions & 46 deletions lib/internal/modules/esm/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,23 @@ const patternRegEx = /\*/g;

function resolvePackageTargetString(
target, subpath, match, packageJSONUrl, base, pattern, internal, conditions) {

const composeResult = (resolved) => {
let format;
try {
format = getPackageType(resolved);
} catch (err) {
if (err.code === 'ERR_INVALID_FILE_URL_PATH') {
const invalidModuleErr = new ERR_INVALID_MODULE_SPECIFIER(
resolved, 'must not include encoded "/" or "\\" characters', base);
invalidModuleErr.cause = err;
throw invalidModuleErr;
}
throw err;
}
return { resolved, ...(format !== 'none') && { format } };
};

if (subpath !== '' && !pattern && target[target.length - 1] !== '/')
throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);

Expand All @@ -502,7 +519,8 @@ function resolvePackageTargetString(
const exportTarget = pattern ?
RegExpPrototypeSymbolReplace(patternRegEx, target, () => subpath) :
target + subpath;
return packageResolve(exportTarget, packageJSONUrl, conditions);
return packageResolve(
exportTarget, packageJSONUrl, conditions);
}
}
throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
Expand All @@ -518,15 +536,18 @@ function resolvePackageTargetString(
if (!StringPrototypeStartsWith(resolvedPath, packagePath))
throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);

if (subpath === '') return resolved;
if (subpath === '') return composeResult(resolved);

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

if (pattern)
return new URL(RegExpPrototypeSymbolReplace(patternRegEx, resolved.href,
() => subpath));
return new url("https://github.com/nodejs/node/pull/41752/commits/subpath,%20resolved");
if (pattern) {
return composeResult(new URL(RegExpPrototypeSymbolReplace(patternRegEx,
resolved.href,
() => subpath)));
}

return composeResult(new url("https://github.com/nodejs/node/pull/41752/commits/subpath,%20resolved"));
}

/**
Expand All @@ -552,9 +573,9 @@ function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath,
let lastException;
for (let i = 0; i < target.length; i++) {
const targetItem = target[i];
let resolved;
let resolveResult;
try {
resolved = resolvePackageTarget(
resolveResult = resolvePackageTarget(
packageJSONUrl, targetItem, subpath, packageSubpath, base, pattern,
internal, conditions);
} catch (e) {
Expand All @@ -563,13 +584,13 @@ function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath,
continue;
throw e;
}
if (resolved === undefined)
if (resolveResult === undefined)
continue;
if (resolved === null) {
if (resolveResult === null) {
lastException = null;
continue;
}
return resolved;
return resolveResult;
}
if (lastException === undefined || lastException === null)
return lastException;
Expand All @@ -588,12 +609,12 @@ function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath,
const key = keys[i];
if (key === 'default' || conditions.has(key)) {
const conditionalTarget = target[key];
const resolved = resolvePackageTarget(
const resolveResult = resolvePackageTarget(
packageJSONUrl, conditionalTarget, subpath, packageSubpath, base,
pattern, internal, conditions);
if (resolved === undefined)
if (resolveResult === undefined)
continue;
return resolved;
return resolveResult;
}
}
return undefined;
Expand Down Expand Up @@ -652,12 +673,15 @@ function packageExportsResolve(
!StringPrototypeIncludes(packageSubpath, '*') &&
!StringPrototypeEndsWith(packageSubpath, '/')) {
const target = exports[packageSubpath];
const resolved = resolvePackageTarget(
const resolveResult = resolvePackageTarget(
packageJSONUrl, target, '', packageSubpath, base, false, false, conditions
);
if (resolved === null || resolved === undefined)

if (resolveResult == null) {
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
return { resolved, exact: true };
}

return { ...resolveResult, exact: true };
}

let bestMatch = '';
Expand Down Expand Up @@ -693,14 +717,25 @@ function packageExportsResolve(
if (bestMatch) {
const target = exports[bestMatch];
const pattern = StringPrototypeIncludes(bestMatch, '*');
const resolved = resolvePackageTarget(packageJSONUrl, target,
bestMatchSubpath, bestMatch, base,
pattern, false, conditions);
if (resolved === null || resolved === undefined)
const resolveResult = resolvePackageTarget(
packageJSONUrl,
target,
bestMatchSubpath,
bestMatch,
base,
pattern,
false,
conditions);

if (resolveResult == null) {
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
if (!pattern)
}

if (!pattern) {
emitFolderMapDeprecation(bestMatch, packageJSONUrl, true, base);
return { resolved, exact: pattern };
}

return { ...resolveResult, exact: pattern };
}

throwExportsNotFound(packageSubpath, packageJSONUrl, base);
Expand Down Expand Up @@ -740,11 +775,12 @@ function packageImportsResolve(name, base, conditions) {
if (ObjectPrototypeHasOwnProperty(imports, name) &&
!StringPrototypeIncludes(name, '*') &&
!StringPrototypeEndsWith(name, '/')) {
const resolved = resolvePackageTarget(
const resolveResult = resolvePackageTarget(
packageJSONUrl, imports[name], '', name, base, false, true, conditions
);
if (resolved !== null)
return { resolved, exact: true };
if (resolveResult != null) {
return { resolved: resolveResult.resolved, exact: true };
}
} else {
let bestMatch = '';
let bestMatchSubpath;
Expand Down Expand Up @@ -776,14 +812,15 @@ function packageImportsResolve(name, base, conditions) {
if (bestMatch) {
const target = imports[bestMatch];
const pattern = StringPrototypeIncludes(bestMatch, '*');
const resolved = resolvePackageTarget(packageJSONUrl, target,
bestMatchSubpath, bestMatch,
base, pattern, true,
conditions);
if (resolved !== null) {
const resolveResult = resolvePackageTarget(
packageJSONUrl, target,
bestMatchSubpath, bestMatch,
base, pattern, true,
conditions);
if (resolveResult !== null) {
if (!pattern)
emitFolderMapDeprecation(bestMatch, packageJSONUrl, false, base);
return { resolved, exact: pattern };
return { resolved: resolveResult.resolved, exact: pattern };
}
}
}
Expand Down Expand Up @@ -843,7 +880,7 @@ function parsePackageName(specifier, base) {
* @param {string} specifier
* @param {string | URL | undefined} base
* @param {Set<string>} conditions
* @returns {URL}
* @returns {resolved: URL, format? : string}
*/
function packageResolve(specifier, base, conditions) {
if (NativeModule.canBeRequiredByUsers(specifier))
Expand All @@ -859,8 +896,7 @@ function packageResolve(specifier, base, conditions) {
if (packageConfig.name === packageName &&
packageConfig.exports !== undefined && packageConfig.exports !== null) {
return packageExportsResolve(
packageJSONUrl, packageSubpath, packageConfig, base, conditions
).resolved;
packageJSONUrl, packageSubpath, packageConfig, base, conditions);
}
}

Expand All @@ -882,13 +918,26 @@ function packageResolve(specifier, base, conditions) {

// Package match.
const packageConfig = getPackageConfig(packageJSONPath, specifier, base);
if (packageConfig.exports !== undefined && packageConfig.exports !== null)
if (packageConfig.exports !== undefined && packageConfig.exports !== null) {
return packageExportsResolve(
packageJSONUrl, packageSubpath, packageConfig, base, conditions
).resolved;
if (packageSubpath === '.')
return legacyMainResolve(packageJSONUrl, packageConfig, base);
return new url("https://github.com/nodejs/node/pull/41752/commits/packageSubpath,%20packageJSONUrl");
packageJSONUrl, packageSubpath, packageConfig, base, conditions);
}

if (packageSubpath === '.') {
return {
resolved: legacyMainResolve(
packageJSONUrl,
packageConfig,
base),
...(packageConfig.type !== 'none') && { format: packageConfig.type }
};
}

return {
resolved: new url("https://github.com/nodejs/node/pull/41752/commits/packageSubpath,%20packageJSONUrl"),
...(packageConfig.type !== 'none') && { format: packageConfig.type }
};

// Cross-platform root check.
} while (packageJSONPath.length !== lastPath.length);

Expand Down Expand Up @@ -932,6 +981,7 @@ function moduleResolve(specifier, base, conditions, preserveSymlinks) {
// Order swapped from spec for minor perf gain.
// Ok since relative URLs cannot parse as URLs.
let resolved;
let format;
if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
resolved = new url("https://github.com/nodejs/node/pull/41752/commits/specifier,%20base");
} else if (specifier[0] === '#') {
Expand All @@ -940,12 +990,15 @@ function moduleResolve(specifier, base, conditions, preserveSymlinks) {
try {
resolved = new url("https://github.com/nodejs/node/pull/41752/commits/specifier");
} catch {
resolved = packageResolve(specifier, base, conditions);
({ resolved, format } = packageResolve(specifier, base, conditions));
}
}
if (resolved.protocol !== 'file:')
return resolved;
return finalizeResolution(resolved, base, preserveSymlinks);
return {
url: finalizeResolution(resolved, base, preserveSymlinks),
...(format != null) && { format }
};
}

/**
Expand Down Expand Up @@ -1034,9 +1087,15 @@ function defaultResolve(specifier, context = {}, defaultResolveUnused) {

conditions = getConditionsSet(conditions);
let url;
let format;
try {
url = moduleResolve(specifier, parentURL, conditions,
isMain ? preserveSymlinksMain : preserveSymlinks);
({ url, format } =
moduleResolve(
specifier,
parentURL,
conditions,
isMain ? preserveSymlinksMain : preserveSymlinks
));
} catch (error) {
// Try to give the user a hint of what would have been the
// resolved CommonJS module
Expand Down Expand Up @@ -1064,7 +1123,10 @@ function defaultResolve(specifier, context = {}, defaultResolveUnused) {
url.protocol !== 'node:')
throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(url);

return { url: `${url}` };
return {
url: `${url}`,
...(format != null) && { format }
};
}

module.exports = {
Expand Down
41 changes: 41 additions & 0 deletions test/es-module/test-esm-loader-resolve-type.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Flags: --loader ./test/fixtures/es-module-loaders/hook-resolve-type.mjs
import { allowGlobals } from '../common/index.mjs';
import * as fixtures from '../common/fixtures.mjs';
import { strict as assert } from 'assert';
import * as fs from 'fs';

allowGlobals(global.getModuleTypeStats);

const basePath =
new url("https://github.com/nodejs/node/pull/41752/commits/'./node_modules/',%20import.meta.url");

const rel = (file) => new url("https://github.com/nodejs/node/pull/41752/commits/file,%20basePath");
const createDir = (path) => {
if (!fs.existsSync(path)) {
fs.mkdirSync(path);
}
};

const moduleName = 'module-counter-by-type';

const moduleDir = rel(`${moduleName}`);
createDir(basePath);
createDir(moduleDir);
fs.cpSync(
fixtures.path('es-modules', moduleName),
moduleDir,
{ recursive: true }
);

const { importedESM: importedESMBefore,
importedCJS: importedCJSBefore } = global.getModuleTypeStats();

import(`${moduleName}`).finally(() => {
fs.rmSync(basePath, { recursive: true, force: true });
});

const { importedESM: importedESMAfter,
importedCJS: importedCJSAfter } = global.getModuleTypeStats();

assert.strictEqual(importedESMBefore + 1, importedESMAfter);
assert.strictEqual(importedCJSBefore, importedCJSAfter);
Loading