Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 34.2k
Description
Affected URL(s)
https://nodejs.org/api/packages.html#dual-package-hazard
Description of the problem
Publishing packages with dual CommonJS and ESM sources, while has the benefits of supporting both CJS consumers and ESM-only platforms, is known to cause problems because Node.js might load both versions. Example:
package.json | foo.cjs | foo.mjs |
|---|---|---|
{"name": "foo", "exports":{"require": "./foo.cjs", "import": "./foo.mjs" } } | exports.object={}; | exportconstobject={}; |
package.json | bar.js |
|---|---|
{"name": "bar", "main": "./bar.js" } | constfoo=require("foo");exports.object=foo.object; |
// my appimport{objectasfooObj}from"foo";import{objectasbarObj}from"bar";console.log(fooObj===barObj);// false?????The two suggested solutions boil down to "even when you have an ESM entrypoint, still use only CJS internallly". This solves the dual package hazard, but completely defeats the cross-platform benefits of dual modules.
If foo instead used these export conditions:
{"name": "foo", "exports":{"node": "./foo.cjs", "default": "./foo.mjs" } }Then:
- there would be no dual-package hazard in Node.js, because it only ever loads the CommonJS version
- there would be no dual-package hazard in bundlers, because they would only ever load either the
nodeversion (if they are configured to target Node.js) or thedefaultversion (if they are configured to target other platforms). - the package solves the dual-package hazard while still providing an ESM-only version
We have been using this node/default pattern in @babel/runtime for a couple years, because we wanted to provide an ESM-only version for browsers while still avoiding the dual-package hazard (@babel/runtime is mostly stateless, but @babel/runtime/helpers/temporalUndefined relies on object identity of an object defined in a separate file).