Skip to content

Commit 02d192f

Browse files
authored
feat(build): add hole-punch tool to reduce compressed binary size (#245)
## Summary - Adds `script/hole-punch.ts` — a post-compile tool that zeros unused ICU data entries inside Bun-compiled binaries, making zeroed regions compress to nearly nothing - Reduces gzip download size by ~24% (37 MB -> 28 MB) across all platforms - Runs automatically in CI after build, before smoke test ## How it works The Bun runtime embeds a 29 MB ICU data blob for international locale support. Most entries (legacy charset converters, CJK dictionaries, non-English locale data) are unused by a CLI tool. The tool scans for the ICU blob via magic bytes, parses its TOC, and zeros safe-to-remove entries while preserving root-level data, normalization files, and English locale data that Bun accesses at startup/shutdown. ## Changes - `script/hole-punch.ts` — hole-punch tool with modular exports for testing - `test/lib/hole-punch.test.ts` — 22 unit tests with synthetic ICU blob builder - `.github/workflows/ci.yml` — added hole-punch step after build - `package.json` — added `hole-punch` script
1 parent 74a0642 commit 02d192f

File tree

4 files changed

+1190
-1
lines changed

4 files changed

+1190
-1
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"dev": "bun run src/bin.ts",
1414
"build": "bun run script/build.ts --single",
1515
"build:all": "bun run script/build.ts",
16+
"hole-punch": "bun run script/hole-punch.ts",
1617
"bundle": "bun run script/bundle.ts",
1718
"typecheck": "tsc --noEmit",
1819
"lint": "bunx ultracite check",

script/build.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { promisify } from "node:util";
2424
import { gzip } from "node:zlib";
2525
import { $ } from "bun";
2626
import pkg from "../package.json";
27+
import { processBinary } from "./hole-punch.js";
2728

2829
const gzipAsync = promisify(gzip);
2930

@@ -98,8 +99,17 @@ async function buildTarget(target: BuildTarget): Promise<boolean> {
9899

99100
console.log(` -> ${outfile}`);
100101

102+
// Hole-punch: zero unused ICU data entries so they compress to nearly nothing.
103+
// Must run before gzip so the compressed output benefits from zeroed regions.
104+
const hpStats = processBinary(outfile);
105+
if (hpStats && hpStats.removedEntries > 0) {
106+
console.log(
107+
` -> hole-punched ${hpStats.removedEntries}/${hpStats.totalEntries} ICU entries`
108+
);
109+
}
110+
101111
// In CI, create gzip-compressed copies for release downloads.
102-
// Reduces download size by ~60% (99 MB → 37 MB).
112+
// With hole-punch, reduces download size by ~70% (99 MB → 28 MB).
103113
if (process.env.CI) {
104114
const binary = await Bun.file(outfile).arrayBuffer();
105115
const compressed = await gzipAsync(Buffer.from(binary), { level: 6 });

0 commit comments

Comments
 (0)