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