Skip to content
This repository has been archived by the owner on Apr 16, 2020. It is now read-only.

another conflict #18

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ module.exports = {
files: [
'doc/api/esm.md',
'*.mjs',
'test/es-module/test-esm-example-loader.js',
],
parserOptions: { sourceType: 'module' },
},
Expand Down
17 changes: 0 additions & 17 deletions doc/api/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,13 +97,6 @@ added: v10.0.0

Enable experimental top-level `await` keyword support in REPL.

### `--experimental-vm-modules`
<!-- YAML
added: v9.6.0
-->

Enable experimental ES Module support in the `vm` module.

### `--experimental-worker`
<!-- YAML
added: v10.5.0
Expand Down Expand Up @@ -174,13 +167,6 @@ default) is not firewall-protected.**

See the [debugging security implications][] section for more information.

### `--loader=file`
<!-- YAML
added: v9.0.0
-->

Specify the `file` of the custom [experimental ECMAScript Module][] loader.

### `--napi-modules`
<!-- YAML
added: v7.10.0
Expand Down Expand Up @@ -596,14 +582,12 @@ Node.js options that are allowed are:
- `--enable-fips`
- `--experimental-modules`
- `--experimental-repl-await`
- `--experimental-vm-modules`
- `--experimental-worker`
- `--force-fips`
- `--icu-data-dir`
- `--inspect`
- `--inspect-brk`
- `--inspect-port`
- `--loader`
- `--napi-modules`
- `--no-deprecation`
- `--no-force-async-hooks-checks`
Expand Down Expand Up @@ -788,7 +772,6 @@ greater than `4` (its current default value). For more information, see the
[debugger]: debugger.html
[debugging security implications]: https://nodejs.org/en/docs/guides/debugging-getting-started/#security-implications
[emit_warning]: process.html#process_process_emitwarning_warning_type_code_ctor
[experimental ECMAScript Module]: esm.html#esm_loader_hooks
[libuv threadpool documentation]: http://docs.libuv.org/en/latest/threadpool.html
[remote code execution]: https://www.owasp.org/index.php/Code_Injection
[secureProtocol]: tls.html#tls_tls_createsecurecontext_options
13 changes: 3 additions & 10 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -1393,26 +1393,19 @@ a `dynamicInstantiate` hook.
A `MessagePort` was found in the object passed to a `postMessage()` call,
but not provided in the `transferList` for that call.

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

> Stability: 1 - Experimental

An [ES6 module][] could not be resolved.

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

The V8 platform used by this instance of Node.js does not support creating
Workers. This is caused by lack of embedder support for Workers. In particular,
this error will not occur with standard builds of Node.js.

<a id="ERR_MODULE_RESOLUTION_LEGACY"></a>
### ERR_MODULE_RESOLUTION_LEGACY
<a id="ERR_MODULE_NOT_FOUND"></a>
### ERR_MODULE_NOT_FOUND

> Stability: 1 - Experimental

A failure occurred resolving imports in an [ES6 module][].
An [ESM module][] could not be resolved.

<a id="ERR_MULTIPLE_CALLBACK"></a>
### ERR_MULTIPLE_CALLBACK
Expand Down
213 changes: 74 additions & 139 deletions doc/api/esm.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
<!--name=esm-->

Node.js contains support for ES Modules based upon the
[Node.js EP for ES Modules][].
[Node.js EP for ES Modules][] and the [ESM Minimal Kernel][].

Not all features of the EP are complete and will be landing as both VM support
and implementation is ready. Error messages are still being polished.
The minimal feature set is designed to be compatible with all potential
future implementations. Expect major changes in the implementation including
interoperability support, specifier resolution, and default behavior.

## Enabling

Expand Down Expand Up @@ -54,6 +55,14 @@ 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.

### No NODE_PATH

`NODE_PATH` is not part of resolving `import` specifiers. Please use symlinks
Expand All @@ -78,31 +87,32 @@ Modules will be loaded multiple times if the `import` specifier used to resolve
them have a different query or fragment.

```js
import './foo?query=1'; // loads ./foo with query of "?query=1"
import './foo?query=2'; // loads ./foo with query of "?query=2"
import './foo.mjs?query=1'; // loads ./foo.mjs with query of "?query=1"
import './foo.mjs?query=2'; // loads ./foo.mjs with query of "?query=2"
```

For now, only modules using the `file:` protocol can be loaded.

## Interop with existing modules
## CommonJS, JSON, and Native Modules

All CommonJS, JSON, and C++ modules can be used with `import`.
CommonJS, JSON, and Native modules can be used with [`module.createRequireFromPath()`][].

Modules loaded this way will only be loaded once, even if their query
or fragment string differs between `import` statements.
```js
// cjs.js
module.exports = 'cjs';

When loaded via `import` these modules will provide a single `default` export
representing the value of `module.exports` at the time they finished evaluating.
// esm.mjs
import { createRequireFromPath as createRequire } from 'module';
import { fileURLToPath as fromPath } from 'url';

```js
// foo.js
module.exports = { one: 1 };
const require = createRequire(fromPath(import.meta.url));

// bar.js
import foo from './foo.js';
foo.one === 1; // true
const cjs = require('./cjs');
cjs === 'cjs'; // true
```

## Builtin modules

Builtin modules will provide named exports of their public API, as well as a
default export which can be used for, among other things, modifying the named
exports. Named exports of builtin modules are updated when the corresponding
Expand Down Expand Up @@ -132,127 +142,52 @@ fs.readFileSync = () => Buffer.from('Hello, ESM');
fs.readFileSync === readFileSync;
```

## Loader hooks

<!-- type=misc -->

To customize the default module resolution, loader hooks can optionally be
provided via a `--loader ./loader-name.mjs` argument to Node.js.

When hooks are used they only apply to ES module loading and not to any
CommonJS modules loaded.

### Resolve hook

The resolve hook returns the resolved file URL and module format for a
given module specifier and parent file URL:

```js
const baseURL = new URL('file://');
baseURL.pathname = `${process.cwd()}/`;

export async function resolve(specifier,
parentModuleURL = baseURL,
defaultResolver) {
return {
url: new URL(specifier, parentModuleURL).href,
format: 'esm'
};
}
```

The `parentModuleURL` is provided as `undefined` when performing main Node.js
load itself.

The default Node.js ES module resolution function is provided as a third
argument to the resolver for easy compatibility workflows.

In addition to returning the resolved file URL value, the resolve hook also
returns a `format` property specifying the module format of the resolved
module. This can be one of the following:

| `format` | Description |
| --- | --- |
| `'esm'` | Load a standard JavaScript module |
| `'cjs'` | Load a node-style CommonJS module |
| `'builtin'` | Load a node builtin CommonJS module |
| `'json'` | Load a JSON file |
| `'addon'` | Load a [C++ Addon][addons] |
| `'dynamic'` | Use a [dynamic instantiate hook][] |

For example, a dummy loader to load JavaScript restricted to browser resolution
rules with only JS file extension and Node.js builtin modules support could
be written:

```js
import path from 'path';
import process from 'process';
import Module from 'module';

const builtins = Module.builtinModules;
const JS_EXTENSIONS = new Set(['.js', '.mjs']);

const baseURL = new URL('file://');
baseURL.pathname = `${process.cwd()}/`;

export function resolve(specifier, parentModuleURL = baseURL, defaultResolve) {
if (builtins.includes(specifier)) {
return {
url: specifier,
format: 'builtin'
};
}
if (/^\.{0,2}[/]/.test(specifier) !== true && !specifier.startsWith('file:')) {
// For node_modules support:
// return defaultResolve(specifier, parentModuleURL);
throw new Error(
`imports must begin with '/', './', or '../'; '${specifier}' does not`);
}
const resolved = new URL(specifier, parentModuleURL);
const ext = path.extname(resolved.pathname);
if (!JS_EXTENSIONS.has(ext)) {
throw new Error(
`Cannot load file with non-JavaScript file extension ${ext}.`);
}
return {
url: resolved.href,
format: 'esm'
};
}
```

With this loader, running:

```console
NODE_OPTIONS='--experimental-modules --loader ./custom-loader.mjs' node x.js
```

would load the module `x.js` as an ES module with relative resolution support
(with `node_modules` loading skipped in this example).

### Dynamic instantiate hook

To create a custom dynamic module that doesn't correspond to one of the
existing `format` interpretations, the `dynamicInstantiate` hook can be used.
This hook is called only for modules that return `format: 'dynamic'` from
the `resolve` hook.

```js
export async function dynamicInstantiate(url) {
return {
exports: ['customExportName'],
execute: (exports) => {
// get and set functions provided for pre-allocated export names
exports.customExportName.set('value');
}
};
}
```

With the list of module exports provided upfront, the `execute` function will
then be called at the exact point of module evaluation order for that module
in the import tree.
## Resolution Algorithm

### Features

The resolver has the following properties:

* FileURL-based resolution as is used by ES modules
* Support for builtin module loading
* Relative and absolute URL resolution
* No default extensions
* No folder mains
* Bare specifier package resolution lookup through node_modules

### Resolver Algorithm

The algorithm to resolve an ES module specifier is provided through
_ESM_RESOLVE_:

**ESM_RESOLVE**(_specifier_, _parentURL_)
> 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. 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. 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. Return the string _"node:"_ concatenated with _packageSpecifier_.
> 1. While _parentURL_ contains a non-empty _pathname_,
> 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_.
> 1. Throw a _Module Not Found_ error.

[Node.js EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md
[addons]: addons.html
[dynamic instantiate hook]: #esm_dynamic_instantiate_hook
[`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
Loading