1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
|
# ECMAScript Modules
<!--introduced_in=v8.5.0-->
> Stability: 1 - Experimental
<!--name=esm-->
Node.js contains support for ES Modules based upon the
[Node.js EP for ES Modules][].
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.
## Enabling
<!-- 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.
```sh
node --experimental-modules my-app.mjs
```
## Features
<!-- type=misc -->
### Supported
Only the CLI argument for the main entry point to the program can be an entry
point into an ESM graph. In the future `import()` can be used to create entry
points into ESM graphs at run time.
### Unsupported
| Feature | Reason |
| --- | --- |
| `require('./foo.mjs')` | ES Modules have differing resolution and timing, use language standard `import()` |
| `import()` | pending newer V8 release used in Node.js |
| `import.meta` | pending V8 implementation |
## Notable differences between `import` and `require`
### No NODE_PATH
`NODE_PATH` is not part of resolving `import` specifiers. Please use symlinks
if this behavior is desired.
### No `require.extensions`
`require.extensions` is not used by `import`. The expectation is that loader
hooks can provide this workflow in the future.
### No `require.cache`
`require.cache` is not used by `import`. It has a separate cache.
### URL based paths
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.
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"
```
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`.
Modules loaded this way will only be loaded once, even if their query
or fragment string differs between `import` statements.
When loaded via `import` these modules will provide a single `default` export
representing the value of `module.exports` at the time they finished evaluating.
```js
import fs from 'fs';
fs.readFile('./foo.txt', (err, body) => {
if (err) {
console.error(err);
} else {
console.log(body);
}
});
```
## 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.
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
import url from 'url';
export async function resolve(specifier, parentModuleURL, defaultResolver) {
return {
url: new URL(specifier, parentModuleURL).href,
format: 'esm'
};
}
```
The default NodeJS 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 |
| `"commonjs"` | 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 builtin modules support could
be written:
```js
import url from 'url';
import path from 'path';
import process from 'process';
import Module from 'module';
const builtins = Module.builtinModules;
const JS_EXTENSIONS = new Set(['.js', '.mjs']);
export function resolve(specifier, parentModuleURL/*, 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.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.
[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
|