Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v16.x backport] loader: return package format from defaultResolve if known #41752

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 10 additions & 0 deletions doc/api/fs.md
Expand Up @@ -3518,6 +3518,11 @@ with options `{ recursive: true, force: true }`.

<!-- YAML
added: v14.14.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/41132
description: The `path` parameter can be a WHATWG `URL` object using `file:`
protocol.
-->

* `path` {string|Buffer|URL}
Expand Down Expand Up @@ -5261,6 +5266,11 @@ with options `{ recursive: true, force: true }`.

<!-- YAML
added: v14.14.0
changes:
- version: REPLACEME
pr-url: https://github.com/nodejs/node/pull/41132
description: The `path` parameter can be a WHATWG `URL` object using `file:`
protocol.
-->

* `path` {string|Buffer|URL}
Expand Down
2 changes: 2 additions & 0 deletions lib/fs.js
Expand Up @@ -1185,6 +1185,7 @@ function rm(path, options, callback) {
callback = options;
options = undefined;
}
path = getValidatedPath(path);

validateRmOptions(path, options, false, (err, options) => {
if (err) {
Expand All @@ -1208,6 +1209,7 @@ function rm(path, options, callback) {
* @returns {void}
*/
function rmSync(path, options) {
path = getValidatedPath(path);
options = validateRmOptionsSync(path, options, false);

lazyLoadRimraf();
Expand Down
66 changes: 42 additions & 24 deletions lib/internal/modules/esm/get_format.js
Expand Up @@ -32,6 +32,8 @@ const legacyExtensionFormatMap = {
'.node': 'commonjs'
};

let experimentalSpecifierResolutionWarned = false;

if (experimentalWasmModules)
extensionFormatMap['.wasm'] = legacyExtensionFormatMap['.wasm'] = 'wasm';

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

return format;
},
'file:'(parsed, url) {
const ext = extname(parsed.pathname);
let format;

if (ext === '.js') {
format = getPackageType(parsed.href) === 'module' ? 'module' : 'commonjs';
} else {
format = extensionFormatMap[ext];
}
if (!format) {
if (experimentalSpecifierResolution === 'node') {
process.emitWarning(
'The Node.js specifier resolution in ESM is experimental.',
'ExperimentalWarning');
format = legacyExtensionFormatMap[ext];
} else {
throw new ERR_UNKNOWN_FILE_EXTENSION(ext, fileURLToPath(url));
}
}

return format || null;
},
'file:': getFileProtocolModuleFormat,
'node:'() { return 'builtin'; },
});

function defaultGetFormat(url, context) {
function getLegacyExtensionFormat(ext) {
if (
experimentalSpecifierResolution === 'node' &&
!experimentalSpecifierResolutionWarned
) {
process.emitWarning(
'The Node.js specifier resolution in ESM is experimental.',
'ExperimentalWarning');
experimentalSpecifierResolutionWarned = true;
}
return legacyExtensionFormatMap[ext];
}

function getFileProtocolModuleFormat(url, ignoreErrors) {
const ext = extname(url.pathname);
if (ext === '.js') {
return getPackageType(url) === 'module' ? 'module' : 'commonjs';
}

const format = extensionFormatMap[ext];
if (format) return format;
if (experimentalSpecifierResolution !== 'node') {
// Explicit undefined return indicates load hook should rerun format check
if (ignoreErrors)
return undefined;
throw new ERR_UNKNOWN_FILE_EXTENSION(ext, fileURLToPath(url));
}
return getLegacyExtensionFormat(ext) ?? null;
}

function defaultGetFormatWithoutErrors(url, context) {
const parsed = new URL(url);
if (!ObjectPrototypeHasOwnProperty(protocolHandlers, parsed.protocol))
return null;
return protocolHandlers[parsed.protocol](parsed, true);
}

function defaultGetFormat(url, context) {
const parsed = new URL(url);
return ObjectPrototypeHasOwnProperty(protocolHandlers, parsed.protocol) ?
protocolHandlers[parsed.protocol](parsed, url) :
protocolHandlers[parsed.protocol](parsed, false) :
null;
}

module.exports = {
defaultGetFormat,
defaultGetFormatWithoutErrors,
extensionFormatMap,
legacyExtensionFormatMap,
};
3 changes: 1 addition & 2 deletions lib/internal/modules/esm/load.js
Expand Up @@ -2,7 +2,6 @@

const { defaultGetFormat } = require('internal/modules/esm/get_format');
const { defaultGetSource } = require('internal/modules/esm/get_source');
const { translators } = require('internal/modules/esm/translators');
const { validateAssertions } = require('internal/modules/esm/assert');

/**
Expand All @@ -18,7 +17,7 @@ async function defaultLoad(url, context) {
} = context;
const { importAssertions } = context;

if (!format || !translators.has(format)) {
if (format == null) {
format = defaultGetFormat(url);
}

Expand Down
107 changes: 75 additions & 32 deletions lib/internal/modules/esm/resolve.js
Expand Up @@ -131,7 +131,7 @@ function emitTrailingSlashPatternDeprecation(match, pjsonUrl, isExports, base) {
* @returns {void}
*/
function emitLegacyIndexDeprecation(url, packageJSONUrl, base, main) {
const format = defaultGetFormat(url);
const format = defaultGetFormatWithoutErrors(url);
if (format !== 'module')
return;
const path = fileURLToPath(url);
Expand Down Expand Up @@ -487,6 +487,7 @@ const patternRegEx = /\*/g;

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

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

Expand All @@ -502,7 +503,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 @@ -523,9 +525,16 @@ function resolvePackageTargetString(
if (RegExpPrototypeTest(invalidSegmentRegEx, subpath))
throwInvalidSubpath(match + subpath, packageJSONUrl, internal, base);

if (pattern)
return new URL(RegExpPrototypeSymbolReplace(patternRegEx, resolved.href,
() => subpath));
if (pattern) {
return new URL(
RegExpPrototypeSymbolReplace(
patternRegEx,
resolved.href,
() => subpath
)
);
}

return new URL(subpath, resolved);
}

Expand All @@ -552,9 +561,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 +572,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 +597,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 @@ -655,8 +664,11 @@ function packageExportsResolve(
const resolved = resolvePackageTarget(
packageJSONUrl, target, '', packageSubpath, base, false, false, conditions
);
if (resolved === null || resolved === undefined)

if (resolved == null) {
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
}

return { resolved, exact: true };
}

Expand Down Expand Up @@ -693,13 +705,24 @@ 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 resolved = resolvePackageTarget(
packageJSONUrl,
target,
bestMatchSubpath,
bestMatch,
base,
pattern,
false,
conditions);

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

if (!pattern) {
emitFolderMapDeprecation(bestMatch, packageJSONUrl, true, base);
}

return { resolved, exact: pattern };
}

Expand Down Expand Up @@ -743,8 +766,9 @@ function packageImportsResolve(name, base, conditions) {
const resolved = resolvePackageTarget(
packageJSONUrl, imports[name], '', name, base, false, true, conditions
);
if (resolved !== null)
if (resolved != null) {
return { resolved, exact: true };
}
} else {
let bestMatch = '';
let bestMatchSubpath;
Expand Down Expand Up @@ -776,10 +800,11 @@ 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);
const resolved = resolvePackageTarget(
packageJSONUrl, target,
bestMatchSubpath, bestMatch,
base, pattern, true,
conditions);
if (resolved !== null) {
if (!pattern)
emitFolderMapDeprecation(bestMatch, packageJSONUrl, false, base);
Expand Down Expand Up @@ -882,12 +907,20 @@ 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);
}

if (packageSubpath === '.') {
return legacyMainResolve(
packageJSONUrl,
packageConfig,
base
);
}

return new URL(packageSubpath, packageJSONUrl);
// Cross-platform root check.
} while (packageJSONPath.length !== lastPath.length);
Expand Down Expand Up @@ -994,6 +1027,13 @@ function resolveAsCommonJS(specifier, parentURL) {
}
}

function throwIfUnsupportedURLProtocol(url) {
if (url.protocol !== 'file:' && url.protocol !== 'data:' &&
url.protocol !== 'node:') {
throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(url);
}
}

function defaultResolve(specifier, context = {}, defaultResolveUnused) {
let { parentURL, conditions } = context;
if (parentURL && policy?.manifest) {
Expand Down Expand Up @@ -1060,11 +1100,12 @@ function defaultResolve(specifier, context = {}, defaultResolveUnused) {
throw error;
}

if (url.protocol !== 'file:' && url.protocol !== 'data:' &&
url.protocol !== 'node:')
throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(url);
throwIfUnsupportedURLProtocol(url);

return { url: `${url}` };
return {
url: `${url}`,
format: defaultGetFormatWithoutErrors(url),
};
}

module.exports = {
Expand All @@ -1078,4 +1119,6 @@ module.exports = {
};

// cycle
const { defaultGetFormat } = require('internal/modules/esm/get_format');
const {
defaultGetFormatWithoutErrors,
} = require('internal/modules/esm/get_format');