デスクトップ展開
zfb でビルドしたサイトを Tauri・Electron などのデスクトップアプリケーションに組み込む方法。
このガイドでは、zfb サイトをデスクトップアプリに組み込みたい場合に、現時点で現実的に何ができるかを解説します。zfb と Tauri を組み合わせる構成には 4 つの異なるモードがあり、それぞれ異なるプロジェクトに対する正解です。意図を持って選択してください。
- モード A は、コンテンツがビルド時に作成され、各リリースに同梱される場合に最適です。
- モード B は、Tauri 側のコードを最小限に抑えつつ zfb のランタイムの動的機能をフルに使いたい場合に最適です。
- モード C は、アプリの動的な部分を、zfb をプロセスに含めずに Rust で実現できる場合に最適です。
- モード D は、インプロセス埋め込み API が提供されたあとの将来のモードです。
4 つのモードすべてを支配する中核的な不変条件:
ビルド時 = Node.js が必須。ランタイム = Node.js は一切不要。
zfb の静的な出力は意図的にランタイム非依存に設計されています。一方、ビルドツールはそうではありません。この境界に逆らうのではなく、この境界を前提にアーキテクチャを設計してください。
モード A — dist/ のみを同梱する
概要。 zfb build は dist/ に完全に静的なサイト(HTML・CSS・JavaScript ファイルで、ランタイムにサーバーサイドの依存を持たない)を生成します。デスクトップアプリは、フレームワーク組み込みのアセットサーバーを使ってそのディレクトリを直接配信できます。zfb はランタイムでは見えず、パッケージされたアプリはディスク上のただのファイルです。
選ぶべきとき。 ドキュメントアプリ、ヘルプシステム、そしてコンテンツがビルド時に作成されリリースに同梱されるあらゆるデスクトップアプリ。ユーザーが実行中のアプリ内でコンテンツを編集する必要がないなら、モード A が正解です。
必要な作業。 Tauri の場合、関連する tauri.conf.json のキーは次のとおりです:
{
"build": {
"distDir": "../dist",
"devPath": "http://localhost:4321"
}
}
distDir を dist/ フォルダに向けます(相対パスはプロジェクト構成に合わせて調整してください)。残りは Tauri 組み込みの静的ファイルサーバーが処理します。パッケージされたアプリに Node.js プロセスや HTTP サーバーは不要です。
- 開発マシン(または CI)で
zfb buildを実行する。 - 生成された
dist/を Tauri アプリのバンドルに含める。 - 出荷する。完了です。
アセット配信のオプション一式(カスタムプロトコルやセキュリティポリシーを含む)については、Tauri <code>tauri.
トレードオフ。 コンテンツの更新には新しいアプリのリリースが必要です。新しいビルドなしにユーザーが最新のコンテンツを見られる仕組みはありません。
モード B — zfb バイナリを Tauri のサイドカーとして動かす
概要。 Tauri が zfb バイナリを子プロセスとして起動し、WebView は localhost:<port> を指します。zfb はその子プロセスの中で、リクエスト時の MDX レンダリングやリソース探索などを処理します。サイドカーのバイナリは Rust であり、パッケージされたアプリに Node.js は不要です。
選ぶべきとき。 zfb の動的機能をフルに使いたいが(prerender = false のルート、リクエスト時の MDX レンダリング、リビルドなしのコンテンツ再読み込み)、zfb サーバーを Tauri プロセス自体の中に置く必要はない、という場合。
必要な作業。 Tauri サイドカーとして zfb バイナリを Tauri アプリと並べて同梱します。Tauri フロントエンドがサイドカーを起動し、ポートを発見し、WebView を http: に開けるよう、薄い IPC ブリッジを書きます。サイドカーと Tauri の境界はクリーンですが、統合のためのつなぎ込みは標準では提供されません。
トレードオフ。 追加で可動部品を抱えることになります(子プロセスのライフサイクル管理、ポート発見、Tauri とサイドカー間の IPC 配線)。その代わり、Node.js 依存なしで zfb のフルなレンダリングパイプラインをランタイムで利用できます。
モード C — zfb をビルド時のみ使うカスタム Rust クレート
概要。 zfb はビルド時に一度だけ Preact のシェルを dist/ にコンパイルします。それ以降のランタイムの動的機能(リソース探索、Markdown レンダリング、配信)はすべて手書きの Rust コードであり、典型的にはディスクからファイルを読み込み、事前ビルドしたシェルにコンテンツを差し込む Axum サーバーです。zfb はランタイムには一切存在せず、純粋にビルド時のツールです。
選ぶべきとき。 アプリの動的な部分が Markdown レンダリングか、あるいは素直な Rust ライブラリですでにうまく扱える何かであり、zfb のフルなレンダリングパイプラインをバイナリに持ち込むより、焦点を絞った Rust サーバーコードを書きたい場合。可能な限り小さいランタイムフットプリントを求めている場合です。
必要な作業。 zfb build で Preact のシェルをビルドします。続いて、リクエスト時に Markdown ファイルを読み込み、Rust の Markdown クレートでレンダリングし、その結果をセンチネル置換などのパターンで静的シェルに差し込む、専用の Rust サーバー(または既存の Tauri アプリへの Axum ルートの追加)を書きます。CCResDoc はこのパターンの具体的で動作する実例です。zfb が一度だけ Preact のシェルをコンパイルし、手書きの Rust バックエンドがランタイムにコンテンツを配信します。
トレードオフ。 より多くの Rust のつなぎコードを書くことになり、ランタイムでの TSX レベルのページコンポーネントを諦めることになります。その代わり、可能な限り小さいバイナリ、ランタイム動作の完全な制御、そしてパッケージされたアプリでの V8 依存ゼロを得られます。
モード D — zfb を Rust クレートとして Tauri 内に埋め込む(将来)
概要。 Tauri の setup フックが、Tauri プロセス内の Tokio スレッド上で zfb サーバーを起動します。子プロセスもポート管理もなく、prerender = false のルートや Tauri IPC 呼び出しを Rust 対 Rust のインプロセス受け渡しで処理します。WebView はネットワークポートを介さず、埋め込まれたサーバーと直接やり取りします。
選ぶべきとき(提供されたら)。 zfb のランタイムの動的機能と緊密な Tauri 統合(IPC、ファイルシステムアクセス、コンテンツのホットリロード)を、モード B のサイドカーのライフサイクル配線なしに使いたい場合。
必要な作業。 埋め込み API はまだ存在しません。提供される際には、zfb-core などのクレートに依存し、Tauri の setup フックで初期化関数を呼び出すことが必要になる見込みです。正確な API 表面は現在も設計中です。
このモードは現時点では動作しません。 進捗は次のリサーチイシューで追跡してください:
https:
トレードオフ。 提供されたあとは、サイドカーの配線なし、より緊密な IPC、よりクリーンなパッケージング。提供されるまでは、選択肢になりません。
より難しいケース: アプリ内でコンテンツをリビルドする
ユーザーがローカルで Markdown ファイルを編集し、実行中のアプリ内でライブプレビューを見られるようにする必要がある場合(要するにパッケージされたウィンドウ内で zfb dev を動かす場合)、正しいアプローチは必要なものによって変わります。
- フルな MDX レンダリングが必要なら、モード B(サイドカー)が今日の最善の道です。サイドカーがファイル監視 / インクリメンタルリビルドのループをバックグラウンドで動かし続け、WebView はアプリの再起動なしに変更を反映します。
- モード D(インプロセス埋め込み)は、提供されたあとに望ましい道になります。子プロセスもポートもなく、Tauri IPC とファイルシステムとの統合がより緊密です。
- モード A や C はここでは役に立ちません。どちらもパッケージされたアプリが動く時点でコンテンツが静的であることを前提にしているためです。
ランタイムで Node.js が必要な場合(たとえば Node.js 依存を持つカスタム zfb プラグインを動かす場合)は、代わりに Electron を検討してください。Electron は Node.js を埋め込むため、zfb dev はメインプロセス内で自然に動作します。その代償として、バイナリサイズが大幅に増加し、パッケージングがより複雑になります。これは zfb のモードそのものではなく、ランタイムでの Node.js 要件によって決まるフレームワークの選択です。
Electron・Wails などのデスクトップフレームワークはどうか
どのデスクトップフレームワークを使っても、話は同じです。
- Electron —
dist/は静的アセットディレクトリとして機能します(loadFile()かfile:プロトコルハンドラを使用)。Electron は Node.js を埋め込むためメインプロセス内で/ / zfb devを動かすことは可能ですが、それは zfb のビルドツールチェーン一式をアプリと一緒に同梱することを意味します。 - Wails — WebView を埋め込み、埋め込み Go サーバーからアセットを配信します。
dist/ディレクトリに向けてください。ビルド時の Node.js 要件は上記と同じです。 - Neutralino・Tauri、その他組み込みの静的ファイルサーバーを持つフレームワーク —
dist/を同梱すれば、Node へのランタイム依存はありません。
dist/ の出力はただのファイルです。ディスクからファイルを配信できるフレームワークなら、どれでも機能します。
Tauri 固有のヒント
Tauri 統合についての実践的なメモをいくつか。
アセットソースとしての dist/。 distDir を zfb が生成する dist/ ディレクトリに向けて設定します。開発中は devPath を http:(デフォルトの zfb dev ポート)に設定し、古いスナップショットではなくライブの開発サーバーから Tauri がロードするようにします。
コンテンツセキュリティポリシー(CSP)。 Tauri のデフォルト CSP はインラインスクリプトをブロックすることがあります。zfb のアイランドハイドレーションはインラインの <script type="module"> タグを使用します。アプリでアイランドを使う場合は、tauri.conf.json の CSP ポリシーを緩和または拡張してください。
ファイルパス。 zfb build はデフォルトで絶対ルート相対のパス(/)を生成します。tauri: プロトコルを使う場合、Tauri のカスタムプロトコルがこれらを正しく書き換えます。file: を直接使う場合は、zfb.config.ts の base を相対パスに設定する必要があるかもしれません。
関連項目
- Build pipeline —
zfb buildがどのようにdist/を生成し、各クレートが出力に何を寄与するか。 - Architecture: build engine — クレートの分割と、
dist/への書き込みがアトミックである理由。 - Installation — ビルドおよび開発ツールの Node.js 要件はここに記載されています。