皆様こんにちは。初見の方は、初めまして。普段はマネジメントや別事業をしつつ、インフラからアプリまで何でもこなす現場のIT何でも屋です。
「新言語 Hike(ハイク)」の軽速化シリーズ、今回はWebフロントエンド開発で誰もが一度はお世話になっているであろう定番ライブラリ 「ZIPアーカイブ生成・解凍(JSZip)」 の超軽量化に挑戦します!
1. フロントエンドにおける「ZIPファイル生成」の重すぎる現実
Webアプリケーションで「複数ファイルの一括ダウンロード」「帳票・画像のエクスポート」「バックアップデータ出力」をブラウザ完結で実装する際、デファクトスタンダードとして使われているのが JSZip です。
非常に使いやすく多機能なライブラリですが、現代のWebパフォーマンス(Core Web Vitals やモバイル最適化、スマホでのUX)の視点で見ると、現場のエンジニアにとって無視できない3つの構造的な課題を抱えていました。
- ライブラリサイズが約100KB(
jszip.min.js単体で 97.6KB)と大きい
- 特定の機能やボタン押下時しか使用しない場合でも初期バンドルに含まれ、初期表示(FCP/LCP)の速度やモバイル回線の帯域を圧迫する。
- 大規模データ処理時のメインスレッドブロックとガベージコレクション(GC)の負荷
- 数十MB規模のファイル群をブラウザ上で固めて生成する際、動的オブジェクトの生成によってJavaScriptのGCが頻発し、モバイル端末などで処理遅延やフリーズの原因になる。
- 既存コードにおける移行コストの壁
- 既存のコードベースに
zip.file()やzip.generateAsync()が多数依存しているため、別ライブラリへの全面的な書き換えには相応の修正工数と検証コストが発生する。
「もっと軽くて、速くて、既存コードは1行も直さずに済むドロップイン互換なエンジンはないか?」
この課題を解決するため、新言語「Hike」のゼロランタイム・軽量なWasmコンパイル特性を活用し、ZIP生成エンジンの軽量化と完全互換化を検証することにしました。
2. Go製Wasm(3.8MB)の絶望と、HIKE(4KB)の衝撃
「Go言語なら archive/zip が標準パッケージにあるから、GoからWebAssembly(Wasm)を吐けばすぐできるのでは?」
そう考えて実際に Go の標準パッケージを GOOS=js GOARCH=wasm でビルドしてみた結果がこちらです。
zip_go.wasm : 3,893,378 Bytes (約 3.8 MB !)
ブラウザで動かすにはあまりにも巨大すぎました。Goのランタイム(GC、スケジューラ、リフレクション、型情報など)が丸ごとバンドルされてしまうため、ちょっとしたZIP処理をしたいだけでも数MBのロードを強いられます。
そこで、HIKE を使ってZIPフォーマット(PKWARE仕様)のコアバイナリ生成エンジンを独自実装しました。
サイズ比較
| 実装 / ライブラリ | 配信種別 | サイズ (転送フットプリント) | 削減効果 (JSZip比) |
|---|---|---|---|
従来の jszip.min.js (v3.10.1) |
JavaScript | 97.6 KB | 基準 (100%) |
Go製 Wasm (zip_go.wasm) |
Go + ランタイム | 3.80 MB | 約 40 倍に肥大化 |
HIKE Wasm コア (zip.wasm) |
ゼロランタイム Wasm | 4.20 KB | 約 95.7% 削減 (約1/23) |
HIKE 互換ラッパー (hike_zip_wrapper.js) |
Drop-in JS | ~10.0 KB | - |
| HIKE 合計(Wasm + ラッパー) | 完全置換セット | 約 14.2 KB | 約 85.5% 削減 |
初期化レイテンシ & 単一ZIP生成速度 実測ベンチマーク
(※ テキスト+JSON+PNG画像 計10ファイル・合計 5MB 生成時のブラウザ内実測)
| 評価項目 | 従来の jszip.min.js
|
Go製 Wasm (zip_go.wasm) |
HIKE版 (zip.wasm) |
改善効果 |
|---|---|---|---|---|
| エンジン初期化時間 (冷態起動) | 18.2 ms | 340.5 ms (Goランタイム起動) | 2.1 ms (4KB即時コンパイル) | 約 8.6倍 高速 |
| ZIPアーカイブ生成所要時間 | 142.6 ms | 68.4 ms | 38.2 ms | 約 3.7倍 高速 |
| ヒープメモリ消費ピーク | 48.6 MB | 72.1 MB | 12.4 MB | 約 74.5% 抑制 |
| 初期バンドル転送インパクト | 97.6 KB | 3.80 MB | 4.20 KB | 約 95.7% カット |
Go製Wasmの 約1/900、JSZip単体と比べても 約85%の帯域削減 を達成しました!
3. 既存コードの変更ゼロ!1行で完全置き換え(Drop-in Replacement)
どれだけ軽くなっても、既存の業務ロジックを書き直す必要があっては移行できません。
HIKE版では、従来の JSZip と 100% 同一のAPI を提供する互換ラッパー(hike_zip_wrapper.js)を用意しました。
STEP 1: HTML の <script> タグを差し替え
<!-- 従来の jszip.min.js (97.6 KB) を削除 -->
<!-- <script src="js/jszip.min.js"></script> -->
<!-- HIKE 互換ラッパーに差し替え (SRI 検証付き) -->
<script src="js/hike_zip_wrapper.js"
integrity="sha384-oIa13pYorUwkU9JSChxWcJMKx6rv6wICRw/Xw3RUUU7AVotSF+wvKPQLMAx6Ml68"
crossorigin="anonymous"></script>
STEP 2: アプリ側のJavaScriptは「そのまま」で動く!
// 1. ZIP インスタンス生成
const zip = new JSZip();
// 2. ファイルやフォルダの追加(テキスト、JSON、画像など何でもOK)
zip.file("hello.txt", "Hello World from HIKE!\n");
zip.file("config.json", JSON.stringify({ engine: "HIKE", runtime: "none" }));
const folder = zip.folder("images");
folder.file("logo.png", base64PngData, { base64: true });
// 3. 非同期生成(進捗コールバックも完全互換)
const blob = await zip.generateAsync(
{ type: "blob", compression: "STORE" },
(metadata) => {
console.log(`進捗: ${metadata.percent}% - ${metadata.currentFile}`);
}
);
// 4. ダウンロード実行
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "archive.zip";
a.click();
これだけで、既存のフロントエンド資産を一切汚すことなく、ネットワーク転送量を一気に 14KB(Wasmコアはわずか4KB) に圧縮できます。
もちろん、ZIPの解凍(JSZip.loadAsync(blob))にも完全対応しています。
4. 柔軟な3つのローディング戦略 & SRIセキュリティ
実運用の現場では、セキュリティ要件や配信シナリオに応じた柔軟性も求められます。
-
一括即時ロード (
all):
初期ロード時に Wasm を先行初期化し、ユーザーがダウンロードボタンを押した瞬間に 0 ミリ秒レイテンシでZIP生成を開始します。 -
オンデマンド遅延ロード (
lazy):
初期表示時は Wasm を 1 バイトもダウンロードしません。ユーザーがエクスポートボタンを押して初めてzip.wasm(たったの4KB)を動的取得するため、初期表示パフォーマンスへの影響を完全にゼロにできます。 -
W3C Subresource Integrity (SRI) 自動検証:
Wasmバイナリの改ざんやCDN汚染を防ぐため、ラッパー内部で SHA-384 / SHA-256 によるハッシュ整合性チェックをネイティブ実行します。
5. まとめ
定番ライブラリ JSZip を HIKE Wasm に置き換えることで、既存コードの書き換えコストゼロで 85% の軽量化 を実現できました。
しかし、実際の業務システムに組み込んで数千個のファイルや数百MBのデータをエクスポートしようとしたとき、 「ブラウザの限界」という新たな巨大な壁 に直面することになります……。
- ファイル数が増えるとブラウザのタブがクラッシュ(OOM)する
- 処理中にプログレスバーが止まり、画面全体がフリーズする
- 通常のZIPアルゴリズムはそのままストリーミングできない
次回、【Hike軽速化:ZIP_2】大量ファイルでブラウザが落ちる!?インメモリZIPの罠と、ストリーム・背圧制御でゼロメモリ化に挑む話 に続きます。ご期待ください!
シリーズ一覧
本シリーズの過去記事や今後の続編一覧は、以下のストックリストで一括管理しています。ぜひご活用ください!
👉 Hikeで軽速化シリーズ(Qiitaストック)
前回:【Hike軽速化:パスキー編】新言語「Hike」でPasskey/WebAuthnをフルWasm・ステートレス化。サイズ1/5・セッションI/Oゼロ(0ms)・秒間83万回検証を達成した話
今回:【Hike軽速化:ZIP_1】新言語「Hike」で定番 JSZip をWasm化したら、98KBが4KBに激減&完全互換でそのまま動いた話
次回:【Hike軽速化:ZIP_2】大量ファイルでブラウザが落ちる!?インメモリZIPの罠と、ストリーム・背圧制御でゼロメモリ化に挑む話 (予定)
謝辞
本プロジェクトの実現にあたり、多大なるインスピレーションと恩恵をいただいた皆様に深く感謝申し上げます。
JSZip コミュニティおよびエコシステムの皆様 : 長年にわたりWebフロントエンドにおけるZIPアーカイブ処理のデファクトスタンダードとして jszip.min.js(Stuart Knightley氏をはじめとする開発・メンテナーの皆様)を支え続けてくれた偉大な先達の設計と実績があったからこそ、本Wasm版の設計およびドロップイン互換検証が可能になりました。心より感謝いたします。
本記事の構成案の壁打ちや技術検証のサポートには生成AI(Gemini)を活用しました。AIの強力なアシストを受けながら、スムーズに検証から執筆までをやり切ることができました。
軽量なWasm環境やエッジな開発、AIを活用した開発に興味がある方は、ぜひ Hike を触ってみてください!
※本実装のAPIインターフェースは、既存の JSZip(Stuart Knightley氏)等の仕様を参考に、実務におけるシームレスなDrop-in互換を担保しています。