Skip to content

Commit

Permalink
doc: esm: expand discussion of publishing cjs/esm packages
Browse files Browse the repository at this point in the history
  • Loading branch information
GeoffreyBooth committed Sep 9, 2019
1 parent 0f1286a commit c28ee9b
Showing 1 changed file with 48 additions and 15 deletions.
63 changes: 48 additions & 15 deletions doc/api/esm.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down

0 comments on commit c28ee9b

Please sign in to comment.