diff --git a/doc/api/cli.md b/doc/api/cli.md
index f99066477a013b..574e40232fefb5 100644
--- a/doc/api/cli.md
+++ b/doc/api/cli.md
@@ -81,6 +81,21 @@ $ node --completion-bash > node_bash_completion
$ source node_bash_completion
```
+### `--conditions=condition`
+
+
+> Stability: 1 - Experimental
+
+Enable experimental support for custom conditional exports resolution
+conditions.
+
+Any number of custom string condition names are permitted.
+
+The default Node.js conditions of `"node"`, `"default"`, `"import"`, and
+`"require"` will always apply as defined.
+
### `--cpu-prof`
+* `--conditions`
* `--diagnostic-dir`
* `--disable-proto`
* `--enable-fips`
diff --git a/doc/api/errors.md b/doc/api/errors.md
index 64bd22f337f2c6..e081361a633cc7 100644
--- a/doc/api/errors.md
+++ b/doc/api/errors.md
@@ -1671,6 +1671,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`
@@ -2598,3 +2604,4 @@ such as `process.stdout.on('data')`.
[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 dc543091d75010..4b384dece42eb9 100644
--- a/doc/api/esm.md
+++ b/doc/api/esm.md
@@ -298,14 +298,14 @@ package. It is not a strong encapsulation since a direct require of any
absolute subpath of the package such as
`require('/path/to/node_modules/pkg/subpath.js')` will still load `subpath.js`.
-#### Subpath exports
+### Subpath exports
-When using the `"exports"` field, custom subpaths can be defined along
-with the main entry point by treating the main entry point as the
-`"."` subpath:
+> Stability: 1 - Experimental
-
-```js
+When using the `"exports"` field, custom subpaths can be defined along with the
+main entry point by treating the main entry point as the `"."` subpath:
+
+```json
{
"main": "./main.js",
"exports": {
@@ -315,8 +315,7 @@ with the main entry point by treating the main entry point as the
}
```
-Now only the defined subpath in `"exports"` can be imported by a
-consumer:
+Now only the defined subpath in `"exports"` can be imported by a consumer:
```js
import submodule from 'es-module-package/submodule';
@@ -330,30 +329,46 @@ import submodule from 'es-module-package/private-module.js';
// Throws ERR_PACKAGE_PATH_NOT_EXPORTED
```
-Entire folders can also be mapped with package exports:
+### Subpath export patterns
-
-```js
+> Stability: 1 - Experimental
+
+Explicitly listing each exports subpath entry is recommended for packages with
+a small number of exports. But for packages that have very large numbers of
+subpaths this can start to cause package.json bloat and maintenance issues.
+
+For these use cases, subpath export patterns can be used instead:
+
+```json
// ./node_modules/es-module-package/package.json
{
"exports": {
- "./features/": "./src/features/"
+ "./features/*": "./src/features/*.js"
}
}
```
-With the above, all modules within the `./src/features/` folder
-are exposed deeply to `import` and `require`:
+The left hand matching pattern must always end in `*`. All instances of `*` on
+the right hand side will then be replaced with this value, including if it
+contains any `/` separators.
```js
-import feature from 'es-module-package/features/x.js';
+import featureX from 'es-module-package/features/x';
// Loads ./node_modules/es-module-package/src/features/x.js
+
+import featureY from 'es-module-package/features/y/y';
+// Loads ./node_modules/es-module-package/src/features/y/y.js
```
-When using folder mappings, ensure that you do want to expose every
-module inside the subfolder. Any modules which are not public
-should be moved to another folder to retain the encapsulation
-benefits of exports.
+This is a direct static replacement without any special handling for file
+extensions. In the previous example, `pkg/features/x.json` would be resolved to
+`./src/features/x.json.js` in the mapping.
+
+The property of exports being statically enumerable is maintained with exports
+patterns since the individual exports for a package can be determined by
+treating the right hand side target pattern as a `**` glob against the list of
+files within the package. Because `node_modules` paths are forbidden in exports
+targets, this expansion is dependent on only the files of the package itself.
#### Package exports fallbacks
@@ -501,6 +516,21 @@ a nested conditional does not have any mapping it will continue checking
the remaining conditions of the parent condition. In this way nested
conditions behave analogously to nested JavaScript `if` statements.
+#### Resolving user conditions
+
+When running Node.js, custom user conditions can be added with the
+`--conditions` or `-u` flag:
+
+```bash
+node --conditions=development main.js
+```
+
+which would then resolve the `"development"` condition in package imports and
+exports, while resolving the existing `"node"`, `"default"`, `"import"`, and
+`"require"` conditions as appropriate.
+
+Any number of custom conditions can be set with repeat flags.
+
#### Self-referencing a package using its name
Within a package, the values defined in the package’s
@@ -546,6 +576,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
@@ -1569,7 +1636,7 @@ future updates.
In the following algorithms, all subroutine errors are propagated as errors
of these top-level routines unless stated otherwise.
-_defaultEnv_ is the conditional environment name priority array,
+_defaultConditions_ is the conditional environment name array,
`["node", "import"]`.
The resolver can throw the following errors:
@@ -1577,10 +1644,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.
@@ -1588,37 +1656,41 @@ The resolver can throw the following errors:
**ESM_RESOLVE**(_specifier_, _parentURL_)
-> 1. Let _resolvedURL_ be **undefined**.
+> 1. Let _resolved_ be **undefined**.
> 1. If _specifier_ is a valid URL, then
-> 1. Set _resolvedURL_ to the result of parsing and reserializing
+> 1. Set _resolved_ 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. Set _resolvedURL_ to the URL resolution of _specifier_ relative to
+> 1. Otherwise, if _specifier_ starts with _"/"_, _"./"_ or _"../"_, then
+> 1. Set _resolved_ to the URL resolution of _specifier_ relative to
> _parentURL_.
+> 1. Otherwise, if _specifier_ starts with _"#"_, then
+> 1. Set _resolved_ to the destructured value of the result of
+> **PACKAGE_IMPORTS_RESOLVE**(_specifier_, _parentURL_,
+> _defaultConditions_).
> 1. Otherwise,
> 1. Note: _specifier_ is now a bare specifier.
-> 1. Set _resolvedURL_ the result of
+> 1. Set _resolved_ the result of
> **PACKAGE_RESOLVE**(_specifier_, _parentURL_).
-> 1. If _resolvedURL_ contains any percent encodings of _"/"_ or _"\\"_ (_"%2f"_
+> 1. If _resolved_ contains any percent encodings of _"/"_ or _"\\"_ (_"%2f"_
> and _"%5C"_ respectively), then
> 1. Throw an _Invalid Module Specifier_ error.
-> 1. If the file at _resolvedURL_ is a directory, then
+> 1. If the file at _resolved_ is a directory, then
> 1. Throw an _Unsupported Directory Import_ error.
-> 1. If the file at _resolvedURL_ does not exist, then
+> 1. If the file at _resolved_ does not exist, then
> 1. Throw a _Module Not Found_ error.
-> 1. Set _resolvedURL_ to the real path of _resolvedURL_.
-> 1. Let _format_ be the result of **ESM_FORMAT**(_resolvedURL_).
-> 1. Load _resolvedURL_ as module format, _format_.
-> 1. Return _resolvedURL_.
+> 1. Set _resolved_ to the real path of _resolved_.
+> 1. Let _format_ be the result of **ESM_FORMAT**(_resolved_).
+> 1. Load _resolved_ as module format, _format_.
+> 1. Return _resolved_.
**PACKAGE_RESOLVE**(_packageSpecifier_, _parentURL_)
-> 1. Let _packageName_ be *undefined*.
-> 1. Let _packageSubpath_ be *undefined*.
+> 1. Let _packageName_ be **undefined**.
> 1. If _packageSpecifier_ is an empty string, then
> 1. Throw an _Invalid Module Specifier_ error.
+> 1. If _packageSpecifier_ does not start with _"@"_, then
+> 1. Set _packageName_ to the substring of _packageSpecifier_ until the first
+> _"/"_ separator or the end of the string.
> 1. Otherwise,
> 1. If _packageSpecifier_ does not contain a _"/"_ separator, then
> 1. Throw an _Invalid Module Specifier_ error.
@@ -1626,18 +1698,12 @@ The resolver can throw the following errors:
> until the second _"/"_ separator or the end of the string.
> 1. If _packageName_ starts with _"."_ or contains _"\\"_ or _"%"_, then
> 1. Throw an _Invalid Module Specifier_ error.
-> 1. Let _packageSubpath_ be _undefined_.
-> 1. If the length of _packageSpecifier_ is greater than the length of
-> _packageName_, then
-> 1. Set _packageSubpath_ to _"."_ concatenated with the substring of
+> 1. Let _packageSubpath_ be _"."_ concatenated with the substring of
> _packageSpecifier_ from the position at the length of _packageName_.
-> 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
-> **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
+> 1. Let _selfUrl_ be the result of
+> **PACKAGE_SELF_RESOLVE**(_packageName_, _packageSubpath_, _parentURL_).
+> 1. If _selfUrl_ is not **undefined**, return _selfUrl_.
+> 1. If _packageSubpath_ is _"."_ and _packageName_ is a Node.js builtin
> module, then
> 1. Return the string _"nodejs:"_ concatenated with _packageSpecifier_.
> 1. While _parentURL_ is not the file system root,
@@ -1648,120 +1714,161 @@ The resolver can throw the following errors:
> 1. Set _parentURL_ to the parent URL path of _parentURL_.
> 1. Continue the next loop iteration.
> 1. Let _pjson_ be the result of **READ_PACKAGE_JSON**(_packageURL_).
-> 1. If _packageSubpath_ is equal to _"./"_, then
-> 1. Return _packageURL_ + _"/"_.
-> 1. If _packageSubpath_ is _undefined__, then
-> 1. Return the result of **PACKAGE_MAIN_RESOLVE**(_packageURL_,
-> _pjson_).
+> 1. If _pjson_ is not **null** and _pjson_._exports_ is not **null** or
+> **undefined**, then
+> 1. Let _exports_ be _pjson.exports_.
+> 1. Return the _resolved_ destructured value of the result of
+> **PACKAGE_EXPORTS_RESOLVE**(_packageURL_, _packageSubpath_,
+> _pjson.exports_, _defaultConditions_).
+> 1. Otherwise, if _packageSubpath_ is equal to _"."_, then
+> 1. Return the result applying the legacy **LOAD_AS_DIRECTORY**
+> CommonJS resolver to _packageURL_, throwing a _Module Not Found_
+> error for no resolution.
> 1. Otherwise,
-> 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. Return the URL resolution of _packageSubpath_ in _packageURL_.
> 1. Throw a _Module Not Found_ error.
-**SELF_REFERENCE_RESOLVE**(_packageName_, _packageSubpath_, _parentURL_)
+**PACKAGE_SELF_RESOLVE**(_packageName_, _packageSubpath_, _parentURL_)
> 1. Let _packageURL_ be the result of **READ_PACKAGE_SCOPE**(_parentURL_).
> 1. If _packageURL_ is **null**, then
> 1. Return **undefined**.
> 1. Let _pjson_ be the result of **READ_PACKAGE_JSON**(_packageURL_).
-> 1. If _pjson_ does not include an _"exports"_ property, then
+> 1. If _pjson_ is **null** or if _pjson_._exports_ is **null** or
+> **undefined**, then
> 1. Return **undefined**.
> 1. If _pjson.name_ is equal to _packageName_, then
-> 1. If _packageSubpath_ is equal to _"./"_, then
-> 1. Return _packageURL_ + _"/"_.
-> 1. If _packageSubpath_ is _undefined_, then
-> 1. Return the result of **PACKAGE_MAIN_RESOLVE**(_packageURL_, _pjson_).
-> 1. Otherwise,
-> 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. Return the URL resolution of _subpath_ in _packageURL_.
+> 1. Return the _resolved_ destructured value of the result of
+> **PACKAGE_EXPORTS_RESOLVE**(_packageURL_, _subpath_, _pjson.exports_,
+> _defaultConditions_).
> 1. Otherwise, return **undefined**.
-**PACKAGE_MAIN_RESOLVE**(_packageURL_, _pjson_)
+**PACKAGE_EXPORTS_RESOLVE**(_packageURL_, _subpath_, _exports_, _conditions_)
-> 1. If _pjson_ is **null**, then
-> 1. Throw a _Module Not Found_ error.
-> 1. If _pjson.exports_ is not **null** or **undefined**, then
-> 1. If _exports_ is an Object with both a key starting with _"."_ and a key
-> 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. 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. 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
-> _Module Not Found_ error for no resolution.
-> 1. Return _legacyMainURL_.
-
-**PACKAGE_EXPORTS_RESOLVE**(_packageURL_, _packagePath_, _exports_)
> 1. If _exports_ is an Object with both a key starting with _"."_ and a key not
> starting with _"."_, throw an _Invalid Package Configuration_ error.
-> 1. If _exports_ is an Object and all keys of _exports_ start with _"."_, then
-> 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. Let _directoryKeys_ be the list of keys of _exports_ ending in
-> _"/"_, sorted by length descending.
-> 1. For each key _directory_ in _directoryKeys_, do
-> 1. If _packagePath_ starts with _directory_, then
-> 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. If _subpath_ is equal to _"."_, then
+> 1. Let _mainExport_ be **undefined**.
+> 1. If _exports_ is a String or Array, or an Object containing no keys
+> starting with _"."_, then
+> 1. Set _mainExport_ to _exports_.
+> 1. Otherwise if _exports_ is an Object containing a _"."_ property, then
+> 1. Set _mainExport_ to _exports_\[_"."_\].
+> 1. If _mainExport_ is not **undefined**, then
+> 1. Let _resolved_ be the result of **PACKAGE_TARGET_RESOLVE**(
+> _packageURL_, _mainExport_, _""_, **false**, **false**,
+> _conditions_).
+> 1. If _resolved_ is not **null** or **undefined**, then
+> 1. Return _resolved_.
+> 1. Otherwise, if _exports_ is an Object and all keys of _exports_ start with
+> _"."_, then
+> 1. Let _matchKey_ be the string _"./"_ concatenated with _subpath_.
+> 1. Let _resolvedMatch_ be result of **PACKAGE_IMPORTS_EXPORTS_RESOLVE**(
+> _matchKey_, _exports_, _packageURL_, **false**, _conditions_).
+> 1. If _resolvedMatch_._resolve_ is not **null** or **undefined**, then
+> 1. Return _resolvedMatch_.
> 1. Throw a _Package Path Not Exported_ error.
-**PACKAGE_EXPORTS_TARGET_RESOLVE**(_packageURL_, _target_, _subpath_, _env_)
+**PACKAGE_IMPORTS_RESOLVE**(_specifier_, _parentURL_, _conditions_)
+
+> 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 _resolvedMatch_ be the result of
+> **PACKAGE_IMPORTS_EXPORTS_RESOLVE**(_specifier_, _pjson.imports_,
+> _packageURL_, **true**, _conditions_).
+> 1. If _resolvedMatch_._resolve_ is not **null** or **undefined**, then
+> 1. Return _resolvedMatch_.
+> 1. Throw a _Package Import Not Defined_ error.
+
+**PACKAGE_IMPORTS_EXPORTS_RESOLVE**(_matchKey_, _matchObj_, _packageURL_,
+_isImports_, _conditions_)
+
+> 1. If _matchKey_ is a key of _matchObj_, and does not end in _"*"_, then
+> 1. Let _target_ be the value of _matchObj_\[_matchKey_\].
+> 1. Let _resolved_ be the result of **PACKAGE_TARGET_RESOLVE**(
+> _packageURL_, _target_, _""_, **false**, _isImports_, _conditions_).
+> 1. Return the object _{ resolved, exact: **true** }_.
+> 1. Let _expansionKeys_ be the list of keys of _matchObj_ ending in _"/"_
+> or _"*"_, sorted by length descending.
+> 1. For each key _expansionKey_ in _expansionKeys_, do
+> 1. If _expansionKey_ ends in _"*"_ and _matchKey_ starts with but is
+> not equal to the substring of _expansionKey_ excluding the last _"*"_
+> character, then
+> 1. Let _target_ be the value of _matchObj_\[_expansionKey_\].
+> 1. Let _subpath_ be the substring of _matchKey_ starting at the
+> index of the length of _expansionKey_ minus one.
+> 1. Let _resolved_ be the result of **PACKAGE_TARGET_RESOLVE**(
+> _packageURL_, _target_, _subpath_, **true**, _isImports_,
+> _conditions_).
+> 1. Return the object _{ resolved, exact: **true** }_.
+> 1. If _matchKey_ starts with _expansionKey_, then
+> 1. Let _target_ be the value of _matchObj_\[_expansionKey_\].
+> 1. Let _subpath_ be the substring of _matchKey_ starting at the
+> index of the length of _expansionKey_.
+> 1. Let _resolved_ be the result of **PACKAGE_TARGET_RESOLVE**(
+> _packageURL_, _target_, _subpath_, **false**, _isImports_,
+> _conditions_).
+> 1. Return the object _{ resolved, exact: **false** }_.
+> 1. Return the object _{ resolved: **null**, exact: **true** }_.
+
+**PACKAGE_TARGET_RESOLVE**(_packageURL_, _target_, _subpath_, _pattern_,
+_internal_, _conditions_)
> 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 _pattern_ is **false**, _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 _internal_ is **true** and _target_ does not start with _"../"_ or
+> _"/"_ and is not a valid URL, then
+> 1. If _pattern_ is **true**, then
+> 1. Return **PACKAGE_RESOLVE**(_target_ with every instance of
+> _"*"_ replaced by _subpath_, _packageURL_ + _"/"_)_.
+> 1. Return **PACKAGE_RESOLVE**(_target_ + _subpath_,
+> _packageURL_ + _"/"_)_.
+> 1. Otherwise, throw an _Invalid Package Target_ error.
+> 1. If _target_ split on _"/"_ or _"\\"_ contains any _"."_, _".."_ or
+> _"node_modules"_ segments after the first segment, throw an
+> _Invalid Module Specifier_ 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
-> _Invalid Module Specifier_ error.
-> 1. Return _resolved_.
+> 1. Assert: _resolvedTarget_ is contained in _packageURL_.
+> 1. If _subpath_ split on _"/"_ or _"\\"_ contains any _"."_, _".."_ or
+> _"node_modules"_ segments, throw an _Invalid Module Specifier_ error.
+> 1. If _pattern_ is **true**, then
+> 1. Return the URL resolution of _resolvedTarget_ with every instance of
+> _"*"_ replaced with _subpath_.
+> 1. Otherwise,
+> 1. Return the URL resolution of the concatenation of _subpath_ and
+> _resolvedTarget_.
> 1. Otherwise, if _target_ is a non-null Object, then
> 1. If _exports_ contains any index property keys, as defined in ECMA-262
> [6.1.7 Array Index][], throw an _Invalid Package Configuration_ error.
> 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. If _p_ equals _"default"_ or _conditions_ 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_, _pattern_, _internal_,
+> _conditions_).
+> 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_, _pattern_, _internal_,
+> _conditions_), 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.
**ESM_FORMAT**(_url_)
@@ -1783,11 +1890,11 @@ The resolver can throw the following errors:
> 1. Let _scopeURL_ be _url_.
> 1. While _scopeURL_ is not the file system root,
+> 1. Set _scopeURL_ to the parent URL of _scopeURL_.
> 1. If _scopeURL_ ends in a _"node_modules"_ path segment, return **null**.
> 1. Let _pjson_ be the result of **READ_PACKAGE_JSON**(_scopeURL_).
> 1. If _pjson_ is not **null**, then
> 1. Return _pjson_.
-> 1. Set _scopeURL_ to the parent URL of _scopeURL_.
> 1. Return **null**.
**READ_PACKAGE_JSON**(_packageURL_)
diff --git a/doc/api/modules.md b/doc/api/modules.md
index e8215a2ace67ed..079430bce78e71 100644
--- a/doc/api/modules.md
+++ b/doc/api/modules.md
@@ -160,9 +160,11 @@ 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))
-5. LOAD_NODE_MODULES(X, dirname(Y))
-6. THROW "not found"
+4. If X begins with '#'
+ a. LOAD_PACKAGE_IMPORTS(X, dirname(Y))
+5. LOAD_PACKAGE_SELF(X, dirname(Y))
+6. LOAD_NODE_MODULES(X, dirname(Y))
+7. THROW "not found"
LOAD_AS_FILE(X)
1. If X is a file, load X as its file extension format. STOP
@@ -189,7 +191,7 @@ LOAD_AS_DIRECTORY(X)
LOAD_NODE_MODULES(X, START)
1. let DIRS = NODE_MODULES_PATHS(START)
2. for each DIR in DIRS:
- a. LOAD_PACKAGE_EXPORTS(DIR, X)
+ a. LOAD_PACKAGE_EXPORTS(X, DIR)
b. LOAD_AS_FILE(DIR/X)
c. LOAD_AS_DIRECTORY(DIR/X)
@@ -204,38 +206,45 @@ NODE_MODULES_PATHS(START)
d. let I = I - 1
5. return DIRS
-LOAD_SELF_REFERENCE(X, START)
-1. Find the closest package scope to START.
+LOAD_PACKAGE_IMPORTS(X, DIR)
+1. Find the closest package scope SCOPE to DIR.
2. If no scope was found, return.
-3. If the `package.json` has no "exports", return.
-4. If the name in `package.json` is a prefix of X, then
- a. Load the remainder of X relative to this package as if it was
- loaded via `LOAD_NODE_MODULES` with a name in `package.json`.
+3. If the SCOPE/package.json "imports" is null or undefined, return.
+4. let MATCH = PACKAGE_IMPORTS_RESOLVE(X, pathToFileURL(SCOPE),
+ ["node", "require"]) defined in the ESM resolver.
+5. RESOLVE_ESM_MATCH(MATCH).
-LOAD_PACKAGE_EXPORTS(DIR, X)
-1. Try to interpret X as a combination of name and subpath where the name
+LOAD_PACKAGE_EXPORTS(X, DIR)
+1. Try to interpret X as a combination of NAME and SUBPATH where the name
may have a @scope/ prefix and the subpath begins with a slash (`/`).
-2. If X does not match this pattern or DIR/name/package.json is not a file,
+2. If X does not match this pattern or DIR/NAME/package.json is not a file,
return.
-3. Parse DIR/name/package.json, and look for "exports" field.
+3. Parse DIR/NAME/package.json, and look for "exports" field.
4. If "exports" is null or undefined, return.
-5. If "exports" is an object with some keys starting with "." and some keys
- not starting with ".", throw "invalid config".
-6. If "exports" is a string, or object with no keys starting with ".", treat
- it as having that value as its "." object property.
-7. If subpath is "." and "exports" does not have a "." entry, return.
-8. Find the longest key in "exports" that the subpath starts with.
-9. If no such key can be found, throw "not found".
-10. let RESOLVED =
- fileURLToPath(PACKAGE_EXPORTS_TARGET_RESOLVE(pathToFileURL(DIR/name),
- exports[key], subpath.slice(key.length), ["node", "require"])), as defined
- in the ESM resolver.
-11. If key ends with "/":
- a. LOAD_AS_FILE(RESOLVED)
- b. LOAD_AS_DIRECTORY(RESOLVED)
-12. Otherwise
- a. If RESOLVED is a file, load it as its file extension format. STOP
-13. Throw "not found"
+5. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(DIR/NAME), "." + SUBPATH,
+ `package.json` "exports", ["node", "require"]) defined in the ESM resolver.
+6. RESOLVE_ESM_MATCH(MATCH)
+
+LOAD_PACKAGE_SELF(X, DIR)
+1. Find the closest package scope SCOPE to DIR.
+2. If no scope was found, return.
+3. If the SCOPE/package.json "exports" is null or undefined, return.
+4. If the SCOPE/package.json "name" is not the first segment of X, return.
+5. let MATCH = PACKAGE_EXPORTS_RESOLVE(pathToFileURL(SCOPE),
+ "." + X.slice("name".length), `package.json` "exports", ["node", "require"])
+ defined in the ESM resolver.
+6. RESOLVE_ESM_MATCH(MATCH)
+
+RESOLVE_ESM_MATCH(MATCH)
+1. let { RESOLVED, EXACT } = MATCH
+2. let RESOLVED_PATH = fileURLToPath(RESOLVED)
+3. If EXACT is true,
+ a. If the file at RESOLVED_PATH exists, load RESOLVED_PATH as its extension
+ format. STOP
+4. Otherwise, if EXACT is false,
+ a. LOAD_AS_FILE(RESOLVED_PATH)
+ b. LOAD_AS_DIRECTORY(RESOLVED_PATH)
+5. THROW "not found"
```
## Caching
diff --git a/doc/node.1 b/doc/node.1
index 3cd85d3cb0dfb2..610b21d0eef114 100644
--- a/doc/node.1
+++ b/doc/node.1
@@ -78,6 +78,10 @@ Aborting instead of exiting causes a core file to be generated for analysis.
.It Fl -completion-bash
Print source-able bash completion script for Node.js.
.
+.It Fl -conditions Ar string
+Use custom conditional exports conditions
+.Ar string
+.
.It Fl -cpu-prof
Start the V8 CPU profiler on start up, and write the CPU profile to disk
before exit. If
diff --git a/lib/internal/errors.js b/lib/internal/errors.js
index 921bcce2878706..0d15a0d069fd9c 100644
--- a/lib/internal/errors.js
+++ b/lib/internal/errors.js
@@ -21,14 +21,13 @@ const {
NumberIsInteger,
ObjectDefineProperty,
ObjectKeys,
- StringPrototypeSlice,
StringPrototypeStartsWith,
Symbol,
SymbolFor,
WeakMap,
} = primordials;
-const sep = process.platform === 'win32' ? '\\' : '/';
+const isWindows = process.platform === 'win32';
const messages = new Map();
const codes = {};
@@ -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}"`,
@@ -1114,37 +1106,25 @@ 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', (path, message, hasMessage = true) => {
- if (hasMessage)
- return `Invalid package config ${path}${sep}package.json, ${message}`;
- return `Invalid JSON in ${path} imported from ${message}`;
+E('ERR_INVALID_PACKAGE_CONFIG', (path, base, message) => {
+ return `Invalid package config ${path}${base ? ` while importing ${base}` :
+ ''}${message ? `. ${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 +1273,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,9 +1400,16 @@ 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);
-E('ERR_UNSUPPORTED_ESM_URL_SCHEME', 'Only file and data URLs are supported ' +
- 'by the default ESM loader', Error);
+'resolving ES modules imported from %s', Error);
+E('ERR_UNSUPPORTED_ESM_URL_SCHEME', (url) => {
+ let msg = 'Only file and data URLs are supported by the default ESM loader';
+ if (isWindows && url.protocol.length === 2) {
+ msg +=
+ '. On Windows, absolute paths must be valid file:// URLs';
+ }
+ msg += `. Received protocol '${url.protocol}'`;
+ return msg;
+}, Error);
// This should probably be a `TypeError`.
E('ERR_VALID_PERFORMANCE_ENTRY_TYPE',
diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js
index c5d6c4221db076..ec8a69e494319b 100644
--- a/lib/internal/modules/cjs/loader.js
+++ b/lib/internal/modules/cjs/loader.js
@@ -21,23 +21,29 @@
'use strict';
+// Set first due to cycle with ESM loader functions.
+module.exports = {
+ wrapSafe, Module, toRealPath, readPackageScope,
+ get hasLoadedAnyUserCJSModule() { return hasLoadedAnyUserCJSModule; }
+};
+
const {
ArrayIsArray,
+ ArrayPrototypeJoin,
Error,
JSONParse,
Map,
- Number,
ObjectCreate,
ObjectDefineProperty,
ObjectFreeze,
- ObjectIs,
ObjectKeys,
- ObjectPrototypeHasOwnProperty,
ReflectSet,
RegExpPrototypeTest,
SafeMap,
- String,
+ SafeSet,
+ StringPrototypeEndsWith,
StringPrototypeIndexOf,
+ StringPrototypeLastIndexOf,
StringPrototypeMatch,
StringPrototypeSlice,
StringPrototypeStartsWith,
@@ -48,13 +54,14 @@ const {
maybeCacheSourceMap,
rekeySourceMap
} = require('internal/source_map/source_map_cache');
-const { pathToFileURL, fileURLToPath, URL } = require('internal/url');
+const { pathToFileURL, fileURLToPath, isURLInstance } = require('internal/url');
const { deprecate } = require('internal/util');
const vm = require('vm');
const assert = require('internal/assert');
const fs = require('fs');
const internalFS = require('internal/fs/utils');
const path = require('path');
+const { sep } = path;
const { internalModuleStat } = internalBinding('fs');
const packageJsonReader = require('internal/modules/package_json_reader');
const { safeGetenv } = internalBinding('credentials');
@@ -72,6 +79,7 @@ const manifest = getOptionValue('--experimental-policy') ?
require('internal/process/policy').manifest :
null;
const { compileFunction } = internalBinding('contextify');
+const userConditions = getOptionValue('--conditions');
// Whether any user-provided CJS modules had been loaded (executed).
// Used for internal assertions.
@@ -80,28 +88,26 @@ let hasLoadedAnyUserCJSModule = false;
const {
ERR_INVALID_ARG_VALUE,
ERR_INVALID_OPT_VALUE,
- ERR_INVALID_PACKAGE_CONFIG,
- ERR_INVALID_PACKAGE_TARGET,
ERR_INVALID_MODULE_SPECIFIER,
- ERR_PACKAGE_PATH_NOT_EXPORTED,
ERR_REQUIRE_ESM
} = require('internal/errors').codes;
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 { kEvaluated } = internalBinding('module_wrap');
+const {
+ encodedSepRegEx,
+ packageExportsResolve,
+ packageImportsResolve
+} = require('internal/modules/esm/resolve');
+
const isWindows = process.platform === 'win32';
const relativeResolveCache = ObjectCreate(null);
@@ -259,6 +265,7 @@ function readPackage(requestPath) {
name: parsed.name,
main: parsed.main,
exports: parsed.exports,
+ imports: parsed.imports,
type: parsed.type
};
packageJsonCache.set(jsonPath, filtered);
@@ -271,41 +278,31 @@ function readPackage(requestPath) {
}
function readPackageScope(checkPath) {
- const rootSeparatorIndex = checkPath.indexOf(path.sep);
+ const rootSeparatorIndex = StringPrototypeIndexOf(checkPath, sep);
let separatorIndex;
- while (
- (separatorIndex = checkPath.lastIndexOf(path.sep)) > rootSeparatorIndex
- ) {
- checkPath = checkPath.slice(0, separatorIndex);
- if (checkPath.endsWith(path.sep + 'node_modules'))
+ do {
+ separatorIndex = StringPrototypeLastIndexOf(checkPath, sep);
+ checkPath = StringPrototypeSlice(checkPath, 0, separatorIndex);
+ if (StringPrototypeEndsWith(checkPath, sep + 'node_modules'))
return false;
- const pjson = readPackage(checkPath);
+ const pjson = readPackage(checkPath + sep);
if (pjson) return {
+ data: pjson,
path: checkPath,
- data: pjson
};
- }
+ } while (separatorIndex > rootSeparatorIndex);
return false;
}
-function readPackageMain(requestPath) {
- const pkg = readPackage(requestPath);
- return pkg ? pkg.main : undefined;
-}
-
-function readPackageExports(requestPath) {
- const pkg = readPackage(requestPath);
- return pkg ? pkg.exports : undefined;
-}
-
function tryPackage(requestPath, exts, isMain, originalPath) {
- const pkg = readPackageMain(requestPath);
+ const pkg = readPackage(requestPath);
+ const pkgMain = pkg && pkg.main;
- if (!pkg) {
+ if (!pkgMain) {
return tryExtensions(path.resolve(requestPath, 'index'), exts, isMain);
}
- const filename = path.resolve(requestPath, pkg);
+ const filename = path.resolve(requestPath, pkgMain);
let actual = tryFile(filename, isMain) ||
tryExtensions(filename, exts, isMain) ||
tryExtensions(path.resolve(filename, 'index'), exts, isMain);
@@ -325,7 +322,7 @@ function tryPackage(requestPath, exts, isMain, originalPath) {
} else if (pendingDeprecation) {
const jsonPath = path.resolve(requestPath, 'package.json');
process.emitWarning(
- `Invalid 'main' field in '${jsonPath}' of '${pkg}'. ` +
+ `Invalid 'main' field in '${jsonPath}' of '${pkgMain}'. ` +
'Please either fix that or report it to the module author',
'DeprecationWarning',
'DEP0128'
@@ -387,102 +384,45 @@ function findLongestRegisteredExtension(filename) {
return '.js';
}
+function trySelfParentPath(parent) {
+ if (!parent) return false;
+
+ if (parent.filename) {
+ return parent.filename;
+ } else if (parent.id === '' || parent.id === 'internal/preload') {
+ try {
+ return process.cwd() + path.sep;
+ } catch {
+ return false;
+ }
+ }
+}
+
function trySelf(parentPath, request) {
- const { data: pkg, path: basePath } = readPackageScope(parentPath) || {};
+ if (!parentPath) return false;
+
+ const { data: pkg, path: pkgPath } = readPackageScope(parentPath) || {};
if (!pkg || pkg.exports === undefined) return false;
if (typeof pkg.name !== 'string') return false;
let expansion;
if (request === pkg.name) {
- expansion = '';
+ expansion = '.';
} else if (StringPrototypeStartsWith(request, `${pkg.name}/`)) {
- expansion = StringPrototypeSlice(request, pkg.name.length);
+ expansion = '.' + StringPrototypeSlice(request, pkg.name.length);
} else {
return false;
}
- const fromExports = applyExports(basePath, expansion);
- if (fromExports) {
- return tryFile(fromExports, false);
- }
- assert(fromExports !== false);
-}
-
-function isConditionalDotExportSugar(exports, basePath) {
- if (typeof exports === 'string')
- return true;
- if (ArrayIsArray(exports))
- return true;
- if (typeof exports !== 'object')
- return false;
- let isConditional = false;
- let firstCheck = true;
- for (const key of ObjectKeys(exports)) {
- const curIsConditional = key[0] !== '.';
- if (firstCheck) {
- firstCheck = false;
- isConditional = curIsConditional;
- } else if (isConditional !== curIsConditional) {
- throw new ERR_INVALID_PACKAGE_CONFIG(basePath, '"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 isConditional;
-}
-
-function applyExports(basePath, expansion) {
- const mappingKey = `.${expansion}`;
-
- let pkgExports = readPackageExports(basePath);
- if (pkgExports === undefined || pkgExports === null)
- return false;
-
- if (isConditionalDotExportSugar(pkgExports, basePath))
- pkgExports = { '.': pkgExports };
-
- if (typeof pkgExports === 'object') {
- if (ObjectPrototypeHasOwnProperty(pkgExports, mappingKey)) {
- const mapping = pkgExports[mappingKey];
- return resolveExportsTarget(pathToFileURL(basePath + '/'), mapping, '',
- mappingKey);
- }
-
- let dirMatch = '';
- for (const candidateKey of ObjectKeys(pkgExports)) {
- if (candidateKey[candidateKey.length - 1] !== '/') continue;
- if (candidateKey.length > dirMatch.length &&
- StringPrototypeStartsWith(mappingKey, candidateKey)) {
- dirMatch = candidateKey;
- }
- }
-
- if (dirMatch !== '') {
- const mapping = pkgExports[dirMatch];
- const subpath = StringPrototypeSlice(mappingKey, dirMatch.length);
- const resolved = resolveExportsTarget(pathToFileURL(basePath + '/'),
- mapping, subpath, mappingKey);
- // Extension searching for folder exports only
- const rc = stat(resolved);
- if (rc === 0) return resolved;
- if (!(RegExpPrototypeTest(trailingSlashRegex, resolved))) {
- const exts = ObjectKeys(Module._extensions);
- const filename = tryExtensions(resolved, exts, false);
- if (filename) return filename;
- }
- if (rc === 1) {
- const exts = ObjectKeys(Module._extensions);
- const filename = tryPackage(resolved, exts, false,
- basePath + expansion);
- if (filename) return filename;
- }
- // Undefined means not found
- return;
- }
+ try {
+ return finalizeEsmResolution(packageExportsResolve(
+ pathToFileURL(pkgPath + '/package.json'), expansion, pkg,
+ pathToFileURL(parentPath), cjsConditions), request, parentPath, pkgPath);
+ } catch (e) {
+ if (e.code === 'ERR_MODULE_NOT_FOUND')
+ throw createEsmNotFoundErr(request, pkgPath + '/package.json');
+ throw e;
}
-
- throw new ERR_PACKAGE_PATH_NOT_EXPORTED(basePath, mappingKey);
}
// This only applies to requests of a specific form:
@@ -493,108 +433,21 @@ function resolveExports(nmPath, request) {
// The implementation's behavior is meant to mirror resolution in ESM.
const [, name, expansion = ''] =
StringPrototypeMatch(request, EXPORTS_PATTERN) || [];
- if (!name) {
- return false;
- }
-
- const basePath = path.resolve(nmPath, name);
- const fromExports = applyExports(basePath, expansion);
- if (fromExports) {
- return tryFile(fromExports, false);
- }
- return fromExports;
-}
-
-function isArrayIndex(p) {
- assert(typeof p === 'string');
- const n = Number(p);
- if (String(n) !== p)
- return false;
- if (ObjectIs(n, +0))
- return true;
- if (!Number.isInteger(n))
- return false;
- return n >= 0 && n < (2 ** 32) - 1;
-}
-
-function resolveExportsTarget(baseUrl, target, subpath, mappingKey) {
- if (typeof target === 'string') {
- let resolvedTarget, resolvedTargetPath;
- const pkgPathPath = baseUrl.pathname;
- if (StringPrototypeStartsWith(target, './')) {
- resolvedTarget = new URL(target, baseUrl);
- resolvedTargetPath = resolvedTarget.pathname;
- if (!StringPrototypeStartsWith(resolvedTargetPath, pkgPathPath) ||
- StringPrototypeIndexOf(resolvedTargetPath, '/node_modules/',
- pkgPathPath.length - 1) !== -1)
- resolvedTarget = undefined;
- }
- 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);
- const resolved = new URL(subpath, resolvedTarget);
- const resolvedPath = resolved.pathname;
- if (StringPrototypeStartsWith(resolvedPath, resolvedTargetPath) &&
- StringPrototypeIndexOf(resolvedPath, '/node_modules/',
- pkgPathPath.length - 1) === -1) {
- return fileURLToPath(resolved);
- }
- throw new ERR_INVALID_MODULE_SPECIFIER(StringPrototypeSlice(baseUrl.pathname
- , 0, -1), mappingKey);
- } else if (ArrayIsArray(target)) {
- if (target.length === 0)
- throw new ERR_PACKAGE_PATH_NOT_EXPORTED(
- StringPrototypeSlice(baseUrl.pathname, 0, -1), mappingKey + subpath);
- let lastException;
- for (const targetValue of target) {
- try {
- return resolveExportsTarget(baseUrl, targetValue, subpath, mappingKey);
- } catch (e) {
- lastException = e;
- if (e.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED' &&
- e.code !== 'ERR_INVALID_PACKAGE_TARGET')
- throw e;
- }
- }
- // Throw last fallback error
- assert(lastException !== undefined);
- throw lastException;
- } else if (typeof target === 'object' && target !== null) {
- const keys = ObjectKeys(target);
- if (keys.some(isArrayIndex)) {
- throw new ERR_INVALID_PACKAGE_CONFIG(baseUrl, '"exports" cannot ' +
- 'contain numeric property keys.');
- }
- for (const p of keys) {
- switch (p) {
- case 'node':
- case 'require':
- try {
- return resolveExportsTarget(baseUrl, target[p], subpath,
- mappingKey);
- } catch (e) {
- if (e.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') throw e;
- }
- break;
- case 'default':
- try {
- return resolveExportsTarget(baseUrl, target.default, subpath,
- mappingKey);
- } catch (e) {
- if (e.code !== 'ERR_PACKAGE_PATH_NOT_EXPORTED') throw e;
- }
- }
+ if (!name)
+ return;
+ const pkgPath = path.resolve(nmPath, name);
+ const pkg = readPackage(pkgPath);
+ if (pkg && pkg.exports !== null && pkg.exports !== undefined) {
+ try {
+ return finalizeEsmResolution(packageExportsResolve(
+ pathToFileURL(pkgPath + '/package.json'), '.' + expansion, pkg, null,
+ cjsConditions), request, null, pkgPath);
+ } catch (e) {
+ if (e.code === 'ERR_MODULE_NOT_FOUND')
+ throw createEsmNotFoundErr(request, pkgPath + '/package.json');
+ throw e;
}
- throw new ERR_PACKAGE_PATH_NOT_EXPORTED(
- StringPrototypeSlice(baseUrl.pathname, 0, -1), mappingKey + subpath);
- } else if (target === null) {
- throw new ERR_PACKAGE_PATH_NOT_EXPORTED(
- StringPrototypeSlice(baseUrl.pathname, 0, -1), mappingKey + subpath);
}
- throw new ERR_INVALID_PACKAGE_TARGET(
- StringPrototypeSlice(baseUrl.pathname, 0, -1), mappingKey, subpath, target);
}
const trailingSlashRegex = /(?:^|\/)\.?\.$/;
@@ -627,12 +480,8 @@ Module._findPath = function(request, paths, isMain) {
if (!absoluteRequest) {
const exportsResolved = resolveExports(curPath, request);
- // Undefined means not found, false means no exports
- if (exportsResolved === undefined)
- break;
- if (exportsResolved) {
+ if (exportsResolved)
return exportsResolved;
- }
}
const basePath = path.resolve(curPath, request);
@@ -892,6 +741,7 @@ Module._load = function(request, parent, isMain) {
return module.exports;
};
+const cjsConditions = new SafeSet(['require', 'node', ...userConditions]);
Module._resolveFilename = function(request, parent, isMain, options) {
if (NativeModule.canBeRequiredByUsers(request)) {
return request;
@@ -934,15 +784,34 @@ Module._resolveFilename = function(request, parent, isMain, options) {
}
if (parent && parent.filename) {
- const filename = trySelf(parent.filename, request);
- if (filename) {
- const cacheKey = request + '\x00' +
- (paths.length === 1 ? paths[0] : paths.join('\x00'));
- Module._pathCache[cacheKey] = filename;
- return filename;
+ if (request[0] === '#') {
+ const pkg = readPackageScope(parent.filename) || {};
+ if (pkg.data && pkg.data.imports !== null &&
+ pkg.data.imports !== undefined) {
+ try {
+ return finalizeEsmResolution(
+ packageImportsResolve(request, pathToFileURL(parent.filename),
+ cjsConditions), request, parent.filename,
+ pkg.path);
+ } catch (e) {
+ if (e.code === 'ERR_MODULE_NOT_FOUND')
+ throw createEsmNotFoundErr(request);
+ throw e;
+ }
+ }
}
}
+ // Try module self resoultion first
+ const parentPath = trySelfParentPath(parent);
+ const selfResolved = trySelf(parentPath, request);
+ if (selfResolved) {
+ const cacheKey = request + '\x00' +
+ (paths.length === 1 ? paths[0] : ArrayPrototypeJoin(paths, '\x00'));
+ Module._pathCache[cacheKey] = selfResolved;
+ return selfResolved;
+ }
+
// Look up the filename first, since that's the cache key.
const filename = Module._findPath(request, paths, isMain, false);
if (filename) return filename;
@@ -963,6 +832,34 @@ Module._resolveFilename = function(request, parent, isMain, options) {
throw err;
};
+function finalizeEsmResolution(match, request, parentPath, pkgPath) {
+ const { resolved, exact } = match;
+ if (StringPrototypeMatch(resolved, encodedSepRegEx))
+ throw new ERR_INVALID_MODULE_SPECIFIER(
+ resolved, 'must not include encoded "/" or "\\" characters', parentPath);
+ const filename = fileURLToPath(resolved);
+ let actual = tryFile(filename);
+ if (!exact && !actual) {
+ const exts = ObjectKeys(Module._extensions);
+ actual = tryExtensions(filename, exts, false) ||
+ tryPackage(filename, exts, false, request);
+ }
+ if (actual)
+ return actual;
+ const err = createEsmNotFoundErr(filename,
+ path.resolve(pkgPath, 'package.json'));
+ throw err;
+}
+
+function createEsmNotFoundErr(request, path) {
+ // eslint-disable-next-line no-restricted-syntax
+ const err = new Error(`Cannot find module '${request}'`);
+ err.code = 'MODULE_NOT_FOUND';
+ if (path)
+ err.path = path;
+ // TODO(BridgeAR): Add the requireStack as well.
+ return err;
+}
// Given a file name, pass it to the proper extension handler.
Module.prototype.load = function(filename) {
@@ -981,29 +878,13 @@ Module.prototype.load = function(filename) {
this.loaded = true;
const ESMLoader = asyncESM.ESMLoader;
- const url = `${pathToFileURL(filename)}`;
- const module = ESMLoader.moduleMap.get(url);
// Create module entry at load time to snapshot exports correctly
const exports = this.exports;
- // Called from cjs translator
- if (module !== undefined && module.module !== undefined) {
- if (module.module.getStatus() >= kInstantiated)
- module.module.setExport('default', exports);
- } else {
- // Preemptively cache
- // We use a function to defer promise creation for async hooks.
- ESMLoader.moduleMap.set(
- url,
- // Module job creation will start promises.
- // We make it a function to lazily trigger those promises
- // for async hooks compatibility.
- () => new ModuleJob(ESMLoader, url, () =>
- new ModuleWrap(url, undefined, ['default'], function() {
- this.setExport('default', exports);
- })
- , false /* isMain */, false /* inspectBrk */)
- );
- }
+ // Preemptively cache
+ if ((!module || module.module === undefined ||
+ module.module.getStatus() < kEvaluated) &&
+ !ESMLoader.cjsCache.has(this))
+ ESMLoader.cjsCache.set(this, exports);
};
@@ -1206,7 +1087,7 @@ const createRequireError = 'must be a file URL object, file URL string, or ' +
function createRequire(filename) {
let filepath;
- if (filename instanceof URL ||
+ if (isURLInstance(filename) ||
(typeof filename === 'string' && !path.isAbsolute(filename))) {
try {
filepath = fileURLToPath(filename);
@@ -1282,8 +1163,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/loader.js b/lib/internal/modules/esm/loader.js
index be5868553fa8df..0d1c09f3d38c7f 100644
--- a/lib/internal/modules/esm/loader.js
+++ b/lib/internal/modules/esm/loader.js
@@ -1,9 +1,12 @@
'use strict';
+// This is needed to avoid cycles in esm/resolve <-> cjs/loader
+require('internal/modules/cjs/loader');
+
const {
FunctionPrototypeBind,
ObjectSetPrototypeOf,
- SafeMap,
+ SafeWeakMap,
} = primordials;
const {
@@ -49,7 +52,7 @@ class Loader {
this.moduleMap = new ModuleMap();
// Map of already-loaded CJS modules to use
- this.cjsCache = new SafeMap();
+ this.cjsCache = new SafeWeakMap();
// This hook is called before the first root module is imported. It's a
// function that returns a piece of code that runs as a sloppy-mode script.
diff --git a/lib/internal/modules/esm/module_job.js b/lib/internal/modules/esm/module_job.js
index 3f11ffc768eedb..ed681541d12723 100644
--- a/lib/internal/modules/esm/module_job.js
+++ b/lib/internal/modules/esm/module_job.js
@@ -107,16 +107,23 @@ class ModuleJob {
await this.loader.resolve(childSpecifier, parentFileUrl);
const format = await this.loader.getFormat(childFileURL);
if (format === 'commonjs') {
- const importStatement = splitStack[1];
- const namedImports = StringPrototypeMatch(importStatement, /{.*}/)[0];
- const destructuringAssignment = StringPrototypeReplace(namedImports, /\s+as\s+/g, ': ');
e.message = `The requested module '${childSpecifier}' is expected ` +
'to be of type CommonJS, which does not support named exports. ' +
'CommonJS modules can be imported by importing the default ' +
- 'export.\n' +
- 'For example:\n' +
- `import pkg from '${childSpecifier}';\n` +
- `const ${destructuringAssignment} = pkg;`;
+ 'export.';
+ // TODO(@ctavan): The original error stack only provides the single
+ // line which causes the error. For multi-line import statements we
+ // cannot generate an equivalent object descructuring assignment by
+ // just parsing the error stack.
+ const importStatement = splitStack[1];
+ const oneLineNamedImports = StringPrototypeMatch(importStatement, /{.*}/);
+ if (oneLineNamedImports) {
+ const destructuringAssignment =
+ StringPrototypeReplace(oneLineNamedImports[0], /\s+as\s+/g, ': ');
+ e.message += '\nFor example:\n' +
+ `import pkg from '${childSpecifier}';\n` +
+ `const ${destructuringAssignment} = pkg;`;
+ }
const newStack = StringPrototypeSplit(e.stack, '\n');
newStack[3] = `SyntaxError: ${e.message}`;
e.stack = ArrayPrototypeJoin(newStack, '\n');
diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js
index 987a139c6aae57..92760d201beb4c 100644
--- a/lib/internal/modules/esm/resolve.js
+++ b/lib/internal/modules/esm/resolve.js
@@ -10,11 +10,11 @@ const {
ObjectGetOwnPropertyNames,
ObjectPrototypeHasOwnProperty,
RegExp,
+ RegExpPrototypeTest,
SafeMap,
SafeSet,
String,
StringPrototypeEndsWith,
- StringPrototypeIncludes,
StringPrototypeIndexOf,
StringPrototypeReplace,
StringPrototypeSlice,
@@ -22,7 +22,6 @@ const {
StringPrototypeStartsWith,
StringPrototypeSubstr,
} = primordials;
-const assert = require('internal/assert');
const internalFS = require('internal/fs/utils');
const { NativeModule } = require('internal/bootstrap/loaders');
const {
@@ -32,7 +31,6 @@ const {
} = require('fs');
const { getOptionValue } = require('internal/options');
const { sep, relative } = require('path');
-const { Module: CJSModule } = require('internal/modules/cjs/loader');
const preserveSymlinks = getOptionValue('--preserve-symlinks');
const preserveSymlinksMain = getOptionValue('--preserve-symlinks-main');
const typeFlag = getOptionValue('--input-type');
@@ -44,15 +42,19 @@ 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,
} = require('internal/errors').codes;
+const { Module: CJSModule } = require('internal/modules/cjs/loader');
const packageJsonReader = require('internal/modules/package_json_reader');
-const DEFAULT_CONDITIONS = ObjectFreeze(['node', 'import']);
+const userConditions = getOptionValue('--conditions');
+const DEFAULT_CONDITIONS = ObjectFreeze(['node', 'import', ...userConditions]);
const DEFAULT_CONDITIONS_SET = new SafeSet(DEFAULT_CONDITIONS);
+
function getConditionsSet(conditions) {
if (conditions !== undefined && conditions !== DEFAULT_CONDITIONS) {
if (!ArrayIsArray(conditions)) {
@@ -75,15 +77,7 @@ function tryStatSync(path) {
}
}
-/**
- *
- * '/foo/package.json' -> '/foo'
- */
-function removePackageJsonFromPath(path) {
- return StringPrototypeSlice(path, 0, path.length - 13);
-}
-
-function getPackageConfig(path) {
+function getPackageConfig(path, specifier, base) {
const existing = packageJSONCache.get(path);
if (existing !== undefined) {
return existing;
@@ -91,11 +85,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;
@@ -105,35 +101,42 @@ function getPackageConfig(path) {
try {
packageJSON = JSONParse(source);
} catch (error) {
- const errorPath = removePackageJsonFromPath(path);
- throw new ERR_INVALID_PACKAGE_CONFIG(errorPath, error.message, true);
+ throw new ERR_INVALID_PACKAGE_CONFIG(
+ path,
+ (base ? `"${specifier}" from ` : '') + fileURLToPath(base || specifier),
+ error.message
+ );
}
- 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;
}
-function getPackageScopeConfig(resolved, base) {
+function getPackageScopeConfig(resolved) {
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);
+ const packageConfig = getPackageConfig(fileURLToPath(packageJSONUrl),
+ resolved);
if (packageConfig.exists) return packageConfig;
const lastPackageJSONUrl = packageJSONUrl;
@@ -143,14 +146,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;
}
@@ -166,7 +172,7 @@ function fileExists(url) {
return tryStatSync(fileURLToPath(url)).isFile();
}
-function legacyMainResolve(packageJSONUrl, packageConfig) {
+function legacyMainResolve(packageJSONUrl, packageConfig, base) {
let guess;
if (packageConfig.main !== undefined) {
// Note: fs check redundances will be handled by Descriptor cache here.
@@ -211,7 +217,8 @@ function legacyMainResolve(packageJSONUrl, packageConfig) {
return guess;
}
// Not found.
- return undefined;
+ throw new ERR_MODULE_NOT_FOUND(
+ fileURLToPath(new URL('.', packageJSONUrl)), fileURLToPath(base));
}
function resolveExtensionsWithTryExactName(search) {
@@ -233,85 +240,117 @@ function resolveIndex(search) {
return resolveExtensions(new URL('index', search));
}
+const encodedSepRegEx = /%2F|%2C/i;
function finalizeResolution(resolved, base) {
+ 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);
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));
+ if (!StringPrototypeEndsWith(path, '/')) {
+ file = resolveIndex(new URL(`${resolved}/`));
+ if (file !== undefined) return file;
} else {
- file = resolveIndex(resolved);
+ return resolveIndex(resolved) || resolved;
}
- if (file !== undefined) return file;
throw new ERR_MODULE_NOT_FOUND(
resolved.pathname, fileURLToPath(base), 'module');
}
- const path = fileURLToPath(resolved);
- const stats = tryStatSync(path);
-
+ const stats = tryStatSync(StringPrototypeEndsWith(path, '/') ?
+ StringPrototypeSlice(path, -1) : path);
if (stats.isDirectory()) {
- const err = new ERR_UNSUPPORTED_DIR_IMPORT(
- path || resolved.pathname, fileURLToPath(base));
+ const err = new ERR_UNSUPPORTED_DIR_IMPORT(path, fileURLToPath(base));
err.url = String(resolved);
throw err;
} else if (!stats.isFile()) {
throw new ERR_MODULE_NOT_FOUND(
- path || resolved.pathname, fileURLToPath(base), 'module');
+ path || resolved.pathname, base && fileURLToPath(base), 'module');
}
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,
+ base && 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)}`;
+ throw new ERR_INVALID_MODULE_SPECIFIER(subpath, reason,
+ base && 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, base && 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 invalidSegmentRegEx = /(^|\\|\/)(\.\.?|node_modules)(\\|\/|$)/;
+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);
+
+ if (!StringPrototypeStartsWith(target, './')) {
+ if (internal && !StringPrototypeStartsWith(target, '../') &&
+ !StringPrototypeStartsWith(target, '/')) {
+ let isURL = false;
+ try {
+ new URL(target);
+ isURL = true;
+ } catch {}
+ if (!isURL) {
+ const exportTarget = pattern ?
+ StringPrototypeReplace(target, patternRegEx, subpath) :
+ target + subpath;
+ return packageResolve(exportTarget, packageJSONUrl, conditions);
+ }
+ }
+ throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base);
}
+ if (RegExpPrototypeTest(invalidSegmentRegEx, StringPrototypeSlice(target, 2)))
+ throwInvalidPackageTarget(match, target, packageJSONUrl, internal, 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 (!StringPrototypeStartsWith(resolvedPath, packagePath))
+ 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);
- }
- return subpathResolved;
+
+ if (RegExpPrototypeTest(invalidSegmentRegEx, subpath))
+ throwInvalidSubpath(match + subpath, packageJSONUrl, internal, base);
+
+ if (pattern)
+ return new URL(StringPrototypeReplace(resolved.href, patternRegEx,
+ subpath));
+ return new URL(subpath, resolved);
}
/**
@@ -324,36 +363,40 @@ function isArrayIndex(key) {
return keyNum >= 0 && keyNum < 0xFFFF_FFFF;
}
-function resolveExportsTarget(
- packageJSONUrl, target, subpath, packageSubpath, base, conditions) {
+function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath,
+ base, pattern, internal, conditions) {
if (typeof target === 'string') {
- const resolved = resolveExportsTargetString(
- target, subpath, packageSubpath, packageJSONUrl, base);
- return finalizeResolution(resolved, base);
+ return resolvePackageTargetString(
+ target, subpath, packageSubpath, packageJSONUrl, base, pattern, internal,
+ conditions);
} 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,
- conditions);
+ resolved = resolvePackageTarget(
+ packageJSONUrl, targetItem, subpath, packageSubpath, base, pattern,
+ 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;
}
-
- return finalizeResolution(resolved, base);
+ if (resolved === undefined)
+ continue;
+ if (resolved === null) {
+ lastException = null;
+ continue;
+ }
+ return resolved;
}
- assert(lastException !== undefined);
+ if (lastException === undefined || lastException === null)
+ return lastException;
throw lastException;
} else if (typeof target === 'object' && target !== null) {
const keys = ObjectGetOwnPropertyNames(target);
@@ -361,29 +404,28 @@ function resolveExportsTarget(
const key = keys[i];
if (isArrayIndex(key)) {
throw new ERR_INVALID_PACKAGE_CONFIG(
- fileURLToPath(packageJSONUrl),
- '"exports" cannot contain numeric property keys');
+ fileURLToPath(packageJSONUrl), base,
+ '"exports" cannot contain numeric property keys.');
}
}
for (let i = 0; i < keys.length; i++) {
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,
+ pattern, 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) {
@@ -400,7 +442,7 @@ function isConditionalExportsMainSugar(exports, packageJSONUrl, base) {
isConditionalSugar = curIsConditionalSugar;
} else if (isConditionalSugar !== curIsConditionalSugar) {
throw new ERR_INVALID_PACKAGE_CONFIG(
- fileURLToPath(packageJSONUrl),
+ fileURLToPath(packageJSONUrl), base,
'"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.');
@@ -409,38 +451,6 @@ 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);
- } else if (typeof exports === 'object' && exports !== null) {
- const target = exports['.'];
- if (target !== undefined)
- return resolveExportsTarget(packageJSONUrl, target, '', '', base,
- conditions);
- }
-
- throw new ERR_PACKAGE_PATH_NOT_EXPORTED(packageJSONUrl, '.');
- }
- if (getOptionValue('--experimental-specifier-resolution') === 'node') {
- if (packageConfig.main !== undefined) {
- return finalizeResolution(
- new URL(packageConfig.main, packageJSONUrl), base);
- }
- return finalizeResolution(
- new URL('index', packageJSONUrl), base);
- }
- return legacyMainResolve(packageJSONUrl, packageConfig);
- }
-
- throw new ERR_MODULE_NOT_FOUND(
- fileURLToPath(new URL('.', packageJSONUrl)), fileURLToPath(base));
-}
-
/**
* @param {URL} packageJSONUrl
* @param {string} packageSubpath
@@ -451,44 +461,107 @@ function packageMainResolve(packageJSONUrl, packageConfig, base, conditions) {
*/
function packageExportsResolve(
packageJSONUrl, packageSubpath, packageConfig, base, conditions) {
- const exports = packageConfig.exports;
- if (exports === undefined ||
- isConditionalExportsMainSugar(exports, packageJSONUrl, base)) {
- throwExportsNotFound(packageSubpath, packageJSONUrl, base);
- }
-
+ let exports = packageConfig.exports;
+ if (isConditionalExportsMainSugar(exports, packageJSONUrl, base))
+ exports = { '.': exports };
if (ObjectPrototypeHasOwnProperty(exports, packageSubpath)) {
const target = exports[packageSubpath];
- const resolved = resolveExportsTarget(
- packageJSONUrl, target, '', packageSubpath, base, conditions);
- return finalizeResolution(resolved, base);
+ const resolved = resolvePackageTarget(
+ packageJSONUrl, target, '', packageSubpath, base, false, false, conditions
+ );
+ if (resolved === null || resolved === undefined)
+ throwExportsNotFound(packageSubpath, packageJSONUrl, base);
+ return { resolved, exact: true };
}
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) &&
+ if (key[key.length - 1] === '*' &&
+ StringPrototypeStartsWith(packageSubpath,
+ StringPrototypeSlice(key, 0, -1)) &&
+ packageSubpath.length >= key.length &&
key.length > bestMatch.length) {
bestMatch = key;
+ } else if (key[key.length - 1] === '/' &&
+ 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, conditions);
- return finalizeResolution(resolved, base);
+ const pattern = bestMatch[bestMatch.length - 1] === '*';
+ const subpath = StringPrototypeSubstr(packageSubpath, bestMatch.length -
+ (pattern ? 1 : 0));
+ const resolved = resolvePackageTarget(packageJSONUrl, target, subpath,
+ bestMatch, base, pattern, false,
+ conditions);
+ if (resolved === null || resolved === undefined)
+ throwExportsNotFound(packageSubpath, packageJSONUrl, base);
+ return { resolved, exact: pattern };
}
throwExportsNotFound(packageSubpath, packageJSONUrl, base);
}
+function packageImportsResolve(name, base, conditions) {
+ if (name === '#' || StringPrototypeStartsWith(name, '#/')) {
+ 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);
+ 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, false, true, conditions
+ );
+ if (resolved !== null)
+ return { resolved, exact: true };
+ } else {
+ let bestMatch = '';
+ const keys = ObjectGetOwnPropertyNames(imports);
+ for (let i = 0; i < keys.length; i++) {
+ const key = keys[i];
+ if (key[key.length - 1] === '*' &&
+ StringPrototypeStartsWith(name,
+ StringPrototypeSlice(key, 0, -1)) &&
+ name.length >= key.length &&
+ key.length > bestMatch.length) {
+ bestMatch = key;
+ } else if (key[key.length - 1] === '/' &&
+ StringPrototypeStartsWith(name, key) &&
+ key.length > bestMatch.length) {
+ bestMatch = key;
+ }
+ }
+
+ if (bestMatch) {
+ const target = imports[bestMatch];
+ const pattern = bestMatch[bestMatch.length - 1] === '*';
+ const subpath = StringPrototypeSubstr(name, bestMatch.length -
+ (pattern ? 1 : 0));
+ const resolved = resolvePackageTarget(
+ packageJSONUrl, target, subpath, bestMatch, base, pattern, true,
+ conditions);
+ if (resolved !== null)
+ return { resolved, exact: pattern };
+ }
+ }
+ }
+ }
+ throwImportNotDefined(name, packageJSONUrl, base);
+}
+
function getPackageType(url) {
- const packageConfig = getPackageScopeConfig(url, url);
+ const packageConfig = getPackageScopeConfig(url);
return packageConfig.type;
}
@@ -526,35 +599,21 @@ 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 ?
- '' : '.' + StringPrototypeSlice(specifier, separatorIndex);
+ const packageSubpath = '.' + (separatorIndex === -1 ? '' :
+ StringPrototypeSlice(specifier, separatorIndex));
// ResolveSelf
- const packageConfig = getPackageScopeConfig(base, base);
+ const packageConfig = getPackageScopeConfig(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,
- conditions);
- }
+ const packageJSONUrl = pathToFileURL(packageConfig.pjsonPath);
+ if (packageConfig.name === packageName &&
+ packageConfig.exports !== undefined && packageConfig.exports !== null) {
return packageExportsResolve(
- packageJSONUrl, packageSubpath, packageConfig, base, conditions);
+ packageJSONUrl, packageSubpath, packageConfig, base, conditions
+ ).resolved;
}
}
@@ -563,7 +622,8 @@ function packageResolve(specifier, base, conditions) {
let packageJSONPath = fileURLToPath(packageJSONUrl);
let lastPath;
do {
- const stat = tryStatSync(removePackageJsonFromPath(packageJSONPath));
+ const stat = tryStatSync(StringPrototypeSlice(packageJSONPath, 0,
+ packageJSONPath.length - 13));
if (!stat.isDirectory()) {
lastPath = packageJSONPath;
packageJSONUrl = new URL((isScoped ?
@@ -574,18 +634,14 @@ function packageResolve(specifier, base, conditions) {
}
// Package match.
- const packageConfig = getPackageConfig(packageJSONPath, base);
- if (packageSubpath === './') {
- return new URL('./', packageJSONUrl);
- } else if (packageSubpath === '') {
- return packageMainResolve(packageJSONUrl, packageConfig, base,
- conditions);
- } else if (packageConfig.exports !== undefined) {
+ const packageConfig = getPackageConfig(packageJSONPath, specifier, base);
+ if (packageConfig.exports !== undefined && packageConfig.exports !== null)
return packageExportsResolve(
- packageJSONUrl, packageSubpath, packageConfig, base, conditions);
- }
- return finalizeResolution(
- new URL(packageSubpath, packageJSONUrl), base);
+ packageJSONUrl, packageSubpath, packageConfig, base, conditions
+ ).resolved;
+ if (packageSubpath === '.')
+ return legacyMainResolve(packageJSONUrl, packageConfig, base);
+ return new URL(packageSubpath, packageJSONUrl);
// Cross-platform root check.
} while (packageJSONPath.length !== lastPath.length);
@@ -626,11 +682,13 @@ function moduleResolve(specifier, base, conditions) {
let resolved;
if (shouldBeTreatedAsRelativeOrAbsolutePath(specifier)) {
resolved = new URL(specifier, base);
+ } else if (specifier[0] === '#') {
+ ({ resolved } = packageImportsResolve(specifier, base, conditions));
} else {
try {
resolved = new URL(specifier);
} catch {
- return packageResolve(specifier, base, conditions);
+ resolved = packageResolve(specifier, base, conditions);
}
}
return finalizeResolution(resolved, base);
@@ -695,7 +753,7 @@ function defaultResolve(specifier, context = {}, defaultResolveUnused) {
if (parsed && parsed.protocol === 'nodejs:')
return { url: specifier };
if (parsed && parsed.protocol !== 'file:' && parsed.protocol !== 'data:')
- throw new ERR_UNSUPPORTED_ESM_URL_SCHEME();
+ throw new ERR_UNSUPPORTED_ESM_URL_SCHEME(parsed);
if (NativeModule.canBeRequiredByUsers(specifier)) {
return {
url: 'nodejs:' + specifier
@@ -764,5 +822,8 @@ function defaultResolve(specifier, context = {}, defaultResolveUnused) {
module.exports = {
DEFAULT_CONDITIONS,
defaultResolve,
- getPackageType
+ encodedSepRegEx,
+ getPackageType,
+ packageExportsResolve,
+ packageImportsResolve
};
diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js
index f314ba96b3476c..99bf560657ab20 100644
--- a/lib/internal/modules/esm/translators.js
+++ b/lib/internal/modules/esm/translators.js
@@ -48,6 +48,8 @@ const debug = debuglog('esm');
const translators = new SafeMap();
exports.translators = translators;
+const asyncESM = require('internal/process/esm_loader');
+
let DECODER = null;
function assertBufferSource(body, allowString, hookName) {
if (allowString && typeof body === 'string') {
@@ -80,21 +82,14 @@ function errPath(url) {
return url;
}
-let esmLoader;
async function importModuleDynamically(specifier, { url }) {
- if (!esmLoader) {
- esmLoader = require('internal/process/esm_loader').ESMLoader;
- }
- return esmLoader.import(specifier, url);
+ return asyncESM.ESMLoader.import(specifier, url);
}
function createImportMetaResolve(defaultParentUrl) {
return async function resolve(specifier, parentUrl = defaultParentUrl) {
- if (!esmLoader) {
- esmLoader = require('internal/process/esm_loader').ESMLoader;
- }
return PromisePrototypeCatch(
- esmLoader.resolve(specifier, parentUrl),
+ asyncESM.ESMLoader.resolve(specifier, parentUrl),
(error) => (
error.code === 'ERR_UNSUPPORTED_DIR_IMPORT' ?
error.url : PromiseReject(error))
@@ -132,27 +127,18 @@ const isWindows = process.platform === 'win32';
const winSepRegEx = /\//g;
translators.set('commonjs', function commonjsStrategy(url, isMain) {
debug(`Translating CJSModule ${url}`);
- const pathname = internalURLModule.fileURLToPath(new URL(url));
- const cached = this.cjsCache.get(url);
- if (cached) {
- this.cjsCache.delete(url);
- return cached;
- }
- const module = CJSModule._cache[
- isWindows ? StringPrototypeReplace(pathname, winSepRegEx, '\\') : pathname
- ];
- if (module && module.loaded) {
- const exports = module.exports;
- return new ModuleWrap(url, undefined, ['default'], function() {
- this.setExport('default', exports);
- });
- }
return new ModuleWrap(url, undefined, ['default'], function() {
debug(`Loading CJSModule ${url}`);
- // We don't care about the return val of _load here because Module#load
- // will handle it for us by checking the loader registry and filling the
- // exports like above
- CJSModule._load(pathname, undefined, isMain);
+ const pathname = internalURLModule.fileURLToPath(new URL(url));
+ let exports;
+ const cachedModule = CJSModule._cache[pathname];
+ if (cachedModule && asyncESM.ESMLoader.cjsCache.has(cachedModule)) {
+ exports = asyncESM.ESMLoader.cjsCache.get(cachedModule);
+ asyncESM.ESMLoader.cjsCache.delete(cachedModule);
+ } else {
+ exports = CJSModule._load(pathname, undefined, isMain);
+ }
+ this.setExport('default', exports);
});
});
diff --git a/src/node_file.cc b/src/node_file.cc
index ecf49dead7cdaa..e5b5a298b24ee5 100644
--- a/src/node_file.cc
+++ b/src/node_file.cc
@@ -908,6 +908,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/src/node_options.cc b/src/node_options.cc
index 53e4c9bb3a315f..824004631f5301 100644
--- a/src/node_options.cc
+++ b/src/node_options.cc
@@ -283,6 +283,10 @@ DebugOptionsParser::DebugOptionsParser() {
}
EnvironmentOptionsParser::EnvironmentOptionsParser() {
+ AddOption("--conditions",
+ "additional user conditions for conditional exports and imports",
+ &EnvironmentOptions::conditions,
+ kAllowedInEnvironment);
AddOption("--diagnostic-dir",
"set dir for all output files"
" (default: current working directory)",
diff --git a/src/node_options.h b/src/node_options.h
index 603edb0c405270..1e7ffadf0dafbc 100644
--- a/src/node_options.h
+++ b/src/node_options.h
@@ -100,6 +100,7 @@ class DebugOptions : public Options {
class EnvironmentOptions : public Options {
public:
bool abort_on_uncaught_exception = false;
+ std::vector conditions;
bool enable_source_maps = false;
bool experimental_json_modules = false;
bool experimental_modules = false;
diff --git a/test/es-module/test-esm-cjs-named-error.mjs b/test/es-module/test-esm-cjs-named-error.mjs
index d71dc959e21fb7..e9ddc67c0fbcea 100644
--- a/test/es-module/test-esm-cjs-named-error.mjs
+++ b/test/es-module/test-esm-cjs-named-error.mjs
@@ -10,6 +10,10 @@ const expectedRelative = 'The requested module \'./fail.cjs\' is expected to ' +
'import pkg from \'./fail.cjs\';\n' +
'const { comeOn } = pkg;';
+const expectedWithoutExample = 'The requested module \'./fail.cjs\' is ' +
+ 'expected to be of type CommonJS, which does not support named exports. ' +
+ 'CommonJS modules can be imported by importing the default export.';
+
const expectedRenamed = 'The requested module \'./fail.cjs\' is expected to ' +
'be of type CommonJS, which does not support named exports. CommonJS ' +
'modules can be imported by importing the default export.\n' +
@@ -52,6 +56,13 @@ rejects(async () => {
message: expectedRenamed
}, 'should correctly format named imports with renames');
+rejects(async () => {
+ await import(`${fixtureBase}/multi-line.mjs`);
+}, {
+ name: 'SyntaxError',
+ message: expectedWithoutExample,
+}, 'should correctly format named imports across multiple lines');
+
rejects(async () => {
await import(`${fixtureBase}/json-hack.mjs`);
}, {
diff --git a/test/es-module/test-esm-custom-exports.mjs b/test/es-module/test-esm-custom-exports.mjs
new file mode 100644
index 00000000000000..cf0557fa44215e
--- /dev/null
+++ b/test/es-module/test-esm-custom-exports.mjs
@@ -0,0 +1,10 @@
+// Flags: --conditions=custom-condition --conditions another
+import { mustCall } from '../common/index.mjs';
+import { strictEqual } from 'assert';
+import { requireFixture, importFixture } from '../fixtures/pkgexports.mjs';
+[requireFixture, importFixture].forEach((loadFixture) => {
+ loadFixture('pkgexports/condition')
+ .then(mustCall((actual) => {
+ strictEqual(actual.default, 'from custom condition');
+ }));
+});
diff --git a/test/es-module/test-esm-dynamic-import.js b/test/es-module/test-esm-dynamic-import.js
index e01b86eed143ed..4e87866b2bad3b 100644
--- a/test/es-module/test-esm-dynamic-import.js
+++ b/test/es-module/test-esm-dynamic-import.js
@@ -8,15 +8,11 @@ const absolutePath = require.resolve('../fixtures/es-modules/test-esm-ok.mjs');
const targetURL = new URL('file:///');
targetURL.pathname = absolutePath;
-function expectErrorProperty(result, propertyKey, value) {
- Promise.resolve(result)
- .catch(common.mustCall((error) => {
- assert.strictEqual(error[propertyKey], value);
- }));
-}
-
-function expectModuleError(result, err) {
- expectErrorProperty(result, 'code', err);
+function expectModuleError(result, code, message) {
+ Promise.resolve(result).catch(common.mustCall((error) => {
+ assert.strictEqual(error.code, code);
+ if (message) assert.strictEqual(error.message, message);
+ }));
}
function expectOkNamespace(result) {
@@ -63,4 +59,13 @@ function expectFsNamespace(result) {
'ERR_MODULE_NOT_FOUND');
expectModuleError(import('http://example.com/foo.js'),
'ERR_UNSUPPORTED_ESM_URL_SCHEME');
+ if (common.isWindows) {
+ const msg =
+ 'Only file and data URLs are supported by the default ESM loader. ' +
+ 'On Windows, absolute paths must be valid file:// URLs. ' +
+ "Received protocol 'c:'";
+ expectModuleError(import('C:\\example\\foo.mjs'),
+ 'ERR_UNSUPPORTED_ESM_URL_SCHEME',
+ msg);
+ }
})();
diff --git a/test/es-module/test-esm-exports.mjs b/test/es-module/test-esm-exports.mjs
index a0348d4a1ab0b2..d234099732e3aa 100644
--- a/test/es-module/test-esm-exports.mjs
+++ b/test/es-module/test-esm-exports.mjs
@@ -1,5 +1,6 @@
import { mustCall } from '../common/index.mjs';
import { ok, deepStrictEqual, strictEqual } from 'assert';
+import { sep } from 'path';
import { requireFixture, importFixture } from '../fixtures/pkgexports.mjs';
import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js';
@@ -32,6 +33,9 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js';
{ default: 'self-cjs' } : { default: 'self-mjs' }],
// Resolve self sugar
['pkgexports-sugar', { default: 'main' }],
+ // Path patterns
+ ['pkgexports/subpath/sub-dir1', { default: 'main' }],
+ ['pkgexports/features/dir1', { default: 'main' }]
]);
if (isRequire) {
@@ -118,7 +122,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);
}));
}
@@ -134,9 +139,9 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js';
const notFoundExports = new Map([
// Non-existing file
- ['pkgexports/sub/not-a-file.js', 'pkgexports/sub/not-a-file.js'],
+ ['pkgexports/sub/not-a-file.js', `pkgexports${sep}not-a-file.js`],
// No extension lookups
- ['pkgexports/no-ext', 'pkgexports/no-ext'],
+ ['pkgexports/no-ext', `pkgexports${sep}asdf`],
]);
if (!isRequire) {
@@ -152,16 +157,14 @@ import fromInside from '../fixtures/node_modules/pkgexports/lib/hole.js';
for (const [specifier, request] of notFoundExports) {
loadFixture(specifier).catch(mustCall((err) => {
strictEqual(err.code, (isRequire ? '' : 'ERR_') + 'MODULE_NOT_FOUND');
- // ESM returns a full file path
- assertStartsWith(err.message, isRequire ?
- `Cannot find module '${request}'` :
- 'Cannot find module');
+ assertIncludes(err.message, request);
+ assertStartsWith(err.message, 'Cannot find module');
}));
}
// 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/es-module/test-esm-invalid-pjson.js b/test/es-module/test-esm-invalid-pjson.js
index 83f4ad5baba4a7..9f4711321230bf 100644
--- a/test/es-module/test-esm-invalid-pjson.js
+++ b/test/es-module/test-esm-invalid-pjson.js
@@ -19,11 +19,9 @@ child.on('close', mustCall((code, signal) => {
strictEqual(signal, null);
ok(
stderr.includes(
- [
- '[ERR_INVALID_PACKAGE_CONFIG]: ',
- `Invalid package config ${invalidJson}, `,
- `Unexpected token } in JSON at position ${isWindows ? 16 : 14}`
- ].join(''),
+ `[ERR_INVALID_PACKAGE_CONFIG]: Invalid package config ${invalidJson} ` +
+ `while importing "invalid-pjson" from ${entry}. ` +
+ `Unexpected token } in JSON at position ${isWindows ? 16 : 14}`
),
stderr);
}));
diff --git a/test/fixtures/es-modules/package-cjs-named-error/fail.cjs b/test/fixtures/es-modules/package-cjs-named-error/fail.cjs
index 40c512ab0e5ad2..cab82d3eb60d60 100644
--- a/test/fixtures/es-modules/package-cjs-named-error/fail.cjs
+++ b/test/fixtures/es-modules/package-cjs-named-error/fail.cjs
@@ -1,3 +1,4 @@
module.exports = {
- comeOn: 'fhqwhgads'
+ comeOn: 'fhqwhgads',
+ everybody: 'to the limit',
};
diff --git a/test/fixtures/es-modules/package-cjs-named-error/multi-line.mjs b/test/fixtures/es-modules/package-cjs-named-error/multi-line.mjs
new file mode 100644
index 00000000000000..a4f80eba042576
--- /dev/null
+++ b/test/fixtures/es-modules/package-cjs-named-error/multi-line.mjs
@@ -0,0 +1,4 @@
+import {
+ comeOn,
+ everybody,
+} from './fail.cjs';
diff --git a/test/fixtures/es-modules/pjson-invalid/package.json b/test/fixtures/es-modules/pjson-invalid/package.json
new file mode 100644
index 00000000000000..c91736ab5c7dbc
--- /dev/null
+++ b/test/fixtures/es-modules/pjson-invalid/package.json
@@ -0,0 +1 @@
+syntax error
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..a2224b39ddd2ac
--- /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';
+
diff --git a/test/fixtures/node_modules/pkgexports/custom-condition.js b/test/fixtures/node_modules/pkgexports/custom-condition.js
new file mode 100644
index 00000000000000..63d77460d8d6b7
--- /dev/null
+++ b/test/fixtures/node_modules/pkgexports/custom-condition.js
@@ -0,0 +1 @@
+module.exports = 'from custom condition';
diff --git a/test/fixtures/node_modules/pkgexports/package.json b/test/fixtures/node_modules/pkgexports/package.json
index b99e5c7b79f6a8..240122d4aaec95 100644
--- a/test/fixtures/node_modules/pkgexports/package.json
+++ b/test/fixtures/node_modules/pkgexports/package.json
@@ -21,7 +21,10 @@
"./nofallback2": [null, {}, "builtin:x"],
"./nodemodules": "./node_modules/internalpkg/x.js",
"./condition": [{
- "custom-condition": "./custom-condition.mjs",
+ "custom-condition": {
+ "import": "./custom-condition.mjs",
+ "require": "./custom-condition.js"
+ },
"import": "///overridden",
"require": {
"require": {
@@ -44,6 +47,8 @@
"require": "./resolve-self-invalid.js",
"import": "./resolve-self-invalid.mjs"
},
- "./subpath/": "./subpath/"
+ "./subpath/": "./subpath/",
+ "./subpath/sub-*": "./subpath/dir1/*.js",
+ "./features/*": "./subpath/*/*.js"
}
}
diff --git a/test/fixtures/self_ref_module/index.js b/test/fixtures/self_ref_module/index.js
new file mode 100644
index 00000000000000..7faa73693b54aa
--- /dev/null
+++ b/test/fixtures/self_ref_module/index.js
@@ -0,0 +1,4 @@
+'use strict'
+
+module.exports = 'Self resolution working';
+
diff --git a/test/fixtures/self_ref_module/package.json b/test/fixtures/self_ref_module/package.json
new file mode 100644
index 00000000000000..7280b184c71357
--- /dev/null
+++ b/test/fixtures/self_ref_module/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "self_ref",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "exports": "./index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC"
+}
diff --git a/test/message/esm_loader_not_found_cjs_hint_bare.out b/test/message/esm_loader_not_found_cjs_hint_bare.out
index 77c5248bb59423..a4691dfb928a0e 100644
--- a/test/message/esm_loader_not_found_cjs_hint_bare.out
+++ b/test/message/esm_loader_not_found_cjs_hint_bare.out
@@ -6,7 +6,6 @@ internal/modules/run_main.js:*
Error [ERR_MODULE_NOT_FOUND]: Cannot find module '*test*fixtures*node_modules*some_module*obj' imported from *test*fixtures*esm_loader_not_found_cjs_hint_bare.mjs
Did you mean to import some_module/obj.js?
at finalizeResolution (internal/modules/esm/resolve.js:*:*)
- 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:*:*)
diff --git a/test/parallel/test-preload-self-referential.js b/test/parallel/test-preload-self-referential.js
new file mode 100644
index 00000000000000..2624527deb3984
--- /dev/null
+++ b/test/parallel/test-preload-self-referential.js
@@ -0,0 +1,20 @@
+'use strict';
+
+const common = require('../common');
+const fixtures = require('../common/fixtures');
+const assert = require('assert');
+const { exec } = require('child_process');
+
+const nodeBinary = process.argv[0];
+
+if (!common.isMainThread)
+ common.skip('process.chdir is not available in Workers');
+
+const selfRefModule = fixtures.path('self_ref_module');
+const fixtureA = fixtures.path('printA.js');
+
+exec(`"${nodeBinary}" -r self_ref "${fixtureA}"`, { cwd: selfRefModule },
+ (err, stdout, stderr) => {
+ assert.ifError(err);
+ assert.strictEqual(stdout, 'A\n');
+ });
diff --git a/test/parallel/test-repl-require-self-referential.js b/test/parallel/test-repl-require-self-referential.js
new file mode 100644
index 00000000000000..7ced6dbf11721e
--- /dev/null
+++ b/test/parallel/test-repl-require-self-referential.js
@@ -0,0 +1,25 @@
+'use strict';
+
+const common = require('../common');
+const fixtures = require('../common/fixtures');
+const assert = require('assert');
+const { spawn } = require('child_process');
+
+if (!common.isMainThread)
+ common.skip('process.chdir is not available in Workers');
+
+const selfRefModule = fixtures.path('self_ref_module');
+const child = spawn(process.execPath,
+ ['--interactive'],
+ { cwd: selfRefModule }
+);
+let output = '';
+child.stdout.on('data', (chunk) => output += chunk);
+child.on('exit', common.mustCall(() => {
+ const results = output.replace(/^> /mg, '').split('\n').slice(2);
+ assert.deepStrictEqual(results, [ "'Self resolution working'", '' ]);
+}));
+
+child.stdin.write('require("self_ref");\n');
+child.stdin.write('.exit');
+child.stdin.end();