@@ -17,15 +17,17 @@ specifier resolution, and default behavior.
17
17
18
18
<!-- type=misc -->
19
19
20
- The ` --experimental-modules ` flag can be used to enable features for loading
21
- ECMAScript modules.
20
+ The ` --experimental-modules ` flag can be used to enable support for
21
+ ECMAScript modules (ES modules) .
22
22
23
- Once this has been set, there are a few different ways to run a file as an ES
24
- module:
23
+ ## Running Node.js with an ECMAScript Module
25
24
26
- ### < code >.mjs</ code > extension
25
+ There are a few ways to start Node.js with an ES module as its input.
27
26
28
- Files ending with ` .mjs ` will be loaded as ES modules.
27
+ ### Initial entry point with an <code >.mjs</code > extension
28
+
29
+ A file ending with ` .mjs ` passed to Node.js as an initial entry point will be
30
+ loaded as an ES module.
29
31
30
32
``` sh
31
33
node --experimental-modules my-app.mjs
@@ -55,7 +57,7 @@ to Node.js via `STDIN`.
55
57
node --experimental-modules --type=module --eval \
56
58
" import { sep } from 'path'; console.log(sep);"
57
59
58
- coffee --print file-containing- import-statements.coffee | \
60
+ echo " import { sep } from 'path'; console.log(sep); " | \
59
61
node --experimental-modules --type=module
60
62
```
61
63
@@ -111,11 +113,11 @@ import './startup/init.js';
111
113
// Loaded as ES module since ./startup contains no package.json file,
112
114
// and therefore inherits the ES module package scope from one level up
113
115
114
- import ' ./node_modules/ commonjs-package/index.js ' ;
116
+ import ' commonjs-package' ;
115
117
// Loaded as CommonJS since ./node_modules/commonjs-package/package.json
116
118
// lacks a "type" field or contains "type": "commonjs"
117
119
118
- import ' commonjs-package' ;
120
+ import ' ./node_modules/ commonjs-package/index.js ' ;
119
121
// Loaded as CommonJS since ./node_modules/commonjs-package/package.json
120
122
// lacks a "type" field or contains "type": "commonjs"
121
123
```
@@ -172,23 +174,52 @@ An attempt to `require` the above `es-module-package` would attempt to load
172
174
an error as Node.js would not be able to parse the ` export ` statement in
173
175
CommonJS.
174
176
175
- Even if the ` package.json ` ` "main" ` points to a file ending in ` .mjs ` , the
176
- ` "type": "module" ` is required.
177
-
178
177
As with ` import ` statements, for ES module usage the value of ` "main" ` must be
179
178
a full path including extension: ` "./index.mjs" ` , not ` "./index" ` .
180
179
181
- > Currently a package can define _ either_ a CommonJS entry point or an ES module
182
- > entry point; there is no way to specify separate entry points for CommonJS and
183
- > ES module usage. This means that a package entry point can be included via
184
- > ` require ` or via ` import ` but not both.
180
+ > Currently a package can define _ either_ a CommonJS entry point ** or ** an ES
181
+ > module entry point; there is no way to specify separate entry points for
182
+ > CommonJS and ES module usage. This means that a package entry point can be
183
+ > included via ` require ` or via ` import ` but not both.
185
184
>
186
185
> Such a limitation makes it difficult for packages to support both new versions
187
186
> of Node.js that understand ES modules and older versions of Node.js that
188
187
> understand only CommonJS. There is work ongoing to remove this limitation, and
189
188
> it will very likely entail changes to the behavior of ` "main" ` as defined
190
189
> here.
191
190
191
+ ## <code >import</code > Specifiers
192
+
193
+ ### Terminology
194
+
195
+ The _ specifier_ of an ` import ` statement is the string after the ` from ` keyword,
196
+ e.g. ` 'path' ` in ` import { sep } from 'path' ` . Specifiers are also used in
197
+ ` export from ` statements, and as the argument to an ` import() ` expression.
198
+
199
+ There are four types of specifiers:
200
+
201
+ - _ Bare specifiers_ like ` 'some-package' ` . They refer to an entry point of a
202
+ package by the package name.
203
+
204
+ - _ Deep import specifiers_ like ` 'some-package/lib/shuffle.mjs' ` . They refer to
205
+ a path within a package prefixed by the package name.
206
+
207
+ - _ Relative specifiers_ like ` './startup.js' ` or ` '../config.mjs' ` . They refer
208
+ to a path relative to the location of the importing file.
209
+
210
+ - _ Absolute specifiers_ like ` 'file:///opt/nodejs/config.js' ` . They refer
211
+ directly and explicitly to a full path.
212
+
213
+ Bare specifiers, and the bare specifier portion of deep import specifiers, are
214
+ strings; but everything else in a specifier is a URL.
215
+
216
+ Only ` file:// ` URLs are supported. A specifier like
217
+ ` 'https://example.com/app.js' ` may be supported by browsers but it is not
218
+ supported in Node.js.
219
+
220
+ Specifiers may not begin with ` / ` or ` // ` . These are reserved for potential
221
+ future use. The root of the current volume may be referenced via ` file:/// ` .
222
+
192
223
## import.meta
193
224
194
225
* {Object}
@@ -282,9 +313,9 @@ import { sin, cos } from 'geometry/trigonometry-functions.mjs';
282
313
>
283
314
> <!-- eslint-disable no-duplicate-imports -->
284
315
> ``` js
285
- > import _ from ' underscore ' ; // Works
316
+ > import packageMain from ' commonjs-package ' ; // Works
286
317
>
287
- > import { shuffle } from ' underscore ' ; // Errors
318
+ > import { method } from ' commonjs-package ' ; // Errors
288
319
> ` ` `
289
320
>
290
321
> There are ongoing efforts to make the latter code possible.
@@ -349,6 +380,127 @@ fs.readFileSync = () => Buffer.from('Hello, ESM');
349
380
fs .readFileSync === readFileSync;
350
381
` ` `
351
382
383
+ ## Experimental Loader hooks
384
+
385
+ **Note: This API is currently being redesigned and will still change.**.
386
+
387
+ <!-- type=misc -->
388
+
389
+ To customize the default module resolution, loader hooks can optionally be
390
+ provided via a ` -- loader ./ loader- name .mjs ` argument to Node.js.
391
+
392
+ When hooks are used they only apply to ES module loading and not to any
393
+ CommonJS modules loaded.
394
+
395
+ ### Resolve hook
396
+
397
+ The resolve hook returns the resolved file URL and module format for a
398
+ given module specifier and parent file URL:
399
+
400
+ ` ` ` js
401
+ const baseURL = new URL (' file://' );
402
+ baseURL .pathname = ` ${ process .cwd ()} /` ;
403
+
404
+ export async function resolve (specifier ,
405
+ parentModuleURL = baseURL ,
406
+ defaultResolver ) {
407
+ return {
408
+ url: new URL (specifier, parentModuleURL).href ,
409
+ format: ' esm'
410
+ };
411
+ }
412
+ ` ` `
413
+
414
+ The ` parentModuleURL` is provided as ` undefined ` when performing main Node.js
415
+ load itself.
416
+
417
+ The default Node.js ES module resolution function is provided as a third
418
+ argument to the resolver for easy compatibility workflows.
419
+
420
+ In addition to returning the resolved file URL value, the resolve hook also
421
+ returns a ` format` property specifying the module format of the resolved
422
+ module. This can be one of the following:
423
+
424
+ | ` format` | Description |
425
+ | --- | --- |
426
+ | ` ' module' ` | Load a standard JavaScript module |
427
+ | ` ' commonjs' ` | Load a Node.js CommonJS module |
428
+ | ` ' builtin' ` | Load a Node.js builtin module |
429
+ | ` ' dynamic' ` | Use a [dynamic instantiate hook][] |
430
+
431
+ For example, a dummy loader to load JavaScript restricted to browser resolution
432
+ rules with only JS file extension and Node.js builtin modules support could
433
+ be written:
434
+
435
+ ` ` ` js
436
+ import path from ' path' ;
437
+ import process from ' process' ;
438
+ import Module from ' module' ;
439
+
440
+ const builtins = Module .builtinModules ;
441
+ const JS_EXTENSIONS = new Set ([' .js' , ' .mjs' ]);
442
+
443
+ const baseURL = new URL (' file://' );
444
+ baseURL .pathname = ` ${ process .cwd ()} /` ;
445
+
446
+ export function resolve (specifier , parentModuleURL = baseURL , defaultResolve ) {
447
+ if (builtins .includes (specifier)) {
448
+ return {
449
+ url: specifier,
450
+ format: ' builtin'
451
+ };
452
+ }
453
+ if (/ ^ \. {0,2} [/] / .test (specifier) !== true && ! specifier .startsWith (' file:' )) {
454
+ // For node_modules support:
455
+ // return defaultResolve(specifier, parentModuleURL);
456
+ throw new Error (
457
+ ` imports must begin with '/', './', or '../'; '${ specifier} ' does not` );
458
+ }
459
+ const resolved = new URL (specifier, parentModuleURL);
460
+ const ext = path .extname (resolved .pathname );
461
+ if (! JS_EXTENSIONS .has (ext)) {
462
+ throw new Error (
463
+ ` Cannot load file with non-JavaScript file extension ${ ext} .` );
464
+ }
465
+ return {
466
+ url: resolved .href ,
467
+ format: ' esm'
468
+ };
469
+ }
470
+ ` ` `
471
+
472
+ With this loader, running:
473
+
474
+ ` ` ` console
475
+ NODE_OPTIONS = ' --experimental-modules --loader ./custom-loader.mjs' node x .js
476
+ ` ` `
477
+
478
+ would load the module ` x .js ` as an ES module with relative resolution support
479
+ (with ` node_modules` loading skipped in this example).
480
+
481
+ ### Dynamic instantiate hook
482
+
483
+ To create a custom dynamic module that doesn't correspond to one of the
484
+ existing ` format` interpretations, the ` dynamicInstantiate` hook can be used.
485
+ This hook is called only for modules that return ` format: ' dynamic' ` from
486
+ the ` resolve` hook.
487
+
488
+ ` ` ` js
489
+ export async function dynamicInstantiate (url ) {
490
+ return {
491
+ exports: [' customExportName' ],
492
+ execute : (exports ) => {
493
+ // Get and set functions provided for pre-allocated export names
494
+ exports .customExportName .set (' value' );
495
+ }
496
+ };
497
+ }
498
+ ` ` `
499
+
500
+ With the list of module exports provided upfront, the ` execute` function will
501
+ then be called at the exact point of module evaluation order for that module
502
+ in the import tree.
503
+
352
504
## Resolution Algorithm
353
505
354
506
### Features
@@ -385,6 +537,9 @@ entirely for the CommonJS loader.
385
537
If the top-level ` -- type` is _"module"_, then the ESM resolver is used
386
538
as described here, with the conditional ` -- type` check in **ESM_FORMAT**.
387
539
540
+ <details>
541
+ <summary>Resolver algorithm psuedocode</summary>
542
+
388
543
**ESM_RESOLVE(_specifier_, _parentURL_, _isMain_)**
389
544
> 1. Let _resolvedURL_ be **undefined**.
390
545
> 1. If _specifier_ is a valid URL, then
@@ -500,126 +655,7 @@ READ_PACKAGE_JSON(_packageURL_)
500
655
> 1. Throw an _Invalid Package Configuration_ error.
501
656
> 1. Return the parsed JSON source of the file at _pjsonURL_.
502
657
503
- ## Experimental Loader hooks
504
-
505
- **Note: This API is currently being redesigned and will still change.**.
506
-
507
- <!-- type=misc -->
508
-
509
- To customize the default module resolution, loader hooks can optionally be
510
- provided via a ` -- loader ./ loader- name .mjs ` argument to Node.js.
511
-
512
- When hooks are used they only apply to ES module loading and not to any
513
- CommonJS modules loaded.
514
-
515
- ### Resolve hook
516
-
517
- The resolve hook returns the resolved file URL and module format for a
518
- given module specifier and parent file URL:
519
-
520
- ` ` ` js
521
- const baseURL = new URL (' file://' );
522
- baseURL .pathname = ` ${ process .cwd ()} /` ;
523
-
524
- export async function resolve (specifier ,
525
- parentModuleURL = baseURL ,
526
- defaultResolver ) {
527
- return {
528
- url: new URL (specifier, parentModuleURL).href ,
529
- format: ' esm'
530
- };
531
- }
532
- ` ` `
533
-
534
- The ` parentModuleURL` is provided as ` undefined ` when performing main Node.js
535
- load itself.
536
-
537
- The default Node.js ES module resolution function is provided as a third
538
- argument to the resolver for easy compatibility workflows.
539
-
540
- In addition to returning the resolved file URL value, the resolve hook also
541
- returns a ` format` property specifying the module format of the resolved
542
- module. This can be one of the following:
543
-
544
- | ` format` | Description |
545
- | --- | --- |
546
- | ` ' module' ` | Load a standard JavaScript module |
547
- | ` ' commonjs' ` | Load a Node.js CommonJS module |
548
- | ` ' builtin' ` | Load a Node.js builtin module |
549
- | ` ' dynamic' ` | Use a [dynamic instantiate hook][] |
550
-
551
- For example, a dummy loader to load JavaScript restricted to browser resolution
552
- rules with only JS file extension and Node.js builtin modules support could
553
- be written:
554
-
555
- ` ` ` js
556
- import path from ' path' ;
557
- import process from ' process' ;
558
- import Module from ' module' ;
559
-
560
- const builtins = Module .builtinModules ;
561
- const JS_EXTENSIONS = new Set ([' .js' , ' .mjs' ]);
562
-
563
- const baseURL = new URL (' file://' );
564
- baseURL .pathname = ` ${ process .cwd ()} /` ;
565
-
566
- export function resolve (specifier , parentModuleURL = baseURL , defaultResolve ) {
567
- if (builtins .includes (specifier)) {
568
- return {
569
- url: specifier,
570
- format: ' builtin'
571
- };
572
- }
573
- if (/ ^ \. {0,2} [/] / .test (specifier) !== true && ! specifier .startsWith (' file:' )) {
574
- // For node_modules support:
575
- // return defaultResolve(specifier, parentModuleURL);
576
- throw new Error (
577
- ` imports must begin with '/', './', or '../'; '${ specifier} ' does not` );
578
- }
579
- const resolved = new URL (specifier, parentModuleURL);
580
- const ext = path .extname (resolved .pathname );
581
- if (! JS_EXTENSIONS .has (ext)) {
582
- throw new Error (
583
- ` Cannot load file with non-JavaScript file extension ${ ext} .` );
584
- }
585
- return {
586
- url: resolved .href ,
587
- format: ' esm'
588
- };
589
- }
590
- ` ` `
591
-
592
- With this loader, running:
593
-
594
- ` ` ` console
595
- NODE_OPTIONS = ' --experimental-modules --loader ./custom-loader.mjs' node x .js
596
- ` ` `
597
-
598
- would load the module ` x .js ` as an ES module with relative resolution support
599
- (with ` node_modules` loading skipped in this example).
600
-
601
- ### Dynamic instantiate hook
602
-
603
- To create a custom dynamic module that doesn't correspond to one of the
604
- existing ` format` interpretations, the ` dynamicInstantiate` hook can be used.
605
- This hook is called only for modules that return ` format: ' dynamic' ` from
606
- the ` resolve` hook.
607
-
608
- ` ` ` js
609
- export async function dynamicInstantiate (url ) {
610
- return {
611
- exports: [' customExportName' ],
612
- execute : (exports ) => {
613
- // Get and set functions provided for pre-allocated export names
614
- exports .customExportName .set (' value' );
615
- }
616
- };
617
- }
618
- ` ` `
619
-
620
- With the list of module exports provided upfront, the ` execute` function will
621
- then be called at the exact point of module evaluation order for that module
622
- in the import tree.
658
+ </details>
623
659
624
660
[Node.js EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md
625
661
[dynamic instantiate hook]: #esm_dynamic_instantiate_hook
0 commit comments