zfb

Type to search...

to open search from anywhere

なぜ Rust か

作成2026年6月1日Takeshi Takatsudo

静的サイトフレームワークを Rust で書くという賭け、それが何をもたらし、何を犠牲にするか。

zfb は、ユーザーが TypeScript と JSX を書く静的サイトフレームワークです。フレームワーク自体は Rust です。このページではその理由を説明します。

パフォーマンス

見出しは、ミリ秒単位で計測されるページ単位のリビルド時間です。パース、型チェック、コンテンツ処理、アトミックなファイル書き込みはすべてネイティブな Rust であり、「十分速い」ではなく、純粋に本当に速いのです。2,000 ページのサイトでも、共有ヘッダーが変更されたとき影響を受けるページを 1 秒を十分に下回る時間でリビルドします。作業が既知の依存グラフ上を回る緊密な Rust のループだからです。

dev ループも同じ形です。保存が FS イベントを生み、ウォッチャーがデバウンスし、オーケストレーターがグラフに問い合わせ、影響を受けるページが再レンダリングされ、dev サーバーがリロードをブロードキャストします。典型的な編集では、エンドツーエンドのレイテンシは数十ミリ秒の範囲に収まります。遅いのはブラウザのリフレッシュの部分です。

これは魔法ではありません。フレームワークが保存のたびにインタプリタを JIT ウォームアップせず、数ギガバイトのモジュールグラフをメモリに保持しないときに得られるものです。

メモリ安全性と構造化されたエラー

Rust のコンパイラは、use-after-free、データ競合、null 参照をコンパイル時に排除します。ビルドは dist/ を書き込んでいる途中でセグフォルトしません。ウォッチャーは保存のバーストが重なってもパニックしません。退屈な勝利、いったん手にすると気づかなくなる類のものです。

目に見える勝利はエラーの質です。zfb は anyhow を端から端まで使うため、すべてのエラーはチェーンとして CLI に届きます。直接の失敗、失敗した操作、ファイルパス、より高レベルの意図です。TSX のコンパイルエラーは、ファイル・行・列・それを引き起こした操作を示します — 汎用的な「build failed」ではありません。

適合する並行性

ビルドオーケストレーターは CPU をまたいでスケジューリングし、dev サーバーは axum のリスナーと SSE のブロードキャストを同時に走らせます。Rayon が並列ページレンダリングを扱い、tokio が非同期 I/O を扱います。これらは儀式なしに共存します — Rust の型システムが境界を強制するため、スレッドが共有すべきでない状態を共有することは決してありません。

同じコードを Node で書けば、同等の隔離のためにワーカースレッドかチャイルドプロセスが必要になります。ワーカーはモジュールを共有せず、チャイルドプロセスはメモリを共有しません。どちらにせよ、Rust 版がただで行うことに対して、ページごとに調整のオーバーヘッドを支払います。

配布

zfb は npm 経由で配布される単一バイナリです。@takazudo/zfb は npm の optional-deps を介してプリビルドのプラットフォームバイナリを引き込みます — その実行ファイルがフレームワークです。zfb 自体には node_modules がなく、監査すべき推移的なツリーもありません。あなたのプロジェクトは import する依存関係(TypeScript、npm パッケージ、ロックファイル)のために自前の pnpm を持ち込みますが、フレームワークはそのツリーに含まれません。(ソースからビルドする必要があるコントリビューターは From source の手順に従えます。)

単一バイナリはきれいにピン留めされます。CI は必要なバージョンをインストールし、本番デプロイは dev で動いたのと同じバイナリを出荷します。チームが使っているバージョンは 1 つの数字であり、package.json のレンジの星座ではありません。

正直な反論: ビルドコスト

zfb のワークスペースの最初の cargo build は遅いです。V8 のソースバンドル(zfb-renderdeno_core を介して引き込む)は、典型的なコントリビューターのマシンで 15 分から 30 分かけてコンパイルされます。増分ビルドは速いものの、最初の 1 回はこたえます。このコストは受け入れられています。Tauri での配布はエンドユーザーのマシンに Node 依存のない単一バイナリを必要とし、deno_core を組み込むことがその制約を満たす唯一の方法だからです。完全な理由は JS runtime を参照してください。

zfb は zfb-render のデフォルトで有効な embed_v8 cargo フィーチャーの背後で V8 のビルドをゲートします。JavaScript のレンダリングを必要としないクレートは V8 を一切引き込みません。cargo test -p zfb-content を走らせるコントリビューターは数秒でコンパイルします。エンドユーザーはこのコストを支払いません — プリビルドのバイナリをインストールするからです。

このゲートは、リンクされた zfb バイナリそのものにとってはタダの昼食ではありません。V8 が成果物を支配し(バンドルされた v8 rlib はそれ自体で約 169 MiB、残りの deno_* ツリーがさらに約 13 MiB を加えます)、そのため V8 オフの zfb はストリップ後で V8 オンの出荷バイナリより約 82 MiB 小さくなります。今日のところ、その軽いバイナリは zfb-render ライブラリの V8 なしの利用者にとってのみ有用です — SSG レンダリングがユーザーのページを動かすために V8 を必要とするため、V8 なしの zfb ビルドステップはまだありません。このフィーチャーは、将来の出荷経路(Tauri サイドカー、スタンドアロン SSR サーバー、cargo install によるデプロイ)がワークスペースを再設計せずに小さい成果物をオプトインできるよう配線されています。

JS ベースの代替との比較

Vite、Next、Astro、SvelteKit — すべて優れたツールです。zfb の賭けは異なるものであり、あらゆる方向で優れているわけではありません。もしフレームワークが Rust で、ユーザーのコードだけが JS / TS だったら? というものです。

それはトレードオフを傾けます。ビルド速度は安くなります。フレームワークのコストで JS のモジュールグラフを走査するバンドラーがないからです。配布はシンプルになります — フレームワークの node_modules がありません。メモリと並行性は言語レベルの保証を得ます。エラーの質は anyhow を通して統一された形を得ます。

コストは、フレームワークのネイティブ言語における小さめのプラグインエコシステムと、コントリビューターにとっての長めの初回ビルドです。zfb は両方を意図的に支払います。設計の狙いが、ビルドに邪魔されたくないユーザーだからです。

膨大なプラグインエコシステムと、何年もかけて磨かれたホットリロードの体験を備えた JS ネイティブのフレームワークが欲しいなら、そうしたツールは存在します。フレームワークが速いバイナリの中に消え去り、リビルドが何も起きなかったかのように感じられることを望むなら、それこそが zfb の目的です。

Revision History