@@ -184,6 +184,19 @@ unspecified.
184
184
185
185
### Package Entry Points
186
186
187
+ There are two fields that can define entry points for a package: ` "main" ` and
188
+ ` "exports" ` . The ` "main" ` field is supported in all versions of Node.js, but its
189
+ capabilities are limited: it only defines the main entry point of the package.
190
+ The ` "exports" ` field, part of [ Package Exports] [ ] , provides an alternative to
191
+ ` "main" ` where the package main entry point can be defined while also
192
+ encapsulating the package, preventing any other entry points besides those
193
+ defined in ` "exports" ` . If package entry points are defined in both ` "main" ` and
194
+ ` "exports" ` , the latter takes precedence in versions of Node.js that support
195
+ ` "exports" ` . [ Conditional Exports] [ ] can also be used within ` "exports" ` to
196
+ define different package entry points per environment.
197
+
198
+ #### <code >package.json</code > <code >"main"</code >
199
+
187
200
The ` package.json ` ` "main" ` field defines the entry point for a package,
188
201
whether the package is included into CommonJS via ` require ` or into an ES
189
202
module via ` import ` .
@@ -219,7 +232,7 @@ The `"main"` field can point to exactly one file, regardless of whether the
219
232
package is referenced via ` require ` (in a CommonJS context) or ` import ` (in an
220
233
ES module context).
221
234
222
- ### Package Exports
235
+ #### Package Exports
223
236
224
237
By default, all subpaths from a package can be imported (` import 'pkg/x.js' ` ).
225
238
Custom subpath aliasing and encapsulation can be provided through the
@@ -423,9 +436,6 @@ thrown:
423
436
424
437
### Dual CommonJS/ES Module Packages
425
438
426
- _ These patterns are currently experimental and only work under the
427
- ` --experimental-conditional-exports ` flag._
428
-
429
439
Prior to the introduction of support for ES modules in Node.js, it was a common
430
440
pattern for package authors to include both CommonJS and ES module JavaScript
431
441
sources in their package, with ` package.json ` ` "main" ` specifying the CommonJS
@@ -434,61 +444,36 @@ This enabled Node.js to run the CommonJS entry point while build tools such as
434
444
bundlers used the ES module entry point, since Node.js ignored (and still
435
445
ignores) the top-level ` "module" ` field.
436
446
437
- Node.js can now run ES module entry points, and using [ Conditional Exports] [ ]
438
- with the ` --experimental-conditional-exports ` flag it is possible to define
439
- separate package entry points for CommonJS and ES module consumers. Unlike in
440
- the scenario where ` "module" ` is only used by bundlers, or ES module files are
447
+ Node.js can now run ES module entry points, and a package can contain both
448
+ CommonJS and ES module entry points (either via separate specifiers such as
449
+ ` 'pkg' ` and ` 'pkg/es-module' ` , or both at the same specifier via [ Conditional
450
+ Exports] [ ] with the ` --experimental-conditional-exports ` flag). Unlike in the
451
+ scenario where ` "module" ` is only used by bundlers, or ES module files are
441
452
transpiled into CommonJS on the fly before evaluation by Node.js, the files
442
453
referenced by the ES module entry point are evaluated as ES modules.
443
454
444
- #### Divergent Specifier Hazard
455
+ #### Dual Package Hazard
445
456
446
457
When an application is using a package that provides both CommonJS and ES module
447
458
sources, there is a risk of certain bugs if both versions of the package get
448
- loaded (for example, because one version is imported by the application and the
449
- other version is required by one of the application’s dependencies). Such a
450
- package might look like this:
451
-
452
- <!-- eslint-skip -->
453
- ``` js
454
- // ./node_modules/pkg/package.json
455
- {
456
- " type" : " module" ,
457
- " main" : " ./pkg.cjs" ,
458
- " exports" : {
459
- " require" : " ./pkg.cjs" ,
460
- " default" : " ./pkg.mjs"
461
- }
462
- }
463
- ```
464
-
465
- In this example, ` require('pkg') ` always resolves to ` pkg.cjs ` , including in
466
- versions of Node.js where ES modules are unsupported. In Node.js where ES
467
- modules are supported, ` import 'pkg' ` references ` pkg.mjs ` .
468
-
469
- The potential for bugs comes from the fact that the ` pkg ` created by `const pkg
470
- = require('pkg')` is not the same as the ` pkg` created by ` import pkg from
471
- 'pkg'` . This is the “divergent specifier hazard,” where one specifer ( ` 'pkg'`)
472
- resolves to separate files (` pkg.cjs ` and ` pkg.mjs ` ) in separate module systems,
473
- yet both versions might get loaded within an application because Node.js
474
- supports intermixing CommonJS and ES modules.
475
-
476
- If the export is a constructor, an ` instanceof ` comparison of instances created
477
- by the two returns ` false ` , and if the export is an object, properties added to
478
- one (like ` pkg.foo = 3 ` ) are not present on the other. This differs from how
479
- ` import ` and ` require ` statements work in all-CommonJS or all-ES module
480
- environments, respectively, and therefore is surprising to users. It also
481
- differs from the behavior users are familiar with when using transpilation via
482
- tools like [ Babel] [ ] or [ ` esm ` ] [ ] .
483
-
484
- Even if the user consistently uses either ` require ` or ` import ` to refer to
485
- ` pkg ` , if any dependencies of the application use the other method the hazard is
486
- still present.
487
-
488
- The ` --experimental-conditional-exports ` flag should be set for modern Node.js
489
- for this behavior to work out. If it is not set, only the ES module version can
490
- be used in modern Node.js and the package will throw when accessed via
491
- ` require() ` .
459
+ loaded. This potential comes from the fact that the ` pkgInstance ` created by
460
+ ` const pkgInstance = require('pkg') ` is not the same as the ` pkgInstance `
461
+ created by ` import pkgInstance from 'pkg' ` (or an alternative main path like
462
+ ` 'pkg/module' ` ). This is the “dual package hazard,” where two versions of the
463
+ same package can be loaded within the same runtime environment. While it is
464
+ unlikely that an application or package would intentionally load both versions
465
+ directly, it is common for an application to load one version while a dependency
466
+ of the application loads the other version. This hazard can happen because
467
+ Node.js supports intermixing CommonJS and ES modules, and can lead to unexpected
468
+ behavior.
469
+
470
+ If the package main export is a constructor, an ` instanceof ` comparison of
471
+ instances created by the two versions returns ` false ` , and if the export is an
472
+ object, properties added to one (like ` pkgInstance.foo = 3 ` ) are not present on
473
+ the other. This differs from how ` import ` and ` require ` statements work in
474
+ all-CommonJS or all-ES module environments, respectively, and therefore is
475
+ surprising to users. It also differs from the behavior users are familiar with
476
+ when using transpilation via tools like [ Babel] [ ] or [ ` esm ` ] [ ] .
492
477
493
478
#### Writing Dual Packages While Avoiding or Minimizing Hazards
494
479
@@ -524,8 +509,14 @@ following conditions:
524
509
525
510
Write the package in CommonJS or transpile ES module sources into CommonJS, and
526
511
create an ES module wrapper file that defines the named exports. Using
527
- [ Conditional Exports] [ ] , the ES module wrapper is used for ` import ` and the
528
- CommonJS entry point for ` require ` .
512
+ [ Conditional Exports] [ ] via the ` --experimental-conditional-exports ` flag, the
513
+ ES module wrapper is used for ` import ` and the CommonJS entry point for
514
+ ` require ` .
515
+
516
+ > Note: While ` --experimental-conditional-exports ` is flagged, a package
517
+ > using this pattern will throw when loaded via ` require() ` in modern
518
+ > Node.js, unless package consumers use the ` --experimental-conditional-exports `
519
+ > flag.
529
520
530
521
<!-- eslint-skip -->
531
522
``` js
@@ -581,17 +572,37 @@ This approach is appropriate for any of the following use cases:
581
572
* The package stores internal state, and the package author would prefer not to
582
573
refactor the package to isolate its state management. See the next section.
583
574
584
- A variant of this approach would add an export, e.g. ` "./module" ` , to point to
585
- an all-ES module-syntax version the package. This could be used via `import
575
+ A variant of this approach not requiring ` --experimental-conditional-exports `
576
+ for consumers could be to add an export, e.g. ` "./module" ` , to point to an
577
+ all-ES module-syntax version of the package. This could be used via `import
586
578
'pkg/module'` by users who are certain that the CommonJS version will not be
587
579
loaded anywhere in the application, such as by dependencies; or if the CommonJS
588
580
version can be loaded but doesn’t affect the ES module version (for example,
589
- because the package is stateless).
581
+ because the package is stateless):
582
+
583
+ <!-- eslint-skip -->
584
+ ``` js
585
+ // ./node_modules/pkg/package.json
586
+ {
587
+ " type" : " module" ,
588
+ " main" : " ./index.cjs" ,
589
+ " exports" : {
590
+ " ." : " ./index.cjs" ,
591
+ " ./module" : " ./wrapper.mjs"
592
+ }
593
+ }
594
+ ```
595
+
596
+ If the ` --experimental-conditional-exports ` flag is dropped and therefore
597
+ [ Conditional Exports] [ ] become available without a flag, this variant could be
598
+ easily updated to use conditional exports by adding conditions to the ` "." `
599
+ path; while keeping ` "./module" ` for backward compatibility.
590
600
591
601
##### Approach #2 : Isolate State
592
602
593
603
The most straightforward ` package.json ` would be one that defines the separate
594
- CommonJS and ES module entry points directly:
604
+ CommonJS and ES module entry points directly (requires
605
+ ` --experimental-conditional-exports ` ):
595
606
596
607
<!-- eslint-skip -->
597
608
``` js
@@ -672,6 +683,28 @@ This approach is appropriate for any of the following use cases:
672
683
Even with isolated state, there is still the cost of possible extra code
673
684
execution between the CommonJS and ES module versions of a package.
674
685
686
+ As with the previous approach, a variant of this approach not requiring
687
+ `--experimental-conditional-exports` for consumers could be to add an export,
688
+ e.g. `"./module"`, to point to an all-ES module-syntax version of the package:
689
+
690
+ <!-- eslint-skip -->
691
+ ```js
692
+ // ./node_modules/pkg/package.json
693
+ {
694
+ " type" : " module" ,
695
+ " main" : " ./index.cjs" ,
696
+ " exports" : {
697
+ " ." : " ./index.cjs" ,
698
+ " ./module" : " ./index.mjs"
699
+ }
700
+ }
701
+ ```
702
+
703
+ If the ` --experimental-conditional-exports ` flag is dropped and therefore
704
+ [ Conditional Exports] [ ] become available without a flag, this variant could be
705
+ easily updated to use conditional exports by adding conditions to the ` "." `
706
+ path; while keeping ` "./module" ` for backward compatibility.
707
+
675
708
## ` import ` Specifiers
676
709
677
710
### Terminology
@@ -1363,6 +1396,7 @@ success!
1363
1396
[ECMAScript-modules implementation]: https://github.com/nodejs/modules/blob/master/doc/plan-for-new-modules-implementation.md
1364
1397
[ES Module Integration Proposal for Web Assembly]: https://github.com/webassembly/esm-integration
1365
1398
[Node.js EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md
1399
+ [Package Exports]: #esm_package_exports
1366
1400
[Terminology]: #esm_terminology
1367
1401
[WHATWG JSON modules specification]: https://html.spec.whatwg.org/#creating-a-json-module-script
1368
1402
[` " exports" ` field]: #esm_package_exports
0 commit comments