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("