Add typeof window DCE for bun and node targets

- Handle typeof checks with undefined defines in SideEffects.zig
- Mark window as undefined for both --target=bun and --target=node
- Enable DCE for typeof window === "undefined" checks
- Update tests to verify window checks are eliminated

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude Bot
2025-09-09 14:56:39 +00:00
parent e198b9b630
commit 31c48c4436
3 changed files with 82 additions and 5 deletions

View File

@@ -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)

View File

@@ -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);

View File

@@ -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
});