Skip to content

fix: cache the result of inline require() of bundled ESM modules#4441

Open
MarshallOfSound wants to merge 1 commit intoevanw:mainfrom
MarshallOfSound:fix-require-esm-identity
Open

fix: cache the result of inline require() of bundled ESM modules#4441
MarshallOfSound wants to merge 1 commit intoevanw:mainfrom
MarshallOfSound:fix-require-esm-identity

Conversation

@MarshallOfSound
Copy link
Copy Markdown

@MarshallOfSound MarshallOfSound commented Apr 5, 2026

Note

Found this issue while attempting to switch Electron core from webpack -> esbuild. Claude identified the underlying issue and I manually verified. This PR and the issue linked below 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)) at each call site. Since f4ff26d (0.14.27), __toCommonJS allocates a fresh wrapper on every call, so two require() calls for the same module return different objects — diverging from Node's CJS cache semantics, webpack's __webpack_require__ cache, and esbuild ≤ 0.14.26.

This adds a separate __toCommonJSCached runtime helper (WeakMap-memoized, with a no-cache fallback when WeakMap is unavailable) and routes only the inline-require() code path to it. The entry-point path (module.exports = __toCommonJS(exports) / return __toCommonJS(exports), called once) continues to use the uncached helper, so bundles with no inline ESM require() pay no extra cost.

// 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
# before: a === b: false
# after:  a === b: true

Fixes #4440.

When a bundled CJS-shaped file calls require() on a bundled ESM-shaped
module, esbuild emits `(init_foo(), __toCommonJS(foo_exports))` at
each call site. Since f4ff26d the __toCommonJS helper allocates a
fresh wrapper on every call, so two require() calls for the same
module return different objects — diverging from Node's CJS cache
semantics, webpack's __webpack_require__ cache, and esbuild ≤ 0.14.26.

This adds a separate __toCommonJSCached runtime helper (WeakMap-
memoized, falling back to no-cache when WeakMap is unavailable) and
routes only the inline-require code path to it. The entry-point path
(`module.exports = __toCommonJS(exports)`, called once) continues to
use the uncached helper, so bundles with no inline ESM require() pay
no extra cost.

Fixes evanw#4440.
@MarshallOfSound MarshallOfSound changed the title Cache the result of inline require() of bundled ESM modules fix: cache the result of inline require() of bundled ESM modules Apr 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

1 participant