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 <noreply@anthropic.com>
This commit is contained in:
Claude Bot
2026-02-17 02:02:12 +00:00
parent 8a68805dfe
commit 80e5b3d352
2 changed files with 59 additions and 47 deletions

View File

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

View File

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