The Dual-Package Hazard
Shipping both a CommonJS and ESM build risks an app loading two separate instances of the same package, with two separate copies of any module-level state.
Purpose
The dual-package hazard occurs when a package ships both a CommonJS and an ESM build, and an application ends up loading it through both paths (a direct ESM dependency, plus a transitive CommonJS dependency of another package). Node treats these as two entirely separate module instances.
Example
// If pkg ships: exports: { require: "./dist/cjs.js", import: "./dist/esm.js" }
const cjsVersion = require('pkg');
import esmVersion from 'pkg'; // a SEPARATE instance
cjsVersion.registry.set('a', 1);
esmVersion.registry.has('a'); // false — genuinely different objects
Behavior notes
- Any module-level mutable state (a cache, a singleton, a registry) diverges silently between the two loaded instances — not a bug in the consuming application’s code, but a structural consequence of the dual-build setup.
- The fix belongs in the library’s own design: avoid module-level mutable state entirely, or explicitly document that dual-loading is unsupported and provide guidance to avoid it (a single version resolution strategy, for instance).
- This hazard is specific to state shared within a package instance — a package with no module-level mutable state at all is unaffected regardless of how many instances get loaded.
Common mistakes
- Debugging mysteriously inconsistent state without considering that two different module instances of the same package might be loaded simultaneously.
- Designing a library’s public API around a module-level singleton or cache without considering dual-package risk for a library expected to ship both module formats.
Runtime notes
A Node-specific consequence of supporting both CommonJS and ESM in one package; doesn’t arise in a browser-only or single-module-system context.