静的HTMLの抽出
任意のWebアプリケーションから、自己完結型の静的HTMLファイルを抽出します。
使用する戦略について
処理を開始する前に、ユーザーにどの戦略を使用するかを尋ねる必要があります。選択肢を明確に提示し、推奨デフォルトとして戦略Aを推奨し、各オプションについて簡単な長所・短所の要約を提供して、ユーザーが情報に基づいた決定を下せるように支援してください。
| 戦略A (Puppeteer) | 戦略B (ブラウザサブエージェント) | |
|---|---|---|
| 適用条件 | アプリがローカルで動作し、認証壁がない場合 | 事前にページとのインタラクション(クリック、フォーム入力)が必要な場合 |
| 忠実度 | 最高 — 算出されたスタイルが解決される | 高 — レンダリングされたDOM |
| セットアップ | 不要 — モックは不要 | 不要 — モックは不要 |
| フレームワーク | 任意 | 任意 |
| 出力 | ファイルに書き込む — サイズ制限なし | エージェントコンテキスト内で切り詰められる可能性あり |
[!WARNING] チェックポイント — ユーザー確認必須。 処理を進める前に、ユーザーにどの戦略を希望するかを尋ねなければなりません。 上記の比較表を提示し、デフォルトとして戦略Aを推奨し、 明示的な承認を待ってください。ユーザーが確認するまで、自分で決定したり処理を進めたりしないでください。
戦略A: Puppeteerスナップショット(推奨)
ヘッドレスChromeを起動し、完全にレンダリングされたDOMをキャプチャし、すべてのCSSをインライン化し、画像をbase64として埋め込んだ自己完結型のHTMLファイルを生成します。任意のフレームワークで動作します — MockPage.jsxは不要です。
前提条件
- アプリがローカルで動作していること(例:
npm run dev) - Node.jsと
puppeteerが利用可能であること(確認:node -e "require('puppeteer')")
ワークフロー
-
アプリを起動し、ポート番号をメモします。
[!WARNING] チェックポイント — ユーザー確認必須。 ローカルサーバーを起動した後、スナップショットスクリプトを実行したりブラウザサブエージェントを起動したりする前に、一時停止してユーザーに確認を求める必要があります。URLとポートをユーザーに報告し、アプリが正しく起動してレンダリングされていることを確認できるようにします。ユーザーが確認するまで、スナップショットのステップに進まないでください。
-
スナップショットスクリプトを実行:
npx tsx <SKILL_DIR>/scripts/snapshot.ts \ --url http://localhost:5173 \ --output .stitch/home.html \ --wait 2000 -
複数ページ — ルートごとに1回実行:
npx tsx <SKILL_DIR>/scripts/snapshot.ts \ --url http://localhost:5173 --output .stitch/home.html --wait 2000 npx tsx <SKILL_DIR>/scripts/snapshot.ts \ --url http://localhost:5173/pricing --output .stitch/pricing.html --wait 2000 npx tsx <SKILL_DIR>/scripts/snapshot.ts \ --url http://localhost:5173/dashboard --output .stitch/dashboard.html --wait 2000 --html-class dark -
開発サーバーの後片付け: スナップショット抽出のためにローカル開発サーバーを起動した場合は、抽出が完了したら必ずサーバープロセスを停止するか、バックグラウンドタスクを終了してください。
スクリプトフラグ
| フラグ | デフォルト | 説明 |
|---|---|---|
--url | (必須) | キャプチャするURL |
--output | (必須) | 出力ファイルパス |
--wait | 1000 | ネットワークアイドル後の追加待機時間(ミリ秒)。遅延読み込みアプリの場合は増やしてください。 |
--viewport | 1280x800 | ビューポートサイズ(WIDTHxHEIGHT) |
--html-class | — | <html>要素に追加するクラス(例: dark) |
--remove-fixed | false | 固定/スティッキー要素(クッキーバナー、チャットウィジェットなど)を削除 |
--full-height | false | ビューポートをスクロール全体の高さにリサイズ |
--title | — | ページタイトルを上書き(ルートパス、例: /dashboard または /settings/profile) |
--auth-script | — | 認証用のデフォルトエクスポート async (page) => void 関数をエクスポートするJS/TSモジュールへのパス |
--inline-canvas | false | <canvas>要素(ECharts、Chart.js、D3)をbase64の<img>タグに変換 |
自動的に行われる処理
document.styleSheetsからすべてのCSSOMルールをキャプチャ(動的なVite/Tailwind開発スタイルやCSS-in-JSを保持)- すべての
<link rel="stylesheet">を<style>ブロックにインライン化 <img>のsrcおよびsrcsetをbase64データURIに変換(外部フォントはスキップ)- 同一オリジンおよび相対パスのアイコンフォントファイル(
@font-face)をbase64データURIとしてインライン化し、合字がASCIIテキストとして表示されるのを防止 <source srcset>URLをbase64としてインライン化- 失敗した/無効な
srcsetエントリを削除し、ブラウザがインライン化されたsrcにフォールバックできるようにする <script>タグ、Vite HMR開発スタイルブロック(createHotContext、import.meta.hot)、開発オーバーレイを削除- 相対CSS
url()パスをインライン化前に解決
フレームワーク別注意事項
| フレームワーク | 備考 |
|---|---|
| React + Vite | そのまま動作。--wait 1000。 |
| Next.js | SSRハイドレーションには--wait 3000。URL: http://localhost:3000。/_next/image からの<img srcset>は自動的にbase64としてインライン化されます。 |
| Angular (@angular/cli / v17+) | ng serve でそのまま動作(デフォルトURL: http://localhost:4200)。Angular Material / PrimeNGのアニメーションハイドレーションと遅延読み込みルートには--wait 2000。 |
| Vue / Nuxt | そのまま動作。 |
| Svelte / SvelteKit | そのまま動作。 |
| Storybook | ストーリーURLを使用: --url http://localhost:6006/?path=/story/... |
| SSR (Webpack) | より長い--waitが必要な場合があります。 |
トラブルシューティング
| 問題 | 解決策 |
|---|---|
| 画像が表示されない | --wait を増やす |
| サーバー停止後に画像が壊れて表示される | srcset がインライン化されたか確認 — ログで「Inlined N images」を確認。srcset URLが失敗した場合は自動的に削除され、src(インライン化済み)が使用されます。 |
| アイコンがテキスト/セリフ装飾なしフォントで表示される | snapshot.ts が document.styleSheets(ステップ0)からCSSOMをキャプチャし、同一オリジンのアイコンフォント(@font-face)がbase64データURIとしてインライン化されていることを確認。 |
Next.jsの/_next/imageがインライン化されない | スナップショット実行時に開発サーバーが動作していることを確認 — スクリプトは動作中のサーバーから最適化された画像を取得します。 |
| ダークモードが適用されない | --html-class dark |
| 出力にクッキーバナーが含まれる | --remove-fixed |
| ページにログインが必要 | --auth-script ./auth.ts を使用(下記「認証が必要なページ」を参照) |
| チャート/グラフが空白のボックスで表示される | --inline-canvas を使用して <canvas> を base64 <img> にシリアライズ |
Cannot find module 'puppeteer' エラー | npm install -g puppeteer |
認証が必要なページ
ログインガード(Vue RouterのbeforeEach、ReactのProtectedRouteなど)があるアプリの場合は、Puppeteerセッション内で実行される小さな認証スクリプトを作成します:
// auth-myapp.ts
import type { Page } from 'puppeteer';
export default async function authenticate(page: Page) {
// 例1: ログインフォームに記入して送信
await page.type('#username', 'admin');
await page.type('#password', 'password123');
await page.click('#login-button');
await page.waitForNavigation({ waitUntil: 'networkidle2' });
// 例2: Cookie/localStorageを直接注入
// await page.evaluate(() => {
// localStorage.setItem('token', 'mock-jwt-token');
// });
// 例3: モジュール注入を介してアプリ独自のログインAPIを呼び出す(Vue/Vite)
// await page.evaluate(() => {
// return new Promise((resolve) => {
// const script = document.createElement('script');
// script.type = 'module';
// script.textContent = `
// import { useUserStore } from '/src/store/modules/user.ts';
// import { fetchLogin } from '/src/api/auth.ts';
// const res = await fetchLogin({ userName: 'Admin', password: '123456' });
// useUserStore().setToken(res.token, res.refreshToken);
// window.dispatchEvent(new CustomEvent('auth-done'));
// `;
// document.head.appendChild(script);
// window.addEventListener('auth-done', () => resolve(true), { once: true });
// });
// });
}
次に、以下のように使用します:
npx tsx <SKILL_DIR>/scripts/snapshot.ts \
--url http://localhost:5173/#/dashboard \
--output .stitch/dashboard.html \
--auth-script ./auth-myapp.ts \
--inline-canvas \
--wait 5000
スクリプトは最初に--urlに移動し(ログインページにリダイレクトされる可能性あり)、認証関数を実行した後、認証済みセッションで元の--urlに再度移動します。
戦略B: ブラウザサブエージェントによるキャプチャ
キャプチャ前にページとのインタラクション(ボタンクリック、フォーム入力、タブ移動など)が必要な場合に使用します。ブラウザサブエージェントは完全な制御を提供しますが、大規模なページでは出力が切り詰められる可能性があります。
ワークフロー
-
アプリをローカルで起動します。
-
ブラウザサブエージェントを使用して移動します。
-
必要に応じてインタラクション(クリック、スクロール、フォーム入力)を行います。
-
DOMを抽出:
document.documentElement.outerHTML[!WARNING] 大規模なページは切り詰められる可能性があります。これに対処するには:
- 抽出前に
<style>タグを削除:document.querySelectorAll('style').forEach(el => el.remove()) - スタイルを静的に再追加(Tailwind CDNリンク、ソースCSS)
- 抽出前に
-
ファイルに保存します。
付録: 静的フォールバック (MockPage.jsx)
[!NOTE] この方法は、アプリをローカルで実行できない場合(依存関係の破損、バックエンドの欠如、回避策のない認証壁など)の最終手段です。Reactコンポーネントを手動で単一のJSXファイルにフラット化する必要があります。可能な限り戦略Aを優先してください。
使用するタイミング
- アプリがローカルでまったく実行できない場合
- ページにモック/バイパス手段のない認証が必要な場合
- ナビゲーションでは到達不可能な特定のUI状態(エラー画面、空の状態など)が必要な場合
クイックリファレンス
npx tsx <SKILL_DIR>/scripts/extract_inline_html.ts \
--index-css src/css/App.css \
--extra-css index.html \
--outdir .stitch \
--page src/MockPage.jsx:Page.html:"Page Title"
主要なフラグ: --no-tailwind(非Tailwindアプリ)、--html-class dark(ダークモード)、--css-files(追加CSSファイル)。
自動検出: Tailwind設定は自動検出されます。@apply ディレクティブは自動的に <style type="text/tailwindcss"> を使用します。
MockPage.jsx ルール
- 完全なレイアウトを含める — ヘッダー、サイドバー、フッター(最初に
App.jsを読むこと) - すべての条件分岐をフラット化する — 1つの状態を選択し、すべての三項演算子と
&&ガードを削除する - すべてのデータをハードコードする —
{variable}を具体的な値で置き換え、.map()ループを展開する - ロゴを保持する — ローカルパスを持つ
<img>を使用する(後処理でインライン化されます) - 浮遊要素を削除する — クッキーバナー、チャットウィジェット、フィードバックボタン
後処理
ローカル画像をインライン化:
npx tsx <SKILL_DIR>/scripts/post_process.ts \
.stitch/Page.html --base-dir <app-directory>