8
8
<!-- name=esm-->
9
9
10
10
Node.js contains support for ES Modules based upon the
11
- [ Node.js EP for ES Modules] [ ] .
11
+ [ Node.js EP for ES Modules] [ ] and the [ ESM Minimal Kernel ] [ ] .
12
12
13
- Not all features of the EP are complete and will be landing as both VM support
14
- and implementation is ready. Error messages are still being polished.
13
+ The minimal feature set is designed to be compatible with all potential
14
+ future implementations. Expect major changes in the implementation including
15
+ interoperability support, specifier resolution, and default behavior.
15
16
16
17
## Enabling
17
18
@@ -54,6 +55,10 @@ property:
54
55
55
56
## Notable differences between ` import ` and ` require `
56
57
58
+ ### Mandatory file extensions
59
+
60
+ You must provide a file extension when using the ` import ` keyword.
61
+
57
62
### No NODE_PATH
58
63
59
64
` NODE_PATH ` is not part of resolving ` import ` specifiers. Please use symlinks
@@ -78,31 +83,32 @@ Modules will be loaded multiple times if the `import` specifier used to resolve
78
83
them have a different query or fragment.
79
84
80
85
``` js
81
- import ' ./foo?query=1' ; // loads ./foo with query of "?query=1"
82
- import ' ./foo?query=2' ; // loads ./foo with query of "?query=2"
86
+ import ' ./foo.mjs ?query=1' ; // loads ./foo.mjs with query of "?query=1"
87
+ import ' ./foo.mjs ?query=2' ; // loads ./foo.mjs with query of "?query=2"
83
88
```
84
89
85
90
For now, only modules using the ` file: ` protocol can be loaded.
86
91
87
- ## Interop with existing modules
92
+ ## CommonJS, JSON, and Native Modules
88
93
89
- All CommonJS, JSON, and C++ modules can be used with ` import ` .
94
+ CommonJS, JSON, and Native modules can be used with [ ` module.createRequireFromPath() ` ] [ ] .
90
95
91
- Modules loaded this way will only be loaded once, even if their query
92
- or fragment string differs between ` import ` statements.
96
+ ``` js
97
+ // cjs.js
98
+ module .exports = ' cjs' ;
93
99
94
- When loaded via ` import ` these modules will provide a single ` default ` export
95
- representing the value of ` module.exports ` at the time they finished evaluating.
100
+ // esm.mjs
101
+ import { createRequireFromPath as createRequire } from ' module' ;
102
+ import { fileURLToPath as fromPath } from ' url' ;
96
103
97
- ``` js
98
- // foo.js
99
- module .exports = { one: 1 };
104
+ const require = createRequire (fromPath (import .meta.url));
100
105
101
- // bar.mjs
102
- import foo from ' ./foo.js' ;
103
- foo .one === 1 ; // true
106
+ const cjs = require (' ./cjs' );
107
+ cjs === ' cjs' ; // true
104
108
` ` `
105
109
110
+ ## Builtin modules
111
+
106
112
Builtin modules will provide named exports of their public API, as well as a
107
113
default export which can be used for, among other things, modifying the named
108
114
exports. Named exports of builtin modules are updated when the corresponding
@@ -132,7 +138,160 @@ fs.readFileSync = () => Buffer.from('Hello, ESM');
132
138
fs .readFileSync === readFileSync;
133
139
` ` `
134
140
135
- ## Loader hooks
141
+ ## Resolution Algorithm
142
+
143
+ ### Features
144
+
145
+ The resolver has the following properties:
146
+
147
+ * FileURL-based resolution as is used by ES modules
148
+ * Support for builtin module loading
149
+ * Relative and absolute URL resolution
150
+ * No default extensions
151
+ * No folder mains
152
+ * Bare specifier package resolution lookup through node_modules
153
+
154
+ ### Resolver Algorithm
155
+
156
+ The algorithm to load an ES module specifier is given through the
157
+ **ESM_RESOLVE** method below. It returns the resolved URL for a
158
+ module specifier relative to a parentURL, in addition to the unique module
159
+ format for that resolved URL given by the **ESM_FORMAT** routine.
160
+
161
+ The _"module"_ format is returned for an ECMAScript Module, while the
162
+ _"commonjs"_ format is used to indicate loading through the legacy
163
+ CommonJS loader. Additional formats such as _"wasm"_ or _"addon"_ can be
164
+ extended in future updates.
165
+
166
+ In the following algorithms, all subroutine errors are propogated as errors
167
+ of these top-level routines.
168
+
169
+ _isMain_ is **true** when resolving the Node.js application entry point.
170
+
171
+ If the top-level ` -- type` is _"commonjs"_, then the ESM resolver is skipped
172
+ entirely for the CommonJS loader.
173
+
174
+ If the top-level ` -- type` is _"module"_, then the ESM resolver is used
175
+ as described here, with the conditional ` -- type` check in **ESM_FORMAT**.
176
+
177
+ **ESM_RESOLVE(_specifier_, _parentURL_, _isMain_)**
178
+ > 1. Let _resolvedURL_ be **undefined**.
179
+ > 1. If _specifier_ is a valid URL, then
180
+ > 1. Set _resolvedURL_ to the result of parsing and reserializing
181
+ > _specifier_ as a URL.
182
+ > 1. Otherwise, if _specifier_ starts with _"/"_, then
183
+ > 1. Throw an _Invalid Specifier_ error.
184
+ > 1. Otherwise, if _specifier_ starts with _"./"_ or _"../"_, then
185
+ > 1. Set _resolvedURL_ to the URL resolution of _specifier_ relative to
186
+ > _parentURL_.
187
+ > 1. Otherwise,
188
+ > 1. Note: _specifier_ is now a bare specifier.
189
+ > 1. Set _resolvedURL_ the result of
190
+ > **PACKAGE_RESOLVE**(_specifier_, _parentURL_).
191
+ > 1. If the file at _resolvedURL_ does not exist, then
192
+ > 1. Throw a _Module Not Found_ error.
193
+ > 1. Set _resolvedURL_ to the real path of _resolvedURL_.
194
+ > 1. Let _format_ be the result of **ESM_FORMAT**(_resolvedURL_, _isMain_).
195
+ > 1. Load _resolvedURL_ as module format, _format_.
196
+
197
+ PACKAGE_RESOLVE(_packageSpecifier_, _parentURL_)
198
+ > 1. Let _packageName_ be *undefined*.
199
+ > 1. Let _packageSubpath_ be *undefined*.
200
+ > 1. If _packageSpecifier_ is an empty string, then
201
+ > 1. Throw an _Invalid Specifier_ error.
202
+ > 1. If _packageSpecifier_ does not start with _"@"_, then
203
+ > 1. Set _packageName_ to the substring of _packageSpecifier_ until the
204
+ > first _"/"_ separator or the end of the string.
205
+ > 1. Otherwise,
206
+ > 1. If _packageSpecifier_ does not contain a _"/"_ separator, then
207
+ > 1. Throw an _Invalid Specifier_ error.
208
+ > 1. Set _packageName_ to the substring of _packageSpecifier_
209
+ > until the second _"/"_ separator or the end of the string.
210
+ > 1. Let _packageSubpath_ be the substring of _packageSpecifier_ from the
211
+ > position at the length of _packageName_ plus one, if any.
212
+ > 1. Assert: _packageName_ is a valid package name or scoped package name.
213
+ > 1. Assert: _packageSubpath_ is either empty, or a path without a leading
214
+ > separator.
215
+ > 1. If _packageSubpath_ contains any _"."_ or _".."_ segments or percent
216
+ > encoded strings for _"/"_ or _"\" _ then,
217
+ > 1. Throw an _Invalid Specifier_ error.
218
+ > 1. If _packageSubpath_ is empty and _packageName_ is a Node.js builtin
219
+ > module, then
220
+ > 1. Return the string _"node:"_ concatenated with _packageSpecifier_.
221
+ > 1. While _parentURL_ is not the file system root,
222
+ > 1. Set _parentURL_ to the parent folder URL of _parentURL_.
223
+ > 1. Let _packageURL_ be the URL resolution of the string concatenation of
224
+ > _parentURL_, _"/node_modules/"_ and _packageSpecifier_.
225
+ > 1. If the folder at _packageURL_ does not exist, then
226
+ > 1. Set _parentURL_ to the parent URL path of _parentURL_.
227
+ > 1. Continue the next loop iteration.
228
+ > 1. Let _pjson_ be the result of **READ_PACKAGE_JSON**(_packageURL_).
229
+ > 1. If _packageSubpath_ is empty, then
230
+ > 1. Return the result of **PACKAGE_MAIN_RESOLVE**(_packageURL_,
231
+ > _pjson_).
232
+ > 1. Otherwise,
233
+ > 1. Return the URL resolution of _packageSubpath_ in _packageURL_.
234
+ > 1. Throw a _Module Not Found_ error.
235
+
236
+ PACKAGE_MAIN_RESOLVE(_packageURL_, _pjson_)
237
+ > 1. If _pjson_ is **null**, then
238
+ > 1. Throw a _Module Not Found_ error.
239
+ > 1. If _pjson.main_ is a String, then
240
+ > 1. Let _resolvedMain_ be the concatenation of _packageURL_, "/", and
241
+ > _pjson.main_.
242
+ > 1. If the file at _resolvedMain_ exists, then
243
+ > 1. Return _resolvedMain_.
244
+ > 1. If _pjson.type_ is equal to _"module"_, then
245
+ > 1. Throw a _Module Not Found_ error.
246
+ > 1. Let _legacyMainURL_ be the result applying the legacy
247
+ > **LOAD_AS_DIRECTORY** CommonJS resolver to _packageURL_, throwing a
248
+ > _Module Not Found_ error for no resolution.
249
+ > 1. If _legacyMainURL_ does not end in _".js"_ then,
250
+ > 1. Throw an _Unsupported File Extension_ error.
251
+ > 1. Return _legacyMainURL_.
252
+
253
+ **ESM_FORMAT(_url_, _isMain_)**
254
+ > 1. Assert: _url_ corresponds to an existing file.
255
+ > 1. If _isMain_ is **true** and the ` -- type` flag is _"module"_, then
256
+ > 1. If _url_ ends with _".cjs"_, then
257
+ > 1. Throw a _Type Mismatch_ error.
258
+ > 1. Return _"module"_.
259
+ > 1. Let _pjson_ be the result of **READ_PACKAGE_BOUNDARY**(_url_).
260
+ > 1. If _pjson_ is **null** and _isMain_ is **true**, then
261
+ > 1. If _url_ ends in _".mjs"_, then
262
+ > 1. Return _"module"_.
263
+ > 1. Return _"commonjs"_.
264
+ > 1. If _pjson.type_ exists and is _"module"_, then
265
+ > 1. If _url_ ends in _".cjs"_, then
266
+ > 1. Return _"commonjs"_.
267
+ > 1. Return _"module"_.
268
+ > 1. Otherwise,
269
+ > 1. If _url_ ends in _".mjs"_, then
270
+ > 1. Return _"module"_.
271
+ > 1. If _url_ does not end in _".js"_, then
272
+ > 1. Throw an _Unsupported File Extension_ error.
273
+ > 1. Return _"commonjs"_.
274
+
275
+ READ_PACKAGE_BOUNDARY(_url_)
276
+ > 1. Let _boundaryURL_ be _url_.
277
+ > 1. While _boundaryURL_ is not the file system root,
278
+ > 1. Let _pjson_ be the result of **READ_PACKAGE_JSON**(_boundaryURL_).
279
+ > 1. If _pjson_ is not **null**, then
280
+ > 1. Return _pjson_.
281
+ > 1. Set _boundaryURL_ to the parent URL of _boundaryURL_.
282
+ > 1. Return **null**.
283
+
284
+ READ_PACKAGE_JSON(_packageURL_)
285
+ > 1. Let _pjsonURL_ be the resolution of _"package.json"_ within _packageURL_.
286
+ > 1. If the file at _pjsonURL_ does not exist, then
287
+ > 1. Return **null**.
288
+ > 1. If the file at _packageURL_ does not parse as valid JSON, then
289
+ > 1. Throw an _Invalid Package Configuration_ error.
290
+ > 1. Return the parsed JSON source of the file at _pjsonURL_.
291
+
292
+ ## Experimental Loader hooks
293
+
294
+ **Note: This API is currently being redesigned and will still change.**.
136
295
137
296
<!-- type=misc -->
138
297
@@ -173,11 +332,9 @@ module. This can be one of the following:
173
332
174
333
| ` format` | Description |
175
334
| --- | --- |
176
- | ` 'esm' ` | Load a standard JavaScript module |
177
- | ` 'cjs' ` | Load a node-style CommonJS module |
178
- | ` 'builtin' ` | Load a node builtin CommonJS module |
179
- | ` 'json' ` | Load a JSON file |
180
- | ` 'addon' ` | Load a [ C++ Addon] [ addons ] |
335
+ | ` ' module' ` | Load a standard JavaScript module |
336
+ | ` ' commonjs' ` | Load a Node.js CommonJS module |
337
+ | ` ' builtin' ` | Load a Node.js builtin module |
181
338
| ` ' dynamic' ` | Use a [dynamic instantiate hook][] |
182
339
183
340
For example, a dummy loader to load JavaScript restricted to browser resolution
@@ -254,5 +411,6 @@ then be called at the exact point of module evaluation order for that module
254
411
in the import tree.
255
412
256
413
[Node.js EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md
257
- [ addons ] : addons.html
258
414
[dynamic instantiate hook]: #esm_dynamic_instantiate_hook
415
+ [` module .createRequireFromPath ()` ]: modules.html#modules_module_createrequirefrompath_filename
416
+ [ESM Minimal Kernel]: https://github.com/nodejs/modules/blob/master/doc/plan-for-new-modules-implementation.md
0 commit comments