From d28866a2cd80746acf84fc3ec46de6fd847a70fc Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Sun, 19 Jul 2026 23:04:46 +0000 Subject: [PATCH 1/4] fix: preserve sourcemap mappings for chunks containing user code Co-authored-by: Abdelrahman Awad --- src/rollup/plugins/sourcemap-min.ts | 5 +- test/unit/sourcemap-min.test.ts | 80 +++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 test/unit/sourcemap-min.test.ts diff --git a/src/rollup/plugins/sourcemap-min.ts b/src/rollup/plugins/sourcemap-min.ts index cf22ae88a8..d873fb3008 100644 --- a/src/rollup/plugins/sourcemap-min.ts +++ b/src/rollup/plugins/sourcemap-min.ts @@ -15,9 +15,10 @@ export function sourcemapMininify() { } // Parse sourcemap const sourcemap: ExistingRawSourceMap = JSON.parse(asset.source); - // Only process sourcemaps with node_module sources + // Only wipe mappings when every source is from node_modules, so that + // chunks mixing user code with hoisted library code keep their mappings if ( - !(sourcemap.sources || []).some((s) => s.includes("node_modules")) + !(sourcemap.sources || []).every((s) => s.includes("node_modules")) ) { continue; } diff --git a/test/unit/sourcemap-min.test.ts b/test/unit/sourcemap-min.test.ts new file mode 100644 index 0000000000..18f19377b9 --- /dev/null +++ b/test/unit/sourcemap-min.test.ts @@ -0,0 +1,80 @@ +import { describe, expect, it } from "vitest"; +import { sourcemapMininify } from "../../src/rollup/plugins/sourcemap-min"; + +type BundleAsset = { type: "asset"; source: string }; + +function createSourcemapAsset(sourcemap: { + sources?: string[]; + mappings?: string; +}): BundleAsset { + return { + type: "asset", + source: JSON.stringify({ + version: 3, + sources: [], + mappings: "AAAA,CAAC", + ...sourcemap, + }), + }; +} + +function runPlugin(bundle: Record) { + const plugin = sourcemapMininify(); + (plugin.generateBundle as Function).call(null, {}, bundle); + const results: Record> = {}; + for (const [key, asset] of Object.entries(bundle)) { + if (key.endsWith(".map")) { + results[key] = JSON.parse(asset.source); + } + } + return results; +} + +describe("sourcemapMininify", () => { + it("wipes mappings for pure library chunks", () => { + const bundle = { + "_libs/express.mjs.map": createSourcemapAsset({ + sources: [ + "../../node_modules/express/index.js", + "../../node_modules/express/lib/router.js", + ], + mappings: "AAAA,CAAC", + }), + }; + const results = runPlugin(bundle); + expect(results["_libs/express.mjs.map"].mappings).toBe(""); + }); + + it("preserves mappings for pure user code chunks", () => { + const bundle = { + "_routes/api/hello.mjs.map": createSourcemapAsset({ + sources: ["src/routes/api/hello.ts"], + mappings: "AAAA,CAAC", + }), + }; + const results = runPlugin(bundle); + expect(results["_routes/api/hello.mjs.map"].mappings).toBe("AAAA,CAAC"); + }); + + it("preserves mappings when library is hoisted into user chunk", () => { + const bundle = { + "_routes/api/hello.mjs.map": createSourcemapAsset({ + sources: [ + "src/routes/api/hello.ts", + "../../node_modules/some-lib/index.js", + ], + mappings: "AAAA,CAAC", + }), + }; + const results = runPlugin(bundle); + expect(results["_routes/api/hello.mjs.map"].mappings).toBe("AAAA,CAAC"); + }); + + it("skips non-sourcemap files", () => { + const bundle = { + "index.mjs": { type: "asset" as const, source: "console.log(42)" }, + }; + runPlugin(bundle as any); + expect(bundle["index.mjs"].source).toBe("console.log(42)"); + }); +}); From f5b051beaa560dc764d0017eb1f56436c1ff0300 Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Mon, 20 Jul 2026 11:25:31 +0100 Subject: [PATCH 2/4] chore: lint --- test/unit/sourcemap-min.test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/unit/sourcemap-min.test.ts b/test/unit/sourcemap-min.test.ts index 18f19377b9..0978a38932 100644 --- a/test/unit/sourcemap-min.test.ts +++ b/test/unit/sourcemap-min.test.ts @@ -20,7 +20,12 @@ function createSourcemapAsset(sourcemap: { function runPlugin(bundle: Record) { const plugin = sourcemapMininify(); - (plugin.generateBundle as Function).call(null, {}, bundle); + const generateBundle = plugin.generateBundle as ( + this: unknown, + options: Record, + bundle: Record + ) => void; + generateBundle.call(null, {}, bundle); const results: Record> = {}; for (const [key, asset] of Object.entries(bundle)) { if (key.endsWith(".map")) { From ce144c16aab90c7b4f4e06dc1ae1066aff4f7a9f Mon Sep 17 00:00:00 2001 From: Daniel Roe Date: Tue, 21 Jul 2026 12:36:40 +0100 Subject: [PATCH 3/4] chore: type assertion --- test/unit/sourcemap-min.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/sourcemap-min.test.ts b/test/unit/sourcemap-min.test.ts index 0978a38932..34c4bd3de6 100644 --- a/test/unit/sourcemap-min.test.ts +++ b/test/unit/sourcemap-min.test.ts @@ -20,7 +20,7 @@ function createSourcemapAsset(sourcemap: { function runPlugin(bundle: Record) { const plugin = sourcemapMininify(); - const generateBundle = plugin.generateBundle as ( + const generateBundle = plugin.generateBundle as unknown as ( this: unknown, options: Record, bundle: Record From cacd6b7c1e5de9734e17869ccd1f69b4ad513a36 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Tue, 21 Jul 2026 13:45:30 +0200 Subject: [PATCH 4/4] Delete test/unit/sourcemap-min.test.ts --- test/unit/sourcemap-min.test.ts | 85 --------------------------------- 1 file changed, 85 deletions(-) delete mode 100644 test/unit/sourcemap-min.test.ts diff --git a/test/unit/sourcemap-min.test.ts b/test/unit/sourcemap-min.test.ts deleted file mode 100644 index 34c4bd3de6..0000000000 --- a/test/unit/sourcemap-min.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { sourcemapMininify } from "../../src/rollup/plugins/sourcemap-min"; - -type BundleAsset = { type: "asset"; source: string }; - -function createSourcemapAsset(sourcemap: { - sources?: string[]; - mappings?: string; -}): BundleAsset { - return { - type: "asset", - source: JSON.stringify({ - version: 3, - sources: [], - mappings: "AAAA,CAAC", - ...sourcemap, - }), - }; -} - -function runPlugin(bundle: Record) { - const plugin = sourcemapMininify(); - const generateBundle = plugin.generateBundle as unknown as ( - this: unknown, - options: Record, - bundle: Record - ) => void; - generateBundle.call(null, {}, bundle); - const results: Record> = {}; - for (const [key, asset] of Object.entries(bundle)) { - if (key.endsWith(".map")) { - results[key] = JSON.parse(asset.source); - } - } - return results; -} - -describe("sourcemapMininify", () => { - it("wipes mappings for pure library chunks", () => { - const bundle = { - "_libs/express.mjs.map": createSourcemapAsset({ - sources: [ - "../../node_modules/express/index.js", - "../../node_modules/express/lib/router.js", - ], - mappings: "AAAA,CAAC", - }), - }; - const results = runPlugin(bundle); - expect(results["_libs/express.mjs.map"].mappings).toBe(""); - }); - - it("preserves mappings for pure user code chunks", () => { - const bundle = { - "_routes/api/hello.mjs.map": createSourcemapAsset({ - sources: ["src/routes/api/hello.ts"], - mappings: "AAAA,CAAC", - }), - }; - const results = runPlugin(bundle); - expect(results["_routes/api/hello.mjs.map"].mappings).toBe("AAAA,CAAC"); - }); - - it("preserves mappings when library is hoisted into user chunk", () => { - const bundle = { - "_routes/api/hello.mjs.map": createSourcemapAsset({ - sources: [ - "src/routes/api/hello.ts", - "../../node_modules/some-lib/index.js", - ], - mappings: "AAAA,CAAC", - }), - }; - const results = runPlugin(bundle); - expect(results["_routes/api/hello.mjs.map"].mappings).toBe("AAAA,CAAC"); - }); - - it("skips non-sourcemap files", () => { - const bundle = { - "index.mjs": { type: "asset" as const, source: "console.log(42)" }, - }; - runPlugin(bundle as any); - expect(bundle["index.mjs"].source).toBe("console.log(42)"); - }); -});