From 80e5b3d352fae481eceaf4b80819f668284da67f Mon Sep 17 00:00:00 2001 From: Claude Bot Date: Tue, 17 Feb 2026 02:02:12 +0000 Subject: [PATCH] refactor(bundler): route standalone HTML disk writes through writeOutputFilesToDisk Instead of custom disk-writing logic in generateChunksInParallel, teach writeOutputFilesToDisk to handle standalone mode: - Accept optional standalone_chunk_contents parameter - Skip non-HTML chunks (insert placeholder output files for index alignment) - Use codeStandalone() for HTML chunks to produce the inlined output - Skip writing additional output files (assets are inlined) - Use chunk.content.loader() for the output file loader field This removes the separate bun.sys.File disk-writing block and lets standalone HTML go through the same writeOutputFilesToDisk code path as all other bundler output. Co-Authored-By: Claude --- .../generateChunksInParallel.zig | 35 +-------- .../linker_context/writeOutputFilesToDisk.zig | 71 +++++++++++++++---- 2 files changed, 59 insertions(+), 47 deletions(-) diff --git a/src/bundler/linker_context/generateChunksInParallel.zig b/src/bundler/linker_context/generateChunksInParallel.zig index e9f76b9109..61e7c16bf2 100644 --- a/src/bundler/linker_context/generateChunksInParallel.zig +++ b/src/bundler/linker_context/generateChunksInParallel.zig @@ -432,8 +432,8 @@ pub fn generateChunksInParallel( // Don't write to disk if compile mode is enabled - we need buffer values for compilation const is_compile = bundler.transpiler.options.compile; - if (root_path.len > 0 and !is_compile and !is_standalone) { - try c.writeOutputFilesToDisk(root_path, chunks, &output_files); + if (root_path.len > 0 and !is_compile) { + try c.writeOutputFilesToDisk(root_path, chunks, &output_files, standalone_chunk_contents); } else { // In-memory build (also used for standalone mode) for (chunks, 0..) |*chunk, chunk_index_in_chunks_list| { @@ -762,37 +762,6 @@ pub fn generateChunksInParallel( } } result.items.len = write_idx; - - // Write standalone HTML files to disk if outdir is specified - if (root_path.len > 0) { - for (result.items) |*item| { - if (item.value == .buffer) { - var buf: bun.PathBuffer = undefined; - const abs_path = bun.path.joinAbsStringBufZ(root_path, &buf, &.{item.dest_path}, .auto); - const file = switch (bun.sys.File.makeOpen(abs_path, bun.O.WRONLY | bun.O.CREAT | bun.O.TRUNC, 0o644)) { - .result => |f| f, - .err => |err| { - try c.log.addErrorFmt(null, Logger.Loc.Empty, bun.default_allocator, "Failed to write {f}: {s}", .{ - bun.fmt.quote(item.dest_path), - @tagName(err.getErrno()), - }); - continue; - }, - }; - defer file.close(); - switch (file.writeAll(item.value.buffer.bytes)) { - .result => {}, - .err => |err| { - try c.log.addErrorFmt(null, Logger.Loc.Empty, bun.default_allocator, "Failed to write {f}: {s}", .{ - bun.fmt.quote(item.dest_path), - @tagName(err.getErrno()), - }); - }, - } - } - } - } - return result; } diff --git a/src/bundler/linker_context/writeOutputFilesToDisk.zig b/src/bundler/linker_context/writeOutputFilesToDisk.zig index 8a3170df50..6a56979a02 100644 --- a/src/bundler/linker_context/writeOutputFilesToDisk.zig +++ b/src/bundler/linker_context/writeOutputFilesToDisk.zig @@ -3,6 +3,7 @@ pub fn writeOutputFilesToDisk( root_path: string, chunks: []Chunk, output_files: *OutputFileListBuilder, + standalone_chunk_contents: ?[]const ?[]const u8, ) !void { const trace = bun.perf.trace("Bundler.writeOutputFilesToDisk"); defer trace.end(); @@ -42,6 +43,29 @@ pub fn writeOutputFilesToDisk( const bv2: *bundler.BundleV2 = @fieldParentPtr("linker", c); for (chunks, 0..) |*chunk, chunk_index_in_chunks_list| { + // In standalone mode, only write HTML chunks to disk. + // Insert placeholder output files for non-HTML chunks to keep indices aligned. + if (standalone_chunk_contents != null and chunk.content != .html) { + _ = output_files.insertForChunk(options.OutputFile.init(.{ + .data = .{ .saved = 0 }, + .hash = null, + .loader = chunk.content.loader(), + .input_path = "", + .display_size = 0, + .output_kind = .chunk, + .input_loader = .js, + .output_path = "", + .is_executable = false, + .source_map_index = null, + .bytecode_index = null, + .module_info_index = null, + .side = .client, + .entry_point_index = null, + .referenced_css_chunks = &.{}, + })); + continue; + } + const trace2 = bun.perf.trace("Bundler.writeChunkToDisk"); defer trace2.end(); defer max_heap_allocator.reset(); @@ -65,17 +89,31 @@ pub fn writeOutputFilesToDisk( else c.resolver.opts.public_path; - var code_result = chunk.intermediate_output.code( - code_allocator, - c.parse_graph, - &c.graph, - public_path, - chunk, - chunks, - &display_size, - c.resolver.opts.compile and !chunk.flags.is_browser_chunk_from_server_build, - chunk.content.sourcemap(c.options.source_maps) != .none, - ) catch |err| bun.Output.panic("Failed to create output chunk: {s}", .{@errorName(err)}); + var code_result = if (standalone_chunk_contents) |scc| + chunk.intermediate_output.codeStandalone( + code_allocator, + c.parse_graph, + &c.graph, + public_path, + chunk, + chunks, + &display_size, + false, + false, + scc, + ) catch |err| bun.Output.panic("Failed to create output chunk: {s}", .{@errorName(err)}) + else + chunk.intermediate_output.code( + code_allocator, + c.parse_graph, + &c.graph, + public_path, + chunk, + chunks, + &display_size, + c.resolver.opts.compile and !chunk.flags.is_browser_chunk_from_server_build, + chunk.content.sourcemap(c.options.source_maps) != .none, + ) catch |err| bun.Output.panic("Failed to create output chunk: {s}", .{@errorName(err)}); var source_map_output_file: ?options.OutputFile = null; @@ -318,7 +356,7 @@ pub fn writeOutputFilesToDisk( .js, .hash = chunk.template.placeholder.hash, .output_kind = output_kind, - .loader = .js, + .loader = chunk.content.loader(), .source_map_index = source_map_index, .bytecode_index = bytecode_index, .size = @as(u32, @truncate(code_result.buffer.len)), @@ -344,10 +382,15 @@ pub fn writeOutputFilesToDisk( }, })); - // We want the chunk index to remain the same in `output_files` so the indices in `OutputFile.referenced_css_chunks` work - bun.assertf(chunk_index == chunk_index_in_chunks_list, "chunk_index ({d}) != chunk_index_in_chunks_list ({d})", .{ chunk_index, chunk_index_in_chunks_list }); + // We want the chunk index to remain the same in `output_files` so the indices in `OutputFile.referenced_css_chunks` work. + // In standalone mode, non-HTML chunks are skipped so this invariant doesn't apply. + if (standalone_chunk_contents == null) + bun.assertf(chunk_index == chunk_index_in_chunks_list, "chunk_index ({d}) != chunk_index_in_chunks_list ({d})", .{ chunk_index, chunk_index_in_chunks_list }); } + // In standalone mode, additional output files (assets) are inlined into the HTML. + if (standalone_chunk_contents != null) return; + { const additional_output_files = output_files.getMutableAdditionalOutputFiles(); output_files.total_insertions += @intCast(additional_output_files.len);