@@ -902,273 +902,7 @@ $ node other.js
902
902
903
903
## Dual CommonJS/ES module packages
904
904
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.
1172
906
1173
907
## Node.js ` package.json ` field definitions
1174
908
@@ -1412,7 +1146,6 @@ Package imports permit mapping to external packages.
1412
1146
1413
1147
This field defines [ subpath imports] [ ] for the current package.
1414
1148
1415
- [ Babel ] : https://babeljs.io/
1416
1149
[ CommonJS ] : modules.md
1417
1150
[ Conditional exports ] : #conditional-exports
1418
1151
[ Corepack ] : corepack.md
@@ -1432,7 +1165,6 @@ This field defines [subpath imports][] for the current package.
1432
1165
[ `--experimental-default-type` ] : cli.md#--experimental-default-typetype
1433
1166
[ `--no-addons` flag ] : cli.md#--no-addons
1434
1167
[ `ERR_PACKAGE_PATH_NOT_EXPORTED` ] : errors.md#err_package_path_not_exported
1435
- [ `esm` ] : https://github.com/standard-things/esm#readme
1436
1168
[ `package.json` ] : #nodejs-packagejson-field-definitions
1437
1169
[ entry points ] : #package-entry-points
1438
1170
[ folders as modules ] : modules.md#folders-as-modules
@@ -1446,3 +1178,4 @@ This field defines [subpath imports][] for the current package.
1446
1178
[ supported package managers ] : corepack.md#supported-package-managers
1447
1179
[ the dual CommonJS/ES module packages section ] : #dual-commonjses-module-packages
1448
1180
[ the full specifier path ] : esm.md#mandatory-file-extensions
1181
+ [ the package examples repository ] : https://github.com/nodejs/package-examples
0 commit comments