mirror of
https://github.com/oven-sh/bun
synced 2026-02-02 15:08:46 +00:00
fix(bundler): fix --compile with 8+ embedded files (#25859)
## 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 <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user