evanw / evanw/esbuild

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

Open
#4,440 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
40.1k
Forks
1.3k
PR merge metrics
No merged PRs in 30d

Description

> [!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 f4ff26d37cc75bd5757161a29bac7e986e7ecef9 (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](https://esbuild.github.io/try/#YgAwLjI4LjAALS1idW5kbGUAZQBlbnRyeS5qcwBjb25zdCBhID0gcmVxdWlyZSgnLi9lc20tbW9kJyk7CmNvbnN0IGIgPSByZXF1aXJlKCcuL2VzbS1tb2QnKTsKY29uc29sZS5sb2coJ2EgPT09IGI6JywgYSA9PT0gYik7IC8vIGZhbHNlIChyZWdyZXNzaW9uKQoAAGVzbS1tb2QuanMAZXhwb3J0IGNvbnN0IHZhbHVlID0gNDI7Cg)):

```js
// 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);
```

```sh
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.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with the entry.js and esm-mod.js reproduction and run the provided npx esbuild entry.js --bundle | node command. Inspect the generated __toCommonJS path and compare the two require() results; done means repeated requires of the bundled ESM module return the same object, with the reproduction printing true.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, javascript
Domain
build-system, compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.