diff --git a/src/ast/SideEffects.zig b/src/ast/SideEffects.zig index 87152bebbc..7e1f318a41 100644 --- a/src/ast/SideEffects.zig +++ b/src/ast/SideEffects.zig @@ -790,6 +790,19 @@ pub const SideEffects = enum(u1) { } } } + // Check if the define value is undefined + if (define.value == .e_undefined) { + // window is undefined, so typeof window is "undefined" + if (e_.right.data == .e_string) { + const str = e_.right.data.e_string; + if (str.eqlComptime("undefined")) { + // typeof window === "undefined" -> true + // typeof window !== "undefined" -> false + const is_equal = e_.op == .bin_strict_eq or e_.op == .bin_loose_eq; + return Result{ .ok = true, .value = is_equal, .side_effects = .could_have_side_effects }; + } + } + } } } // Handle typeof dot expression (e.g., typeof globalThis.Bun) @@ -837,6 +850,19 @@ pub const SideEffects = enum(u1) { } } } + // Check if the define value is undefined + if (define.value == .e_undefined) { + // window is undefined, so typeof window is "undefined" + if (e_.left.data == .e_string) { + const str = e_.left.data.e_string; + if (str.eqlComptime("undefined")) { + // "undefined" === typeof window -> true + // "undefined" !== typeof window -> false + const is_equal = e_.op == .bin_strict_eq or e_.op == .bin_loose_eq; + return Result{ .ok = true, .value = is_equal, .side_effects = .could_have_side_effects }; + } + } + } } } // Handle typeof dot expression (e.g., typeof globalThis.Bun) diff --git a/src/options.zig b/src/options.zig index e8e55dbd40..0c62f37aae 100644 --- a/src/options.zig +++ b/src/options.zig @@ -1511,6 +1511,11 @@ pub fn definesFromTransformOptions( })); } + // process.isBun - simple boolean true for Bun runtime detection + if (!user_defines.contains("process.isBun")) { + _ = try user_defines.getOrPutValue("process.isBun", "true"); + } + // For --target=bun, mark these as truthy for DCE without replacing values // This enables dead code elimination while preserving runtime values @@ -1545,6 +1550,17 @@ pub fn definesFromTransformOptions( })); } } + + // For --target=node, mark window as undefined for DCE + if (target.isNode()) { + if (!user_defines.contains("window")) { + _ = try environment_defines.getOrPutValue("window", .init(.{ + .valueless = true, + .original_name = "window", + .value = .{ .e_undefined = .{} }, + })); + } + } const resolved_defines = try defines.DefineData.fromInput(user_defines, drop, log, allocator); diff --git a/test/bundler/bun-target-dce.test.ts b/test/bundler/bun-target-dce.test.ts index 3a8f6f8fc5..7f230d451d 100644 --- a/test/bundler/bun-target-dce.test.ts +++ b/test/bundler/bun-target-dce.test.ts @@ -26,6 +26,13 @@ if (process.versions.bun) { require("./should-not-import-3.js"); } +// process.isBun - should be replaced with true +if (process.isBun) { + exports.test3a = "process-isBun-true"; +} else { + exports.test3a = "process-isBun-false"; +} + // ============ typeof checks ============ if (typeof Bun !== "undefined") { exports.test4 = "typeof-bun-defined"; @@ -140,6 +147,7 @@ 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.test3a = "process-isBun-true"; exports.test4 = "typeof-bun-defined"; globalThis.Bun, exports.test5 = "typeof-globalThis-bun-defined"; exports.test6 = "typeof-bun-reverse-defined"; @@ -171,10 +179,7 @@ var require_HASH = __commonJS((exports) => { exports.test17 = "node-version-exists"; else exports.test17 = "node-version-missing"; - if (typeof window !== "undefined") - exports.test18 = "window-exists"; - else - exports.test18 = "window-missing"; + exports.test18 = "window-missing"; var isBun = typeof Bun !== "undefined"; if (!isBun) exports.test19 = "const-not-bun"; @@ -210,7 +215,8 @@ export default require_HASH();" // Non-Bun runtime checks preserved expect(bundled).toContain("process.versions.node"); - expect(bundled).toContain("typeof window"); + // typeof window check is eliminated since window is undefined for bun target + expect(bundled).not.toContain("typeof window"); // Const patterns don't work (needs constant propagation) expect(bundled).toContain("const-not-bun"); @@ -232,6 +238,20 @@ if (typeof Bun !== "undefined") { } else { exports.hasBun = false; } + +// window check - should be undefined for both bun and node targets +if (typeof window === "undefined") { + exports.isServer = true; +} else { + exports.isServer = false; +} + +// process.isBun check +if (process.isBun) { + exports.isBun = true; +} else { + exports.isBun = false; +} `; // Build for Bun @@ -262,6 +282,14 @@ if (typeof Bun !== "undefined") { expect(bunBundle).not.toContain('exports.runtime = "unknown"'); expect(bunBundle).toContain('exports.hasBun = !0'); // minified true expect(bunBundle).not.toContain('exports.hasBun = !1'); // minified false + + // window is undefined for bun target (server environment) + expect(bunBundle).toContain('exports.isServer = !0'); // true - window is undefined + expect(bunBundle).not.toContain('exports.isServer = !1'); // false branch eliminated + + // process.isBun is replaced with true for bun target + expect(bunBundle).toContain('exports.isBun = !0'); // true + expect(bunBundle).not.toContain('exports.isBun = !1'); // false branch eliminated // Node bundle should keep all branches (Bun is unknown at runtime) expect(nodeBundle).toContain('exports.runtime = "bun"'); @@ -269,4 +297,11 @@ if (typeof Bun !== "undefined") { expect(nodeBundle).toContain('exports.runtime = "unknown"'); expect(nodeBundle).toContain('exports.hasBun = !0'); // minified true expect(nodeBundle).toContain('exports.hasBun = !1'); // minified false + + // window is undefined for node target (server environment) + expect(nodeBundle).toContain('exports.isServer = !0'); // true - window is undefined + expect(nodeBundle).not.toContain('exports.isServer = !1'); // false branch eliminated + + // process.isBun doesn't exist for node target - both branches kept + expect(nodeBundle).toContain('process.isBun'); // The check is still there }); \ No newline at end of file