@@ -902,273 +902,7 @@ $ node other.js
902902
903903## Dual CommonJS/ES module packages
904904
905- <!-- This section should not be in the API documentation:
906-
907- 1. It teaches opinionated practices that some consider dangerous, see
908- https://github.com/nodejs/node/issues/52174
909- 2. It will soon be obsolete when we unflag --experimental-require-module.
910- 3. It's difficult to understand a multi-file structure via long texts and snippets in
911- a markdown document.
912-
913- TODO(?): Move this section to its own repository with example folders.
914- -->
915-
916- Prior to the introduction of support for ES modules in Node.js, it was a common
917- pattern for package authors to include both CommonJS and ES module JavaScript
918- sources in their package, with ` package.json ` [ ` "main" ` ] [ ] specifying the
919- CommonJS entry point and ` package.json ` ` "module" ` specifying the ES module
920- entry point.
921- This enabled Node.js to run the CommonJS entry point while build tools such as
922- bundlers used the ES module entry point, since Node.js ignored (and still
923- ignores) the top-level ` "module" ` field.
924-
925- Node.js can now run ES module entry points, and a package can contain both
926- CommonJS and ES module entry points (either via separate specifiers such as
927- ` 'pkg' ` and ` 'pkg/es-module' ` , or both at the same specifier via [ Conditional
928- exports] [ ] ). Unlike in the scenario where top-level ` "module" ` field is only used by bundlers,
929- or ES module files are transpiled into CommonJS on the fly before evaluation by
930- Node.js, the files referenced by the ES module entry point are evaluated as ES
931- modules.
932-
933- ### Dual package hazard
934-
935- When an application is using a package that provides both CommonJS and ES module
936- sources, there is a risk of certain bugs if both versions of the package get
937- loaded. This potential comes from the fact that the ` pkgInstance ` created by
938- ` const pkgInstance = require('pkg') ` is not the same as the ` pkgInstance `
939- created by ` import pkgInstance from 'pkg' ` (or an alternative main path like
940- ` 'pkg/module' ` ). This is the “dual package hazard,” where two versions of the
941- same package can be loaded within the same runtime environment. While it is
942- unlikely that an application or package would intentionally load both versions
943- directly, it is common for an application to load one version while a dependency
944- of the application loads the other version. This hazard can happen because
945- Node.js supports intermixing CommonJS and ES modules, and can lead to unexpected
946- behavior.
947-
948- If the package main export is a constructor, an ` instanceof ` comparison of
949- instances created by the two versions returns ` false ` , and if the export is an
950- object, properties added to one (like ` pkgInstance.foo = 3 ` ) are not present on
951- the other. This differs from how ` import ` and ` require ` statements work in
952- all-CommonJS or all-ES module environments, respectively, and therefore is
953- surprising to users. It also differs from the behavior users are familiar with
954- when using transpilation via tools like [ Babel] [ ] or [ ` esm ` ] [ ] .
955-
956- ### Writing dual packages while avoiding or minimizing hazards
957-
958- First, the hazard described in the previous section occurs when a package
959- contains both CommonJS and ES module sources and both sources are provided for
960- use in Node.js, either via separate main entry points or exported paths. A
961- package might instead be written where any version of Node.js receives only
962- CommonJS sources, and any separate ES module sources the package might contain
963- are intended only for other environments such as browsers. Such a package
964- would be usable by any version of Node.js, since ` import ` can refer to CommonJS
965- files; but it would not provide any of the advantages of using ES module syntax.
966-
967- A package might also switch from CommonJS to ES module syntax in a [ breaking
968- change] ( https://semver.org/ ) version bump. This has the disadvantage that the
969- newest version of the package would only be usable in ES module-supporting
970- versions of Node.js.
971-
972- Every pattern has tradeoffs, but there are two broad approaches that satisfy the
973- following conditions:
974-
975- 1 . The package is usable via both ` require ` and ` import ` .
976- 2 . The package is usable in both current Node.js and older versions of Node.js
977- that lack support for ES modules.
978- 3 . The package main entry point, e.g. ` 'pkg' ` can be used by both ` require ` to
979- resolve to a CommonJS file and by ` import ` to resolve to an ES module file.
980- (And likewise for exported paths, e.g. ` 'pkg/feature' ` .)
981- 4 . The package provides named exports, e.g. ` import { name } from 'pkg' ` rather
982- than ` import pkg from 'pkg'; pkg.name ` .
983- 5 . The package is potentially usable in other ES module environments such as
984- browsers.
985- 6 . The hazards described in the previous section are avoided or minimized.
986-
987- #### Approach #1 : Use an ES module wrapper
988-
989- Write the package in CommonJS or transpile ES module sources into CommonJS, and
990- create an ES module wrapper file that defines the named exports. Using
991- [ Conditional exports] [ ] , the ES module wrapper is used for ` import ` and the
992- CommonJS entry point for ` require ` .
993-
994- ``` json
995- // ./node_modules/pkg/package.json
996- {
997- "type" : " module" ,
998- "exports" : {
999- "import" : " ./wrapper.mjs" ,
1000- "require" : " ./index.cjs"
1001- }
1002- }
1003- ```
1004-
1005- The preceding example uses explicit extensions ` .mjs ` and ` .cjs ` .
1006- If your files use the ` .js ` extension, ` "type": "module" ` will cause such files
1007- to be treated as ES modules, just as ` "type": "commonjs" ` would cause them
1008- to be treated as CommonJS.
1009- See [ Enabling] ( esm.md#enabling ) .
1010-
1011- ``` cjs
1012- // ./node_modules/pkg/index.cjs
1013- exports .name = ' value' ;
1014- ```
1015-
1016- ``` js
1017- // ./node_modules/pkg/wrapper.mjs
1018- import cjsModule from ' ./index.cjs' ;
1019- export const name = cjsModule .name ;
1020- ```
1021-
1022- In this example, the ` name ` from ` import { name } from 'pkg' ` is the same
1023- singleton as the ` name ` from ` const { name } = require('pkg') ` . Therefore ` === `
1024- returns ` true ` when comparing the two ` name ` s and the divergent specifier hazard
1025- is avoided.
1026-
1027- If the module is not simply a list of named exports, but rather contains a
1028- unique function or object export like ` module.exports = function () { ... } ` ,
1029- or if support in the wrapper for the ` import pkg from 'pkg' ` pattern is desired,
1030- then the wrapper would instead be written to export the default optionally
1031- along with any named exports as well:
1032-
1033- ``` js
1034- import cjsModule from ' ./index.cjs' ;
1035- export const name = cjsModule .name ;
1036- export default cjsModule ;
1037- ```
1038-
1039- This approach is appropriate for any of the following use cases:
1040-
1041- * The package is currently written in CommonJS and the author would prefer not
1042- to refactor it into ES module syntax, but wishes to provide named exports for
1043- ES module consumers.
1044- * The package has other packages that depend on it, and the end user might
1045- install both this package and those other packages. For example a ` utilities `
1046- package is used directly in an application, and a ` utilities-plus ` package
1047- adds a few more functions to ` utilities ` . Because the wrapper exports
1048- underlying CommonJS files, it doesn't matter if ` utilities-plus ` is written in
1049- CommonJS or ES module syntax; it will work either way.
1050- * The package stores internal state, and the package author would prefer not to
1051- refactor the package to isolate its state management. See the next section.
1052-
1053- A variant of this approach not requiring conditional exports for consumers could
1054- be to add an export, e.g. ` "./module" ` , to point to an all-ES module-syntax
1055- version of the package. This could be used via ` import 'pkg/module' ` by users
1056- who are certain that the CommonJS version will not be loaded anywhere in the
1057- application, such as by dependencies; or if the CommonJS version can be loaded
1058- but doesn't affect the ES module version (for example, because the package is
1059- stateless):
1060-
1061- ``` json
1062- // ./node_modules/pkg/package.json
1063- {
1064- "type" : " module" ,
1065- "exports" : {
1066- "." : " ./index.cjs" ,
1067- "./module" : " ./wrapper.mjs"
1068- }
1069- }
1070- ```
1071-
1072- #### Approach #2 : Isolate state
1073-
1074- A [ ` package.json ` ] [ ] file can define the separate CommonJS and ES module entry
1075- points directly:
1076-
1077- ``` json
1078- // ./node_modules/pkg/package.json
1079- {
1080- "type" : " module" ,
1081- "exports" : {
1082- "import" : " ./index.mjs" ,
1083- "require" : " ./index.cjs"
1084- }
1085- }
1086- ```
1087-
1088- This can be done if both the CommonJS and ES module versions of the package are
1089- equivalent, for example because one is the transpiled output of the other; and
1090- the package's management of state is carefully isolated (or the package is
1091- stateless).
1092-
1093- The reason that state is an issue is because both the CommonJS and ES module
1094- versions of the package might get used within an application; for example, the
1095- user's application code could ` import ` the ES module version while a dependency
1096- ` require ` s the CommonJS version. If that were to occur, two copies of the
1097- package would be loaded in memory and therefore two separate states would be
1098- present. This would likely cause hard-to-troubleshoot bugs.
1099-
1100- Aside from writing a stateless package (if JavaScript's ` Math ` were a package,
1101- for example, it would be stateless as all of its methods are static), there are
1102- some ways to isolate state so that it's shared between the potentially loaded
1103- CommonJS and ES module instances of the package:
1104-
1105- 1 . If possible, contain all state within an instantiated object. JavaScript's
1106- ` Date ` , for example, needs to be instantiated to contain state; if it were a
1107- package, it would be used like this:
1108-
1109- ``` js
1110- import Date from ' date' ;
1111- const someDate = new Date ();
1112- // someDate contains state; Date does not
1113- ```
1114-
1115- The ` new ` keyword isn't required; a package's function can return a new
1116- object, or modify a passed-in object, to keep the state external to the
1117- package.
1118-
1119- 2 . Isolate the state in one or more CommonJS files that are shared between the
1120- CommonJS and ES module versions of the package. For example, if the CommonJS
1121- and ES module entry points are ` index.cjs ` and ` index.mjs ` , respectively:
1122-
1123- ``` cjs
1124- // ./node_modules/pkg/index.cjs
1125- const state = require (' ./state.cjs' );
1126- module .exports .state = state;
1127- ```
1128-
1129- ``` js
1130- // ./node_modules/pkg/index.mjs
1131- import state from ' ./state.cjs' ;
1132- export {
1133- state ,
1134- };
1135- ```
1136-
1137- Even if ` pkg ` is used via both ` require ` and ` import ` in an application (for
1138- example, via ` import ` in application code and via ` require ` by a dependency)
1139- each reference of ` pkg ` will contain the same state; and modifying that
1140- state from either module system will apply to both.
1141-
1142- Any plugins that attach to the package's singleton would need to separately
1143- attach to both the CommonJS and ES module singletons.
1144-
1145- This approach is appropriate for any of the following use cases:
1146-
1147- * The package is currently written in ES module syntax and the package author
1148- wants that version to be used wherever such syntax is supported.
1149- * The package is stateless or its state can be isolated without too much
1150- difficulty.
1151- * The package is unlikely to have other public packages that depend on it, or if
1152- it does, the package is stateless or has state that need not be shared between
1153- dependencies or with the overall application.
1154-
1155- Even with isolated state, there is still the cost of possible extra code
1156- execution between the CommonJS and ES module versions of a package.
1157-
1158- As with the previous approach, a variant of this approach not requiring
1159- conditional exports for consumers could be to add an export, e.g.
1160- ` "./module" ` , to point to an all-ES module-syntax version of the package:
1161-
1162- ``` json
1163- // ./node_modules/pkg/package.json
1164- {
1165- "type" : " module" ,
1166- "exports" : {
1167- "." : " ./index.cjs" ,
1168- "./module" : " ./index.mjs"
1169- }
1170- }
1171- ```
905+ See [ the package examples repository] [ ] for details.
1172906
1173907## Node.js ` package.json ` field definitions
1174908
@@ -1412,7 +1146,6 @@ Package imports permit mapping to external packages.
14121146
14131147This field defines [ subpath imports] [ ] for the current package.
14141148
1415- [ Babel ] : https://babeljs.io/
14161149[ CommonJS ] : modules.md
14171150[ Conditional exports ] : #conditional-exports
14181151[ Corepack ] : corepack.md
@@ -1432,7 +1165,6 @@ This field defines [subpath imports][] for the current package.
14321165[ `--experimental-default-type` ] : cli.md#--experimental-default-typetype
14331166[ `--no-addons` flag ] : cli.md#--no-addons
14341167[ `ERR_PACKAGE_PATH_NOT_EXPORTED` ] : errors.md#err_package_path_not_exported
1435- [ `esm` ] : https://github.com/standard-things/esm#readme
14361168[ `package.json` ] : #nodejs-packagejson-field-definitions
14371169[ entry points ] : #package-entry-points
14381170[ folders as modules ] : modules.md#folders-as-modules
@@ -1446,3 +1178,4 @@ This field defines [subpath imports][] for the current package.
14461178[ supported package managers ] : corepack.md#supported-package-managers
14471179[ the dual CommonJS/ES module packages section ] : #dual-commonjses-module-packages
14481180[ the full specifier path ] : esm.md#mandatory-file-extensions
1181+ [ the package examples repository ] : https://github.com/nodejs/package-examples
0 commit comments