From 7463fb046652fcaa48b78c76a6d7eb82bbab4765 Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Mon, 16 Feb 2026 05:27:05 +0000 Subject: [PATCH] fix(bundler): preserve compile:true backward compat with default target When compile:true is used without an explicit target (or with target:"browser" but non-HTML entrypoints), fall through to normal bun executable compile instead of erroring. Standalone HTML mode only activates when ALL entrypoints are .html files AND target is browser. This preserves backward compatibility: compile:true alone still produces a bun executable. The test harness defaults target to "browser", so compile tests were breaking with the HTML-only validation. Co-Authored-By: Claude --- src/bun.js/api/JSBundler.zig | 31 ++++++++++++++++--------- src/bundler/bundle_v2.zig | 9 +++++++- src/cli/build_command.zig | 31 ++++++++++++------------- test/bundler/standalone.test.ts | 40 ++++++++++++++++++--------------- 4 files changed, 64 insertions(+), 47 deletions(-) diff --git a/src/bun.js/api/JSBundler.zig b/src/bun.js/api/JSBundler.zig index 85d1123ccd..7d5ac9d566 100644 --- a/src/bun.js/api/JSBundler.zig +++ b/src/bun.js/api/JSBundler.zig @@ -977,9 +977,17 @@ pub const JSBundler = struct { } if (this.compile) |*compile| { - // When compile + target=browser, skip the bun executable compile setup. - // The standalone HTML compilation is handled in the bundler instead. - if (this.target != .browser) { + // When compile + target=browser + all HTML entrypoints, produce standalone HTML. + // Otherwise, default to bun executable compile. + const has_all_html_entrypoints = brk: { + if (this.entry_points.count() == 0) break :brk false; + for (this.entry_points.keys()) |ep| { + if (!strings.hasSuffixComptime(ep, ".html")) break :brk false; + } + break :brk true; + }; + const is_standalone_html = this.target == .browser and has_all_html_entrypoints; + if (!is_standalone_html) { this.target = .bun; const define_keys = compile.compile_target.defineKeys(); @@ -1030,16 +1038,17 @@ pub const JSBundler = struct { return globalThis.throwInvalidArguments("ESM bytecode requires compile: true. Use format: 'cjs' for bytecode without compile.", .{}); } - // --compile --target=browser: produce self-contained HTML + // Validate standalone HTML mode: compile + browser target + all HTML entrypoints if (this.compile != null and this.target == .browser) { - if (this.code_splitting) { - return globalThis.throwInvalidArguments("Cannot use compile with target 'browser' and splitting", .{}); - } - // All entrypoints must be HTML files - for (this.entry_points.keys()) |ep| { - if (!strings.hasSuffixComptime(ep, ".html")) { - return globalThis.throwInvalidArguments("compile with target 'browser' requires all entrypoints to be HTML files", .{}); + const has_all_html = brk: { + if (this.entry_points.count() == 0) break :brk false; + for (this.entry_points.keys()) |ep| { + if (!strings.hasSuffixComptime(ep, ".html")) break :brk false; } + break :brk true; + }; + if (has_all_html and this.code_splitting) { + return globalThis.throwInvalidArguments("Cannot use compile with target 'browser' and splitting for standalone HTML", .{}); } } diff --git a/src/bundler/bundle_v2.zig b/src/bundler/bundle_v2.zig index bc8bd4d553..0d8030f0ab 100644 --- a/src/bundler/bundle_v2.zig +++ b/src/bundler/bundle_v2.zig @@ -1993,7 +1993,14 @@ pub const BundleV2 = struct { transpiler.options.emit_dce_annotations = config.emit_dce_annotations orelse !config.minify.whitespace; transpiler.options.ignore_dce_annotations = config.ignore_dce_annotations; transpiler.options.css_chunking = config.css_chunking; - transpiler.options.compile_to_standalone_html = config.compile != null and config.target == .browser; + transpiler.options.compile_to_standalone_html = brk: { + if (config.compile == null or config.target != .browser) break :brk false; + // Only activate standalone HTML when all entrypoints are HTML files + for (config.entry_points.keys()) |ep| { + if (!bun.strings.hasSuffixComptime(ep, ".html")) break :brk false; + } + break :brk config.entry_points.count() > 0; + }; // When compiling to standalone HTML, don't use the bun executable compile path if (transpiler.options.compile_to_standalone_html) { transpiler.options.compile = false; diff --git a/src/cli/build_command.zig b/src/cli/build_command.zig index fc4cce7da7..4e3fe6a6e2 100644 --- a/src/cli/build_command.zig +++ b/src/cli/build_command.zig @@ -3,14 +3,10 @@ pub const BuildCommand = struct { Global.configureAllocator(.{ .long_running = true }); const allocator = ctx.allocator; var log = ctx.log; + const user_requested_browser_target = ctx.args.target != null and ctx.args.target.? == .browser; if (ctx.bundler_options.compile or ctx.bundler_options.bytecode) { // set this early so that externals are set up correctly and define is right - // When --compile --target=browser is used, keep browser target for standalone HTML - if (ctx.args.target != null and ctx.args.target.? == .browser) { - // Keep browser target - } else { - ctx.args.target = .bun; - } + ctx.args.target = .bun; } if (ctx.bundler_options.bake) { @@ -109,23 +105,24 @@ pub const BuildCommand = struct { return; } - if (ctx.args.target != null and ctx.args.target.? == .browser) { - // --compile --target=browser: produce self-contained HTML with all assets inlined + // Check if all entrypoints are HTML files for standalone HTML mode + const has_all_html_entrypoints = brk: { + if (this_transpiler.options.entry_points.len == 0) break :brk false; + for (this_transpiler.options.entry_points) |entry_point| { + if (!strings.hasSuffixComptime(entry_point, ".html")) break :brk false; + } + break :brk true; + }; + + if (user_requested_browser_target and has_all_html_entrypoints) { + // --compile --target=browser with all HTML entrypoints: produce self-contained HTML + ctx.args.target = .browser; if (ctx.bundler_options.code_splitting) { Output.prettyErrorln("error: cannot use --compile --target browser with --splitting", .{}); Global.exit(1); return; } - // All entrypoints must be HTML files - for (this_transpiler.options.entry_points) |entry_point| { - if (!strings.hasSuffixComptime(entry_point, ".html")) { - Output.prettyErrorln("error: --compile --target browser requires all entrypoints to be HTML files, but got: {s}", .{entry_point}); - Global.exit(1); - return; - } - } - this_transpiler.options.compile_to_standalone_html = true; // This is not a bun executable compile - clear compile flags this_transpiler.options.compile = false; diff --git a/test/bundler/standalone.test.ts b/test/bundler/standalone.test.ts index 5e945baa18..6db81006b4 100644 --- a/test/bundler/standalone.test.ts +++ b/test/bundler/standalone.test.ts @@ -278,33 +278,37 @@ body { color: blue; }`, expect(html).toContain("