Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 0 additions & 7 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,6 @@ added: v6.0.0
Enable FIPS-compliant crypto at startup. (Requires Node.js to be built with
`./configure --openssl-fips`.)

### `--experimental-modules`
<!-- YAML
added: v8.5.0
-->

Enable experimental ES module support and caching modules.

### `--experimental-repl-await`
<!-- YAML
added: v10.0.0
Expand Down
16 changes: 8 additions & 8 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -1788,14 +1788,6 @@ A Unix group or user identifier that does not exist was passed.

An invalid or unknown encoding option was passed to an API.

<a id="ERR_UNKNOWN_FILE_EXTENSION"></a>
### ERR_UNKNOWN_FILE_EXTENSION

> Stability: 1 - Experimental

An attempt was made to load a module with an unknown or unsupported file
extension.

<a id="ERR_UNKNOWN_MODULE_FORMAT"></a>
### ERR_UNKNOWN_MODULE_FORMAT

Expand All @@ -1809,6 +1801,14 @@ An attempt was made to load a module with an unknown or unsupported format.
An invalid or unknown process signal was passed to an API expecting a valid
signal (such as [`subprocess.kill()`][]).

<a id="ERR_UNSUPPORTED_FILE_EXTENSION"></a>
### ERR_UNSUPPORTED_FILE_EXTENSION

> Stability: 1 - Experimental

An attempt was made to load a module with an unknown or unsupported file
extension.

<a id="ERR_V8BREAKITERATOR"></a>
### ERR_V8BREAKITERATOR

Expand Down
186 changes: 160 additions & 26 deletions doc/api/esm.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,10 @@ interoperability support, specifier resolution, and default behavior.

<!-- type=misc -->

The `--experimental-modules` flag can be used to enable features for loading
ESM modules.

Once this has been set, files ending with `.mjs` will be able to be loaded
as ES Modules.
Modules are enabled by default for files ending with `.mjs`:

```sh
node --experimental-modules my-app.mjs
node my-app.mjs
```

## Features
Expand Down Expand Up @@ -55,10 +51,6 @@ property:

## Notable differences between `import` and `require`

### Only Support for .mjs

ESM must have the `.mjs` extension.

### Mandatory file extensions

You must provide a file extension when using the `import` keyword.
Expand Down Expand Up @@ -157,37 +149,179 @@ The resolver has the following properties:

### Resolver Algorithm

The algorithm to resolve an ES module specifier is provided through
_ESM_RESOLVE_:
The algorithm to load an ES module specifier is given through the
**ESM_RESOLVE** method below. It returns the resolved URL for a
module specifier relative to a parentURL, in addition to the unique module
format for that resolved URL given by the **ESM_FORMAT** routine.

The _"esm"_ format is returned for an ECMAScript Module, while the
_"legacy"_ format is used to indicate loading through the legacy
CommonJS loader. Additional formats such as _"wasm"_ or _"addon"_ can be
extended in future updates.

**ESM_RESOLVE**(_specifier_, _parentURL_)
> 1. Let _resolvedURL_ be _undefined_.
> 1. If _specifier_ is a valid URL then,
In the following algorithms, all subroutine errors are propogated as errors
of these top-level routines.

_isMain_ is **true** when resolving the Node.js application entry point.

**ESM_RESOLVE(_specifier_, _parentURL_, _isMain_)**
> 1. Let _resolvedURL_ be **undefined**.
> 1. If _specifier_ is a valid URL, then
> 1. Set _resolvedURL_ to the result of parsing and reserializing
> _specifier_ as a URL.
> 1. Otherwise, if _specifier_ starts with _"/"_, _"./"_ or _"../"_ then,
> 1. Otherwise, if _specifier_ starts with _"/"_, then
> 1. Throw an _Invalid Specifier_ error.
> 1. Otherwise, if _specifier_ starts with _"./"_ or _"../"_, then
> 1. Set _resolvedURL_ to the URL resolution of _specifier_ relative to
> _parentURL_.
> 1. Otherwise,
> 1. Note: _specifier_ is now a bare specifier.
> 1. Set _resolvedURL_ the result of
> **PACKAGE_RESOLVE**(_specifier_, _parentURL_).
> 1. If the file at _resolvedURL_ does not exist then,
> 1. If the file at _resolvedURL_ does not exist, then
> 1. Throw a _Module Not Found_ error.
> 1. Return _resolvedURL_.

**PACKAGE_RESOLVE**(_packageSpecifier_, _parentURL_)
> 1. Assert: _packageSpecifier_ is a bare specifier.
> 1. If _packageSpecifier_ is a Node.js builtin module then,
> 1. Let _format_ be the result of **ESM_FORMAT**(_url_, _isMain_).
> 1. Load _resolvedURL_ as module format, _format_.

PACKAGE_RESOLVE(_packageSpecifier_, _parentURL_)
> 1. Let _packageName_ be *undefined*.
> 1. Let _packageSubpath_ be *undefined*.
> 1. If _packageSpecifier_ is an empty string, then
> 1. Throw an _Invalid 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 Specifier_ error.
> 1. Set _packageName_ to the substring of _packageSpecifier_
> until the second _"/"_ separator or the end of the string.
> 1. Let _packageSubpath_ be the substring of _packageSpecifier_ from the
> position at the length of _packageName_ plus one, if any.
> 1. Assert: _packageName_ is a valid package name or scoped package name.
> 1. Assert: _packageSubpath_ is either empty, or a path without a leading
> separator.
> 1. If _packageSubpath_ contains any _"."_ or _".."_ segments or percent
> encoded strings for _"/"_ or _"\"_ then,
> 1. Throw an _Invalid Specifier_ error.
> 1. If _packageSubpath_ is empty and _packageName_ is a Node.js builtin
> module, then
> 1. Return the string _"node:"_ concatenated with _packageSpecifier_.
> 1. While _parentURL_ contains a non-empty _pathname_,
> 1. While _parentURL_ is not the file system root,
> 1. Set _parentURL_ to the parent folder URL of _parentURL_.
> 1. Let _packageURL_ be the URL resolution of the string concatenation of
> _parentURL_, _"/node_modules/"_ and _"packageSpecifier"_.
> 1. If the file at _packageURL_ exists then,
> 1. Return _packageURL_.
> _parentURL_, _"/node_modules/"_ and _packageSpecifier_.
> 1. If the folder at _packageURL_ does not exist, then
> 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 empty, then
> 1. Return the result of **PACKAGE_MAIN_RESOLVE**(_packageURL_,
> _pjson_).
> 1. Otherwise,
> 1. If **HAS_ESM_PROPERTIES**(_pjson_) is *true*, then
> 1. Return **PACKAGE_EXPORTS_RESOLVE**(_packageURL_, _packagePath_,
> _pjson_).
> 1. Return the URL resolution of _packagePath_ in _packageURL_.
> 1. Throw a _Module Not Found_ error.

PACKAGE_MAIN_RESOLVE(_packageURL_, _pjson_)
> 1. Let _pjsonURL_ be the URL of the file _"package.json"_ within the parent
> path _packageURL_.
> 1. If **HAS_ESM_PROPERTIES**(_pjson_) is **false**, then
> 1. Let _mainURL_ be the result applying the legacy
> **LOAD_AS_DIRECTORY** CommonJS resolver to _packageURL_, throwing a
> _Module Not Found_ error for no resolution.
> 1. Return _mainURL_.
> 1. If _pjson.exports_ is a String, then
> 1. Return the URL of _pjson.exports_ within the parent path _packageURL_.
> 1. Assert: _pjson.exports_ is an Object.
> 1. If _pjson.exports["."]_ is a String, then
> 1. Let _target_ be _pjson.exports["."]_.
> 1. If **IS_VALID_EXPORTS_TARGET**(_target_) is **false**, then
> 1. Emit an _"Invalid Exports Target"_ warning.
> 1. Otherwise,
> 1. Return the URL of _pjson.exports.default_ within the parent path
> _packageURL_.
> 1. Return **null**.

PACKAGE_EXPORTS_RESOLVE(_packageURL_, _packagePath_, _pjson_)
> 1. Assert: _pjson_ is not **null**.
> 1. If _pjson.exports_ is a String, then
> 1. Throw a _Module Not Found_ error.
> 1. Assert: _pjson.exports_ is an Object.
> 1. Set _packagePath_ to _"./"_ concatenated with _packagePath_.
> 1. If _packagePath_ is a key of _pjson.exports_, then
> 1. Let _target_ be the value of _pjson.exports[packagePath]_.
> 1. If **IS_VALID_EXPORTS_TARGET**(_target_) is **false**, then
> 1. Emit an _"Invalid Exports Target"_ warning.
> 1. Throw a _Module Not Found_ error.
> 1. Return the URL resolution of the concatenation of _packageURL_ and
> _target_.
> 1. Let _directoryKeys_ be the list of keys of _pjson.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 _pjson.exports[directory]_.
> 1. If **IS_VALID_EXPORTS_TARGET**(_target_) is **false** or _target_
> does not end in _"/"_, then
> 1. Emit an _"Invalid Exports Target"_ warning.
> 1. Continue the loop.
> 1. Let _subpath_ be the substring of _target_ starting at the index of
> the length of _directory_.
> 1. Return the URL resolution of the concatenation of _packageURL_,
> _target_ and _subpath_.
> 1. Throw a _Module Not Found_ error.

IS_VALID_EXPORTS_TARGET(_target_)
> 1. If _target_ is not a valid String, then
> 1. Return **false**.
> 1. If _target_ does not start with _"./"_, then
> 1. Return **false**.
> 1. If _target_ contains any _".."_ or _"."_ path segments, then
> 1. Return **false**.
> 1. If _target_ contains any percent-encoded characters for _"/"_ or _"\"_,
> then
> 1. Return **false**.
> 1. Return **true**.

**ESM_FORMAT(_url_, _isMain_)**
> 1. Assert: _url_ corresponds to an existing file.
> 1. Let _pjson_ be the result of **READ_PACKAGE_BOUNDARY**(_url_).
> 1. If _pjson_ is **null** and _isMain_ is **true**, then
> 1. Return _"legacy"_.
> 1. If **HAS_ESM_PROPERTIES**(_pjson_) is **true**, then
> 1. If _url_ does not end in _".js"_ or _".mjs"_, then
> 1. Throw an _Unsupported File Extension_ error.
> 1. Return _"esm"_.
> 1. Otherwise,
> 1. If _url_ ends in _".mjs"_, then
> 1. Return _"esm"_.
> 1. Otherwise,
> 1. Return _"legacy"_.

READ_PACKAGE_BOUNDARY(_url_)
> 1. Let _boundaryURL_ be _url_.
> 1. While _boundaryURL_ is not the file system root,
> 1. Let _pjson_ be the result of **READ_PACKAGE_JSON**(_boundaryURL_).
> 1. If _pjson_ is not **null**, then
> 1. Return _pjson_.
> 1. Set _boundaryURL_ to the parent URL of _boundaryURL_.
> 1. Return **null**.

READ_PACKAGE_JSON(_packageURL_)
> 1. Let _pjsonURL_ be the resolution of _"package.json"_ within _packageURL_.
> 1. If the file at _pjsonURL_ does not exist, then
> 1. Return **null**.
> 1. If the file at _packageURL_ does not parse as valid JSON, then
> 1. Throw an _Invalid Package Configuration_ error.
> 1. Return the parsed JSON source of the file at _url_.

HAS_ESM_PROPERTIES(_pjson_)
> 1. If _pjson_ is not **null** and _pjson.exports_ is a String or Object, then
> 1. Return *true*.
> 1. Return *false*.

[Node.js EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md
[`module.createRequireFromPath()`]: modules.html#modules_module_createrequirefrompath_filename
[ESM Minimal Kernel]: https://github.com/nodejs/modules/blob/master/doc/plan-for-new-modules-implementation.md
2 changes: 1 addition & 1 deletion lib/internal/bootstrap/loaders.js
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ NativeModule.prototype.compile = function() {
const fn = compileFunction(id);
fn(this.exports, requireFn, this, process, internalBinding);

if (config.experimentalModules && this.canBeRequiredByUsers) {
if (this.canBeRequiredByUsers) {
this.proxifyExports();
}

Expand Down
29 changes: 15 additions & 14 deletions lib/internal/bootstrap/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,15 +217,7 @@ function startup() {
'DeprecationWarning', 'DEP0062', startup, true);
}

const experimentalModules = getOptionValue('--experimental-modules');
if (experimentalModules) {
if (experimentalModules) {
process.emitWarning(
'The ESM module loader is experimental.',
'ExperimentalWarning', undefined);
}
NativeModule.require('internal/process/esm_loader').setup();
}
NativeModule.require('internal/process/esm_loader').setup();

const { deprecate } = NativeModule.require('internal/util');
{
Expand Down Expand Up @@ -425,11 +417,20 @@ function executeUserCode() {
process.exit(0);
}

// Note: this actually tries to run the module as a ESM first if
// --experimental-modules is on.
// TODO(joyeecheung): can we move that logic to here? Note that this
// is an undocumented method available via `require('module').runMain`
CJSModule.runMain();
// Load the main module--the command line argument.
const { pathToFileURL } = NativeModule.require('url');
const asyncESM = NativeModule.require('internal/process/esm_loader');
const decorateErrorStack =
NativeModule.require('internal/util').decorateErrorStack;
asyncESM.loaderPromise.then((loader) =>
loader.import(pathToFileURL(process.argv[1]).pathname)
)
.catch((e) => {
decorateErrorStack(e);
console.error(e);
process.exit(1);
});
process._tickCallback();
return;
}

Expand Down
11 changes: 2 additions & 9 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -842,13 +842,6 @@ E('ERR_MISSING_ARGS',
E('ERR_MISSING_DYNAMIC_INSTANTIATE_HOOK',
'The ES Module loader may not return a format of \'dynamic\' when no ' +
'dynamicInstantiate function was provided', Error);
E('ERR_MODULE_NOT_FOUND', (module, base, legacyResolution) => {
let msg = `Cannot find module '${module}' imported from ${base}.`;
if (legacyResolution)
msg += ' Legacy behavior in require() would have found it at ' +
legacyResolution;
return msg;
}, Error);
E('ERR_MULTIPLE_CALLBACK', 'Callback called multiple times', Error);
E('ERR_NAPI_CONS_FUNCTION', 'Constructor must be a function', TypeError);
E('ERR_NAPI_INVALID_DATAVIEW_ARGS',
Expand Down Expand Up @@ -947,10 +940,10 @@ E('ERR_UNKNOWN_CREDENTIAL', '%s identifier does not exist: %s', Error);
E('ERR_UNKNOWN_ENCODING', 'Unknown encoding: %s', TypeError);

// This should probably be a `TypeError`.
E('ERR_UNKNOWN_FILE_EXTENSION', 'Unknown file extension: \'%s\' imported ' +
'from %s', Error);
E('ERR_UNKNOWN_MODULE_FORMAT', 'Unknown module format: %s', RangeError);
E('ERR_UNKNOWN_SIGNAL', 'Unknown signal: %s', TypeError);
E('ERR_UNSUPPORTED_FILE_EXTENSION', 'Unsupported file extension: \'%s\' ' +
'imported from %s', Error);

E('ERR_V8BREAKITERATOR',
'Full ICU data not installed. See https://github.com/nodejs/node/wiki/Intl',
Expand Down
Loading