最小埋め込みガイド(TASK-7.1b)
本ドキュメントは REQ-7(docs/spec/04-requirements.md REQ-7 節「最小埋め込み〜 フルスタックの共通コア(思想のグラデーション)」)の受け入れ基準「最小埋め込み構成 (既存 HTML ページの <div> へのマウント)とフルスタック構成(SSR+ルーティング)が、 コンポーネントロジックに一切分岐を持たない同一関数を呼び出すこと」を、利用者が 実際に手を動かして体験するためのガイドです。TASK-7.1(#51)の子タスクとして、 templates/embed/embed.html(TASK-7.1a・#52)と対になる成果物として作成します。
本書のステータスと前提: 本書は fandhe-frontend-core・fandhe-frontend-app(いずれもマージ済み)の 公開 API と docs/api/hydration-api.md(TASK-6.2a、設計確定済み)の凍結契約を 前提として執筆しています。templates/embed/embed.html(TASK-7.1a・#52)はマージ 済みで、3.1 節のコード例は実物と一致させています。fandhe-frontend-wasm-client クレート本体 の実装(TASK-6.2b・#48)・fandhe-frontend-server(TASK-6.1c・#44)は本書更新時点ではいずれも 未マージで並行進行中です。したがって本書に登場する mount_csr() / hydrate() の 呼び出し例は、クレート実装済みコードの引用ではなく docs/api/hydration-api.md で 確定した設計契約として提示しています。実装が完了しマージされた時点で、本書の 記述と実物に乖離があれば、各設計確定書・実装を正として本書を追随更新します。
1. 目的と対象読者
このフレームワークは「思想のグラデーション」を中核価値の一つとして掲げています。 既存の静的 HTML ページの一部分(アイランド的な <div> 一つ)だけをフレームワークで 動かす最小埋め込み構成から始めて、プロジェクトが成長したらフルスタック (SSR+ルーティング)構成へ移行できます。このとき、コンポーネントの実装コードを 書き直す必要はありません。
対象読者は、次のいずれかに当てはまる開発者です。
- 既存の静的サイト・レガシーページの一部だけを部分的にリッチにしたい
- 「最初は小さく、後で大きく」育てたいが、将来的なフレームワーク乗り換えコストを 避けたい
- REQ-7 の受け入れ基準(分岐なしの共通コア)を具体的なコードで確認したい
トレーサビリティ: REQ-7(docs/spec/04-requirements.md)/TASK-7.1(docs/spec/05-tasks.md)/ PoC-3(docs/spec/03-poc/rendering-web-standards/、差別化空白 E への対応)。
2. 仕組み — 共通コアと分岐なし契約
最小埋め込み構成とフルスタック構成の違いは「コンポーネント関数をどこから呼ぶか」 だけであり、コンポーネント自身の実装には一切現れません。
fandhe-frontend-app(list_page / detail_page / page_shell)
▲ ▲
│ │
CSR(最小埋め込み) SSR/SSG(フルスタック)
fandhe-frontend-wasm-client の fandhe-frontend-server(axum ハンドラ想定)
mount_csr("app-list") が同じ関数を呼び出す
が同じ関数を呼び出すcrates/app/src/lib.rs の list_page / detail_page / page_shell は、SSR・SSG・CSR の いずれのモードからも同一関数がそのまま呼ばれることを前提に設計されています (三モード契約、REQ-6)。関数側にモード分岐の if や #[cfg(...)] は存在しません。 呼び出し側(CSR なら fandhe-frontend-wasm-client、SSR/SSG なら fandhe-frontend-server)が変わるだけで、 コンポーネントの記述は共通です。
この契約の一次情報源は crates/app/src/lib.rs の rustdoc(cargo doc -p fandhe-frontend-app --open) です。本ガイドはそこへの導線であり、契約そのものの詳細は rustdoc を参照してください。
3. 最小埋め込みの手順
最小埋め込みは、既存の静的 HTML ページに次の 3 要素を足すだけで成立します。
- マウントポイントとなる
<div id="...">を置く <script type="module">から WASM モジュールをinit()し、mount_csr("...")を呼ぶ- WASM モジュールをビルドし、ページと同一オリジンで配信する
3.1 マウントポイントを置く
docs/spec/05-tasks.md(TASK-7.1 節)が定める製品版の正典テンプレートは templates/embed/embed.html(TASK-7.1a・#52、マージ済み)です。以下は同ファイル の抜粋(実物)です。既存の素の HTML はそのままに、<div> 一つだけをフレームワーク の管理下に置きます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>既存ページへの部分埋め込みサンプル</title>
</head>
<body>
<h1>既存の静的ページ(フレームワーク外で書かれた想定)</h1>
<p>この段落は素の HTML であり、フレームワークの管理下にありません。</p>
<!-- ここだけ fandhe-frontend-app の CSR コンポーネントで埋める(アイランド的な最小構成) -->
<div id="app-list"></div>
<p>この段落も素の HTML です。フレームワークは上の div の中身にしか関与しません。</p>
</body>
</html>正典テンプレートの所在: 上記は templates/embed/embed.html の抜粋です。 コメント(役割・責務境界・呼び出し契約の説明)を含む完全な内容は同ファイルを 直接参照してください。本節のコード例と実物が乖離した場合は、実物 (templates/embed/embed.html)を正とします。
fw new --template embed での生成(イシュー #410): 上記テンプレートを 手動コピーする代わりに fw new <project-name> --template embed を実行すると、 embed.html(上記正典テンプレートとバイト一致)と structure.toml (fw gate が唯一の情報源として読む静的専用マニフェスト)の 2 ファイルが 決定的に生成されます。生成直後に fw gate --project <project-name> を 実行すると、cargo パッケージを持たない静的単一ファイル構成として 無編集で PASS します(詳細は docs/design/fw-new-design.md §3.3・ docs/design/gate-design.md §2.5 を参照)。手動コピーの手順(本節)は 既存ページへ後から部分埋め込みを追加する場合に引き続き使えます。
3.2 WASM を初期化してマウントする
<script type="module"> から WASM を初期化し、mount_csr(root_id) を呼びます。 mount_csr は docs/api/hydration-api.md 第 3 節で凍結された API で、指定 ID の要素に 対して fandhe_frontend_app のページ関数 → fandhe_frontend_core::render() の既定エスケープ済み出力を 反映します(CSR が SSR/SSG と同一関数を呼ぶという REQ-6 受け入れ基準そのものです)。
<script type="module">
import init, { mount_csr } from "./wasm/fandhe_frontend_wasm_client.js";
async function main() {
await init();
mount_csr("app-list");
}
main();
</script>mount_csr は fandhe-frontend-wasm-client クレート(TASK-6.2b・#48、本書執筆時点で未マージ) が提供する予定の API です。設計契約(シグネチャ・不変条件)は docs/api/hydration-api.md 第 3 節・第 6 節で確定済みですが、クレート本体・実際にビルドされる .js/.wasm 成果物は #48 のマージを待って利用可能になります。
3.3 ビルド・配置
WASM モジュールのビルド手順の正式な確定はクレート実装(#48)と関連テスト整備 (TASK-6.2c・#49)の完了後に固まります。本書執筆時点で docs/api/hydration-api.md が 示す方向性は次のとおりです。
- ターゲットは
wasm32-unknown-unknown(cargo build --target wasm32-unknown-unknown -p fandhe-frontend-wasm-clientでコンパイル成立を確認する、docs/api/hydration-api.md第 7 節) wasm-pack等のツールで JS グルーコード(init/mount_csrのバインディング) を生成する運用を想定- 生成物はページと同一オリジンで配信する(4 節・5 節参照)
具体的なビルドコマンド・出力パスの確定手順は #48/#49 の実装完了後に本書へ追記 します。現時点では固定の手順書として案内できる段階にないため、本書では方向性の 提示にとどめます。
4. フルスタック構成への移行パス
プロジェクトが成長し、SSR やルーティングが必要になったら、同じ list_page / detail_page / page_shell を fandhe-frontend-server(TASK-6.1c・#44、本書執筆時点で未マージ) の axum ハンドラから呼び出す構成に移行できます。
| 最小埋め込み(本書) | フルスタック | |
|---|---|---|
| 呼び出し元 | fandhe-frontend-wasm-client の mount_csr | fandhe-frontend-server の SSR/SSG ハンドラ |
| 呼び出す関数 | fandhe_frontend_app::list_page 等 | 同じ fandhe_frontend_app::list_page 等 |
| コンポーネント側の分岐 | なし | なし |
| 出力の反映先 | <div> の innerHTML(CSR) | HTTP レスポンス本文(SSR/SSG) |
移行時にコンポーネントの記述(crates/app/src/lib.rs 相当のコード)を書き直す必要は ありません。書き直すのは「どこから呼ぶか」(埋め込みページの <script> か、 サーバーのルーティングハンドラか)だけです。これが REQ-7 の「フレームワークを 乗り換えたくない」というユーザーストーリーに対する具体的な回答です。
5. セキュリティ不変条件
最小埋め込み構成であっても、既定エスケープ(REQ-1)はフルスタック構成と同じ強度で 維持されます。
mount_csrが DOM へ反映する内容は、fandhe_frontend_appのページ関数 →fandhe_frontend_core::render()を経由した既定エスケープ済みの HTML のみです。format!によるタグ文字列の 直接組み立てや、ユーザー入力を直接innerHTMLに代入するコードは、フレームワーク 側にも埋め込みページ側にも書いてはいけません(.claude/rules/coding-rust.md「HTML 文字列の直接組み立て禁止」)。fandhe_frontend_core::raw_html()は既定エスケープを迂回する明示的オプトイン API です。 信頼できない入力(ユーザー投稿・外部 API のレスポンス等)をraw_html()に 渡してはいけません。埋め込みページのコンポーネントであっても、この制約は フルスタック構成と同一です。- WASM モジュール(
.wasm/ グルー.js)は埋め込み先ページと同一オリジンで 配信することを前提とします。外部 CDN からの読み込みはサプライチェーン面の 拡大につながるため推奨しません(.claude/rules/security.md)。 - 責務境界の明示: フレームワークが安全性を保証するのは、フレームワークが 管理する
<div>(マウントポイント)の内側だけです。埋め込みページの残りの 部分(素の HTML・他の<script>タグ・ページ全体の CSP 設定等)はフレームワーク の管理下になく、その安全性は埋め込みページの作者の責務です。この境界を誤解し、 「フレームワークを使えばページ全体が自動的に安全になる」という期待を持たない でください。
6. v1 スコープと制約
REQ-7 の受け入れ基準にあるとおり、v1 の共通コアはパスマッチングによる基本的な ルート解決(TASK-7.2)までを提供します。ネストレイアウト・データローディング・ 高度なルーティング等のフルスタック機能一式の完全実装は本要件の対象外です (docs/spec/04-requirements.md の対象外事項一覧と整合)。
「最小埋め込みとフルスタックがコンポーネントロジックに分岐を持たない」ことの 機械的検証(静的解析・テスト)は TASK-7.3(crates/core/tests/no_branching_across_modes.rs) のスコープであり、本ガイドの対象外です。
7. 関連文書・並行タスクとの整合
本書は次の一次情報源を前提とし、そこに矛盾があれば各文書・実装を正とします。
crates/app/src/lib.rsの rustdoc(三モード契約・既定エスケープの引き継ぎ、マージ済み)docs/api/hydration-api.md(mount_csr/hydrateの設計確定、TASK-6.2a・マージ済み)docs/guides/component-authoring.md(コンポーネント記述の一般的な書き方)templates/embed/embed.html(TASK-7.1a・#52、マージ済み。3.1 節の正典テンプレート)
以下は本書更新時点で未マージ・並行進行中であり、マージされ次第、本書の該当箇所 (3 節・4 節)を追随更新します。
| 未マージの成果物 | 対応タスク | 影響する本書の節 |
|---|---|---|
fandhe-frontend-wasm-client クレート実装 | TASK-6.2b(#48) | 3.2・3.3 節(ビルド・配置) |
fandhe-frontend-server(SSR/SSG エントリ) | TASK-6.1c(#44) | 4 節(移行パス) |
8. スコープ外(本書では扱わない事項)
mount_csr/hydrateの実装(TASK-6.2b・#48)fandhe-frontend-serverの SSR/SSG エントリ(TASK-6.1c・#44)- 分岐なしの静的解析・テストによる機械検証(TASK-7.3)
- View Transitions API の
<meta>タグ・薄いラッパー(TASK-8.1、docs/spec/04-requirements.mdREQ-8)
これらはいずれも既存タスク・Issue が存在するため、本書では新規の起票提案は行いません。