From 4c492c66b821173048a79e0d252e9363ddfb2c7c Mon Sep 17 00:00:00 2001 From: Dylan Conway Date: Tue, 6 Jan 2026 15:05:01 -0800 Subject: [PATCH] fix(bundler): fix --compile with 8+ embedded files (#25859) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fixes #20821 When `bun build --compile` was used with 8 or more embedded files, the compiled binary would silently fail to execute any code (exit code 0, no output). **Root cause:** Chunks were sorted alphabetically by their `entry_bits` key bytes. For entry point 0, the key starts with bit 0 set (byte pattern `0x01`), but for entry point 8, the key has bit 8 set in the byte (pattern `0x00, 0x01`). Alphabetically, `0x00 < 0x01`, so entry point 8's chunk sorted before entry point 0. This caused the wrong entry point to be identified as the main entry, resulting in asset wrapper code being executed instead of the user's code. **Fix:** Custom sort that ensures `entry_point_id=0` (the main entry point) always sorts first, with remaining chunks sorted alphabetically for determinism. ## Test plan - Added regression test `compile/ManyEmbeddedFiles` that embeds 8 files and verifies the main entry point runs correctly - Verified manually with reproduction case from issue 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude --- src/bundler/linker_context/computeChunks.zig | 24 ++++++++++++++++++-- src/collections/baby_list.zig | 4 ++++ test/bundler/bundler_compile.test.ts | 20 ++++++++++++++++ 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/src/bundler/linker_context/computeChunks.zig b/src/bundler/linker_context/computeChunks.zig index 15f825b601..82b19346d0 100644 --- a/src/bundler/linker_context/computeChunks.zig +++ b/src/bundler/linker_context/computeChunks.zig @@ -249,9 +249,29 @@ pub noinline fn computeChunks( var sorted_keys = try BabyList(string).initCapacity(temp_allocator, js_chunks.count()); - // JS Chunks sorted_keys.appendSliceAssumeCapacity(js_chunks.keys()); - sorted_keys.sortAsc(); + + // sort by entry_point_id to ensure the main entry point (id=0) comes first, + // then by key for determinism among the rest. + const ChunkSortContext = struct { + chunks: *const bun.StringArrayHashMap(Chunk), + + pub fn lessThan(ctx: @This(), a_key: string, b_key: string) bool { + const a_chunk = ctx.chunks.get(a_key) orelse return true; + const b_chunk = ctx.chunks.get(b_key) orelse return false; + const a_id = a_chunk.entry_point.entry_point_id; + const b_id = b_chunk.entry_point.entry_point_id; + + // Main entry point (id=0) always comes first + if (a_id == 0 and b_id != 0) return true; + if (b_id == 0 and a_id != 0) return false; + + // Otherwise sort alphabetically by key for determinism + return bun.strings.order(a_key, b_key) == .lt; + } + }; + + sorted_keys.sort(ChunkSortContext, .{ .chunks = &js_chunks }); var js_chunk_indices_with_css = try BabyList(u32).initCapacity(temp_allocator, js_chunks_with_css); for (sorted_keys.slice()) |key| { const chunk = js_chunks.get(key) orelse unreachable; diff --git a/src/collections/baby_list.zig b/src/collections/baby_list.zig index bac768a269..400ffdfe4b 100644 --- a/src/collections/baby_list.zig +++ b/src/collections/baby_list.zig @@ -349,6 +349,10 @@ pub fn BabyList(comptime Type: type) type { bun.strings.sortAsc(this.slice()); } + pub fn sort(this: *Self, comptime Context: type, context: Context) void { + std.sort.pdq(Type, this.slice(), context, Context.lessThan); + } + pub fn writableSlice( this: *Self, allocator: std.mem.Allocator, diff --git a/test/bundler/bundler_compile.test.ts b/test/bundler/bundler_compile.test.ts index 766e2a7fed..6be7076f73 100644 --- a/test/bundler/bundler_compile.test.ts +++ b/test/bundler/bundler_compile.test.ts @@ -735,4 +735,24 @@ const server = serve({ .env(bunEnv) .throws(true); }); + + // When compiling with 8+ entry points, the main entry point should still run correctly. + test("compile with 8+ entry points runs main entry correctly", async () => { + const dir = tempDirWithFiles("compile-many-entries", { + "app.js": `console.log("IT WORKS");`, + "assets/file-1": "", + "assets/file-2": "", + "assets/file-3": "", + "assets/file-4": "", + "assets/file-5": "", + "assets/file-6": "", + "assets/file-7": "", + "assets/file-8": "", + }); + + await Bun.$`${bunExe()} build --compile app.js assets/* --outfile app`.cwd(dir).env(bunEnv).throws(true); + + const result = await Bun.$`./app`.cwd(dir).env(bunEnv).nothrow(); + expect(result.stdout.toString().trim()).toBe("IT WORKS"); + }); });