2017-06-06 02:44:56 +02:00
|
|
|
# ECMAScript Modules
|
|
|
|
|
2017-11-04 09:08:46 +01:00
|
|
|
<!--introduced_in=v8.5.0-->
|
2018-04-14 04:05:56 +02:00
|
|
|
<!-- type=misc -->
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
|
|
> Stability: 1 - Experimental
|
|
|
|
|
|
|
|
<!--name=esm-->
|
|
|
|
|
2017-09-10 14:13:47 +02:00
|
|
|
Node.js contains support for ES Modules based upon the
|
|
|
|
[Node.js EP for ES Modules][].
|
2017-06-06 02:44:56 +02:00
|
|
|
|
2017-09-10 14:13:47 +02:00
|
|
|
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.
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
|
|
## Enabling
|
|
|
|
|
|
|
|
<!-- type=misc -->
|
|
|
|
|
2017-09-10 14:13:47 +02:00
|
|
|
The `--experimental-modules` flag can be used to enable features for loading
|
|
|
|
ESM modules.
|
2017-06-06 02:44:56 +02:00
|
|
|
|
2017-09-10 14:13:47 +02:00
|
|
|
Once this has been set, files ending with `.mjs` will be able to be loaded
|
|
|
|
as ES Modules.
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
|
|
```sh
|
|
|
|
node --experimental-modules my-app.mjs
|
|
|
|
```
|
|
|
|
|
|
|
|
## Features
|
|
|
|
|
|
|
|
<!-- type=misc -->
|
|
|
|
|
|
|
|
### Supported
|
|
|
|
|
2017-09-10 14:13:47 +02:00
|
|
|
Only the CLI argument for the main entry point to the program can be an entry
|
2018-01-25 23:32:43 +01:00
|
|
|
point into an ESM graph. Dynamic import can also be used to create entry points
|
|
|
|
into ESM graphs at runtime.
|
2017-06-06 02:44:56 +02:00
|
|
|
|
2018-04-15 15:05:55 +02:00
|
|
|
#### import.meta
|
|
|
|
|
|
|
|
* {Object}
|
2017-12-24 16:26:24 +01:00
|
|
|
|
|
|
|
The `import.meta` metaproperty is an `Object` that contains the following
|
|
|
|
property:
|
2018-04-15 15:05:55 +02:00
|
|
|
|
|
|
|
* `url` {string} The absolute `file:` URL of the module.
|
2017-12-24 16:26:24 +01:00
|
|
|
|
2017-06-06 02:44:56 +02:00
|
|
|
### Unsupported
|
|
|
|
|
|
|
|
| Feature | Reason |
|
|
|
|
| --- | --- |
|
2017-11-30 02:15:47 +01:00
|
|
|
| `require('./foo.mjs')` | ES Modules have differing resolution and timing, use dynamic import |
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
|
|
## Notable differences between `import` and `require`
|
|
|
|
|
|
|
|
### No NODE_PATH
|
|
|
|
|
2017-09-10 14:13:47 +02:00
|
|
|
`NODE_PATH` is not part of resolving `import` specifiers. Please use symlinks
|
|
|
|
if this behavior is desired.
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
|
|
### No `require.extensions`
|
|
|
|
|
2017-09-10 14:13:47 +02:00
|
|
|
`require.extensions` is not used by `import`. The expectation is that loader
|
|
|
|
hooks can provide this workflow in the future.
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
|
|
### No `require.cache`
|
|
|
|
|
|
|
|
`require.cache` is not used by `import`. It has a separate cache.
|
|
|
|
|
|
|
|
### URL based paths
|
|
|
|
|
2017-09-10 14:13:47 +02:00
|
|
|
ESM are resolved and cached based upon [URL](https://url.spec.whatwg.org/)
|
|
|
|
semantics. This means that files containing special characters such as `#` and
|
|
|
|
`?` need to be escaped.
|
2017-06-06 02:44:56 +02:00
|
|
|
|
2017-09-10 14:13:47 +02:00
|
|
|
Modules will be loaded multiple times if the `import` specifier used to resolve
|
|
|
|
them have a different query or fragment.
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
|
|
```js
|
|
|
|
import './foo?query=1'; // loads ./foo with query of "?query=1"
|
|
|
|
import './foo?query=2'; // loads ./foo with query of "?query=2"
|
|
|
|
```
|
|
|
|
|
|
|
|
For now, only modules using the `file:` protocol can be loaded.
|
|
|
|
|
|
|
|
## Interop with existing modules
|
|
|
|
|
|
|
|
All CommonJS, JSON, and C++ modules can be used with `import`.
|
|
|
|
|
2017-09-10 14:13:47 +02:00
|
|
|
Modules loaded this way will only be loaded once, even if their query
|
|
|
|
or fragment string differs between `import` statements.
|
2017-06-06 02:44:56 +02:00
|
|
|
|
2017-09-10 14:13:47 +02:00
|
|
|
When loaded via `import` these modules will provide a single `default` export
|
|
|
|
representing the value of `module.exports` at the time they finished evaluating.
|
2017-06-06 02:44:56 +02:00
|
|
|
|
|
|
|
```js
|
|
|
|
import fs from 'fs';
|
|
|
|
fs.readFile('./foo.txt', (err, body) => {
|
|
|
|
if (err) {
|
|
|
|
console.error(err);
|
|
|
|
} else {
|
|
|
|
console.log(body);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
```
|
|
|
|
|
2017-09-03 13:20:06 +02:00
|
|
|
## Loader hooks
|
|
|
|
|
|
|
|
<!-- type=misc -->
|
|
|
|
|
|
|
|
To customize the default module resolution, loader hooks can optionally be
|
2018-03-04 14:46:49 +01:00
|
|
|
provided via a `--loader ./loader-name.mjs` argument to Node.js.
|
2017-09-03 13:20:06 +02:00
|
|
|
|
|
|
|
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
|
2018-02-12 12:02:42 +01:00
|
|
|
const baseURL = new URL('file://');
|
2018-02-17 03:58:50 +01:00
|
|
|
baseURL.pathname = `${process.cwd()}/`;
|
2017-09-03 13:20:06 +02:00
|
|
|
|
2018-02-12 12:02:42 +01:00
|
|
|
export async function resolve(specifier,
|
|
|
|
parentModuleURL = baseURL,
|
|
|
|
defaultResolver) {
|
2017-09-03 13:20:06 +02:00
|
|
|
return {
|
|
|
|
url: new URL(specifier, parentModuleURL).href,
|
|
|
|
format: 'esm'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
2018-02-12 08:31:55 +01:00
|
|
|
The parentURL is provided as `undefined` when performing main Node.js load
|
|
|
|
itself.
|
2018-02-12 12:02:42 +01:00
|
|
|
|
|
|
|
The default Node.js ES module resolution function is provided as a third
|
2017-09-03 13:20:06 +02:00
|
|
|
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
|
2017-10-22 06:03:18 +02:00
|
|
|
module. This can be one of the following:
|
2017-09-03 13:20:06 +02:00
|
|
|
|
2017-10-22 06:03:18 +02:00
|
|
|
| `format` | Description |
|
|
|
|
| --- | --- |
|
2018-04-02 07:38:48 +02:00
|
|
|
| `'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][] |
|
2017-10-22 06:03:18 +02:00
|
|
|
|
|
|
|
For example, a dummy loader to load JavaScript restricted to browser resolution
|
2018-03-04 14:46:49 +01:00
|
|
|
rules with only JS file extension and Node.js builtin modules support could
|
2017-09-03 13:20:06 +02:00
|
|
|
be written:
|
|
|
|
|
|
|
|
```js
|
|
|
|
import path from 'path';
|
|
|
|
import process from 'process';
|
2017-11-29 09:58:11 +01:00
|
|
|
import Module from 'module';
|
2017-09-03 13:20:06 +02:00
|
|
|
|
2017-11-29 09:58:11 +01:00
|
|
|
const builtins = Module.builtinModules;
|
2017-09-03 13:20:06 +02:00
|
|
|
const JS_EXTENSIONS = new Set(['.js', '.mjs']);
|
|
|
|
|
2018-02-12 12:02:42 +01:00
|
|
|
const baseURL = new URL('file://');
|
2018-02-17 03:58:50 +01:00
|
|
|
baseURL.pathname = `${process.cwd()}/`;
|
2018-02-12 12:02:42 +01:00
|
|
|
|
|
|
|
export function resolve(specifier, parentModuleURL = baseURL, defaultResolve) {
|
2017-11-29 09:58:11 +01:00
|
|
|
if (builtins.includes(specifier)) {
|
2017-09-03 13:20:06 +02:00
|
|
|
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`);
|
|
|
|
}
|
2018-01-21 17:11:47 +01:00
|
|
|
const resolved = new URL(specifier, parentModuleURL);
|
2017-09-03 13:20:06 +02:00
|
|
|
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:
|
|
|
|
|
2017-10-22 17:51:14 +02:00
|
|
|
```console
|
2017-09-03 13:20:06 +02:00
|
|
|
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.
|
2018-04-02 07:38:48 +02:00
|
|
|
This hook is called only for modules that return `format: 'dynamic'` from
|
2017-09-03 13:20:06 +02:00
|
|
|
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
|
2017-12-31 10:45:43 +01:00
|
|
|
then be called at the exact point of module evaluation order for that module
|
2017-09-03 13:20:06 +02:00
|
|
|
in the import tree.
|
|
|
|
|
2017-09-10 14:13:47 +02:00
|
|
|
[Node.js EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md
|
2017-10-22 06:03:18 +02:00
|
|
|
[addons]: addons.html
|
|
|
|
[dynamic instantiate hook]: #esm_dynamic_instantiate_hook
|