From 792ee03e75fa281c3a778d7759fd1494f19c71ae Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Thu, 15 Jan 2026 01:06:42 +0000 Subject: [PATCH] fix(bundler): sourcemap sources should be relative to root, not output dir When using `Bun.build()` with a `root` option and external sourcemaps, the sourcemap `sources` paths were incorrectly relative to the output directory instead of the specified root directory. For example, with `root: "."` and `outdir: "./dist"`, source paths were being generated as `"../src/index.ts"` (relative to `dist/src/`) instead of `"src/index.ts"` (relative to root). The fix changes both JS and CSS sourcemap generation to use `root_dir` instead of `output_dir` when relativizing source file paths. Fixes #3332 Co-Authored-By: Claude Opus 4.5 --- .../linker_context/postProcessCSSChunk.zig | 2 +- .../linker_context/postProcessJSChunk.zig | 2 +- test/regression/issue/3332.test.ts | 68 +++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) create mode 100644 test/regression/issue/3332.test.ts diff --git a/src/bundler/linker_context/postProcessCSSChunk.zig b/src/bundler/linker_context/postProcessCSSChunk.zig index e3f680676c..7941bebac5 100644 --- a/src/bundler/linker_context/postProcessCSSChunk.zig +++ b/src/bundler/linker_context/postProcessCSSChunk.zig @@ -105,7 +105,7 @@ pub fn postProcessCSSChunk(ctx: GenerateChunkCtx, worker: *ThreadPool.Worker, ch chunk.isolated_hash, worker, compile_results_for_source_map, - c.resolver.opts.output_dir, + c.resolver.opts.root_dir, can_have_shifts, ); } diff --git a/src/bundler/linker_context/postProcessJSChunk.zig b/src/bundler/linker_context/postProcessJSChunk.zig index bfe0035a44..14541ed6b0 100644 --- a/src/bundler/linker_context/postProcessJSChunk.zig +++ b/src/bundler/linker_context/postProcessJSChunk.zig @@ -429,7 +429,7 @@ pub fn postProcessJSChunk(ctx: GenerateChunkCtx, worker: *ThreadPool.Worker, chu chunk.isolated_hash, worker, compile_results_for_source_map, - c.resolver.opts.output_dir, + c.resolver.opts.root_dir, can_have_shifts, ); } diff --git a/test/regression/issue/3332.test.ts b/test/regression/issue/3332.test.ts new file mode 100644 index 0000000000..f358111c94 --- /dev/null +++ b/test/regression/issue/3332.test.ts @@ -0,0 +1,68 @@ +import { describe, expect, test } from "bun:test"; +import { tempDir } from "harness"; +import path from "path"; + +describe("issue #3332 - sourcemap sources should be relative to root", () => { + test("JS sourcemap sources are relative to root, not output directory", async () => { + using dir = tempDir("issue-3332-js", { + "src/index.ts": `import { helper } from './nested/helper'; +console.log(helper());`, + "src/nested/helper.ts": `export function helper() { + return 'hello'; +}`, + }); + + const result = await Bun.build({ + entrypoints: [path.join(String(dir), "src/index.ts")], + outdir: path.join(String(dir), "dist"), + root: String(dir), + sourcemap: "external", + }); + + expect(result.success).toBe(true); + + // Find the sourcemap output + const sourcemapOutput = result.outputs.find(o => o.kind === "sourcemap"); + expect(sourcemapOutput).toBeDefined(); + + // Parse the sourcemap + const mapContent = await sourcemapOutput!.text(); + const map = JSON.parse(mapContent); + + // Sources should be relative to root (the project directory), not output directory + // Expected: ["src/nested/helper.ts", "src/index.ts"] (or similar) + // Bug behavior: ["../src/nested/helper.ts", "../src/index.ts"] (relative to dist/src/) + for (const source of map.sources) { + expect(source).not.toMatch(/^\.\.\//); + expect(source).toMatch(/^src\//); + } + + expect(map.sources).toContain("src/index.ts"); + expect(map.sources).toContain("src/nested/helper.ts"); + }); + + test("sourcemap sources without explicit root use cwd", async () => { + using dir = tempDir("issue-3332-no-root", { + "index.ts": `console.log('hello');`, + }); + + const result = await Bun.build({ + entrypoints: [path.join(String(dir), "index.ts")], + outdir: path.join(String(dir), "dist"), + sourcemap: "external", + }); + + expect(result.success).toBe(true); + + const sourcemapOutput = result.outputs.find(o => o.kind === "sourcemap"); + expect(sourcemapOutput).toBeDefined(); + + const mapContent = await sourcemapOutput!.text(); + const map = JSON.parse(mapContent); + + // Sources should contain just the filename or a relative path, not "../" + for (const source of map.sources) { + expect(source).not.toMatch(/^\.\.\//); + } + }); +});