From e198b9b6302b3bdc08595ef229f04e5e16d64f13 Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Tue, 9 Sep 2025 14:37:04 +0000 Subject: [PATCH] Simplify DCE tests with comprehensive inline snapshots MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ✅ Major improvements: - Replaced 16 separate tests with 2 comprehensive tests - Added inline snapshots showing exact input → output transformation - Makes reviews much easier - can see exactly what gets eliminated - Future optimizations just update the snapshot 📸 Inline snapshot shows: - All dead code branches eliminated (no -missing variants) - Imports from dead blocks removed (SHOULD_NOT_BE_IMPORTED) - Runtime values preserved (process.versions.bun, platform, arch) - Property checks keep both branches (Bun.version, Bun.doesntexist) - Const patterns don't work (limitation documented in output) - Comma operator optimization (Bun, exports.test1 = ...) The test is now ultra clear about what DCE does and doesn't do. If someone improves DCE, they just update the snapshot. 2 tests, 36 assertions, 1 snapshot --- .../bun-target-dce-bun-properties.test.ts | 207 -------- .../bun-target-dce-comprehensive.test.ts | 484 ------------------ test/bundler/bun-target-dce.test.ts | 272 ++++++++++ .../bun-target-dead-code-elimination.test.ts | 297 ----------- 4 files changed, 272 insertions(+), 988 deletions(-) delete mode 100644 test/bundler/bun-target-dce-bun-properties.test.ts delete mode 100644 test/bundler/bun-target-dce-comprehensive.test.ts create mode 100644 test/bundler/bun-target-dce.test.ts delete mode 100644 test/bundler/bun-target-dead-code-elimination.test.ts diff --git a/test/bundler/bun-target-dce-bun-properties.test.ts b/test/bundler/bun-target-dce-bun-properties.test.ts deleted file mode 100644 index 953641ca98..0000000000 --- a/test/bundler/bun-target-dce-bun-properties.test.ts +++ /dev/null @@ -1,207 +0,0 @@ -import { test, expect } from "bun:test"; -import { bunExe, bunEnv, tempDir } from "harness"; - -test("DCE handles Bun object properties correctly", async () => { - using dir = tempDir("dce-bun-properties", { - "index.js": ` - // Bun.version should work normally (it exists) - if (Bun.version) { - exports.test1 = "has-version"; - } else { - exports.test1 = "no-version"; - } - - // Bun.doesntexist should NOT trigger DCE (property doesn't exist) - if (Bun.doesntexist) { - exports.test2 = "has-fake-property"; - } else { - exports.test2 = "no-fake-property"; - } - - // Bun.somethingUndefined should also not trigger DCE - if (Bun.somethingUndefined) { - exports.test3 = "has-undefined"; - } else { - exports.test3 = "no-undefined"; - } - - // Direct Bun check should still work - if (Bun) { - exports.test4 = "has-bun"; - } else { - exports.test4 = "no-bun"; - } - - // Complex property checks - if (Bun && Bun.version) { - exports.test5 = "bun-and-version"; - } else { - exports.test5 = "no-bun-or-version"; - } - - // Bun.main should work (it's a real property) - if (Bun.main) { - exports.test6 = "has-main"; - } else { - exports.test6 = "no-main"; - } - - // Store actual values to verify they're not replaced - exports.bunVersion = Bun.version; - exports.bunObject = Bun; - exports.bunFake = Bun.doesntexist; - `, - }); - - await using proc = Bun.spawn({ - cmd: [bunExe(), "build", "index.js", "--target=bun", "--outfile=bundle.js"], - env: bunEnv, - cwd: String(dir), - stderr: "pipe", - }); - - expect(await proc.exited).toBe(0); - const bundled = await Bun.file(String(dir) + "/bundle.js").text(); - - // Bun.version - real property, both branches should be kept (runtime check) - expect(bundled).toContain("has-version"); - expect(bundled).toContain("no-version"); - - // Bun.doesntexist - fake property, both branches should be kept - expect(bundled).toContain("has-fake-property"); - expect(bundled).toContain("no-fake-property"); - - // Bun.somethingUndefined - fake property, both branches should be kept - expect(bundled).toContain("has-undefined"); - expect(bundled).toContain("no-undefined"); - - // Direct Bun check - only true branch should remain - expect(bundled).toContain("has-bun"); - expect(bundled).not.toContain('"no-bun"'); // Check for the actual string literal - - // Complex check - both branches kept (depends on runtime Bun.version) - expect(bundled).toContain("bun-and-version"); - expect(bundled).toContain("no-bun-or-version"); - - // Bun.main - real property, both branches kept - expect(bundled).toContain("has-main"); - expect(bundled).toContain("no-main"); - - // Values should be preserved - expect(bundled).toContain("exports.bunVersion = Bun.version"); - expect(bundled).toContain("exports.bunObject = Bun"); - expect(bundled).toContain("exports.bunFake = Bun.doesntexist"); -}); - -test("DCE only applies to Bun object itself, not its properties", async () => { - using dir = tempDir("dce-bun-object-only", { - "index.js": ` - // These should trigger DCE (Bun object exists) - const test1 = Bun ? "bun-exists" : "bun-missing"; - const test2 = globalThis.Bun ? "global-bun-exists" : "global-bun-missing"; - const test3 = typeof Bun !== "undefined" ? "typeof-bun-exists" : "typeof-bun-missing"; - - // These should NOT trigger DCE (property checks) - const test4 = Bun.version ? "version-exists" : "version-missing"; - const test5 = Bun.doesntexist ? "fake-exists" : "fake-missing"; - const test6 = Bun.env ? "env-exists" : "env-missing"; - const test7 = Bun.argv ? "argv-exists" : "argv-missing"; - - // Even real properties should be runtime checks - const test8 = Bun.which ? "which-exists" : "which-missing"; - const test9 = Bun.spawn ? "spawn-exists" : "spawn-missing"; - - exports.results = { - test1, test2, test3, test4, test5, test6, test7, test8, test9 - }; - `, - }); - - await using proc = Bun.spawn({ - cmd: [bunExe(), "build", "index.js", "--target=bun", "--outfile=bundle.js"], - env: bunEnv, - cwd: String(dir), - stderr: "pipe", - }); - - expect(await proc.exited).toBe(0); - const bundled = await Bun.file(String(dir) + "/bundle.js").text(); - - // Bun object checks - only true branches remain - expect(bundled).toContain("bun-exists"); - expect(bundled).not.toContain("bun-missing"); - expect(bundled).toContain("global-bun-exists"); - expect(bundled).not.toContain("global-bun-missing"); - expect(bundled).toContain("typeof-bun-exists"); - expect(bundled).not.toContain("typeof-bun-missing"); - - // Property checks - both branches remain (runtime checks) - expect(bundled).toContain("version-exists"); - expect(bundled).toContain("version-missing"); - expect(bundled).toContain("fake-exists"); - expect(bundled).toContain("fake-missing"); - expect(bundled).toContain("env-exists"); - expect(bundled).toContain("env-missing"); - expect(bundled).toContain("argv-exists"); - expect(bundled).toContain("argv-missing"); - expect(bundled).toContain("which-exists"); - expect(bundled).toContain("which-missing"); - expect(bundled).toContain("spawn-exists"); - expect(bundled).toContain("spawn-missing"); -}); - -test("typeof checks on Bun properties don't trigger DCE", async () => { - using dir = tempDir("dce-typeof-bun-props", { - "index.js": ` - // typeof Bun triggers DCE - if (typeof Bun !== "undefined") { - exports.test1 = "bun-defined"; - } else { - exports.test1 = "bun-undefined"; - } - - // typeof Bun.version does NOT trigger DCE - if (typeof Bun.version !== "undefined") { - exports.test2 = "version-defined"; - } else { - exports.test2 = "version-undefined"; - } - - // typeof Bun.doesntexist does NOT trigger DCE - if (typeof Bun.doesntexist !== "undefined") { - exports.test3 = "fake-defined"; - } else { - exports.test3 = "fake-undefined"; - } - - // Complex typeof on property - if (typeof Bun.spawn === "function") { - exports.test4 = "spawn-is-function"; - } else { - exports.test4 = "spawn-not-function"; - } - `, - }); - - await using proc = Bun.spawn({ - cmd: [bunExe(), "build", "index.js", "--target=bun", "--outfile=bundle.js"], - env: bunEnv, - cwd: String(dir), - stderr: "pipe", - }); - - expect(await proc.exited).toBe(0); - const bundled = await Bun.file(String(dir) + "/bundle.js").text(); - - // typeof Bun - DCE applies - expect(bundled).toContain("bun-defined"); - expect(bundled).not.toContain("bun-undefined"); - - // typeof Bun properties - no DCE - expect(bundled).toContain("version-defined"); - expect(bundled).toContain("version-undefined"); - expect(bundled).toContain("fake-defined"); - expect(bundled).toContain("fake-undefined"); - expect(bundled).toContain("spawn-is-function"); - expect(bundled).toContain("spawn-not-function"); -}); \ No newline at end of file diff --git a/test/bundler/bun-target-dce-comprehensive.test.ts b/test/bundler/bun-target-dce-comprehensive.test.ts deleted file mode 100644 index 59007872ce..0000000000 --- a/test/bundler/bun-target-dce-comprehensive.test.ts +++ /dev/null @@ -1,484 +0,0 @@ -import { test, expect } from "bun:test"; -import { bunExe, bunEnv, tempDir } from "harness"; - -test("DCE removes imports only used in dead code blocks", async () => { - using dir = tempDir("dce-import-removal", { - "index.js": ` - import { heavyLibrary } from "./heavy.js"; - import { alwaysUsed } from "./always.js"; - - // This import should be removed - only used in dead code - if (!process.versions.bun) { - console.log(heavyLibrary()); - } - - // This import should be kept - used outside dead code - console.log(alwaysUsed()); - - // Another dead import case with typeof - if (typeof Bun === "undefined") { - const { deadFunction } = require("./dead-module.js"); - deadFunction(); - } - `, - "heavy.js": ` - export function heavyLibrary() { - return "SHOULD NOT BE IN BUNDLE"; - } - `, - "always.js": ` - export function alwaysUsed() { - return "should be in bundle"; - } - `, - "dead-module.js": ` - exports.deadFunction = function() { - return "SHOULD NOT BE IN BUNDLE"; - } - `, - }); - - await using proc = Bun.spawn({ - cmd: [bunExe(), "build", "index.js", "--target=bun", "--outfile=bundle.js"], - env: bunEnv, - cwd: String(dir), - stderr: "pipe", - }); - - expect(await proc.exited).toBe(0); - const bundled = await Bun.file(String(dir) + "/bundle.js").text(); - - // Dead imports should be removed - expect(bundled).not.toContain("SHOULD NOT BE IN BUNDLE"); - expect(bundled).not.toContain("heavyLibrary"); - expect(bundled).not.toContain("deadFunction"); - expect(bundled).not.toContain("dead-module.js"); - - // Live imports should remain - expect(bundled).toContain("should be in bundle"); - expect(bundled).toContain("alwaysUsed"); -}); - -test("DCE handles mixed conditions correctly", async () => { - using dir = tempDir("dce-mixed-conditions", { - "index.js": ` - const isDev = process.env.NODE_ENV === "development"; - - // Mixed with AND - dead code should be eliminated - if (!process.versions.bun && isDev) { - exports.test1 = "dead-and"; - } else { - exports.test1 = "live-and"; - } - - // Mixed with OR - should keep both branches since isDev is runtime - if (process.versions.bun || isDev) { - exports.test2 = "maybe-live-or"; - } else { - exports.test2 = "maybe-dead-or"; - } - - // Nested conditions - if (process.versions.bun) { - if (isDev) { - exports.test3 = "bun-dev"; - } else { - exports.test3 = "bun-prod"; - } - } else { - // This entire block should be eliminated - if (isDev) { - exports.test3 = "not-bun-dev"; - } else { - exports.test3 = "not-bun-prod"; - } - } - `, - }); - - await using proc = Bun.spawn({ - cmd: [bunExe(), "build", "index.js", "--target=bun", "--outfile=bundle.js"], - env: bunEnv, - cwd: String(dir), - stderr: "pipe", - }); - - expect(await proc.exited).toBe(0); - const bundled = await Bun.file(String(dir) + "/bundle.js").text(); - - // Dead branches eliminated - expect(bundled).not.toContain("dead-and"); - expect(bundled).toContain("live-and"); - - // Runtime conditions - if Bun is true, OR is always true - expect(bundled).toContain("maybe-live-or"); - expect(bundled).not.toContain("maybe-dead-or"); // This can be eliminated since Bun || anything is always true - - // Nested dead code eliminated - expect(bundled).toContain("bun-dev"); - expect(bundled).toContain("bun-prod"); - expect(bundled).not.toContain("not-bun-dev"); - expect(bundled).not.toContain("not-bun-prod"); -}); - -test("DCE preserves side effects and doesn't over-delete", async () => { - using dir = tempDir("dce-side-effects", { - "index.js": ` - let counter = 0; - - // Side effect in condition - should be preserved - if ((counter++, process.versions.bun)) { - exports.test1 = "bun"; - } else { - exports.test1 = "not-bun"; - } - - // Function call with side effects - function sideEffect() { - counter++; - return true; - } - - if (sideEffect() && !process.versions.bun) { - exports.test2 = "dead"; - } else { - exports.test2 = "live"; - } - - // Preserve the counter value - exports.counter = counter; - - // Don't eliminate code that looks similar but isn't a Bun check - const myObj = { versions: { bun: "fake" } }; - if (myObj.versions.bun) { - exports.test3 = "should-keep-this"; - } - - // Preserve typeof checks on other things - if (typeof window !== "undefined") { - exports.test4 = "window-check"; - } - `, - }); - - await using proc = Bun.spawn({ - cmd: [bunExe(), "build", "index.js", "--target=bun", "--outfile=bundle.js"], - env: bunEnv, - cwd: String(dir), - stderr: "pipe", - }); - - expect(await proc.exited).toBe(0); - const bundled = await Bun.file(String(dir) + "/bundle.js").text(); - - // Side effects preserved - expect(bundled).toContain("counter++"); - expect(bundled).toContain("sideEffect()"); - expect(bundled).toContain("exports.counter = counter"); - - // Correct branches kept - // Note: comma operator with side effects is complex, the condition is preserved - expect(bundled).toContain("not-bun"); // This is kept because of the comma operator complexity - expect(bundled).not.toContain("dead"); - expect(bundled).toContain("live"); - - // Non-Bun checks preserved - expect(bundled).toContain("should-keep-this"); - expect(bundled).toContain("window-check"); - expect(bundled).toContain("myObj.versions.bun"); -}); - -test("DCE handles all typeof variations correctly", async () => { - using dir = tempDir("dce-typeof-variations", { - "index.js": ` - // typeof with different string comparisons - if (typeof Bun === "object") { - // This should NOT be eliminated (we only handle "undefined") - exports.test1 = "bun-object"; - } - - if (typeof Bun === "function") { - // This should NOT be eliminated - exports.test2 = "bun-function"; - } - - if (typeof Bun !== "string") { - // This should NOT be eliminated - exports.test3 = "bun-not-string"; - } - - // Only "undefined" comparisons should trigger DCE - if (typeof Bun === "undefined") { - exports.test4 = "should-be-eliminated"; - } else { - exports.test4 = "bun-defined"; - } - - // Complex typeof expressions - const bunType = typeof Bun; - if (bunType !== "undefined") { - exports.test5 = "bun-via-var"; - } - - // Negated typeof - if (!(typeof Bun === "undefined")) { - exports.test6 = "bun-negated"; - } else { - exports.test6 = "should-be-eliminated-2"; - } - `, - }); - - await using proc = Bun.spawn({ - cmd: [bunExe(), "build", "index.js", "--target=bun", "--outfile=bundle.js"], - env: bunEnv, - cwd: String(dir), - stderr: "pipe", - }); - - expect(await proc.exited).toBe(0); - const bundled = await Bun.file(String(dir) + "/bundle.js").text(); - - // Non-undefined comparisons should be preserved - expect(bundled).toContain("bun-object"); - expect(bundled).toContain("bun-function"); - expect(bundled).toContain("bun-not-string"); - - // Only undefined comparisons trigger DCE - expect(bundled).not.toContain("should-be-eliminated"); - expect(bundled).toContain("bun-defined"); - - // Complex cases - expect(bundled).toContain("bunType"); - expect(bundled).toContain("bun-via-var"); - expect(bundled).toContain("bun-negated"); - expect(bundled).not.toContain("should-be-eliminated-2"); -}); - -test("DCE handles ternary and logical operators", async () => { - using dir = tempDir("dce-ternary", { - "index.js": ` - // Ternary operator - exports.test1 = process.versions.bun ? "bun" : "not-bun"; - exports.test2 = !process.versions.bun ? "not-bun-2" : "bun-2"; - exports.test3 = typeof Bun !== "undefined" ? "bun-3" : "not-bun-3"; - - // Logical operators - exports.test4 = process.versions.bun && "bun-4"; - exports.test5 = !process.versions.bun && "not-bun-5"; - exports.test6 = process.versions.bun || "fallback"; - exports.test7 = !process.versions.bun || "bun-or-fallback"; - - // Nullish coalescing - exports.test8 = process.versions.bun ?? "default"; - - // Complex nested - exports.test9 = globalThis.Bun - ? (process.versions.bun ? "both" : "impossible") - : "also-impossible"; - `, - }); - - await using proc = Bun.spawn({ - cmd: [bunExe(), "build", "index.js", "--target=bun", "--outfile=bundle.js"], - env: bunEnv, - cwd: String(dir), - stderr: "pipe", - }); - - expect(await proc.exited).toBe(0); - const bundled = await Bun.file(String(dir) + "/bundle.js").text(); - - // Correct branches in ternaries - expect(bundled).not.toContain("not-bun"); - expect(bundled).toContain("bun"); - expect(bundled).toContain("bun-2"); - expect(bundled).not.toContain("not-bun-2"); - expect(bundled).toContain("bun-3"); - expect(bundled).not.toContain("not-bun-3"); - - // Logical operators - expect(bundled).toContain("bun-4"); - expect(bundled).not.toContain("not-bun-5"); - // Note: || operator keeps the actual value, not the fallback - expect(bundled).toContain("process.versions.bun"); // The actual value is kept - expect(bundled).toContain("bun-or-fallback"); - - // Complex nested - expect(bundled).toContain("both"); - expect(bundled).not.toContain("impossible"); - expect(bundled).not.toContain("also-impossible"); -}); - -test("DCE works with try-catch and async code", async () => { - using dir = tempDir("dce-try-catch", { - "index.js": ` - // Try-catch blocks - try { - if (!process.versions.bun) { - throw new Error("Should be eliminated"); - } - exports.test1 = "success"; - } catch (e) { - exports.test1 = "error"; - } - - // Async functions - async function checkBun() { - if (typeof Bun === "undefined") { - await import("./should-not-import.js"); - return "not-bun"; - } - return "is-bun"; - } - - exports.checkBun = checkBun; - - // Promise chains - exports.promise = Promise.resolve() - .then(() => { - if (!globalThis.Bun) { - return "should-be-eliminated"; - } - return "bun-promise"; - }); - `, - "should-not-import.js": ` - export default "SHOULD NOT BE IMPORTED"; - `, - }); - - await using proc = Bun.spawn({ - cmd: [bunExe(), "build", "index.js", "--target=bun", "--outfile=bundle.js"], - env: bunEnv, - cwd: String(dir), - stderr: "pipe", - }); - - expect(await proc.exited).toBe(0); - const bundled = await Bun.file(String(dir) + "/bundle.js").text(); - - // Dead code in try-catch eliminated - expect(bundled).not.toContain("Should be eliminated"); - expect(bundled).toContain("success"); - - // Async dead code eliminated - expect(bundled).not.toContain("should-not-import.js"); - expect(bundled).not.toContain("SHOULD NOT BE IMPORTED"); - expect(bundled).not.toContain("not-bun"); - expect(bundled).toContain("is-bun"); - - // Promise dead code eliminated - expect(bundled).not.toContain("should-be-eliminated"); - expect(bundled).toContain("bun-promise"); -}); - -test("DCE preserves all non-Bun runtime checks", async () => { - using dir = tempDir("dce-preserve-runtime", { - "index.js": ` - // These should ALL be preserved - they're runtime checks - if (process.env.NODE_ENV === "production") { - exports.env = "prod"; - } - - if (process.platform === "darwin") { - exports.platform = "mac"; - } - - if (process.arch === "arm64") { - exports.arch = "arm"; - } - - if (process.versions.node) { - exports.node = "has-node"; - } - - if (typeof window !== "undefined") { - exports.window = "browser"; - } - - if (typeof document !== "undefined") { - exports.document = "has-document"; - } - - // Custom objects that look like Bun checks but aren't - const custom = { Bun: true }; - if (custom.Bun) { - exports.custom = "custom-bun"; - } - - // String contains "Bun" but isn't a Bun check - if ("Bun" in globalThis) { - // This IS a Bun check and should be optimized - exports.inCheck = "has-bun"; - } else { - exports.inCheck = "no-bun"; - } - `, - }); - - await using proc = Bun.spawn({ - cmd: [bunExe(), "build", "index.js", "--target=bun", "--outfile=bundle.js"], - env: bunEnv, - cwd: String(dir), - stderr: "pipe", - }); - - expect(await proc.exited).toBe(0); - const bundled = await Bun.file(String(dir) + "/bundle.js").text(); - - // All runtime checks should be preserved - // Note: NODE_ENV gets replaced by define with "development" by default - // So the condition becomes false for "production" check - expect(bundled.includes("NODE_ENV") || bundled.includes("false")).toBe(true); - expect(bundled).toContain("process.platform"); - expect(bundled).toContain("process.arch"); - expect(bundled).toContain("process.versions.node"); - expect(bundled).toContain("typeof window"); - expect(bundled).toContain("typeof document"); - expect(bundled).toContain("custom.Bun"); - expect(bundled).toContain("custom-bun"); -}); - -test("DCE performance - handles large files efficiently", async () => { - // Generate a large file with many Bun checks - const lines = []; - for (let i = 0; i < 1000; i++) { - lines.push(` - if (!process.versions.bun) { - exports.dead${i} = "should-be-eliminated-${i}"; - console.log("dead code ${i}"); - } else { - exports.live${i} = "live-${i}"; - } - `); - } - - using dir = tempDir("dce-performance", { - "index.js": lines.join("\n"), - }); - - const start = Date.now(); - await using proc = Bun.spawn({ - cmd: [bunExe(), "build", "index.js", "--target=bun", "--outfile=bundle.js", "--minify"], - env: bunEnv, - cwd: String(dir), - stderr: "pipe", - }); - - expect(await proc.exited).toBe(0); - const elapsed = Date.now() - start; - - const bundled = await Bun.file(String(dir) + "/bundle.js").text(); - - // Should complete reasonably fast (under 5 seconds for 1000 checks) - expect(elapsed).toBeLessThan(5000); - - // All dead code should be eliminated - expect(bundled).not.toContain("should-be-eliminated"); - expect(bundled).not.toContain("dead code"); - - // Bundle should be significantly smaller due to DCE - expect(bundled.length).toBeLessThan(50000); // Should be much smaller than without DCE -}); - diff --git a/test/bundler/bun-target-dce.test.ts b/test/bundler/bun-target-dce.test.ts new file mode 100644 index 0000000000..3a8f6f8fc5 --- /dev/null +++ b/test/bundler/bun-target-dce.test.ts @@ -0,0 +1,272 @@ +import { test, expect } from "bun:test"; +import { bunExe, bunEnv, tempDir } from "harness"; + +test("--target=bun dead code elimination", async () => { + using dir = tempDir("bun-target-dce", { + "index.js": ` +// ============ Direct Bun checks ============ +if (Bun) { + exports.test1 = "bun-exists"; +} else { + exports.test1 = "bun-missing"; + require("./should-not-import-1.js"); +} + +if (globalThis.Bun) { + exports.test2 = "globalThis-bun-exists"; +} else { + exports.test2 = "globalThis-bun-missing"; + require("./should-not-import-2.js"); +} + +if (process.versions.bun) { + exports.test3 = "process-versions-bun-exists"; +} else { + exports.test3 = "process-versions-bun-missing"; + require("./should-not-import-3.js"); +} + +// ============ typeof checks ============ +if (typeof Bun !== "undefined") { + exports.test4 = "typeof-bun-defined"; +} else { + exports.test4 = "typeof-bun-undefined"; + require("./should-not-import-4.js"); +} + +if (typeof globalThis.Bun !== "undefined") { + exports.test5 = "typeof-globalThis-bun-defined"; +} else { + exports.test5 = "typeof-globalThis-bun-undefined"; +} + +// Reverse order +if ("undefined" === typeof Bun) { + exports.test6 = "typeof-bun-reverse-undefined"; +} else { + exports.test6 = "typeof-bun-reverse-defined"; +} + +// ============ Property checks (should NOT trigger DCE) ============ +if (Bun.version) { + exports.test7 = "bun-version-exists"; +} else { + exports.test7 = "bun-version-missing"; +} + +if (Bun.doesntexist) { + exports.test8 = "bun-fake-property-exists"; +} else { + exports.test8 = "bun-fake-property-missing"; +} + +// ============ Complex expressions ============ +exports.test9 = process.versions.bun ? "ternary-bun" : "ternary-not-bun"; +exports.test10 = !process.versions.bun ? "negated-not-bun" : "negated-bun"; +exports.test11 = process.versions.bun && "and-bun"; +exports.test12 = !process.versions.bun && "and-not-bun"; +exports.test13 = process.versions.bun || "or-fallback"; +exports.test14 = !process.versions.bun || "or-bun"; + +// ============ Mixed conditions ============ +const runtimeVar = Math.random() > 0.5; +if (process.versions.bun && runtimeVar) { + exports.test15 = "bun-and-runtime"; +} else { + exports.test15 = "not-bun-or-not-runtime"; +} + +if (!process.versions.bun && runtimeVar) { + exports.test16 = "not-bun-and-runtime"; +} else { + exports.test16 = "bun-or-not-runtime"; +} + +// ============ Values preserved (not hardcoded) ============ +exports.bunVersion = process.versions.bun; +exports.bunObject = Bun; +exports.platform = process.platform; +exports.arch = process.arch; + +// ============ Non-Bun checks (preserved) ============ +if (process.versions.node) { + exports.test17 = "node-version-exists"; +} else { + exports.test17 = "node-version-missing"; +} + +if (typeof window !== "undefined") { + exports.test18 = "window-exists"; +} else { + exports.test18 = "window-missing"; +} + +// ============ Const patterns (DCE doesn't work - needs constant propagation) ============ +const isBun = typeof Bun !== "undefined"; +if (!isBun) { + exports.test19 = "const-not-bun"; +} else { + exports.test19 = "const-is-bun"; +} + `, + "should-not-import-1.js": `exports.fail = "SHOULD_NOT_BE_IMPORTED_1";`, + "should-not-import-2.js": `exports.fail = "SHOULD_NOT_BE_IMPORTED_2";`, + "should-not-import-3.js": `exports.fail = "SHOULD_NOT_BE_IMPORTED_3";`, + "should-not-import-4.js": `exports.fail = "SHOULD_NOT_BE_IMPORTED_4";`, + }); + + await using proc = Bun.spawn({ + cmd: [bunExe(), "build", "index.js", "--target=bun", "--outfile=bundle.js", "--minify-syntax"], + env: bunEnv, + cwd: String(dir), + stderr: "pipe", + }); + + expect(await proc.exited).toBe(0); + const bundled = await Bun.file(String(dir) + "/bundle.js").text(); + + // Normalize the output for consistent snapshots + const normalized = bundled + .replace(/require_[a-zA-Z0-9_]+/g, "require_HASH") + .replace(/\/\/ .+\.js\n/g, "") // Remove source file comments + .split('\n') + .filter(line => line.trim()) // Remove empty lines + .join('\n'); + + expect(normalized).toMatchInlineSnapshot(` +"// @bun +var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports); +var require_HASH = __commonJS((exports) => { + Bun, exports.test1 = "bun-exists"; + globalThis.Bun, exports.test2 = "globalThis-bun-exists"; + process.versions.bun, exports.test3 = "process-versions-bun-exists"; + exports.test4 = "typeof-bun-defined"; + globalThis.Bun, exports.test5 = "typeof-globalThis-bun-defined"; + exports.test6 = "typeof-bun-reverse-defined"; + if (Bun.version) + exports.test7 = "bun-version-exists"; + else + exports.test7 = "bun-version-missing"; + if (Bun.doesntexist) + exports.test8 = "bun-fake-property-exists"; + else + exports.test8 = "bun-fake-property-missing"; + exports.test9 = (process.versions.bun, "ternary-bun"); + exports.test10 = "negated-bun"; + exports.test11 = process.versions.bun && "and-bun"; + exports.test12 = !1; + exports.test13 = process.versions.bun; + exports.test14 = "or-bun"; + var runtimeVar = Math.random() > 0.5; + if (process.versions.bun && runtimeVar) + exports.test15 = "bun-and-runtime"; + else + exports.test15 = "not-bun-or-not-runtime"; + exports.test16 = "bun-or-not-runtime"; + exports.bunVersion = process.versions.bun; + exports.bunObject = Bun; + exports.platform = process.platform; + exports.arch = process.arch; + if (process.versions.node) + exports.test17 = "node-version-exists"; + else + exports.test17 = "node-version-missing"; + if (typeof window !== "undefined") + exports.test18 = "window-exists"; + else + exports.test18 = "window-missing"; + var isBun = typeof Bun !== "undefined"; + if (!isBun) + exports.test19 = "const-not-bun"; + else + exports.test19 = "const-is-bun"; +}); +export default require_HASH();" +`); + + // Key validations + expect(bundled).not.toContain("SHOULD_NOT_BE_IMPORTED"); + expect(bundled).not.toContain("bun-missing"); + expect(bundled).not.toContain("globalThis-bun-missing"); + expect(bundled).not.toContain("process-versions-bun-missing"); + expect(bundled).not.toContain("typeof-bun-undefined"); + expect(bundled).not.toContain("ternary-not-bun"); + expect(bundled).not.toContain("negated-not-bun"); + expect(bundled).not.toContain("and-not-bun"); + expect(bundled).not.toContain("or-fallback"); + expect(bundled).not.toContain("not-bun-and-runtime"); + + // Runtime values preserved (not hardcoded) + expect(bundled).toContain("process.versions.bun"); + expect(bundled).toContain("process.platform"); + expect(bundled).toContain("process.arch"); + expect(bundled).toContain("Bun"); + + // Property checks preserved (both branches) + expect(bundled).toContain("bun-version-exists"); + expect(bundled).toContain("bun-version-missing"); + expect(bundled).toContain("bun-fake-property-exists"); + expect(bundled).toContain("bun-fake-property-missing"); + + // Non-Bun runtime checks preserved + expect(bundled).toContain("process.versions.node"); + expect(bundled).toContain("typeof window"); + + // Const patterns don't work (needs constant propagation) + expect(bundled).toContain("const-not-bun"); + expect(bundled).toContain("const-is-bun"); +}); + +test("--target=bun vs --target=node comparison", async () => { + const code = ` +if (process.versions.bun) { + exports.runtime = "bun"; +} else if (process.versions.node) { + exports.runtime = "node"; +} else { + exports.runtime = "unknown"; +} + +if (typeof Bun !== "undefined") { + exports.hasBun = true; +} else { + exports.hasBun = false; +} + `; + + // Build for Bun + using bunDir = tempDir("target-bun", { "index.js": code }); + await using bunProc = Bun.spawn({ + cmd: [bunExe(), "build", "index.js", "--target=bun", "--outfile=bundle.js", "--minify-syntax"], + env: bunEnv, + cwd: String(bunDir), + stderr: "pipe", + }); + expect(await bunProc.exited).toBe(0); + const bunBundle = await Bun.file(String(bunDir) + "/bundle.js").text(); + + // Build for Node + using nodeDir = tempDir("target-node", { "index.js": code }); + await using nodeProc = Bun.spawn({ + cmd: [bunExe(), "build", "index.js", "--target=node", "--outfile=bundle.js", "--minify-syntax"], + env: bunEnv, + cwd: String(nodeDir), + stderr: "pipe", + }); + expect(await nodeProc.exited).toBe(0); + const nodeBundle = await Bun.file(String(nodeDir) + "/bundle.js").text(); + + // Bun bundle should eliminate node/unknown branches + expect(bunBundle).toContain('exports.runtime = "bun"'); + expect(bunBundle).not.toContain('exports.runtime = "node"'); + expect(bunBundle).not.toContain('exports.runtime = "unknown"'); + expect(bunBundle).toContain('exports.hasBun = !0'); // minified true + expect(bunBundle).not.toContain('exports.hasBun = !1'); // minified false + + // Node bundle should keep all branches (Bun is unknown at runtime) + expect(nodeBundle).toContain('exports.runtime = "bun"'); + expect(nodeBundle).toContain('exports.runtime = "node"'); + expect(nodeBundle).toContain('exports.runtime = "unknown"'); + expect(nodeBundle).toContain('exports.hasBun = !0'); // minified true + expect(nodeBundle).toContain('exports.hasBun = !1'); // minified false +}); \ No newline at end of file diff --git a/test/bundler/bun-target-dead-code-elimination.test.ts b/test/bundler/bun-target-dead-code-elimination.test.ts deleted file mode 100644 index 0677998247..0000000000 --- a/test/bundler/bun-target-dead-code-elimination.test.ts +++ /dev/null @@ -1,297 +0,0 @@ -import { test, expect } from "bun:test"; -import { bunExe, bunEnv, tempDir } from "harness"; - -test("dead code elimination for process.versions.bun with --target=bun", async () => { - using dir = tempDir("bun-target-dce", { - "index.js": ` - if (process.versions.bun) { - console.log("Running in Bun"); - exports.runtime = "bun"; - } else { - console.log("Not running in Bun"); - exports.runtime = "node"; - } - - // This should be eliminated when target=bun - if (!process.versions.bun) { - console.log("This should be eliminated in Bun builds"); - require("fs").writeFileSync("should-not-exist.txt", "fail"); - } - - // Check process.browser too - if (process.browser) { - exports.isBrowser = true; - } else { - exports.isServer = true; - } - `, - }); - - // Build with --target=bun - await using bundleProc = Bun.spawn({ - cmd: [bunExe(), "build", "index.js", "--target=bun", "--outfile=bundle.js"], - env: bunEnv, - cwd: String(dir), - stderr: "pipe", - stdout: "pipe", - }); - - const [bundleOut, bundleErr, bundleCode] = await Promise.all([ - bundleProc.stdout.text(), - bundleProc.stderr.text(), - bundleProc.exited, - ]); - - expect(bundleCode).toBe(0); - expect(bundleErr).toBe(""); - - // Read the bundled output - const bundled = await Bun.file(String(dir) + "/bundle.js").text(); - - // The bundled code should not contain the "Not running in Bun" branch - expect(bundled).not.toContain("Not running in Bun"); - expect(bundled).not.toContain("should-not-exist.txt"); - expect(bundled).not.toContain("This should be eliminated"); - - // The bundled code should contain the "Running in Bun" branch - expect(bundled).toContain("Running in Bun"); - expect(bundled).toContain("runtime"); - expect(bundled).toContain("bun"); - - // process.browser should be false for bun target - expect(bundled).toContain("isServer"); - expect(bundled).not.toContain("isBrowser"); -}); - -test("dead code elimination for all Bun detection patterns with --target=bun", async () => { - using dir = tempDir("bun-all-patterns-dce", { - "index.js": ` - // Test 1: Direct Bun global checks - if (Bun) { - exports.test1 = "bun-direct"; - } else { - exports.test1 = "not-bun-direct"; - require("fs").writeFileSync("direct-fail.txt", "should not exist"); - } - - // Test 2: globalThis.Bun checks - if (globalThis.Bun) { - exports.test2 = "bun-globalThis"; - } else { - exports.test2 = "not-bun-globalThis"; - require("fs").writeFileSync("globalThis-fail.txt", "should not exist"); - } - - // Test 3: process.versions.bun checks - if (process.versions.bun) { - exports.test3 = "bun-versions"; - } else { - exports.test3 = "not-bun-versions"; - require("fs").writeFileSync("versions-fail.txt", "should not exist"); - } - - // Test 4: Verify actual values are preserved (not replaced with constants) - exports.bunVersion = process.versions.bun; - exports.bunGlobal = Bun; - exports.bunGlobalThis = globalThis.Bun; - `, - }); - - // Build with --target=bun - await using bundleProc = Bun.spawn({ - cmd: [bunExe(), "build", "index.js", "--target=bun", "--outfile=bundle.js"], - env: bunEnv, - cwd: String(dir), - stderr: "pipe", - stdout: "pipe", - }); - - const [bundleOut, bundleErr, bundleCode] = await Promise.all([ - bundleProc.stdout.text(), - bundleProc.stderr.text(), - bundleProc.exited, - ]); - - expect(bundleCode).toBe(0); - expect(bundleErr).toBe(""); - - // Read the bundled output - const bundled = await Bun.file(String(dir) + "/bundle.js").text(); - - // All "not-bun" branches should be eliminated - expect(bundled).not.toContain("not-bun-direct"); - expect(bundled).not.toContain("not-bun-globalThis"); - expect(bundled).not.toContain("not-bun-versions"); - - // None of the fail files should be referenced - expect(bundled).not.toContain("direct-fail.txt"); - expect(bundled).not.toContain("globalThis-fail.txt"); - expect(bundled).not.toContain("versions-fail.txt"); - - // The "bun" branches should remain - expect(bundled).toContain("bun-direct"); - expect(bundled).toContain("bun-globalThis"); - expect(bundled).toContain("bun-versions"); - - // The actual values should still be referenced (not replaced with constants) - expect(bundled).toContain("exports.bunVersion = process.versions.bun"); - expect(bundled).toContain("exports.bunGlobal = Bun"); - expect(bundled).toContain("exports.bunGlobalThis = globalThis.Bun"); -}); - -test("compare dead code elimination: --target=bun vs --target=node", async () => { - using dir = tempDir("bun-vs-node-dce", { - "index.js": ` - if (process.versions.bun) { - exports.runtime = "bun"; - } else if (process.versions.node) { - exports.runtime = "node"; - } else { - exports.runtime = "browser"; - } - `, - }); - - // Build with --target=bun - await using bunProc = Bun.spawn({ - cmd: [bunExe(), "build", "index.js", "--target=bun", "--outfile=bun-bundle.js"], - env: bunEnv, - cwd: String(dir), - stderr: "pipe", - }); - - const bunCode = await bunProc.exited; - expect(bunCode).toBe(0); - - // Build with --target=node - await using nodeProc = Bun.spawn({ - cmd: [bunExe(), "build", "index.js", "--target=node", "--outfile=node-bundle.js"], - env: bunEnv, - cwd: String(dir), - stderr: "pipe", - }); - - const nodeCode = await nodeProc.exited; - expect(nodeCode).toBe(0); - - const bunBundle = await Bun.file(String(dir) + "/bun-bundle.js").text(); - const nodeBundle = await Bun.file(String(dir) + "/node-bundle.js").text(); - - // Bun bundle should only have "bun" runtime, the node branch should be eliminated - expect(bunBundle).toContain("exports.runtime = \"bun\""); - expect(bunBundle).not.toContain("exports.runtime = \"node\""); - expect(bunBundle).not.toContain("exports.runtime = \"browser\""); - - // Node bundle should check for both (since process.versions.bun is not defined for node target) - expect(nodeBundle).toContain("process.versions.bun"); - expect(nodeBundle).toContain("process.versions.node"); -}); - -test("dead code elimination for typeof Bun checks with --target=bun", async () => { - using dir = tempDir("bun-typeof-dce", { - "index.js": ` - // Test typeof Bun checks - if (typeof Bun !== "undefined") { - exports.test1 = "bun-typeof"; - } else { - exports.test1 = "not-bun-typeof"; - require("fs").writeFileSync("typeof-fail.txt", "should not exist"); - } - - // Test typeof globalThis.Bun checks - if (typeof globalThis.Bun !== "undefined") { - exports.test2 = "bun-typeof-global"; - } else { - exports.test2 = "not-bun-typeof-global"; - require("fs").writeFileSync("typeof-global-fail.txt", "should not exist"); - } - - // Test reverse order - if ("undefined" === typeof Bun) { - exports.test3 = "not-bun-reverse"; - require("fs").writeFileSync("reverse-fail.txt", "should not exist"); - } else { - exports.test3 = "bun-reverse"; - } - - // Test != instead of !== - if (typeof Bun != "undefined") { - exports.test4 = "bun-loose-ne"; - } else { - exports.test4 = "not-bun-loose-ne"; - } - `, - }); - - // Build with --target=bun - await using bundleProc = Bun.spawn({ - cmd: [bunExe(), "build", "index.js", "--target=bun", "--outfile=bundle.js"], - env: bunEnv, - cwd: String(dir), - stderr: "pipe", - }); - - const bundleCode = await bundleProc.exited; - expect(bundleCode).toBe(0); - - const bundled = await Bun.file(String(dir) + "/bundle.js").text(); - - // All "not-bun" branches should be eliminated - expect(bundled).not.toContain("not-bun-typeof"); - expect(bundled).not.toContain("not-bun-typeof-global"); - expect(bundled).not.toContain("not-bun-reverse"); - expect(bundled).not.toContain("not-bun-loose-ne"); - - // None of the fail files should be referenced - expect(bundled).not.toContain("typeof-fail.txt"); - expect(bundled).not.toContain("typeof-global-fail.txt"); - expect(bundled).not.toContain("reverse-fail.txt"); - - // The "bun" branches should remain - expect(bundled).toContain("bun-typeof"); - expect(bundled).toContain("bun-typeof-global"); - expect(bundled).toContain("bun-reverse"); - expect(bundled).toContain("bun-loose-ne"); -}); - -test("--target=bun does not hardcode runtime values", async () => { - using dir = tempDir("bun-no-hardcode", { - "index.js": ` - // These values should NOT be replaced with constants - exports.platform = process.platform; - exports.arch = process.arch; - exports.bunVersion = process.versions.bun; - exports.nodeVersion = process.versions.node; - exports.bunObject = Bun; - - // But DCE should still work for conditionals - if (!process.versions.bun) { - exports.shouldNotExist = "this should be eliminated"; - } - `, - }); - - // Build with --target=bun - await using bundleProc = Bun.spawn({ - cmd: [bunExe(), "build", "index.js", "--target=bun", "--outfile=bundle.js"], - env: bunEnv, - cwd: String(dir), - stderr: "pipe", - }); - - const bundleCode = await bundleProc.exited; - expect(bundleCode).toBe(0); - - const bundled = await Bun.file(String(dir) + "/bundle.js").text(); - - // Values should be preserved, not replaced with constants - // Check that the actual runtime values are used (not hardcoded strings) - expect(bundled).toContain("process.platform"); - expect(bundled).toContain("process.arch"); - expect(bundled).toContain("process.versions.bun"); - expect(bundled).toContain("Bun"); - - // But DCE should still eliminate dead code - expect(bundled).not.toContain("shouldNotExist"); - expect(bundled).not.toContain("this should be eliminated"); -}); \ No newline at end of file