Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
cc59d6fa78 fix: show proper error for mixed ESM/CJS modules in dynamic imports
When a file mixes ESM imports with CJS exports (e.g., `import { x } from 'y'; module.exports = ...`), the parser logs an error. However, RuntimeTranspilerStore wasn't checking for logged errors after parsing, causing a confusing "Expected CommonJS module to have a function wrapper" error instead of the proper "Cannot use import statement with CommonJS-only features" message.

Add error check after parsing to properly propagate parse errors.

Fixes #25609

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-27 04:27:11 +00:00
2 changed files with 107 additions and 0 deletions

View File

@@ -442,6 +442,13 @@ pub const RuntimeTranspilerStore = struct {
return;
};
// Check for errors logged during parsing (e.g., mixed ESM imports with CJS exports)
// These errors don't cause parsing to fail but should prevent module loading
if (log.hasErrors()) {
this.parse_error = error.ParseError;
return;
}
if (vm.isWatcherEnabled()) {
if (input_file_fd.isValid()) {
if (!is_node_override and

View File

@@ -0,0 +1,100 @@
// Regression test for https://github.com/oven-sh/bun/issues/25609
// Files that mix ESM imports with CJS exports should throw a proper error,
// not crash with "Expected CommonJS module to have a function wrapper".
import { expect, test } from "bun:test";
import { bunEnv, bunExe, normalizeBunSnapshot, tempDir } from "harness";
test("dynamic import of mixed ESM/CJS file should throw proper error", async () => {
// Create temp directory with test files
using dir = tempDir("issue-25609", {
// This file dynamically imports b.ts - await it so unhandled rejection shows the error
"a.ts": `await import("./b.ts");`,
// This file mixes ESM imports with CJS exports - this is invalid
"b.ts": `import { foo } from "./c.ts";
module.exports = { foo };`,
// A simple ESM module
"c.ts": `export const foo = "bar";`,
});
// Spawn Bun process
await using proc = Bun.spawn({
cmd: [bunExe(), "a.ts"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// Should show the proper error about mixing ESM/CJS
expect(normalizeBunSnapshot(stderr)).toContain("Cannot use import statement with CommonJS-only features");
// Should NOT show the confusing "function wrapper" error
expect(stderr).not.toContain("Expected CommonJS module to have a function wrapper");
// Exit code should be 1
expect(exitCode).toBe(1);
});
test("static import of mixed ESM/CJS file should throw proper error", async () => {
// Create temp directory with test files
using dir = tempDir("issue-25609-static", {
// This file statically imports b.ts
"a.ts": `import b from "./b.ts"; console.log(b);`,
// This file mixes ESM imports with CJS exports - this is invalid
"b.ts": `import { foo } from "./c.ts";
module.exports = { foo };`,
// A simple ESM module
"c.ts": `export const foo = "bar";`,
});
// Spawn Bun process
await using proc = Bun.spawn({
cmd: [bunExe(), "a.ts"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// Should show the proper error about mixing ESM/CJS
expect(normalizeBunSnapshot(stderr)).toContain("Cannot use import statement with CommonJS-only features");
// Should NOT show the confusing "function wrapper" error
expect(stderr).not.toContain("Expected CommonJS module to have a function wrapper");
// Exit code should be 1
expect(exitCode).toBe(1);
});
test("pure CJS file works with dynamic import", async () => {
// Create temp directory with test files
using dir = tempDir("issue-25609-pure-cjs", {
// This file dynamically imports b.ts
"a.ts": `import("./b.ts").then(m => { console.log("Loaded:", m.default.foo); });`,
// This file uses pure CJS - this should work
"b.ts": `const c = require("./c.ts");
module.exports = { foo: c.foo };`,
// A simple CJS module
"c.ts": `exports.foo = "bar";`,
});
// Spawn Bun process
await using proc = Bun.spawn({
cmd: [bunExe(), "a.ts"],
env: bunEnv,
cwd: String(dir),
stderr: "pipe",
stdout: "pipe",
});
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
// Should work without errors
expect(normalizeBunSnapshot(stdout)).toContain("Loaded: bar");
expect(exitCode).toBe(0);
});