mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
## Summary
Fixes a bug where the `Bun.build()` API with `compile: true` did not
properly apply sourcemaps, even when `sourcemap: "inline"` was
specified. This resulted in error stack traces showing bundled virtual
paths (`/$bunfs/root/`) instead of actual source file names and line
numbers.
## Problem
The CLI `bun build --compile --sourcemap` worked correctly, but the
equivalent API call did not:
```javascript
// This did NOT work (before fix)
await Bun.build({
entrypoints: ['./app.js'],
compile: true,
sourcemap: "inline" // <-- Was ignored/broken
});
```
Error output showed bundled paths:
```
error: Error from helper module
at helperFunction (/$bunfs/root/app.js:4:9) // ❌ Wrong path
at main (/$bunfs/root/app.js:9:17) // ❌ Wrong line numbers
```
## Root Cause
The CLI explicitly overrides any sourcemap type to `.external` when
compile mode is enabled (in `/workspace/bun/src/cli/Arguments.zig`):
```zig
// when using --compile, only `external` works
if (ctx.bundler_options.compile) {
opts.source_map = .external;
}
```
The API implementation in `JSBundler.zig` was missing this override.
## Solution
Added the same sourcemap override logic to `JSBundler.zig` when compile
mode is enabled:
```zig
// 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;
}
```
Now error output correctly shows source file names:
```
error: Error from helper module
at helperFunction (helper.js:2:9) // ✅ Correct file
at main (app.js:4:3) // ✅ Correct line numbers
```
## Tests
Added comprehensive test coverage in
`/workspace/bun/test/bundler/bun-build-compile-sourcemap.test.ts`:
- ✅ `sourcemap: "inline"` works
- ✅ `sourcemap: true` works
- ✅ `sourcemap: "external"` works
- ✅ Multiple source files show correct file names
- ✅ Without sourcemap, bundled paths are shown (expected behavior)
All tests:
- ✅ Fail with `USE_SYSTEM_BUN=1` (confirms bug exists)
- ✅ Pass with `bun bd test` (confirms fix works)
- ✅ Use `tempDir()` to avoid disk space issues
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
---------
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
148 lines
4.4 KiB
TypeScript
148 lines
4.4 KiB
TypeScript
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);
|
|
});
|
|
});
|