mirror of https://github.com/nodejs/node.git
esm: port loader code to JS
There is no reason for this to be in C++. Using JavaScript means that the code is more accessible to more developers, which is important for any Node.js feature. This also simplifies the code significantly in some areas. On the technical side, this potentially also enables making some of the file system operations that are involved asynchronous. PR-URL: https://github.com/nodejs/node/pull/32201 Reviewed-By: Bradley Farias <bradley.meck@gmail.com> Reviewed-By: Guy Bedford <guybedford@gmail.com>
This commit is contained in:
parent
417d847de2
commit
605615e5f3
|
@ -1078,10 +1078,17 @@ E('ERR_INVALID_FILE_URL_PATH', 'File URL path %s', TypeError);
|
|||
E('ERR_INVALID_HANDLE_TYPE', 'This handle type cannot be sent', TypeError);
|
||||
E('ERR_INVALID_HTTP_TOKEN', '%s must be a valid HTTP token ["%s"]', TypeError);
|
||||
E('ERR_INVALID_IP_ADDRESS', 'Invalid IP address: %s', TypeError);
|
||||
E('ERR_INVALID_MODULE_SPECIFIER', (pkgPath, subpath) => {
|
||||
assert(subpath !== '.');
|
||||
return `Package subpath '${subpath}' is not a valid module request for the ` +
|
||||
`"exports" resolution of ${pkgPath}${sep}package.json`;
|
||||
E('ERR_INVALID_MODULE_SPECIFIER', (pkgPath, subpath, base = undefined) => {
|
||||
if (subpath === undefined) {
|
||||
return `Invalid package name '${pkgPath}' imported from ${base}`;
|
||||
} else if (base === undefined) {
|
||||
assert(subpath !== '.');
|
||||
return `Package subpath '${subpath}' is not a valid module request for ` +
|
||||
`the "exports" resolution of ${pkgPath}${sep}package.json`;
|
||||
} else {
|
||||
return `Package subpath '${subpath}' is not a valid module request for ` +
|
||||
`the "exports" resolution of ${pkgPath} imported from ${base}`;
|
||||
}
|
||||
}, TypeError);
|
||||
E('ERR_INVALID_OPT_VALUE', (name, value) =>
|
||||
`The value "${String(value)}" is invalid for option "${name}"`,
|
||||
|
@ -1089,18 +1096,32 @@ E('ERR_INVALID_OPT_VALUE', (name, value) =>
|
|||
RangeError);
|
||||
E('ERR_INVALID_OPT_VALUE_ENCODING',
|
||||
'The value "%s" is invalid for option "encoding"', TypeError);
|
||||
E('ERR_INVALID_PACKAGE_CONFIG',
|
||||
`Invalid package config %s${sep}package.json, %s`, Error);
|
||||
E('ERR_INVALID_PACKAGE_TARGET', (pkgPath, key, subpath, target) => {
|
||||
if (key === '.') {
|
||||
return `Invalid "exports" main target ${JSONStringify(target)} defined ` +
|
||||
`in the package config ${pkgPath}${sep}package.json`;
|
||||
} else {
|
||||
return `Invalid "exports" target ${JSONStringify(target)} defined for '${
|
||||
StringPrototypeSlice(key, 0, -subpath.length || key.length)}' in the ` +
|
||||
`package config ${pkgPath}${sep}package.json`;
|
||||
}
|
||||
E('ERR_INVALID_PACKAGE_CONFIG', (path, message, hasMessage = true) => {
|
||||
if (hasMessage)
|
||||
return `Invalid package config ${path}${sep}package.json, ${message}`;
|
||||
else
|
||||
return `Invalid JSON in ${path} imported from ${message}`;
|
||||
}, Error);
|
||||
E('ERR_INVALID_PACKAGE_TARGET',
|
||||
(pkgPath, key, subpath, target, base = undefined) => {
|
||||
if (key === null) {
|
||||
if (subpath !== '') {
|
||||
return `Invalid "exports" target ${JSONStringify(target)} defined ` +
|
||||
`for '${subpath}' in the package config ${pkgPath} imported from ` +
|
||||
base;
|
||||
} else {
|
||||
return `Invalid "exports" main target ${target} defined in the ` +
|
||||
`package config ${pkgPath} imported from ${base}.`;
|
||||
}
|
||||
} else if (key === '.') {
|
||||
return `Invalid "exports" main target ${JSONStringify(target)} defined ` +
|
||||
`in the package config ${pkgPath}${sep}package.json`;
|
||||
} else {
|
||||
return `Invalid "exports" target ${JSONStringify(target)} defined for '${
|
||||
StringPrototypeSlice(key, 0, -subpath.length || key.length)}' in the ` +
|
||||
`package config ${pkgPath}${sep}package.json`;
|
||||
}
|
||||
}, Error);
|
||||
E('ERR_INVALID_PERFORMANCE_MARK',
|
||||
'The "%s" performance mark has not been set', Error);
|
||||
E('ERR_INVALID_PROTOCOL',
|
||||
|
@ -1211,6 +1232,9 @@ E('ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK',
|
|||
'The ES Module loader may not return a format of \'dynamic\' when no ' +
|
||||
'dynamicInstantiate function was provided', Error);
|
||||
E('ERR_MISSING_OPTION', '%s is required', TypeError);
|
||||
E('ERR_MODULE_NOT_FOUND', (path, base, type = 'package') => {
|
||||
return `Cannot find ${type} '${path}' imported from ${base}`;
|
||||
}, Error);
|
||||
E('ERR_MULTIPLE_CALLBACK', 'Callback called multiple times', Error);
|
||||
E('ERR_NAPI_CONS_FUNCTION', 'Constructor must be a function', TypeError);
|
||||
E('ERR_NAPI_INVALID_DATAVIEW_ARGS',
|
||||
|
@ -1245,12 +1269,15 @@ E('ERR_OUT_OF_RANGE',
|
|||
msg += ` It must be ${range}. Received ${received}`;
|
||||
return msg;
|
||||
}, RangeError);
|
||||
E('ERR_PACKAGE_PATH_NOT_EXPORTED', (pkgPath, subpath) => {
|
||||
E('ERR_PACKAGE_PATH_NOT_EXPORTED', (pkgPath, subpath, base = undefined) => {
|
||||
if (subpath === '.') {
|
||||
return `No "exports" main resolved in ${pkgPath}${sep}package.json`;
|
||||
} else {
|
||||
} else if (base === undefined) {
|
||||
return `Package subpath '${subpath}' is not defined by "exports" in ${
|
||||
pkgPath}${sep}package.json`;
|
||||
} else {
|
||||
return `Package subpath '${subpath}' is not defined by "exports" in ${
|
||||
pkgPath} imported from ${base}`;
|
||||
}
|
||||
}, Error);
|
||||
E('ERR_REQUIRE_ESM',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
const { StringPrototypeStartsWith } = primordials;
|
||||
const { extname } = require('path');
|
||||
const { getOptionValue } = require('internal/options');
|
||||
|
||||
|
@ -7,14 +7,10 @@ const experimentalJsonModules = getOptionValue('--experimental-json-modules');
|
|||
const experimentalSpeciferResolution =
|
||||
getOptionValue('--experimental-specifier-resolution');
|
||||
const experimentalWasmModules = getOptionValue('--experimental-wasm-modules');
|
||||
const { getPackageType } = internalBinding('module_wrap');
|
||||
const { getPackageType } = require('internal/modules/esm/resolve');
|
||||
const { URL, fileURLToPath } = require('internal/url');
|
||||
const { ERR_UNKNOWN_FILE_EXTENSION } = require('internal/errors').codes;
|
||||
|
||||
// const TYPE_NONE = 0;
|
||||
// const TYPE_COMMONJS = 1;
|
||||
const TYPE_MODULE = 2;
|
||||
|
||||
const extensionFormatMap = {
|
||||
'__proto__': null,
|
||||
'.cjs': 'commonjs',
|
||||
|
@ -37,8 +33,8 @@ if (experimentalWasmModules)
|
|||
if (experimentalJsonModules)
|
||||
extensionFormatMap['.json'] = legacyExtensionFormatMap['.json'] = 'json';
|
||||
|
||||
function defaultGetFormat(url, context, defaultGetFormat) {
|
||||
if (url.startsWith('nodejs:')) {
|
||||
function defaultGetFormat(url, context, defaultGetFormatUnused) {
|
||||
if (StringPrototypeStartsWith(url, 'nodejs:')) {
|
||||
return { format: 'builtin' };
|
||||
}
|
||||
const parsed = new URL(url);
|
||||
|
@ -55,8 +51,7 @@ function defaultGetFormat(url, context, defaultGetFormat) {
|
|||
const ext = extname(parsed.pathname);
|
||||
let format;
|
||||
if (ext === '.js') {
|
||||
format = getPackageType(parsed.href) === TYPE_MODULE ?
|
||||
'module' : 'commonjs';
|
||||
format = getPackageType(parsed.href) === 'module' ? 'module' : 'commonjs';
|
||||
} else {
|
||||
format = extensionFormatMap[ext];
|
||||
}
|
||||
|
|
|
@ -1,26 +1,609 @@
|
|||
'use strict';
|
||||
|
||||
const {
|
||||
ArrayIsArray,
|
||||
JSONParse,
|
||||
JSONStringify,
|
||||
ObjectGetOwnPropertyNames,
|
||||
ObjectPrototypeHasOwnProperty,
|
||||
SafeMap,
|
||||
StringPrototypeEndsWith,
|
||||
StringPrototypeIncludes,
|
||||
StringPrototypeIndexOf,
|
||||
StringPrototypeSlice,
|
||||
StringPrototypeStartsWith,
|
||||
StringPrototypeSubstr,
|
||||
} = primordials;
|
||||
|
||||
const internalFS = require('internal/fs/utils');
|
||||
const { NativeModule } = require('internal/bootstrap/loaders');
|
||||
const { realpathSync } = require('fs');
|
||||
const {
|
||||
closeSync,
|
||||
fstatSync,
|
||||
openSync,
|
||||
readFileSync,
|
||||
realpathSync,
|
||||
statSync,
|
||||
Stats,
|
||||
} = require('fs');
|
||||
const { getOptionValue } = require('internal/options');
|
||||
const { sep } = require('path');
|
||||
|
||||
const preserveSymlinks = getOptionValue('--preserve-symlinks');
|
||||
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
|
||||
const typeFlag = getOptionValue('--input-type');
|
||||
const { resolve: moduleWrapResolve } = internalBinding('module_wrap');
|
||||
const { URL, pathToFileURL, fileURLToPath } = require('internal/url');
|
||||
const { ERR_INPUT_TYPE_NOT_ALLOWED,
|
||||
ERR_UNSUPPORTED_ESM_URL_SCHEME } = require('internal/errors').codes;
|
||||
const {
|
||||
ERR_INPUT_TYPE_NOT_ALLOWED,
|
||||
ERR_INVALID_MODULE_SPECIFIER,
|
||||
ERR_INVALID_PACKAGE_CONFIG,
|
||||
ERR_INVALID_PACKAGE_TARGET,
|
||||
ERR_MODULE_NOT_FOUND,
|
||||
ERR_PACKAGE_PATH_NOT_EXPORTED,
|
||||
ERR_UNSUPPORTED_ESM_URL_SCHEME,
|
||||
} = require('internal/errors').codes;
|
||||
|
||||
const realpathCache = new SafeMap();
|
||||
const packageJSONCache = new SafeMap(); /* string -> PackageConfig */
|
||||
|
||||
function defaultResolve(specifier, { parentURL } = {}, defaultResolve) {
|
||||
function tryStatSync(path) {
|
||||
try {
|
||||
return statSync(path);
|
||||
} catch {
|
||||
return new Stats();
|
||||
}
|
||||
}
|
||||
|
||||
function readIfFile(path) {
|
||||
let fd;
|
||||
try {
|
||||
fd = openSync(path, 'r');
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
try {
|
||||
if (!fstatSync(fd).isFile()) return undefined;
|
||||
return readFileSync(fd, 'utf8');
|
||||
} finally {
|
||||
closeSync(fd);
|
||||
}
|
||||
}
|
||||
|
||||
function getPackageConfig(path, base) {
|
||||
const existing = packageJSONCache.get(path);
|
||||
if (existing !== undefined) {
|
||||
if (!existing.isValid) {
|
||||
throw new ERR_INVALID_PACKAGE_CONFIG(path, fileURLToPath(base), false);
|
||||
}
|
||||
return existing;
|
||||
}
|
||||
|
||||
const source = readIfFile(path);
|
||||
if (source === undefined) {
|
||||
const packageConfig = {
|
||||
exists: false,
|
||||
main: undefined,
|
||||
name: undefined,
|
||||
isValid: true,
|
||||
type: 'none',
|
||||
exports: undefined
|
||||
};
|
||||
packageJSONCache.set(path, packageConfig);
|
||||
return packageConfig;
|
||||
}
|
||||
|
||||
let packageJSON;
|
||||
try {
|
||||
packageJSON = JSONParse(source);
|
||||
} catch {
|
||||
const packageConfig = {
|
||||
exists: true,
|
||||
main: undefined,
|
||||
name: undefined,
|
||||
isValid: false,
|
||||
type: 'none',
|
||||
exports: undefined
|
||||
};
|
||||
packageJSONCache.set(path, packageConfig);
|
||||
return packageConfig;
|
||||
}
|
||||
|
||||
let { main, name, type } = packageJSON;
|
||||
const { exports } = packageJSON;
|
||||
if (typeof main !== 'string') main = undefined;
|
||||
if (typeof name !== 'string') name = undefined;
|
||||
// Ignore unknown types for forwards compatibility
|
||||
if (type !== 'module' && type !== 'commonjs') type = 'none';
|
||||
|
||||
const packageConfig = {
|
||||
exists: true,
|
||||
main,
|
||||
name,
|
||||
isValid: true,
|
||||
type,
|
||||
exports
|
||||
};
|
||||
packageJSONCache.set(path, packageConfig);
|
||||
return packageConfig;
|
||||
}
|
||||
|
||||
function getPackageScopeConfig(resolved, base) {
|
||||
let packageJSONUrl = new URL('./package.json', resolved);
|
||||
while (true) {
|
||||
const packageJSONPath = packageJSONUrl.pathname;
|
||||
if (StringPrototypeEndsWith(packageJSONPath, 'node_modules/package.json'))
|
||||
break;
|
||||
const packageConfig = getPackageConfig(fileURLToPath(packageJSONUrl), base);
|
||||
if (packageConfig.exists) return packageConfig;
|
||||
|
||||
const lastPackageJSONUrl = packageJSONUrl;
|
||||
packageJSONUrl = new URL('../package.json', packageJSONUrl);
|
||||
|
||||
// Terminates at root where ../package.json equals ../../package.json
|
||||
// (can't just check "/package.json" for Windows support).
|
||||
if (packageJSONUrl.pathname === lastPackageJSONUrl.pathname) break;
|
||||
}
|
||||
const packageConfig = {
|
||||
exists: false,
|
||||
main: undefined,
|
||||
name: undefined,
|
||||
isValid: true,
|
||||
type: 'none',
|
||||
exports: undefined
|
||||
};
|
||||
packageJSONCache.set(fileURLToPath(packageJSONUrl), packageConfig);
|
||||
return packageConfig;
|
||||
}
|
||||
|
||||
/*
|
||||
* Legacy CommonJS main resolution:
|
||||
* 1. let M = pkg_url + (json main field)
|
||||
* 2. TRY(M, M.js, M.json, M.node)
|
||||
* 3. TRY(M/index.js, M/index.json, M/index.node)
|
||||
* 4. TRY(pkg_url/index.js, pkg_url/index.json, pkg_url/index.node)
|
||||
* 5. NOT_FOUND
|
||||
*/
|
||||
function fileExists(url) {
|
||||
return tryStatSync(fileURLToPath(url)).isFile();
|
||||
}
|
||||
|
||||
function legacyMainResolve(packageJSONUrl, packageConfig) {
|
||||
let guess;
|
||||
if (packageConfig.main !== undefined) {
|
||||
// Note: fs check redundances will be handled by Descriptor cache here.
|
||||
if (fileExists(guess = new URL(`./${packageConfig.main}`,
|
||||
packageJSONUrl))) {
|
||||
return guess;
|
||||
}
|
||||
if (fileExists(guess = new URL(`./${packageConfig.main}.js`,
|
||||
packageJSONUrl))) {
|
||||
return guess;
|
||||
}
|
||||
if (fileExists(guess = new URL(`./${packageConfig.main}.json`,
|
||||
packageJSONUrl))) {
|
||||
return guess;
|
||||
}
|
||||
if (fileExists(guess = new URL(`./${packageConfig.main}.node`,
|
||||
packageJSONUrl))) {
|
||||
return guess;
|
||||
}
|
||||
if (fileExists(guess = new URL(`./${packageConfig.main}/index.js`,
|
||||
packageJSONUrl))) {
|
||||
return guess;
|
||||
}
|
||||
if (fileExists(guess = new URL(`./${packageConfig.main}/index.json`,
|
||||
packageJSONUrl))) {
|
||||
return guess;
|
||||
}
|
||||
if (fileExists(guess = new URL(`./${packageConfig.main}/index.node`,
|
||||
packageJSONUrl))) {
|
||||
return guess;
|
||||
}
|
||||
// Fallthrough.
|
||||
}
|
||||
if (fileExists(guess = new URL('./index.js', packageJSONUrl))) {
|
||||
return guess;
|
||||
}
|
||||
// So fs.
|
||||
if (fileExists(guess = new URL('./index.json', packageJSONUrl))) {
|
||||
return guess;
|
||||
}
|
||||
if (fileExists(guess = new URL('./index.node', packageJSONUrl))) {
|
||||
return guess;
|
||||
}
|
||||
// Not found.
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolveExtensionsWithTryExactName(search) {
|
||||
if (fileExists(search)) return search;
|
||||
return resolveExtensions(search);
|
||||
}
|
||||
|
||||
const extensions = ['.js', '.json', '.node', '.mjs'];
|
||||
function resolveExtensions(search) {
|
||||
for (let i = 0; i < extensions.length; i++) {
|
||||
const extension = extensions[i];
|
||||
const guess = new URL(`${search.pathname}${extension}`, search);
|
||||
if (fileExists(guess)) return guess;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function resolveIndex(search) {
|
||||
return resolveExtensions(new URL('index', search));
|
||||
}
|
||||
|
||||
function finalizeResolution(resolved, base) {
|
||||
if (getOptionValue('--experimental-specifier-resolution') === 'node') {
|
||||
let file = resolveExtensionsWithTryExactName(resolved);
|
||||
if (file !== undefined) return file;
|
||||
if (!StringPrototypeEndsWith(resolved.pathname, '/')) {
|
||||
file = resolveIndex(new URL(`${resolved.pathname}/`, base));
|
||||
} else {
|
||||
file = resolveIndex(resolved);
|
||||
}
|
||||
if (file !== undefined) return file;
|
||||
throw new ERR_MODULE_NOT_FOUND(
|
||||
resolved.pathname, fileURLToPath(base), 'module');
|
||||
}
|
||||
|
||||
if (StringPrototypeEndsWith(resolved.pathname, '/')) return resolved;
|
||||
const path = fileURLToPath(resolved);
|
||||
|
||||
if (!tryStatSync(path).isFile()) {
|
||||
throw new ERR_MODULE_NOT_FOUND(
|
||||
path || resolved.pathname, fileURLToPath(base), 'module');
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
function throwExportsNotFound(subpath, packageJSONUrl, base) {
|
||||
throw new ERR_PACKAGE_PATH_NOT_EXPORTED(
|
||||
fileURLToPath(packageJSONUrl), subpath, fileURLToPath(base));
|
||||
}
|
||||
|
||||
function throwSubpathInvalid(subpath, packageJSONUrl, base) {
|
||||
throw new ERR_INVALID_MODULE_SPECIFIER(
|
||||
fileURLToPath(packageJSONUrl), subpath, fileURLToPath(base));
|
||||
}
|
||||
|
||||
function throwExportsInvalid(
|
||||
subpath, target, packageJSONUrl, base) {
|
||||
if (typeof target === 'object' && target !== null) {
|
||||
target = JSONStringify(target, null, '');
|
||||
} else if (ArrayIsArray(target)) {
|
||||
target = `[${target}]`;
|
||||
} else {
|
||||
target = `${target}`;
|
||||
}
|
||||
throw new ERR_INVALID_PACKAGE_TARGET(
|
||||
fileURLToPath(packageJSONUrl), null, subpath, target, fileURLToPath(base));
|
||||
}
|
||||
|
||||
function resolveExportsTargetString(
|
||||
target, subpath, match, packageJSONUrl, base) {
|
||||
if (target[0] !== '.' || target[1] !== '/' ||
|
||||
(subpath !== '' && target[target.length - 1] !== '/')) {
|
||||
throwExportsInvalid(match, target, packageJSONUrl, base);
|
||||
}
|
||||
|
||||
const resolved = new URL(target, packageJSONUrl);
|
||||
const resolvedPath = resolved.pathname;
|
||||
const packagePath = new URL('.', packageJSONUrl).pathname;
|
||||
|
||||
if (!StringPrototypeStartsWith(resolvedPath, packagePath) ||
|
||||
StringPrototypeIncludes(
|
||||
resolvedPath, '/node_modules/', packagePath.length - 1)) {
|
||||
throwExportsInvalid(match, target, packageJSONUrl, base);
|
||||
}
|
||||
|
||||
if (subpath === '') return resolved;
|
||||
const subpathResolved = new URL(subpath, resolved);
|
||||
const subpathResolvedPath = subpathResolved.pathname;
|
||||
if (!StringPrototypeStartsWith(subpathResolvedPath, resolvedPath) ||
|
||||
StringPrototypeIncludes(subpathResolvedPath,
|
||||
'/node_modules/', packagePath.length - 1)) {
|
||||
throwSubpathInvalid(match + subpath, packageJSONUrl, base);
|
||||
}
|
||||
return subpathResolved;
|
||||
}
|
||||
|
||||
function isArrayIndex(key /* string */) { /* -> boolean */
|
||||
const keyNum = +key;
|
||||
if (`${keyNum}` !== key) return false;
|
||||
return keyNum >= 0 && keyNum < 0xFFFF_FFFF;
|
||||
}
|
||||
|
||||
function resolveExportsTarget(
|
||||
packageJSONUrl, target, subpath, packageSubpath, base) {
|
||||
if (typeof target === 'string') {
|
||||
const resolved = resolveExportsTargetString(
|
||||
target, subpath, packageSubpath, packageJSONUrl, base);
|
||||
return finalizeResolution(resolved, base);
|
||||
} else if (ArrayIsArray(target)) {
|
||||
if (target.length === 0)
|
||||
throwExportsInvalid(packageSubpath, target, packageJSONUrl, base);
|
||||
|
||||
let lastException;
|
||||
for (let i = 0; i < target.length; i++) {
|
||||
const targetItem = target[i];
|
||||
let resolved;
|
||||
try {
|
||||
resolved = resolveExportsTarget(
|
||||
packageJSONUrl, targetItem, subpath, packageSubpath, base);
|
||||
} catch (e) {
|
||||
lastException = e;
|
||||
if (e.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED' ||
|
||||
e.code === 'ERR_INVALID_PACKAGE_TARGET') {
|
||||
continue;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
return finalizeResolution(resolved, base);
|
||||
}
|
||||
throw lastException;
|
||||
} else if (typeof target === 'object' && target !== null) {
|
||||
const keys = ObjectGetOwnPropertyNames(target);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
if (isArrayIndex(key)) {
|
||||
throw new ERR_INVALID_PACKAGE_CONFIG(
|
||||
fileURLToPath(packageJSONUrl),
|
||||
'"exports" cannot contain numeric property keys');
|
||||
}
|
||||
}
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
if (key === 'node' || key === 'import' || key === 'default') {
|
||||
const conditionalTarget = target[key];
|
||||
try {
|
||||
return resolveExportsTarget(
|
||||
packageJSONUrl, conditionalTarget, subpath, packageSubpath, base);
|
||||
} catch (e) {
|
||||
if (e.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED') continue;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
|
||||
}
|
||||
throwExportsInvalid(packageSubpath, target, packageJSONUrl, base);
|
||||
}
|
||||
|
||||
function isConditionalExportsMainSugar(exports, packageJSONUrl, base) {
|
||||
if (typeof exports === 'string' || ArrayIsArray(exports)) return true;
|
||||
if (typeof exports !== 'object' || exports === null) return false;
|
||||
|
||||
const keys = ObjectGetOwnPropertyNames(exports);
|
||||
let isConditionalSugar = false;
|
||||
let i = 0;
|
||||
for (let j = 0; j < keys.length; j++) {
|
||||
const key = keys[j];
|
||||
const curIsConditionalSugar = key === '' || key[0] !== '.';
|
||||
if (i++ === 0) {
|
||||
isConditionalSugar = curIsConditionalSugar;
|
||||
} else if (isConditionalSugar !== curIsConditionalSugar) {
|
||||
throw new ERR_INVALID_PACKAGE_CONFIG(
|
||||
fileURLToPath(packageJSONUrl),
|
||||
'"exports" cannot contain some keys starting with \'.\' and some not.' +
|
||||
' The exports object must either be an object of package subpath keys' +
|
||||
' or an object of main entry condition name keys only.');
|
||||
}
|
||||
}
|
||||
return isConditionalSugar;
|
||||
}
|
||||
|
||||
|
||||
function packageMainResolve(packageJSONUrl, packageConfig, base) {
|
||||
if (packageConfig.exists) {
|
||||
const exports = packageConfig.exports;
|
||||
if (exports !== undefined) {
|
||||
if (isConditionalExportsMainSugar(exports, packageJSONUrl, base)) {
|
||||
return resolveExportsTarget(packageJSONUrl, exports, '', '', base);
|
||||
} else if (typeof exports === 'object' && exports !== null) {
|
||||
const target = exports['.'];
|
||||
if (target !== undefined)
|
||||
return resolveExportsTarget(packageJSONUrl, target, '', '', base);
|
||||
}
|
||||
|
||||
throw new ERR_PACKAGE_PATH_NOT_EXPORTED(packageJSONUrl, '.');
|
||||
}
|
||||
if (packageConfig.main !== undefined) {
|
||||
const resolved = new URL(packageConfig.main, packageJSONUrl);
|
||||
const path = fileURLToPath(resolved);
|
||||
if (tryStatSync(path).isFile()) return resolved;
|
||||
}
|
||||
if (getOptionValue('--experimental-specifier-resolution') === 'node') {
|
||||
if (packageConfig.main !== undefined) {
|
||||
return finalizeResolution(
|
||||
new URL(packageConfig.main, packageJSONUrl), base);
|
||||
} else {
|
||||
return finalizeResolution(
|
||||
new URL('index', packageJSONUrl), base);
|
||||
}
|
||||
}
|
||||
if (packageConfig.type !== 'module') {
|
||||
return legacyMainResolve(packageJSONUrl, packageConfig);
|
||||
}
|
||||
}
|
||||
|
||||
throw new ERR_MODULE_NOT_FOUND(
|
||||
fileURLToPath(new URL('.', packageJSONUrl)), fileURLToPath(base));
|
||||
}
|
||||
|
||||
|
||||
function packageExportsResolve(
|
||||
packageJSONUrl, packageSubpath, packageConfig, base) /* -> URL */ {
|
||||
const exports = packageConfig.exports;
|
||||
if (exports === undefined ||
|
||||
isConditionalExportsMainSugar(exports, packageJSONUrl, base)) {
|
||||
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
|
||||
}
|
||||
|
||||
|
||||
if (ObjectPrototypeHasOwnProperty(exports, packageSubpath)) {
|
||||
const target = exports[packageSubpath];
|
||||
const resolved = resolveExportsTarget(
|
||||
packageJSONUrl, target, '', packageSubpath, base);
|
||||
return finalizeResolution(resolved, base);
|
||||
}
|
||||
|
||||
let bestMatch = '';
|
||||
const keys = ObjectGetOwnPropertyNames(exports);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const key = keys[i];
|
||||
if (key[key.length - 1] !== '/') continue;
|
||||
if (StringPrototypeStartsWith(packageSubpath, key) &&
|
||||
key.length > bestMatch.length) {
|
||||
bestMatch = key;
|
||||
}
|
||||
}
|
||||
|
||||
if (bestMatch) {
|
||||
const target = exports[bestMatch];
|
||||
const subpath = StringPrototypeSubstr(packageSubpath, bestMatch.length);
|
||||
const resolved = resolveExportsTarget(
|
||||
packageJSONUrl, target, subpath, packageSubpath, base);
|
||||
return finalizeResolution(resolved, base);
|
||||
}
|
||||
|
||||
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
|
||||
}
|
||||
|
||||
function getPackageType(url) {
|
||||
const packageConfig = getPackageScopeConfig(url, url);
|
||||
return packageConfig.type;
|
||||
}
|
||||
|
||||
function packageResolve(specifier /* string */, base /* URL */) { /* -> URL */
|
||||
let separatorIndex = StringPrototypeIndexOf(specifier, '/');
|
||||
let validPackageName = true;
|
||||
let isScoped = false;
|
||||
if (specifier[0] === '@') {
|
||||
isScoped = true;
|
||||
if (separatorIndex === -1 || specifier.length === 0) {
|
||||
validPackageName = false;
|
||||
} else {
|
||||
separatorIndex = StringPrototypeIndexOf(
|
||||
specifier, '/', separatorIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
const packageName = separatorIndex === -1 ?
|
||||
specifier : StringPrototypeSlice(specifier, 0, separatorIndex);
|
||||
|
||||
// Package name cannot have leading . and cannot have percent-encoding or
|
||||
// separators.
|
||||
for (let i = 0; i < packageName.length; i++) {
|
||||
if (packageName[i] === '%' || packageName[i] === '\\') {
|
||||
validPackageName = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!validPackageName) {
|
||||
throw new ERR_INVALID_MODULE_SPECIFIER(
|
||||
specifier, undefined, fileURLToPath(base));
|
||||
}
|
||||
|
||||
const packageSubpath = separatorIndex === -1 ?
|
||||
'' : '.' + StringPrototypeSlice(specifier, separatorIndex);
|
||||
|
||||
// ResolveSelf
|
||||
const packageConfig = getPackageScopeConfig(base, base);
|
||||
if (packageConfig.exists) {
|
||||
// TODO(jkrems): Find a way to forward the pair/iterator already generated
|
||||
// while executing GetPackageScopeConfig
|
||||
let packageJSONUrl;
|
||||
for (const [ filename, packageConfigCandidate ] of packageJSONCache) {
|
||||
if (packageConfig === packageConfigCandidate) {
|
||||
packageJSONUrl = pathToFileURL(filename);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (packageJSONUrl !== undefined &&
|
||||
packageConfig.name === packageName &&
|
||||
packageConfig.exports !== undefined) {
|
||||
if (packageSubpath === './') {
|
||||
return new URL('./', packageJSONUrl);
|
||||
} else if (packageSubpath === '') {
|
||||
return packageMainResolve(packageJSONUrl, packageConfig, base);
|
||||
} else {
|
||||
return packageExportsResolve(
|
||||
packageJSONUrl, packageSubpath, packageConfig, base);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let packageJSONUrl =
|
||||
new URL('./node_modules/' + packageName + '/package.json', base);
|
||||
let packageJSONPath = fileURLToPath(packageJSONUrl);
|
||||
let lastPath;
|
||||
do {
|
||||
const stat = tryStatSync(
|
||||
StringPrototypeSlice(packageJSONPath, 0, packageJSONPath.length - 13));
|
||||
if (!stat.isDirectory()) {
|
||||
lastPath = packageJSONPath;
|
||||
packageJSONUrl = new URL((isScoped ?
|
||||
'../../../../node_modules/' : '../../../node_modules/') +
|
||||
packageName + '/package.json', packageJSONUrl);
|
||||
packageJSONPath = fileURLToPath(packageJSONUrl);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Package match.
|
||||
const packageConfig = getPackageConfig(packageJSONPath, base);
|
||||
if (packageSubpath === './') {
|
||||
return new URL('./', packageJSONUrl);
|
||||
} else if (packageSubpath === '') {
|
||||
return packageMainResolve(packageJSONUrl, packageConfig, base);
|
||||
} else if (packageConfig.exports !== undefined) {
|
||||
return packageExportsResolve(
|
||||
packageJSONUrl, packageSubpath, packageConfig, base);
|
||||
} else {
|
||||
return finalizeResolution(
|
||||
new URL(packageSubpath, packageJSONUrl), base);
|
||||
}
|
||||
// Cross-platform root check.
|
||||
} while (packageJSONPath.length !== lastPath.length);
|
||||
|
||||
// eslint can't handle the above code.
|
||||
// eslint-disable-next-line no-unreachable
|
||||
throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base));
|
||||
}
|
||||
|
||||
function shouldBeTreatedAsRelativeOrAbsolutePath(specifier) {
|
||||
if (specifier === '') return false;
|
||||
if (specifier[0] === '/') return true;
|
||||
if (specifier[0] === '.') {
|
||||
if (specifier.length === 1 || specifier[1] === '/') return true;
|
||||
if (specifier[1] === '.') {
|
||||
if (specifier.length === 2 || specifier[2] === '/') return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function moduleResolve(specifier /* string */, base /* URL */) { /* -> URL */
|
||||
// Order swapped from spec for minor perf gain.
|
||||
// Ok since relative URLs cannot parse as URLs.
|
||||
let resolved;
|
||||
if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
|
||||
resolved = new URL(specifier, base);
|
||||
} else {
|
||||
try {
|
||||
resolved = new URL(specifier);
|
||||
} catch {
|
||||
return packageResolve(specifier, base);
|
||||
}
|
||||
}
|
||||
return finalizeResolution(resolved, base);
|
||||
}
|
||||
|
||||
function defaultResolve(specifier, { parentURL } = {}, defaultResolveUnused) {
|
||||
let parsed;
|
||||
try {
|
||||
parsed = new URL(specifier);
|
||||
|
@ -39,7 +622,7 @@ function defaultResolve(specifier, { parentURL } = {}, defaultResolve) {
|
|||
url: 'nodejs:' + specifier
|
||||
};
|
||||
}
|
||||
if (parentURL && parentURL.startsWith('data:')) {
|
||||
if (parentURL && StringPrototypeStartsWith(parentURL, 'data:')) {
|
||||
// This is gonna blow up, we want the error
|
||||
new URL(specifier, parentURL);
|
||||
}
|
||||
|
@ -58,7 +641,7 @@ function defaultResolve(specifier, { parentURL } = {}, defaultResolve) {
|
|||
throw new ERR_INPUT_TYPE_NOT_ALLOWED();
|
||||
}
|
||||
|
||||
let url = moduleWrapResolve(specifier, parentURL);
|
||||
let url = moduleResolve(specifier, new URL(parentURL));
|
||||
|
||||
if (isMain ? !preserveSymlinksMain : !preserveSymlinks) {
|
||||
const urlPath = fileURLToPath(url);
|
||||
|
@ -73,4 +656,8 @@ function defaultResolve(specifier, { parentURL } = {}, defaultResolve) {
|
|||
|
||||
return { url: `${url}` };
|
||||
}
|
||||
exports.defaultResolve = defaultResolve;
|
||||
|
||||
module.exports = {
|
||||
defaultResolve,
|
||||
getPackageType
|
||||
};
|
||||
|
|
|
@ -219,7 +219,6 @@ constexpr size_t kFsStatsBufferLength =
|
|||
V(dns_srv_string, "SRV") \
|
||||
V(dns_txt_string, "TXT") \
|
||||
V(done_string, "done") \
|
||||
V(dot_string, ".") \
|
||||
V(duration_string, "duration") \
|
||||
V(ecdh_string, "ECDH") \
|
||||
V(emit_warning_string, "emitWarning") \
|
||||
|
@ -274,7 +273,6 @@ constexpr size_t kFsStatsBufferLength =
|
|||
V(kind_string, "kind") \
|
||||
V(library_string, "library") \
|
||||
V(mac_string, "mac") \
|
||||
V(main_string, "main") \
|
||||
V(max_buffer_string, "maxBuffer") \
|
||||
V(message_port_constructor_string, "MessagePort") \
|
||||
V(message_port_string, "messagePort") \
|
||||
|
@ -992,9 +990,6 @@ class Environment : public MemoryRetainer {
|
|||
inline uint32_t get_next_script_id();
|
||||
inline uint32_t get_next_function_id();
|
||||
|
||||
std::unordered_map<std::string, const loader::PackageConfig>
|
||||
package_json_cache;
|
||||
|
||||
inline double* heap_statistics_buffer() const;
|
||||
inline void set_heap_statistics_buffer(
|
||||
std::shared_ptr<v8::BackingStore> backing_store);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -65,8 +65,6 @@ class ModuleWrap : public BaseObject {
|
|||
static void GetStaticDependencySpecifiers(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
static void Resolve(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void GetPackageType(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void SetImportModuleDynamicallyCallback(
|
||||
const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void SetInitializeImportMetaObjectCallback(
|
||||
|
|
|
@ -43,9 +43,6 @@ void OnFatalError(const char* location, const char* message);
|
|||
V(ERR_INVALID_ARG_VALUE, TypeError) \
|
||||
V(ERR_OSSL_EVP_INVALID_DIGEST, Error) \
|
||||
V(ERR_INVALID_ARG_TYPE, TypeError) \
|
||||
V(ERR_INVALID_MODULE_SPECIFIER, TypeError) \
|
||||
V(ERR_INVALID_PACKAGE_CONFIG, Error) \
|
||||
V(ERR_INVALID_PACKAGE_TARGET, Error) \
|
||||
V(ERR_INVALID_TRANSFER_OBJECT, TypeError) \
|
||||
V(ERR_MEMORY_ALLOCATION_FAILED, Error) \
|
||||
V(ERR_MISSING_ARGS, TypeError) \
|
||||
|
@ -53,9 +50,7 @@ void OnFatalError(const char* location, const char* message);
|
|||
V(ERR_MISSING_PASSPHRASE, TypeError) \
|
||||
V(ERR_MISSING_PLATFORM_FOR_WORKER, Error) \
|
||||
V(ERR_NON_CONTEXT_AWARE_DISABLED, Error) \
|
||||
V(ERR_MODULE_NOT_FOUND, Error) \
|
||||
V(ERR_OUT_OF_RANGE, RangeError) \
|
||||
V(ERR_PACKAGE_PATH_NOT_EXPORTED, Error) \
|
||||
V(ERR_SCRIPT_EXECUTION_INTERRUPTED, Error) \
|
||||
V(ERR_SCRIPT_EXECUTION_TIMEOUT, Error) \
|
||||
V(ERR_STRING_TOO_LONG, Error) \
|
||||
|
|
|
@ -129,8 +129,7 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js';
|
|||
|
||||
// The use of %2F escapes in paths fails loading
|
||||
loadFixture('pkgexports/sub/..%2F..%2Fbar.js').catch(mustCall((err) => {
|
||||
strictEqual(err.code, isRequire ? 'ERR_INVALID_FILE_URL_PATH' :
|
||||
'ERR_MODULE_NOT_FOUND');
|
||||
strictEqual(err.code, 'ERR_INVALID_FILE_URL_PATH');
|
||||
}));
|
||||
|
||||
// Package export with numeric index properties must throw a validation error
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
(node:*) ExperimentalWarning: The ESM module loader is experimental.
|
||||
(node:*) ExperimentalWarning: --experimental-loader is an experimental feature. This feature could change at any time
|
||||
internal/modules/esm/resolve.js:*
|
||||
let url = moduleWrapResolve(specifier, parentURL);
|
||||
^
|
||||
|
||||
Error: Cannot find package 'i-dont-exist' imported from *
|
||||
internal/modules/run_main.js:*
|
||||
internalBinding('errors').triggerUncaughtException(
|
||||
^
|
||||
Error [ERR_MODULE_NOT_FOUND]: Cannot find package 'i-dont-exist' imported from *
|
||||
at packageResolve (internal/modules/esm/resolve.js:*:*)
|
||||
at moduleResolve (internal/modules/esm/resolve.js:*:*)
|
||||
at Loader.defaultResolve [as _resolve] (internal/modules/esm/resolve.js:*:*)
|
||||
at Loader.resolve (internal/modules/esm/loader.js:*:*)
|
||||
at Loader.getModuleJob (internal/modules/esm/loader.js:*:*)
|
||||
|
@ -12,7 +13,6 @@ Error: Cannot find package 'i-dont-exist' imported from *
|
|||
at internal/process/esm_loader.js:*:*
|
||||
at Object.initializeLoader (internal/process/esm_loader.js:*:*)
|
||||
at runMainESM (internal/modules/run_main.js:*:*)
|
||||
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:*:*)
|
||||
at internal/main/run_main_module.js:*:* {
|
||||
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:*:*) {
|
||||
code: 'ERR_MODULE_NOT_FOUND'
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue