diff --git a/src/bun.js/api/JSBundler.zig b/src/bun.js/api/JSBundler.zig index 2dfe0a5727..7c8673868c 100644 --- a/src/bun.js/api/JSBundler.zig +++ b/src/bun.js/api/JSBundler.zig @@ -694,6 +694,12 @@ pub const JSBundler = struct { const base_public_path = bun.StandaloneModuleGraph.targetBasePublicPath(this.compile.?.compile_target.os, "root/"); try this.public_path.append(base_public_path); + // When using --compile, only `external` sourcemaps work, as we do not + // look at the source map comment. Override any other sourcemap type. + if (this.source_map != .none) { + this.source_map = .external; + } + if (compile.outfile.isEmpty()) { const entry_point = this.entry_points.keys()[0]; var outfile = std.fs.path.basename(entry_point); diff --git a/test/bundler/bun-build-compile-sourcemap.test.ts b/test/bundler/bun-build-compile-sourcemap.test.ts new file mode 100644 index 0000000000..b26c63ebca --- /dev/null +++ b/test/bundler/bun-build-compile-sourcemap.test.ts @@ -0,0 +1,147 @@ +import { describe, expect, test } from "bun:test"; +import { bunEnv, tempDir } from "harness"; +import { join } from "path"; + +describe("Bun.build compile with sourcemap", () => { + const helperFiles = { + "helper.js": `export function helperFunction() { + throw new Error("Error from helper module"); +}`, + "app.js": `import { helperFunction } from "./helper.js"; + +function main() { + helperFunction(); +} + +main();`, + }; + + async function testSourcemapOption(sourcemapValue: "inline" | "external" | true, testName: string) { + using dir = tempDir(`build-compile-sourcemap-${testName}`, helperFiles); + + const result = await Bun.build({ + entrypoints: [join(String(dir), "app.js")], + compile: true, + sourcemap: sourcemapValue, + }); + + expect(result.success).toBe(true); + expect(result.outputs.length).toBe(1); + + const executablePath = result.outputs[0].path; + expect(await Bun.file(executablePath).exists()).toBe(true); + + // Run the compiled executable and capture the error + await using proc = Bun.spawn({ + cmd: [executablePath], + env: bunEnv, + cwd: String(dir), + stdout: "pipe", + stderr: "pipe", + }); + + const [_stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + // With sourcemaps working, we should see the actual file names + expect(stderr).toContain("helper.js"); + expect(stderr).toContain("app.js"); + + // Should NOT see the bundled virtual path (/$bunfs/root/ on Unix, B:/~BUN/root/ on Windows) + expect(stderr).not.toMatch(/(\$bunfs|~BUN)\/root\//); + + // Verify it failed (the error was thrown) + expect(exitCode).not.toBe(0); + } + + test.each([ + ["inline" as const, "inline"], + [true as const, "true"], + ["external" as const, "external"], + ])("compile with sourcemap: %s should work", async (sourcemapValue, testName) => { + await testSourcemapOption(sourcemapValue, testName); + }); + + test("compile without sourcemap should show bundled paths", async () => { + using dir = tempDir("build-compile-no-sourcemap", helperFiles); + + const result = await Bun.build({ + entrypoints: [join(String(dir), "app.js")], + compile: true, + // No sourcemap option + }); + + expect(result.success).toBe(true); + expect(result.outputs.length).toBe(1); + + const executablePath = result.outputs[0].path; + expect(await Bun.file(executablePath).exists()).toBe(true); + + // Run the compiled executable and capture the error + await using proc = Bun.spawn({ + cmd: [executablePath], + env: bunEnv, + cwd: String(dir), + stdout: "pipe", + stderr: "pipe", + }); + + const [_stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + // Without sourcemaps, we should see the bundled virtual path (/$bunfs/root/ on Unix, B:/~BUN/root/ on Windows) + expect(stderr).toMatch(/(\$bunfs|~BUN)\/root\//); + + // Verify it failed (the error was thrown) + expect(exitCode).not.toBe(0); + }); + + test("compile with multiple source files", async () => { + using dir = tempDir("build-compile-sourcemap-multiple-files", { + "utils.js": `export function utilError() { + throw new Error("Error from utils"); +}`, + "helper.js": `import { utilError } from "./utils.js"; +export function helperFunction() { + utilError(); +}`, + "app.js": `import { helperFunction } from "./helper.js"; + +function main() { + helperFunction(); +} + +main();`, + }); + + const result = await Bun.build({ + entrypoints: [join(String(dir), "app.js")], + compile: true, + sourcemap: "inline", + }); + + expect(result.success).toBe(true); + const executable = result.outputs[0].path; + expect(await Bun.file(executable).exists()).toBe(true); + + // Run the executable + await using proc = Bun.spawn({ + cmd: [executable], + env: bunEnv, + cwd: String(dir), + stdout: "pipe", + stderr: "pipe", + }); + + const [_stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]); + + // With sourcemaps, should show all three source file names + expect(stderr).toContain("utils.js"); + expect(stderr).toContain("helper.js"); + expect(stderr).toContain("app.js"); + + // Should NOT show bundled paths (/$bunfs/root/ on Unix, B:/~BUN/root/ on Windows) + expect(stderr).not.toMatch(/(\$bunfs|~BUN)\/root\//); + + // Verify it failed (the error was thrown) + expect(exitCode).not.toBe(0); + }); +});