mirror of
https://github.com/oven-sh/bun
synced 2026-02-16 13:51:47 +00:00
Simplify DCE tests with comprehensive inline snapshots
✅ 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
This commit is contained in:
@@ -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");
|
||||
});
|
||||
@@ -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
|
||||
});
|
||||
|
||||
272
test/bundler/bun-target-dce.test.ts
Normal file
272
test/bundler/bun-target-dce.test.ts
Normal file
@@ -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
|
||||
});
|
||||
@@ -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");
|
||||
});
|
||||
Reference in New Issue
Block a user