Clear module cache when require'ing an es module with TLA throws (#24389)

### What does this PR do?

Fixes #24387

### How did you verify your code works?

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Marko Vejnovic <marko@bun.com>
This commit is contained in:
Alistair Smith
2025-11-05 13:55:49 -08:00
committed by GitHub
parent c7b9e0dc92
commit 995d988c73
4 changed files with 43 additions and 0 deletions

View File

@@ -215,6 +215,8 @@ export function loadEsmIntoCjs(resolvedSpecifier: string) {
if (state < $ModuleLink && $isPromise(fetch)) {
// This will probably never happen, but just in case
if (($getPromiseInternalField(fetch, $promiseFieldFlags) & $promiseStateMask) === $promiseStatePending) {
registry.$delete(resolvedSpecifier);
throw new TypeError(`require() async module "${key}" is unsupported. use "await import()" instead.`);
}
@@ -231,6 +233,8 @@ export function loadEsmIntoCjs(resolvedSpecifier: string) {
let state = flags & $promiseStateMask;
// this branch should never happen, but just to be safe
if (state === $promiseStatePending || (reactionsOrResult && $isPromise(reactionsOrResult))) {
registry.$delete(resolvedSpecifier);
throw new TypeError(`require() async module "${key}" is unsupported. use "await import()" instead.`);
} else if (state === $promiseStateRejected) {
if (!reactionsOrResult?.message) {
@@ -282,6 +286,8 @@ export function loadEsmIntoCjs(resolvedSpecifier: string) {
var linkAndEvaluateResult = loader.linkAndEvaluateModule(resolvedSpecifier, undefined);
if (linkAndEvaluateResult && $isPromise(linkAndEvaluateResult)) {
registry.$delete(resolvedSpecifier);
// if you use top-level await, or any dependencies use top-level await, then we throw here
// this means the module will still actually load eventually, but that's okay.
throw new TypeError(

View File

@@ -0,0 +1,23 @@
import { bunEnv, bunExe } from "harness";
import { join } from "path";
test("regression: require()ing a module with TLA should error and then wipe the module cache, so that importing it again works", async () => {
const proc = Bun.spawn({
cmd: [bunExe(), "run", "--smol", join(import.meta.dir, "24387", "entry.js")],
env: bunEnv,
stdout: "pipe",
stderr: "pipe",
});
const { stdout, stderr } = proc;
expect(await stderr.text()).toBe("");
expect(await stdout.text()).toMatchInlineSnapshot(`
"require() async module "<the module>" is unsupported. use "await import()" instead.
Module {
foo: 67,
}
"
`);
expect(await proc.exited).toBe(0);
});

View File

@@ -0,0 +1,12 @@
// @bun
var { require } = import.meta;
const entrypointPath = "./p.js";
let listener;
try {
listener = await require(entrypointPath);
} catch (e) {
console.log(e.message.replace(require.resolve(entrypointPath), "<the module>"));
listener = await import(entrypointPath);
}
for (let i = 0; i < 5; i++) if (listener.default) listener = listener.default;
console.log(listener);

View File

@@ -0,0 +1,2 @@
await new Promise(resolve => setTimeout(resolve, 100));
export const foo = 67;