Skip to content

require() of a bundled ESM module returns a fresh object on every call (regression from 0.14.27) #4440

@MarshallOfSound

Description

@MarshallOfSound

Note

Found this issue while attempting to switch Electron core from webpack -> esbuild. Claude identified the underlying issue and I manually verified. This issue and the PR that will follow both used AI assistance (via claude code)

When a bundled CJS-shaped file calls require() on a bundled ESM-shaped module, esbuild emits (init_foo(), __toCommonJS(foo_exports)). Since f4ff26d (0.14.27), __toCommonJS allocates a fresh wrapper on every call, so two require() calls for the same module return different objects.

This diverges from Node's CJS semantics (cached module.exports), webpack's __webpack_require__ cache, and esbuild ≤ 0.14.26 (which memoized via WeakMap). The removal commit calls the cache "unnecessary", which is true for the entry-point module.exports = __toCommonJS(...) use, but not for inline require() of a bundled ESM module.

Repro (playground):

// esm-mod.js
export const value = 42;

// entry.js
const a = require('./esm-mod');
const b = require('./esm-mod');
console.log('a === b:', a === b);
npx esbuild entry.js --bundle | node
# a === b: false

Expected: true

Real-world impact: code that aliases two specifiers to the same module (e.g. exposing 'timers' and 'node:timers' from a sandbox require shim) or that registers state on require('foo') and reads it back via a second require('foo') silently breaks.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions