From baa3e509f2dbe7e9f7ebebc55461322ae36037e1 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Mon, 2 Sep 2019 18:01:03 -0700 Subject: [PATCH 1/4] doc: esm: organize sections more hierarchically --- doc/api/esm.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/doc/api/esm.md b/doc/api/esm.md index 1d6ae5d5998e24..2968ae8a3f0037 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -60,7 +60,7 @@ or when referenced by `import` statements within ES module code: - Strings passed in as an argument to `--eval` or `--print`, or piped to `node` via `STDIN`, with the flag `--input-type=commonjs`. -## package.json "type" field +### package.json "type" field Files ending with `.js` or `.mjs`, or lacking any extension, will be loaded as ES modules when the nearest parent `package.json` file @@ -97,7 +97,7 @@ if the nearest parent `package.json` contains `"type": "module"`. import './startup.js'; // Loaded as ES module because of package.json ``` -## Package Scope and File Extensions +### Package Scope and File Extensions A folder containing a `package.json` file, and all subfolders below that folder down until the next folder containing another `package.json`, is @@ -156,7 +156,7 @@ package scope: extension (since both `.js` and `.cjs` files are treated as CommonJS within a `"commonjs"` package scope). -## --input-type flag +### --input-type flag Strings passed in as an argument to `--eval` or `--print` (or `-e` or `-p`), or piped to `node` via `STDIN`, will be treated as ES modules when the @@ -174,7 +174,9 @@ For completeness there is also `--input-type=commonjs`, for explicitly running string input as CommonJS. This is the default behavior if `--input-type` is unspecified. -## Package Entry Points +## Packages + +### Package Entry Points The `package.json` `"main"` field defines the entry point for a package, whether the package is included into CommonJS via `require` or into an ES @@ -217,7 +219,7 @@ a package would be accessible like `require('pkg')` and `import module entry point and legacy users could be informed of the CommonJS entry point path, e.g. `require('pkg/commonjs')`. -## Package Exports +### Package Exports By default, all subpaths from a package can be imported (`import 'pkg/x.js'`). Custom subpath aliasing and encapsulation can be provided through the From 0f1286a8f4eb6d45e0c865c8eedf190c116af1d1 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Mon, 2 Sep 2019 21:11:39 -0700 Subject: [PATCH 2/4] doc: esm: recommend always including "type" field, per 2019-06-19 meeting Reference: https://github.com/nodejs/modules/issues/342#issuecomment-503733187 --- doc/api/esm.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/api/esm.md b/doc/api/esm.md index 2968ae8a3f0037..e3feb556569f3c 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -97,6 +97,15 @@ if the nearest parent `package.json` contains `"type": "module"`. import './startup.js'; // Loaded as ES module because of package.json ``` +It is recommended that package authors always include the `"type"` field, even +in packages where it is unnecessary because all sources are CommonJS (and so +therefore the default `"type": "commonjs"` is implied) or because there are no +files ending in `.js` (for example, all files end in `.mjs` or `.cjs` or +`.wasm`, etc.). Being explicit about the `type` of the package will future-proof +the package in case Node.js’ default type ever changes, and it will also make +things easier for build tools and loaders to determine how the files in the +package should be interpreted. + ### Package Scope and File Extensions A folder containing a `package.json` file, and all subfolders below that From c28ee9be88a46737f9d4b8df834459e3f3287bf4 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Mon, 2 Sep 2019 21:12:13 -0700 Subject: [PATCH 3/4] doc: esm: expand discussion of publishing cjs/esm packages --- doc/api/esm.md | 63 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 15 deletions(-) diff --git a/doc/api/esm.md b/doc/api/esm.md index e3feb556569f3c..55fc48183c6093 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -97,14 +97,11 @@ if the nearest parent `package.json` contains `"type": "module"`. import './startup.js'; // Loaded as ES module because of package.json ``` -It is recommended that package authors always include the `"type"` field, even -in packages where it is unnecessary because all sources are CommonJS (and so -therefore the default `"type": "commonjs"` is implied) or because there are no -files ending in `.js` (for example, all files end in `.mjs` or `.cjs` or -`.wasm`, etc.). Being explicit about the `type` of the package will future-proof -the package in case Node.js’ default type ever changes, and it will also make -things easier for build tools and loaders to determine how the files in the -package should be interpreted. +Package authors should include the `"type"` field, even in packages where all +sources are CommonJS. Being explicit about the `type` of the package will +future-proof the package in case the default type of Node.js ever changes, and +it will also make things easier for build tools and loaders to determine how the +files in the package should be interpreted. ### Package Scope and File Extensions @@ -220,13 +217,48 @@ be interpreted as CommonJS. The `"main"` field can point to exactly one file, regardless of whether the package is referenced via `require` (in a CommonJS context) or `import` (in an -ES module context). Package authors who want to publish a package to be used in -both contexts can do so by setting `"main"` to point to the CommonJS entry point -and informing the package’s users of the path to the ES module entry point. Such -a package would be accessible like `require('pkg')` and `import -'pkg/module.mjs'`. Alternatively the package `"main"` could point to the ES -module entry point and legacy users could be informed of the CommonJS entry -point path, e.g. `require('pkg/commonjs')`. +ES module context). + +#### Compatibility with CommonJS-Only Versions of Node.js + +Prior to the introduction of support for ES modules in Node.js, it was a common +pattern for package authors to include both CommonJS and ES module JavaScript +sources in their package, with `package.json` `"main"` specifying the CommonJS +entry point and `package.json` `"module"` specifying the ES module entry point. +This enabled Node.js to run the CommonJS entry point while build tools such as +bundlers used the ES module entry point, since Node.js ignored (and still +ignores) `"module"`. + +Node.js can now run ES module entry points, but it remains impossible for a +package to define separate CommonJS and ES module entry points. This is for good +reason: the `pkg` variable created from `import pkg from 'pkg'` is not the same +singleton as the `pkg` variable created from `const pkg = require('pkg')`, so if +both are referenced within the same app (including dependencies), unexpected +behavior might occur. + +There are two general approaches to addressing this limitation while still +publishing a package that contains both CommonJS and ES module sources: + +1. Document a new ES module entry point that’s not the package `"main"`, e.g. + `import pkg from 'pkg/module.mjs'` (or `import 'pkg/esm'`, if using [package + exports][]). The package `"main"` would still point to a CommonJS file, and + thus the package would remain compatible with older versions of Node.js that + lack support for ES modules. + +1. Switch the package `"main"` entry point to an ES module file as part of a + semver major version bump. This version and above would only be usable on ES + module-supporting versions of Node.js. If the package still contains a + CommonJS version, it would be accessible via a path within the package, e.g. + `require('pkg/commonjs')`; this is essentially the inverse of the previous + approach. Package consumers who are using CommonJS-only versions of Node.js + would need to update their code from `require('pkg')` to e.g. + `require('pkg/commonjs')`. + +Of course, a package could also include _only_ CommonJS or ES module sources. An +existing package could make a semver major bump to an ES module-only version, +that would only be supported in ES module-supporting versions of Node.js (and +other runtimes). New packages could be published containing only ES module +sources, and would be compatible only with ES module-supporting runtimes. ### Package Exports @@ -908,6 +940,7 @@ success! [CommonJS]: modules.html [ECMAScript-modules implementation]: https://github.com/nodejs/modules/blob/master/doc/plan-for-new-modules-implementation.md +[package exports]: #esm_package_exports [ES Module Integration Proposal for Web Assembly]: https://github.com/webassembly/esm-integration [Node.js EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md [Terminology]: #esm_terminology From 1a5a4b79d86ff4f4cc0b53e26b9c504d1d0ffe64 Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Mon, 16 Sep 2019 10:28:21 -0700 Subject: [PATCH 4/4] doc: esm: fix nits --- doc/api/esm.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/api/esm.md b/doc/api/esm.md index 55fc48183c6093..ab0cc25bc72c7c 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -246,16 +246,16 @@ publishing a package that contains both CommonJS and ES module sources: lack support for ES modules. 1. Switch the package `"main"` entry point to an ES module file as part of a - semver major version bump. This version and above would only be usable on ES - module-supporting versions of Node.js. If the package still contains a + breaking change version bump. This version and above would only be usable on + ES module-supporting versions of Node.js. If the package still contains a CommonJS version, it would be accessible via a path within the package, e.g. `require('pkg/commonjs')`; this is essentially the inverse of the previous approach. Package consumers who are using CommonJS-only versions of Node.js would need to update their code from `require('pkg')` to e.g. `require('pkg/commonjs')`. -Of course, a package could also include _only_ CommonJS or ES module sources. An -existing package could make a semver major bump to an ES module-only version, +Of course, a package could also include only CommonJS or only ES module sources. +An existing package could make a semver major bump to an ES module-only version, that would only be supported in ES module-supporting versions of Node.js (and other runtimes). New packages could be published containing only ES module sources, and would be compatible only with ES module-supporting runtimes. @@ -940,7 +940,6 @@ success! [CommonJS]: modules.html [ECMAScript-modules implementation]: https://github.com/nodejs/modules/blob/master/doc/plan-for-new-modules-implementation.md -[package exports]: #esm_package_exports [ES Module Integration Proposal for Web Assembly]: https://github.com/webassembly/esm-integration [Node.js EP for ES Modules]: https://github.com/nodejs/node-eps/blob/master/002-es-modules.md [Terminology]: #esm_terminology @@ -952,5 +951,6 @@ success! [`import`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import [`module.createRequire()`]: modules.html#modules_module_createrequire_filename [dynamic instantiate hook]: #esm_dynamic_instantiate_hook +[package exports]: #esm_package_exports [special scheme]: https://url.spec.whatwg.org/#special-scheme [the official standard format]: https://tc39.github.io/ecma262/#sec-modules