Dual Package Hazard

Publishing both CommonJS and ES module entry points can cause the same logical package to be loaded as separate module instances. This can produce duplicated state, failed identity checks, and subtle interoperability bugs.

The dual package hazard occurs when a package exposes both CommonJS and ES module entry points, and different parts of an application resolve those entry points independently. Although both entry points may represent the same source-level functionality, the runtime can treat them as distinct modules.

A major consequence is duplicated module state. If a module contains mutable singleton state, loading it once through CommonJS and once through ESM can create two independent copies. A change made through one copy is therefore invisible to consumers using the other copy.

The problem can also appear with object identity. An object or class originating from one module instance may not behave as expected when compared with values created by the other instance. For example, `instanceof` checks can fail when the compared constructors came from separately instantiated module copies.

Interoperability can become more complicated when CommonJS and ESM use different loading semantics, export shapes, or resolution paths. Conditional package exports can make this split easy to introduce because different consumers may receive different files depending on how they import the package.

A common mitigation is to avoid maintaining two independently executed implementations. Packages can instead design a single source of runtime state, carefully coordinate their CommonJS and ESM entry points, or provide compatibility wrappers whose behavior preserves shared state where necessary.

Key points

Interview questions

What is the dual package hazard?

It is a problem that can occur when a package supports both CommonJS and ESM through different entry points. The two paths may cause the package's implementation to be evaluated separately. That can create two copies of module-level state instead of one shared copy. Bugs are especially visible when the package exposes mutable state, classes, or singleton objects.

How can the dual package hazard break a singleton?

A singleton depends on all consumers obtaining the same runtime object or state holder. If CommonJS loads one implementation and ESM loads another, each implementation can initialize its own singleton. Mutations through one copy will not be visible through the other. This means code that assumes process-wide shared state can behave inconsistently.

Why can `instanceof` fail across CommonJS and ESM entry points?

`instanceof` ultimately depends on the constructor identity involved in the prototype chain. If the same class implementation is evaluated separately through two entry points, the resulting constructors are distinct objects. An instance created by one constructor therefore may not satisfy an `instanceof` check against the other constructor. The source code can look equivalent while the runtime identities differ.

How would you design a package that needs to support both CommonJS and ESM?

First, I would make the module-resolution strategy explicit and test consumers using both formats. I would avoid having CommonJS and ESM independently instantiate mutable singleton state whenever possible. If both entry points are required, I would structure them around a shared implementation or carefully designed compatibility layer rather than maintaining two unrelated runtime copies. I would also test mixed-format scenarios because testing each format in isolation may not reveal identity or state-sharing failures.

What symptoms would make you suspect a dual package hazard in production?

Unexpectedly duplicated singleton state is a strong signal, especially when two parts of an application appear to maintain separate caches, registries, or configuration objects. Identity-sensitive failures, such as surprising `instanceof` results, are another clue. The issue becomes more likely when the dependency exposes both CommonJS and ESM entry points and the application mixes import styles or dependency paths. Reproducing the issue with controlled imports through both entry points can help confirm the cause.