mirror of
https://github.com/oven-sh/bun
synced 2026-02-22 08:41:46 +00:00
fix: clean up ESM registry when require() of ESM module fails (#27288)
## Summary - When `require()` loads an ESM module (`.mjs`) that throws during evaluation, the module was removed from `requireMap` but left in the ESM registry (`Loader.registry`) in a partially-initialized state - A subsequent `import()` of the same module would find this corrupt entry and throw `ReferenceError: Cannot access 'foo' before initialization` instead of re-throwing the original evaluation error - Fix by also deleting the module from `Loader.registry` in both `overridableRequire` and `requireESMFromHijackedExtension` when ESM evaluation fails, allowing `import()` to re-evaluate from scratch Closes #27287 ## Test plan - [x] Added regression test in `test/regression/issue/27287.test.ts` - [x] Verified test fails with system bun (`USE_SYSTEM_BUN=1`) - [x] Verified test passes with `bun bd test` - [x] Manual verification: `require()` correctly throws original error, `import()` now re-throws the same error instead of `ReferenceError` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Bot <claude-bot@bun.sh> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Alistair Smith <alistair@anthropic.com>
This commit is contained in:
@@ -108,6 +108,11 @@ export function overridableRequire(this: JSCommonJSModule, originalId: string, o
|
||||
} catch (exception) {
|
||||
// Since the ESM code is mostly JS, we need to handle exceptions here.
|
||||
$requireMap.$delete(id);
|
||||
// Also remove the failed module from the ESM registry so that
|
||||
// a subsequent import() can re-evaluate it from scratch instead
|
||||
// of finding the partially-initialized module entry.
|
||||
// https://github.com/oven-sh/bun/issues/27287
|
||||
Loader.registry.$delete(id);
|
||||
throw exception;
|
||||
}
|
||||
|
||||
@@ -321,6 +326,11 @@ export function requireESMFromHijackedExtension(this: JSCommonJSModule, id: stri
|
||||
} catch (exception) {
|
||||
// Since the ESM code is mostly JS, we need to handle exceptions here.
|
||||
$requireMap.$delete(id);
|
||||
// Also remove the failed module from the ESM registry so that
|
||||
// a subsequent import() can re-evaluate it from scratch instead
|
||||
// of finding the partially-initialized module entry.
|
||||
// https://github.com/oven-sh/bun/issues/27287
|
||||
Loader.registry.$delete(id);
|
||||
throw exception;
|
||||
}
|
||||
|
||||
|
||||
49
test/regression/issue/27287.test.ts
Normal file
49
test/regression/issue/27287.test.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { bunEnv, bunExe, tempDir } from "harness";
|
||||
|
||||
// https://github.com/oven-sh/bun/issues/27287
|
||||
test("CJS require() of failing ESM does not corrupt module for subsequent import()", async () => {
|
||||
using dir = tempDir("issue-27287", {
|
||||
"bad-esm.mjs": `throw globalThis.err;\nexport const foo = 2;\n`,
|
||||
"entry.cjs": `
|
||||
'use strict';
|
||||
globalThis.err = new Error('intentional error');
|
||||
|
||||
// First: require() the failing ESM module
|
||||
try {
|
||||
require('./bad-esm.mjs');
|
||||
} catch (e) {
|
||||
console.log('require_error:', e.message);
|
||||
}
|
||||
|
||||
// Second: import() the same module - should re-throw the original error, not ReferenceError
|
||||
import('./bad-esm.mjs')
|
||||
.then(() => {
|
||||
console.log('import_result: resolved');
|
||||
})
|
||||
.catch((e) => {
|
||||
console.log('import_error_type:', e.constructor.name);
|
||||
console.log('import_error_msg:', e.message);
|
||||
});
|
||||
`,
|
||||
});
|
||||
|
||||
await using proc = Bun.spawn({
|
||||
cmd: [bunExe(), "run", "entry.cjs"],
|
||||
env: bunEnv,
|
||||
cwd: String(dir),
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
});
|
||||
|
||||
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||||
|
||||
expect(stdout).toContain("require_error: intentional error");
|
||||
// The import() should re-throw the original evaluation error, NOT a ReferenceError
|
||||
// about uninitialized exports. The module threw during evaluation, so import() should
|
||||
// reject with the same error.
|
||||
expect(stdout).not.toContain("ReferenceError");
|
||||
expect(stdout).toContain("import_error_type: Error");
|
||||
expect(stdout).toContain("import_error_msg: intentional error");
|
||||
expect(exitCode).toBe(0);
|
||||
});
|
||||
Reference in New Issue
Block a user