diff --git a/doc/api/errors.md b/doc/api/errors.md
index c908846d5471aa..2fc655b3c55e8c 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -1683,6 +1683,12 @@ A non-context-aware native addon was loaded in a process that disallows them.
A given value is out of the accepted range.
+
+### `ERR_PACKAGE_IMPORT_NOT_DEFINED`
+
+The `package.json` ["imports" field][] does not define the given internal
+package specifier mapping.
+
### `ERR_PACKAGE_PATH_NOT_EXPORTED`
@@ -2533,3 +2539,4 @@ closed.
[vm]: vm.html
[self-reference a package using its name]: esm.html#esm_self_referencing_a_package_using_its_name
[define a custom subpath]: esm.html#esm_subpath_exports
+["imports" field]: esm.html#esm_internal_package_imports
diff --git a/doc/api/esm.md b/doc/api/esm.md
index cbba5fc41ec43a..268ccff5f99f7c 100644
--- a/doc/api/esm.md
+++ b/doc/api/esm.md
@@ -546,6 +546,43 @@ and in a CommonJS one. For example, this code will also work:
const { something } = require('a-package/foo'); // Loads from ./foo.js.
```
+### Internal package imports
+
+In addition to the `"exports"` field it is possible to define internal package
+import maps that only apply to import specifiers from within the package itself.
+
+Entries in the imports field must always start with `#` to ensure they are
+clearly disambiguated from package specifiers.
+
+For example, the imports field can be used to gain the benefits of conditional
+exports for internal modules:
+
+```json
+// package.json
+{
+ "imports": {
+ "#dep": {
+ "node": "dep-node-native",
+ "default": "./dep-polyfill.js"
+ }
+ },
+ "dependencies": {
+ "dep-node-native": "^1.0.0"
+ }
+}
+```
+
+where `import '#dep'` would now get the resolution of the external package
+`dep-node-native` (including its exports in turn), and instead get the local
+file `./dep-polyfill.js` relative to the package in other environments.
+
+Unlike the exports field, import maps permit mapping to external packages
+because this provides an important use case for conditional loading and also can
+be done without the risk of cycles, unlike for exports.
+
+Apart from the above, the resolution rules for the imports field are otherwise
+analogous to the exports field.
+
### Dual CommonJS/ES module packages
Prior to the introduction of support for ES modules in Node.js, it was a common
@@ -1577,10 +1614,11 @@ The resolver can throw the following errors:
or package subpath specifier.
* _Invalid Package Configuration_: package.json configuration is invalid or
contains an invalid configuration.
-* _Invalid Package Target_: Package exports define a target module within the
- package that is an invalid type or string target.
+* _Invalid Package Target_: Package exports or imports define a target module
+ for the package that is an invalid type or string target.
* _Package Path Not Exported_: Package exports do not define or permit a target
subpath in the package for the given module.
+* _Package Import Not Defined_: Package imports do not define the specifier.
* _Module Not Found_: The package or module requested does not exist.
@@ -1592,11 +1630,14 @@ The resolver can throw the following errors:
> 1. If _specifier_ is a valid URL, then
> 1. Set _resolvedURL_ to the result of parsing and reserializing
> _specifier_ as a URL.
-> 1. Otherwise, if _specifier_ starts with _"/"_, then
-> 1. Throw an _Invalid Module Specifier_ error.
-> 1. Otherwise, if _specifier_ starts with _"./"_ or _"../"_, then
+> 1. Otherwise, if _specifier_ starts with _"/"_, _"./"_ or _"../"_, then
> 1. Set _resolvedURL_ to the URL resolution of _specifier_ relative to
> _parentURL_.
+> 1. Otherwise, if _specifier_ starts with _"#"_, then
+> 1. Set _resolvedURL_ to the result of
+> **PACKAGE_INTERNAL_RESOLVE**(_specifier_, _parentURL_).
+> 1. If _resolvedURL_ is **null** or **undefined**, throw a
+> _Package Import Not Defined_ error.
> 1. Otherwise,
> 1. Note: _specifier_ is now a bare specifier.
> 1. Set _resolvedURL_ the result of
@@ -1634,7 +1675,7 @@ The resolver can throw the following errors:
> 1. If _packageSubpath_ contains any _"."_ or _".."_ segments or percent
> encoded strings for _"/"_ or _"\\"_, then
> 1. Throw an _Invalid Module Specifier_ error.
-> 1. Set _selfUrl_ to the result of
+> 1. Let _selfUrl_ be the result of
> **SELF_REFERENCE_RESOLVE**(_packageName_, _packageSubpath_, _parentURL_).
> 1. If _selfUrl_ isn't empty, return _selfUrl_.
> 1. If _packageSubpath_ is _undefined_ and _packageName_ is a Node.js builtin
@@ -1657,8 +1698,11 @@ The resolver can throw the following errors:
> 1. If _pjson_ is not **null** and _pjson_ has an _"exports"_ key, then
> 1. Let _exports_ be _pjson.exports_.
> 1. If _exports_ is not **null** or **undefined**, then
-> 1. Return **PACKAGE_EXPORTS_RESOLVE**(_packageURL_,
-> _packageSubpath_, _pjson.exports_).
+> 1. Let _resolved_ be the result of **PACKAGE_EXPORTS_RESOLVE**(
+> _packageURL_, _packageSubpath_, _pjson.exports_).
+> 1. If _resolved_ is **null** or **undefined**, throw a
+> _Package Path Not Exported_ error.
+> 1. Return _resolved_.
> 1. Return the URL resolution of _packageSubpath_ in _packageURL_.
> 1. Throw a _Module Not Found_ error.
@@ -1679,8 +1723,11 @@ The resolver can throw the following errors:
> 1. If _pjson_ is not **null** and _pjson_ has an _"exports"_ key, then
> 1. Let _exports_ be _pjson.exports_.
> 1. If _exports_ is not **null** or **undefined**, then
-> 1. Return **PACKAGE_EXPORTS_RESOLVE**(_packageURL_, _subpath_,
-> _pjson.exports_).
+> 1. Let _resolved_ be the result of **PACKAGE_EXPORTS_RESOLVE**(
+> _packageURL_, _subpath_, _pjson.exports_).
+> 1. If _resolved_ is **null** or **undefined**, throw a
+> _Package Path Not Exported_ error.
+> 1. Return _resolved_.
> 1. Return the URL resolution of _subpath_ in _packageURL_.
> 1. Otherwise, return **undefined**.
@@ -1693,12 +1740,18 @@ The resolver can throw the following errors:
> not starting with _"."_, throw an _Invalid Package Configuration_ error.
> 1. If _pjson.exports_ is a String or Array, or an Object containing no
> keys starting with _"."_, then
-> 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_,
-> _pjson.exports_, _""_).
+> 1. Let _resolved_ be the result of **PACKAGE_TARGET_RESOLVE**(
+> _packageURL_, _pjson.exports_, _""_, **false**, _defaultEnv_).
+> 1. If _resolved_ is **null** or **undefined**, throw a
+> _Package Path Not Exported_ error.
+> 1. Return _resolved_.
> 1. If _pjson.exports_ is an Object containing a _"."_ property, then
> 1. Let _mainExport_ be the _"."_ property in _pjson.exports_.
-> 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_,
-> _mainExport_, _""_).
+> 1. Let _resolved_ be the result of **PACKAGE_TARGET_RESOLVE**(
+> _packageURL_, _mainExport_, _""_, **false**, _defaultEnv_).
+> 1. If _resolved_ is **null** or **undefined**, throw a
+> _Package Path Not Exported_ error.
+> 1. Return _resolved_.
> 1. Throw a _Package Path Not Exported_ error.
> 1. Let _legacyMainURL_ be the result applying the legacy
> **LOAD_AS_DIRECTORY** CommonJS resolver to _packageURL_, throwing a
@@ -1712,8 +1765,8 @@ The resolver can throw the following errors:
> 1. Set _packagePath_ to _"./"_ concatenated with _packagePath_.
> 1. If _packagePath_ is a key of _exports_, then
> 1. Let _target_ be the value of _exports\[packagePath\]_.
-> 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _target_,
-> _""_, _defaultEnv_).
+> 1. Return **PACKAGE_TARGET_RESOLVE**(_packageURL_, _target_,
+> _""_, **false**, _defaultEnv_).
> 1. Let _directoryKeys_ be the list of keys of _exports_ ending in
> _"/"_, sorted by length descending.
> 1. For each key _directory_ in _directoryKeys_, do
@@ -1721,22 +1774,28 @@ The resolver can throw the following errors:
> 1. Let _target_ be the value of _exports\[directory\]_.
> 1. Let _subpath_ be the substring of _target_ starting at the index
> of the length of _directory_.
-> 1. Return **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _target_,
-> _subpath_, _defaultEnv_).
-> 1. Throw a _Package Path Not Exported_ error.
+> 1. Return **PACKAGE_TARGET_RESOLVE**(_packageURL_, _target_,
+> _subpath_, **false**, _defaultEnv_).
+> 1. Return **null**.
-**PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _target_, _subpath_, _env_)
+**PACKAGE_TARGET_RESOLVE**(_packageURL_, _target_, _subpath_, _internal_, _env_)
> 1. If _target_ is a String, then
-> 1. If _target_ does not start with _"./"_ or contains any _"node_modules"_
-> segments including _"node_modules"_ percent-encoding, throw an
-> _Invalid Package Target_ error.
+> 1. If _target_ contains any _"node_modules"_ segments including
+> _"node_modules"_ percent-encoding, throw an _Invalid Package Target_
+> error.
+> 1. If _subpath_ has non-zero length and _target_ does not end with _"/"_,
+> throw an _Invalid Module Specifier_ error.
+> 1. If _target_ does not start with _"./"_, then
+> 1. If _target_ does not start with _"../"_ or _"/"_ and is not a valid
+> URL, then
+> 1. If _internal_ is **true**, return **PACKAGE_RESOLVE**(
+> _target_ + _subpath_, _packageURL_ + _"/"_)_.
+> 1. Otherwise throw an _Invalid Package Target_ error.
> 1. Let _resolvedTarget_ be the URL resolution of the concatenation of
> _packageURL_ and _target_.
> 1. If _resolvedTarget_ is not contained in _packageURL_, throw an
> _Invalid Package Target_ error.
-> 1. If _subpath_ has non-zero length and _target_ does not end with _"/"_,
-> throw an _Invalid Module Specifier_ error.
> 1. Let _resolved_ be the URL resolution of the concatenation of
> _subpath_ and _resolvedTarget_.
> 1. If _resolved_ is not contained in _resolvedTarget_, throw an
@@ -1748,22 +1807,48 @@ The resolver can throw the following errors:
> 1. For each property _p_ of _target_, in object insertion order as,
> 1. If _p_ equals _"default"_ or _env_ contains an entry for _p_, then
> 1. Let _targetValue_ be the value of the _p_ property in _target_.
-> 1. Return the result of **PACKAGE_EXPORTS_TARGET_RESOLVE**(
-> _packageURL_, _targetValue_, _subpath_, _env_), continuing the
-> loop on any _Package Path Not Exported_ error.
-> 1. Throw a _Package Path Not Exported_ error.
+> 1. Let _resolved_ be the result of **PACKAGE_TARGET_RESOLVE**(
+> _packageURL_, _targetValue_, _subpath_, _internal_, _env_)
+> 1. If _resolved_ is equal to **undefined**, continue the loop.
+> 1. Return _resolved_.
+> 1. Return **undefined**.
> 1. Otherwise, if _target_ is an Array, then
-> 1. If _target.length is zero, throw a _Package Path Not Exported_ error.
+> 1. If _target.length is zero, return **null**.
> 1. For each item _targetValue_ in _target_, do
-> 1. If _targetValue_ is an Array, continue the loop.
-> 1. Return the result of **PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_,
-> _targetValue_, _subpath_, _env_), continuing the loop on any
-> _Package Path Not Exported_ or _Invalid Package Target_ error.
-> 1. Throw the last fallback resolution error.
-> 1. Otherwise, if _target_ is _null_, throw a _Package Path Not Exported_
-> error.
+> 1. Let _resolved_ be the result of **PACKAGE_TARGET_RESOLVE**(
+> _packageURL_, _targetValue_, _subpath_, _internal_, _env_),
+> continuing the loop on any _Invalid Package Target_ error.
+> 1. If _resolved_ is **undefined**, continue the loop.
+> 1. Return _resolved_.
+> 1. Return or throw the last fallback resolution **null** return or error.
+> 1. Otherwise, if _target_ is _null_, return **null**.
> 1. Otherwise throw an _Invalid Package Target_ error.
+**PACKAGE_INTERNAL_RESOLVE**(_specifier_, _parentURL_)
+
+> 1. Assert: _specifier_ begins with _"#"_.
+> 1. If _specifier_ is exactly equal to _"#"_ or starts with _"#/"_, then
+> 1. Throw an _Invalid Module Specifier_ error.
+> 1. Let _packageURL_ be the result of **READ_PACKAGE_SCOPE**(_parentURL_).
+> 1. If _packageURL_ is not **null**, then
+> 1. Let _pjson_ be the result of **READ_PACKAGE_JSON**(_packageURL_).
+> 1. If _pjson.imports is a non-null Object, then
+> 1. Let _imports_ be _pjson.imports_.
+> 1. If _specifier_ is a key of _imports_, then
+> 1. Let _target_ be the value of _imports\[specifier\]_.
+> 1. Return **PACKAGE_TARGET_RESOLVE**(_packageURL_, _target_,
+> _""_, **true**, _defaultEnv_).
+> 1. Let _directoryKeys_ be the list of keys of _imports_ ending in
+> _"/"_, sorted by length descending.
+> 1. For each key _directory_ in _directoryKeys_, do
+> 1. If _specifier_ starts with _directory_, then
+> 1. Let _target_ be the value of _imports\[directory\]_.
+> 1. Let _subpath_ be the substring of _target_ starting at the
+> index of the length of _directory_.
+> 1. Return **PACKAGE_TARGET_RESOLVE**(_packageURL_, _target_,
+> _subpath_, **true**, _defaultEnv_).
+> 1. Return **null**.
+
**ESM_FORMAT**(_url_)
> 1. Assert: _url_ corresponds to an existing file.
diff --git a/doc/api/modules.md b/doc/api/modules.md
index e8215a2ace67ed..bcac4a6bf88d41 100644
--- a/doc/api/modules.md
+++ b/doc/api/modules.md
@@ -160,7 +160,9 @@ require(X) from module at path Y
a. LOAD_AS_FILE(Y + X)
b. LOAD_AS_DIRECTORY(Y + X)
c. THROW "not found"
-4. LOAD_SELF_REFERENCE(X, dirname(Y))
+4. If X begins with '#'
+ a. LOAD_INTERAL_IMPORT(X, Y)
+4. LOAD_SELF_REFERENCE(X, Y)
5. LOAD_NODE_MODULES(X, dirname(Y))
6. THROW "not found"
@@ -236,6 +238,15 @@ LOAD_PACKAGE_EXPORTS(DIR, X)
12. Otherwise
a. If RESOLVED is a file, load it as its file extension format. STOP
13. Throw "not found"
+
+LOAD_INTERNAL_IMPORT(X, START)
+1. Find the closest package scope to START.
+2. If no scope was found or the `package.json` has no "imports", return.
+3. let RESOLVED =
+ fileURLToPath(PACKAGE_INTERNAL_RESOLVE(X, pathToFileURL(START)), as defined
+ in the ESM resolver.
+4. If RESOLVED is not a valid file, throw "not found"
+5. Load RESOLVED as its file extension format. STOP
```
## Caching
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index 921bcce2878706..d7b37048bb8163 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -21,7 +21,6 @@ const {
NumberIsInteger,
ObjectDefineProperty,
ObjectKeys,
- StringPrototypeSlice,
StringPrototypeStartsWith,
Symbol,
SymbolFor,
@@ -1097,16 +1096,9 @@ 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, 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`;
- }
- return `Package subpath '${subpath}' is not a valid module request for ` +
- `the "exports" resolution of ${pkgPath} imported from ${base}`;
+E('ERR_INVALID_MODULE_SPECIFIER', (request, reason, base = undefined) => {
+ return `Invalid module "${request}" ${reason}${base ?
+ ` imported from ${base}` : ''}`;
}, TypeError);
E('ERR_INVALID_OPT_VALUE', (name, value) =>
`The value "${String(value)}" is invalid for option "${name}"`,
@@ -1120,31 +1112,20 @@ E('ERR_INVALID_PACKAGE_CONFIG', (path, message, hasMessage = true) => {
return `Invalid JSON in ${path} imported from ${message}`;
}, Error);
E('ERR_INVALID_PACKAGE_TARGET',
- (pkgPath, key, subpath, target, base = undefined) => {
- const relError = typeof target === 'string' &&
+ (pkgPath, key, target, isImport = false, base = undefined) => {
+ const relError = typeof target === 'string' && !isImport &&
target.length && !StringPrototypeStartsWith(target, './');
- if (key === null) {
- if (subpath !== '') {
- return `Invalid "exports" target ${JSONStringify(target)} defined ` +
- `for '${subpath}' in the package config ${pkgPath} imported from ` +
- `${base}.${relError ? '; targets must start with "./"' : ''}`;
- }
- return `Invalid "exports" main target ${target} defined in the ` +
- `package config ${pkgPath} imported from ${base}${relError ?
- '; targets must start with "./"' : ''}`;
- } else if (key === '.') {
+ if (key === '.') {
+ assert(isImport === false);
return `Invalid "exports" main target ${JSONStringify(target)} defined ` +
- `in the package config ${pkgPath}${sep}package.json${relError ?
- '; targets must start with "./"' : ''}`;
- } else if (relError) {
- return `Invalid "exports" target ${JSONStringify(target)} defined for '${
- StringPrototypeSlice(key, 0, -subpath.length || key.length)}' in the ` +
- `package config ${pkgPath}${sep}package.json; ` +
- 'targets must start with "./"';
+ `in the package config ${pkgPath}package.json${base ?
+ ` imported from ${base}` : ''}${relError ?
+ '; targets must start with "./"' : ''}`;
}
- return `Invalid "exports" target ${JSONStringify(target)} defined for '${
- StringPrototypeSlice(key, 0, -subpath.length || key.length)}' in the ` +
- `package config ${pkgPath}${sep}package.json`;
+ return `Invalid "${isImport ? 'imports' : 'exports'}" target ${
+ JSONStringify(target)} defined for '${key}' in the package config ${
+ pkgPath}package.json${base ? ` imported from ${base}` : ''}${relError ?
+ '; targets must start with "./"' : ''}`;
}, Error);
E('ERR_INVALID_PERFORMANCE_MARK',
'The "%s" performance mark has not been set', Error);
@@ -1293,15 +1274,16 @@ E('ERR_OUT_OF_RANGE',
msg += ` It must be ${range}. Received ${received}`;
return msg;
}, RangeError);
+E('ERR_PACKAGE_IMPORT_NOT_DEFINED', (specifier, packagePath, base) => {
+ return `Package import specifier "${specifier}" is not defined${packagePath ?
+ ` in package ${packagePath}package.json` : ''} imported from ${base}`;
+}, TypeError);
E('ERR_PACKAGE_PATH_NOT_EXPORTED', (pkgPath, subpath, base = undefined) => {
- if (subpath === '.') {
- return `No "exports" main resolved in ${pkgPath}${sep}package.json`;
- } else if (base === undefined) {
- return `Package subpath '${subpath}' is not defined by "exports" in ${
- pkgPath}${sep}package.json`;
- }
+ if (subpath === '.')
+ return `No "exports" main defined in ${pkgPath}package.json${base ?
+ ` imported from ${base}` : ''}`;
return `Package subpath '${subpath}' is not defined by "exports" in ${
- pkgPath} imported from ${base}`;
+ pkgPath}package.json${base ? ` imported from ${base}` : ''}`;
}, Error);
E('ERR_REQUIRE_ESM',
(filename, parentPath = null, packageJsonPath = null) => {
@@ -1419,7 +1401,7 @@ E('ERR_UNKNOWN_FILE_EXTENSION',
E('ERR_UNKNOWN_MODULE_FORMAT', 'Unknown module format: %s', RangeError);
E('ERR_UNKNOWN_SIGNAL', 'Unknown signal: %s', TypeError);
E('ERR_UNSUPPORTED_DIR_IMPORT', "Directory import '%s' is not supported " +
-'resolving ES modules, imported from %s', Error);
+'resolving ES modules imported from %s', Error);
E('ERR_UNSUPPORTED_ESM_URL_SCHEME', 'Only file and data URLs are supported ' +
'by the default ESM loader', Error);
diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js
index 7a004eef59d29d..f093f316b4717a 100644
--- a/lib/internal/modules/cjs/loader.js
+++ b/lib/internal/modules/cjs/loader.js
@@ -21,6 +21,12 @@
'use strict';
+// Set first due to cycle with ESM loader functions.
+module.exports = {
+ wrapSafe, Module, toRealPath, readPackageScope,
+ get hasLoadedAnyUserCJSModule() { return hasLoadedAnyUserCJSModule; }
+};
+
const {
ArrayIsArray,
Error,
@@ -36,6 +42,7 @@ const {
ReflectSet,
RegExpPrototypeTest,
SafeMap,
+ SafeSet,
String,
StringPrototypeIndexOf,
StringPrototypeMatch,
@@ -89,19 +96,20 @@ const {
const { validateString } = require('internal/validators');
const pendingDeprecation = getOptionValue('--pending-deprecation');
-module.exports = {
- wrapSafe, Module, toRealPath, readPackageScope,
- get hasLoadedAnyUserCJSModule() { return hasLoadedAnyUserCJSModule; }
-};
-
-let asyncESM, ModuleJob, ModuleWrap, kInstantiated;
-
const {
CHAR_FORWARD_SLASH,
CHAR_BACKWARD_SLASH,
CHAR_COLON
} = require('internal/constants');
+const asyncESM = require('internal/process/esm_loader');
+const ModuleJob = require('internal/modules/esm/module_job');
+const { ModuleWrap, kInstantiated } = internalBinding('module_wrap');
+const {
+ encodedSepRegEx,
+ packageInternalResolve
+} = require('internal/modules/esm/resolve');
+
const isWindows = process.platform === 'win32';
const relativeResolveCache = ObjectCreate(null);
@@ -261,6 +269,7 @@ function readPackage(requestPath) {
name: parsed.name,
main: parsed.main,
exports: parsed.exports,
+ imports: parsed.imports,
type: parsed.type
};
packageJsonCache.set(jsonPath, filtered);
@@ -534,21 +543,26 @@ function resolveExportsTarget(baseUrl, target, subpath, mappingKey) {
if (subpath.length > 0 && target[target.length - 1] !== '/')
resolvedTarget = undefined;
if (resolvedTarget === undefined)
- throw new ERR_INVALID_PACKAGE_TARGET(StringPrototypeSlice(baseUrl.pathname
- , 0, -1), mappingKey, subpath, target);
+ throw new ERR_INVALID_PACKAGE_TARGET(baseUrl.pathname, mappingKey,
+ target);
const resolved = new URL(subpath, resolvedTarget);
const resolvedPath = resolved.pathname;
if (StringPrototypeStartsWith(resolvedPath, resolvedTargetPath) &&
StringPrototypeIndexOf(resolvedPath, '/node_modules/',
pkgPathPath.length - 1) === -1) {
+ if (StringPrototypeMatch(resolvedPath, encodedSepRegEx))
+ throw new ERR_INVALID_MODULE_SPECIFIER(
+ resolvedPath, 'must not include encoded "/" or "\\" characters',
+ fileURLToPath(baseUrl));
return fileURLToPath(resolved);
}
- throw new ERR_INVALID_MODULE_SPECIFIER(StringPrototypeSlice(baseUrl.pathname
- , 0, -1), mappingKey);
+ const reason = 'request is not a valid subpath for the "exports" ' +
+ `resolution of ${baseUrl.pathname}package.json`;
+ throw new ERR_INVALID_MODULE_SPECIFIER(mappingKey + subpath, reason);
} else if (ArrayIsArray(target)) {
if (target.length === 0)
throw new ERR_PACKAGE_PATH_NOT_EXPORTED(
- StringPrototypeSlice(baseUrl.pathname, 0, -1), mappingKey + subpath);
+ baseUrl.pathname, mappingKey + subpath);
let lastException;
for (const targetValue of target) {
try {
@@ -590,13 +604,12 @@ function resolveExportsTarget(baseUrl, target, subpath, mappingKey) {
}
}
throw new ERR_PACKAGE_PATH_NOT_EXPORTED(
- StringPrototypeSlice(baseUrl.pathname, 0, -1), mappingKey + subpath);
+ baseUrl.pathname, mappingKey + subpath);
} else if (target === null) {
throw new ERR_PACKAGE_PATH_NOT_EXPORTED(
- StringPrototypeSlice(baseUrl.pathname, 0, -1), mappingKey + subpath);
+ baseUrl.pathname, mappingKey + subpath);
}
- throw new ERR_INVALID_PACKAGE_TARGET(
- StringPrototypeSlice(baseUrl.pathname, 0, -1), mappingKey, subpath, target);
+ throw new ERR_INVALID_PACKAGE_TARGET(baseUrl.pathname, mappingKey, target);
}
const trailingSlashRegex = /(?:^|\/)\.?\.$/;
@@ -894,6 +907,8 @@ Module._load = function(request, parent, isMain) {
return module.exports;
};
+// TODO: Use this set when resolving pkg#exports conditions.
+const cjsConditions = new SafeSet(['require', 'node']);
Module._resolveFilename = function(request, parent, isMain, options) {
if (NativeModule.canBeRequiredByUsers(request)) {
return request;
@@ -936,6 +951,27 @@ Module._resolveFilename = function(request, parent, isMain, options) {
}
if (parent && parent.filename) {
+ if (request[0] === '#') {
+ const pkg = readPackageScope(parent.filename) || {};
+ if (pkg.data && pkg.data.imports !== null &&
+ pkg.data.imports !== undefined) {
+ try {
+ const resolved = packageInternalResolve(
+ request, pathToFileURL(parent.filename), cjsConditions);
+ return fileURLToPath(resolved);
+ } catch (err) {
+ if (err.code === 'ERR_MODULE_NOT_FOUND') {
+ // eslint-disable-next-line no-restricted-syntax
+ const err = new Error(`Cannot find module '${request}'`);
+ err.code = 'MODULE_NOT_FOUND';
+ err.path = path.resolve(pkg.path, 'package.json');
+ // TODO(BridgeAR): Add the requireStack as well.
+ throw err;
+ }
+ throw err;
+ }
+ }
+ }
const filename = trySelf(parent.filename, request);
if (filename) {
const cacheKey = request + '\x00' +
@@ -1284,8 +1320,3 @@ Module.syncBuiltinESMExports = function syncBuiltinESMExports() {
// Backwards compatibility
Module.Module = Module;
-
-// We have to load the esm things after module.exports!
-asyncESM = require('internal/process/esm_loader');
-ModuleJob = require('internal/modules/esm/module_job');
-({ ModuleWrap, kInstantiated } = internalBinding('module_wrap'));
diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js
index 987a139c6aae57..16ca78880c7a15 100644
--- a/lib/internal/modules/esm/resolve.js
+++ b/lib/internal/modules/esm/resolve.js
@@ -10,6 +10,7 @@ const {
ObjectGetOwnPropertyNames,
ObjectPrototypeHasOwnProperty,
RegExp,
+ RegExpPrototypeTest,
SafeMap,
SafeSet,
String,
@@ -22,7 +23,6 @@ const {
StringPrototypeStartsWith,
StringPrototypeSubstr,
} = primordials;
-const assert = require('internal/assert');
const internalFS = require('internal/fs/utils');
const { NativeModule } = require('internal/bootstrap/loaders');
const {
@@ -44,6 +44,7 @@ const {
ERR_INVALID_PACKAGE_CONFIG,
ERR_INVALID_PACKAGE_TARGET,
ERR_MODULE_NOT_FOUND,
+ ERR_PACKAGE_IMPORT_NOT_DEFINED,
ERR_PACKAGE_PATH_NOT_EXPORTED,
ERR_UNSUPPORTED_DIR_IMPORT,
ERR_UNSUPPORTED_ESM_URL_SCHEME,
@@ -91,11 +92,13 @@ function getPackageConfig(path) {
const source = packageJsonReader.read(path).string;
if (source === undefined) {
const packageConfig = {
+ pjsonPath: path,
exists: false,
main: undefined,
name: undefined,
type: 'none',
- exports: undefined
+ exports: undefined,
+ imports: undefined,
};
packageJSONCache.set(path, packageConfig);
return packageConfig;
@@ -109,19 +112,22 @@ function getPackageConfig(path) {
throw new ERR_INVALID_PACKAGE_CONFIG(errorPath, error.message, true);
}
- let { main, name, type } = packageJSON;
+ let { imports, main, name, type } = packageJSON;
const { exports } = packageJSON;
+ if (typeof imports !== 'object' || imports === null) imports = undefined;
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 = {
+ pjsonPath: path,
exists: true,
main,
name,
type,
- exports
+ exports,
+ imports,
};
packageJSONCache.set(path, packageConfig);
return packageConfig;
@@ -143,14 +149,17 @@ function getPackageScopeConfig(resolved, base) {
// (can't just check "/package.json" for Windows support).
if (packageJSONUrl.pathname === lastPackageJSONUrl.pathname) break;
}
+ const packageJSONPath = fileURLToPath(packageJSONUrl);
const packageConfig = {
+ pjsonPath: packageJSONPath,
exists: false,
main: undefined,
name: undefined,
type: 'none',
- exports: undefined
+ exports: undefined,
+ imports: undefined,
};
- packageJSONCache.set(fileURLToPath(packageJSONUrl), packageConfig);
+ packageJSONCache.set(packageJSONPath, packageConfig);
return packageConfig;
}
@@ -233,6 +242,7 @@ function resolveIndex(search) {
return resolveExtensions(new URL('index', search));
}
+const encodedSepRegEx = /%2F|%2C/i;
function finalizeResolution(resolved, base) {
if (getOptionValue('--experimental-specifier-resolution') === 'node') {
let file = resolveExtensionsWithTryExactName(resolved);
@@ -247,6 +257,11 @@ function finalizeResolution(resolved, base) {
resolved.pathname, fileURLToPath(base), 'module');
}
+ if (RegExpPrototypeTest(encodedSepRegEx, resolved.pathname))
+ throw new ERR_INVALID_MODULE_SPECIFIER(
+ resolved.pathname, 'must not include encoded "/" or "\\" characters',
+ fileURLToPath(base));
+
const path = fileURLToPath(resolved);
const stats = tryStatSync(path);
@@ -263,34 +278,52 @@ function finalizeResolution(resolved, base) {
return resolved;
}
+function throwImportNotDefined(specifier, packageJSONUrl, base) {
+ throw new ERR_PACKAGE_IMPORT_NOT_DEFINED(
+ specifier, packageJSONUrl && fileURLToPath(new URL('.', packageJSONUrl)),
+ fileURLToPath(base));
+}
+
function throwExportsNotFound(subpath, packageJSONUrl, base) {
throw new ERR_PACKAGE_PATH_NOT_EXPORTED(
- fileURLToPath(packageJSONUrl), subpath, fileURLToPath(base));
+ fileURLToPath(new URL('.', packageJSONUrl)), subpath, fileURLToPath(base));
}
-function throwSubpathInvalid(subpath, packageJSONUrl, base) {
- throw new ERR_INVALID_MODULE_SPECIFIER(
- fileURLToPath(packageJSONUrl), subpath, fileURLToPath(base));
+function throwInvalidSubpath(subpath, packageJSONUrl, internal, base) {
+ const reason = `request is not a valid subpath for the "${internal ?
+ 'imports' : 'exports'}" resolution of ${fileURLToPath(packageJSONUrl)}${
+ base ? ` imported from ${base}` : ''}`;
+ throw new ERR_INVALID_MODULE_SPECIFIER(subpath, reason, fileURLToPath(base));
}
-function throwExportsInvalid(
- subpath, target, packageJSONUrl, base) {
+function throwInvalidPackageTarget(
+ subpath, target, packageJSONUrl, internal, 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));
+ fileURLToPath(new URL('.', packageJSONUrl)), subpath, target,
+ internal, fileURLToPath(base));
}
-function resolveExportsTargetString(
- target, subpath, match, packageJSONUrl, base) {
- if (target[0] !== '.' || target[1] !== '/' ||
- (subpath !== '' && target[target.length - 1] !== '/')) {
- throwExportsInvalid(match, target, packageJSONUrl, base);
+function resolvePackageTargetString(
+ target, subpath, match, packageJSONUrl, base, internal, conditions) {
+ if (subpath !== '' && target[target.length - 1] !== '/')
+ throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
+
+ if (!target.startsWith('./')) {
+ if (internal && !target.startsWith('../') && !target.startsWith('/')) {
+ let isURL = false;
+ try {
+ new URL(target);
+ isURL = true;
+ } catch {}
+ if (!isURL)
+ return packageResolve(target + subpath, base, conditions);
+ }
+ throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
}
const resolved = new URL(target, packageJSONUrl);
@@ -299,18 +332,16 @@ function resolveExportsTargetString(
if (!StringPrototypeStartsWith(resolvedPath, packagePath) ||
StringPrototypeIncludes(
- resolvedPath, '/node_modules/', packagePath.length - 1)) {
- throwExportsInvalid(match, target, packageJSONUrl, base);
- }
+ resolvedPath, '/node_modules/', packagePath.length - 1))
+ throwInvalidPackageTarget(match, target, packageJSONUrl, internal, 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);
- }
+ '/node_modules/', packagePath.length - 1))
+ throwInvalidSubpath(match + subpath, packageJSONUrl, internal, base);
return subpathResolved;
}
@@ -324,36 +355,43 @@ function isArrayIndex(key) {
return keyNum >= 0 && keyNum < 0xFFFF_FFFF;
}
-function resolveExportsTarget(
- packageJSONUrl, target, subpath, packageSubpath, base, conditions) {
+function resolvePackageTarget(
+ packageJSONUrl, target, subpath, packageSubpath, base, internal, conditions) {
if (typeof target === 'string') {
- const resolved = resolveExportsTargetString(
- target, subpath, packageSubpath, packageJSONUrl, base);
+ const resolved = resolvePackageTargetString(
+ target, subpath, packageSubpath, packageJSONUrl, base, internal,
+ conditions);
+ if (resolved === null)
+ return null;
return finalizeResolution(resolved, base);
} else if (ArrayIsArray(target)) {
if (target.length === 0)
- throwExportsNotFound(packageSubpath, packageJSONUrl, base);
+ return null;
let lastException;
for (let i = 0; i < target.length; i++) {
const targetItem = target[i];
let resolved;
try {
- resolved = resolveExportsTarget(
- packageJSONUrl, targetItem, subpath, packageSubpath, base,
+ resolved = resolvePackageTarget(
+ packageJSONUrl, targetItem, subpath, packageSubpath, base, internal,
conditions);
} catch (e) {
lastException = e;
- if (e.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED' ||
- e.code === 'ERR_INVALID_PACKAGE_TARGET') {
+ if (e.code === 'ERR_INVALID_PACKAGE_TARGET')
continue;
- }
throw e;
}
-
+ if (resolved === undefined)
+ continue;
+ if (resolved === null) {
+ lastException = null;
+ continue;
+ }
return finalizeResolution(resolved, base);
}
- assert(lastException !== undefined);
+ if (lastException === undefined || lastException === null)
+ return lastException;
throw lastException;
} else if (typeof target === 'object' && target !== null) {
const keys = ObjectGetOwnPropertyNames(target);
@@ -369,21 +407,20 @@ function resolveExportsTarget(
const key = keys[i];
if (key === 'default' || conditions.has(key)) {
const conditionalTarget = target[key];
- try {
- return resolveExportsTarget(
- packageJSONUrl, conditionalTarget, subpath, packageSubpath, base,
- conditions);
- } catch (e) {
- if (e.code === 'ERR_PACKAGE_PATH_NOT_EXPORTED') continue;
- throw e;
- }
+ const resolved = resolvePackageTarget(
+ packageJSONUrl, conditionalTarget, subpath, packageSubpath, base,
+ internal, conditions);
+ if (resolved === undefined)
+ continue;
+ return resolved;
}
}
- throwExportsNotFound(packageSubpath, packageJSONUrl, base);
+ return undefined;
} else if (target === null) {
- throwExportsNotFound(packageSubpath, packageJSONUrl, base);
+ return null;
}
- throwExportsInvalid(packageSubpath, target, packageJSONUrl, base);
+ throwInvalidPackageTarget(packageSubpath, target, packageJSONUrl, internal,
+ base);
}
function isConditionalExportsMainSugar(exports, packageJSONUrl, base) {
@@ -409,19 +446,25 @@ function isConditionalExportsMainSugar(exports, packageJSONUrl, base) {
return isConditionalSugar;
}
-
function packageMainResolve(packageJSONUrl, packageConfig, base, conditions) {
if (packageConfig.exists) {
const exports = packageConfig.exports;
if (exports !== undefined) {
if (isConditionalExportsMainSugar(exports, packageJSONUrl, base)) {
- return resolveExportsTarget(packageJSONUrl, exports, '', '', base,
- conditions);
+ const resolved = resolvePackageTarget(packageJSONUrl, exports, '', '',
+ base, false, conditions);
+ if (resolved === null || resolved === undefined)
+ throwExportsNotFound('.', packageJSONUrl, base);
+ return resolved;
} else if (typeof exports === 'object' && exports !== null) {
const target = exports['.'];
- if (target !== undefined)
- return resolveExportsTarget(packageJSONUrl, target, '', '', base,
- conditions);
+ if (target !== undefined) {
+ const resolved = resolvePackageTarget(packageJSONUrl, target, '', '',
+ base, false, conditions);
+ if (resolved === null || resolved === undefined)
+ throwExportsNotFound('.', packageJSONUrl, base);
+ return resolved;
+ }
}
throw new ERR_PACKAGE_PATH_NOT_EXPORTED(packageJSONUrl, '.');
@@ -457,11 +500,12 @@ function packageExportsResolve(
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
}
-
if (ObjectPrototypeHasOwnProperty(exports, packageSubpath)) {
const target = exports[packageSubpath];
- const resolved = resolveExportsTarget(
- packageJSONUrl, target, '', packageSubpath, base, conditions);
+ const resolved = resolvePackageTarget(
+ packageJSONUrl, target, '', packageSubpath, base, false, conditions);
+ if (resolved === null || resolved === undefined)
+ throwExportsNotFound(packageSubpath, packageJSONUrl, base);
return finalizeResolution(resolved, base);
}
@@ -479,14 +523,59 @@ function packageExportsResolve(
if (bestMatch) {
const target = exports[bestMatch];
const subpath = StringPrototypeSubstr(packageSubpath, bestMatch.length);
- const resolved = resolveExportsTarget(
- packageJSONUrl, target, subpath, packageSubpath, base, conditions);
+ const resolved = resolvePackageTarget(
+ packageJSONUrl, target, subpath, bestMatch, base, false, conditions);
+ if (resolved === null || resolved === undefined)
+ throwExportsNotFound(packageSubpath, packageJSONUrl, base);
return finalizeResolution(resolved, base);
}
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
}
+function packageInternalResolve(name, base, conditions) {
+ if (name === '#' || name.startsWith('#/')) {
+ const reason = 'is not a valid internal imports specifier name';
+ throw new ERR_INVALID_MODULE_SPECIFIER(name, reason, fileURLToPath(base));
+ }
+ let packageJSONUrl;
+ const packageConfig = getPackageScopeConfig(base, base);
+ if (packageConfig.exists) {
+ packageJSONUrl = pathToFileURL(packageConfig.pjsonPath);
+ const imports = packageConfig.imports;
+ if (imports) {
+ if (ObjectPrototypeHasOwnProperty(imports, name)) {
+ const resolved = resolvePackageTarget(
+ packageJSONUrl, imports[name], '', name, base, true, conditions);
+ if (resolved !== null)
+ return finalizeResolution(resolved, base);
+ } else {
+ let bestMatch = '';
+ const keys = ObjectGetOwnPropertyNames(imports);
+ for (let i = 0; i < keys.length; i++) {
+ const key = keys[i];
+ if (key[key.length - 1] !== '/') continue;
+ if (StringPrototypeStartsWith(name, key) &&
+ key.length > bestMatch.length) {
+ bestMatch = key;
+ }
+ }
+
+ if (bestMatch) {
+ const target = imports[bestMatch];
+ const subpath = StringPrototypeSubstr(name, bestMatch.length);
+ const resolved = resolvePackageTarget(
+ packageJSONUrl, target, subpath, bestMatch, base, true,
+ conditions);
+ if (resolved !== null)
+ return finalizeResolution(resolved, base);
+ }
+ }
+ }
+ }
+ throwImportNotDefined(name, packageJSONUrl, base);
+}
+
function getPackageType(url) {
const packageConfig = getPackageScopeConfig(url, url);
return packageConfig.type;
@@ -526,7 +615,7 @@ function packageResolve(specifier, base, conditions) {
if (!validPackageName) {
throw new ERR_INVALID_MODULE_SPECIFIER(
- specifier, undefined, fileURLToPath(base));
+ specifier, 'is not a valid package name', fileURLToPath(base));
}
const packageSubpath = separatorIndex === -1 ?
@@ -535,17 +624,8 @@ function packageResolve(specifier, base, conditions) {
// 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 &&
+ const packageJSONUrl = pathToFileURL(packageConfig.pjsonPath);
+ if (packageConfig.name === packageName &&
packageConfig.exports !== undefined) {
if (packageSubpath === './') {
return new URL('./', packageJSONUrl);
@@ -626,6 +706,8 @@ function moduleResolve(specifier, base, conditions) {
let resolved;
if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
resolved = new URL(specifier, base);
+ } else if (specifier[0] === '#') {
+ resolved = packageInternalResolve(specifier, base, conditions);
} else {
try {
resolved = new URL(specifier);
@@ -764,5 +846,7 @@ function defaultResolve(specifier, context = {}, defaultResolveUnused) {
module.exports = {
DEFAULT_CONDITIONS,
defaultResolve,
- getPackageType
+ encodedSepRegEx,
+ getPackageType,
+ packageInternalResolve
};
diff --git a/src/node_file.cc b/src/node_file.cc
index 83b3f2f6d00a57..968cf6f27c4f74 100644
--- a/src/node_file.cc
+++ b/src/node_file.cc
@@ -946,6 +946,7 @@ static void InternalModuleReadJSON(const FunctionCallbackInfo& args) {
if (0 == memcmp(s, "type", 4)) break;
} else if (n == 7) {
if (0 == memcmp(s, "exports", 7)) break;
+ if (0 == memcmp(s, "imports", 7)) break;
}
}
diff --git a/test/es-module/test-esm-exports.mjs b/test/es-module/test-esm-exports.mjs
index a0348d4a1ab0b2..02caceee64deaa 100644
--- a/test/es-module/test-esm-exports.mjs
+++ b/test/es-module/test-esm-exports.mjs
@@ -118,7 +118,8 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js';
for (const [specifier, subpath] of invalidSpecifiers) {
loadFixture(specifier).catch(mustCall((err) => {
strictEqual(err.code, 'ERR_INVALID_MODULE_SPECIFIER');
- assertStartsWith(err.message, 'Package subpath ');
+ assertStartsWith(err.message, 'Invalid module ');
+ assertIncludes(err.message, 'is not a valid subpath');
assertIncludes(err.message, subpath);
}));
}
@@ -161,7 +162,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, 'ERR_INVALID_FILE_URL_PATH');
+ strictEqual(err.code, 'ERR_INVALID_MODULE_SPECIFIER');
}));
// Package export with numeric index properties must throw a validation error
diff --git a/test/es-module/test-esm-imports.mjs b/test/es-module/test-esm-imports.mjs
new file mode 100644
index 00000000000000..694496a2ff2c93
--- /dev/null
+++ b/test/es-module/test-esm-imports.mjs
@@ -0,0 +1,117 @@
+import { mustCall } from '../common/index.mjs';
+import { ok, deepStrictEqual, strictEqual } from 'assert';
+
+import importer from '../fixtures/es-modules/pkgimports/importer.js';
+import { requireFixture } from '../fixtures/pkgexports.mjs';
+
+const { requireImport, importImport } = importer;
+
+[requireImport, importImport].forEach((loadFixture) => {
+ const isRequire = loadFixture === requireImport;
+
+ const internalImports = new Map([
+ // Base case
+ ['#test', { default: 'test' }],
+ // import / require conditions
+ ['#branch', { default: isRequire ? 'requirebranch' : 'importbranch' }],
+ // Subpath imports
+ ['#subpath/x.js', { default: 'xsubpath' }],
+ // External imports
+ ['#external', { default: 'asdf' }],
+ // External subpath imports
+ ['#external/subpath/asdf.js', { default: 'asdf' }],
+ ]);
+
+ for (const [validSpecifier, expected] of internalImports) {
+ if (validSpecifier === null) continue;
+
+ loadFixture(validSpecifier)
+ .then(mustCall((actual) => {
+ deepStrictEqual({ ...actual }, expected);
+ }));
+ }
+
+ const invalidImportTargets = new Set([
+ // External subpath import without trailing slash
+ ['#external/invalidsubpath/x', '#external/invalidsubpath/'],
+ // Target steps below the package base
+ ['#belowbase', '#belowbase'],
+ // Target is a URL
+ ['#url', '#url'],
+ ]);
+
+ for (const [specifier, subpath] of invalidImportTargets) {
+ loadFixture(specifier).catch(mustCall((err) => {
+ strictEqual(err.code, 'ERR_INVALID_PACKAGE_TARGET');
+ assertStartsWith(err.message, 'Invalid "imports"');
+ assertIncludes(err.message, subpath);
+ assertNotIncludes(err.message, 'targets must start with');
+ }));
+ }
+
+ const invalidImportSpecifiers = new Map([
+ // Backtracking below the package base
+ ['#subpath/sub/../../../belowbase', 'request is not a valid subpath'],
+ // Percent-encoded slash errors
+ ['#external/subpath/x%2Fy', 'must not include encoded "/"'],
+ // Target must have a name
+ ['#', '#'],
+ // Initial slash target must have a leading name
+ ['#/initialslash', '#/initialslash'],
+ // Percent-encoded target paths
+ ['#percent', 'must not include encoded "/"'],
+ ]);
+
+ for (const [specifier, expected] of invalidImportSpecifiers) {
+ loadFixture(specifier).catch(mustCall((err) => {
+ strictEqual(err.code, 'ERR_INVALID_MODULE_SPECIFIER');
+ assertStartsWith(err.message, 'Invalid module');
+ assertIncludes(err.message, expected);
+ }));
+ }
+
+ const undefinedImports = new Set([
+ // Missing import
+ '#missing',
+ // Explicit null import
+ '#null',
+ // No condition match import
+ '#nullcondition',
+ // Null subpath shadowing
+ '#subpath/nullshadow/x',
+ ]);
+
+ for (const specifier of undefinedImports) {
+ loadFixture(specifier).catch(mustCall((err) => {
+ strictEqual(err.code, 'ERR_PACKAGE_IMPORT_NOT_DEFINED');
+ assertStartsWith(err.message, 'Package import ');
+ assertIncludes(err.message, specifier);
+ }));
+ }
+
+ // Handle not found for the defined imports target not existing
+ loadFixture('#notfound').catch(mustCall((err) => {
+ strictEqual(err.code,
+ isRequire ? 'MODULE_NOT_FOUND' : 'ERR_MODULE_NOT_FOUND');
+ }));
+});
+
+// CJS resolver must still support #package packages in node_modules
+requireFixture('#cjs').then(mustCall((actual) => {
+ strictEqual(actual.default, 'cjs backcompat');
+}));
+
+function assertStartsWith(actual, expected) {
+ const start = actual.toString().substr(0, expected.length);
+ strictEqual(start, expected);
+}
+
+function assertIncludes(actual, expected) {
+ ok(actual.toString().indexOf(expected) !== -1,
+ `${JSON.stringify(actual)} includes ${JSON.stringify(expected)}`);
+}
+
+function assertNotIncludes(actual, expected) {
+ ok(actual.toString().indexOf(expected) === -1,
+ `${JSON.stringify(actual)} doesn't include ${JSON.stringify(expected)}`);
+}
diff --git a/test/fixtures/es-modules/pkgimports/importbranch.js b/test/fixtures/es-modules/pkgimports/importbranch.js
new file mode 100644
index 00000000000000..ebae53309112a8
--- /dev/null
+++ b/test/fixtures/es-modules/pkgimports/importbranch.js
@@ -0,0 +1,2 @@
+module.exports = 'importbranch';
+
diff --git a/test/fixtures/es-modules/pkgimports/importer.js b/test/fixtures/es-modules/pkgimports/importer.js
new file mode 100644
index 00000000000000..30fe06bd613492
--- /dev/null
+++ b/test/fixtures/es-modules/pkgimports/importer.js
@@ -0,0 +1,4 @@
+module.exports = {
+ importImport: x => import(x),
+ requireImport: x => Promise.resolve(x).then(x => ({ default: require(x) }))
+};
diff --git a/test/fixtures/es-modules/pkgimports/package.json b/test/fixtures/es-modules/pkgimports/package.json
new file mode 100644
index 00000000000000..7cd179631fa618
--- /dev/null
+++ b/test/fixtures/es-modules/pkgimports/package.json
@@ -0,0 +1,30 @@
+{
+ "imports": {
+ "#test": "./test.js",
+ "#branch": {
+ "import": "./importbranch.js",
+ "require": "./requirebranch.js"
+ },
+ "#subpath/": "./sub/",
+ "#external": "pkgexports/valid-cjs",
+ "#external/subpath/": "pkgexports/sub/",
+ "#external/invalidsubpath/": "pkgexports/sub",
+ "#belowbase": "../belowbase",
+ "#url": "some:url",
+ "#null": null,
+ "#nullcondition": {
+ "import": {
+ "default": null
+ },
+ "require": {
+ "default": null
+ },
+ "default": "./test.js"
+ },
+ "#subpath/nullshadow/": [null],
+ "#": "./test.js",
+ "#/initialslash": "./test.js",
+ "#notfound": "./notfound.js",
+ "#percent": "./..%2F/x.js"
+ }
+}
diff --git a/test/fixtures/es-modules/pkgimports/requirebranch.js b/test/fixtures/es-modules/pkgimports/requirebranch.js
new file mode 100644
index 00000000000000..fd58e34be95332
--- /dev/null
+++ b/test/fixtures/es-modules/pkgimports/requirebranch.js
@@ -0,0 +1,2 @@
+module.exports = 'requirebranch';
+
diff --git a/test/fixtures/es-modules/pkgimports/sub/x.js b/test/fixtures/es-modules/pkgimports/sub/x.js
new file mode 100644
index 00000000000000..48cca8c5646659
--- /dev/null
+++ b/test/fixtures/es-modules/pkgimports/sub/x.js
@@ -0,0 +1,2 @@
+module.exports = 'xsubpath';
+
diff --git a/test/fixtures/es-modules/pkgimports/test.js b/test/fixtures/es-modules/pkgimports/test.js
new file mode 100644
index 00000000000000..37a4648424da6a
--- /dev/null
+++ b/test/fixtures/es-modules/pkgimports/test.js
@@ -0,0 +1 @@
+module.exports = 'test';
diff --git a/test/fixtures/node_modules/#cjs/index.js b/test/fixtures/node_modules/#cjs/index.js
new file mode 100644
index 00000000000000..c60af759886ce6
--- /dev/null
+++ b/test/fixtures/node_modules/#cjs/index.js
@@ -0,0 +1,2 @@
+module.exports = 'cjs backcompat';
+