mcpField
S
stitch::extract-static-html

stitch::extract-static-html

github.com/google-labs-code開発8.6k10.1k 回インストール10 週間前に更新
リポジトリ

stitch::extract-static-html について

Webアプリから単独で表示できる静的HTMLを抽出する開発者向けスキルです。手法の選択をユーザーに求め、Puppeteerによるスナップショット、ブラウザー操作、静的な代替手順を案内します。

主な機能

  1. 01Puppeteerでスナップショットを作成しスタイルと画像をインライン化
  2. 02ブラウザー操作後にDOMを抽出
  3. 03認証スクリプトと複数ルートに対応
  4. 04ビューポート、待機時間、ページ整理を設定
  5. 05アプリを実行できない場合の静的代替手段

活用シーン

  1. 01ローカルのWebページを単独HTMLとして保存
  2. 02ログイン後に保護されたページを取得
  3. 03ページを操作して特定状態をキャプチャ
  4. 04アプリが起動できない場合にページスナップショットを手動作成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/google-labs-code/stitch-skills --skill stitch::extract-static-html

インストール後、Claude Code で/stitch::extract-static-htmlを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

scripts/
post_process.ts
extract_inline_html.ts
snapshot.ts

詳細

静的 HTML の抽出

あらゆるウェブアプリケーションから、単独で動作する静的 HTML ファイルを抽出します。

使用する方法の選択

作業を進める前に、必ずユーザーに使用する方法を選んでもらってください。選択肢を明確に提示し、既定の推奨方法として方法 A を推奨し、十分な情報に基づいて選択できるよう、各方法のメリットとデメリットを簡潔にまとめてください。

方法 A(Puppeteer)方法 B(ブラウザーサブエージェント)
用途アプリがローカルで実行され、認証の壁がない場合先にページを操作する必要がある場合(クリック、フォーム入力など)
忠実度最高 — 計算済みスタイルを解決高 — レンダリング済み DOM
セットアップ不要 — モックも不要不要 — モックも不要
フレームワーク任意任意
出力ファイルに書き込むため、サイズ制限なしエージェントのコンテキスト内で切り詰められる場合がある

警告

チェックポイント — ユーザーの確認が必要です。 作業を進める前に、必ずユーザーにどちらの方法を希望するか尋ねてください。 上記の比較表を提示し、既定の方法として方法 A を推奨したうえで、 明示的な承認を待ってください。ユーザーの確認なしに、自分で決定したり 作業を進めたりしてはいけません。


方法 A:Puppeteer スナップショット(推奨)

ヘッドレス Chrome を起動して、完全にレンダリングされた DOM を取得し、すべての CSS をインライン化し、画像を base64 として埋め込んだ、単独で動作する HTML ファイルを生成します。あらゆるフレームワークで動作し、MockPage.jsx は不要です。

前提条件
  • アプリがローカルで実行されていること(例:npm run dev)
  • puppeteer を利用できる Node.js(確認方法:node -e "require('puppeteer')")
ワークフロー
  1. アプリを起動し、ポート番号を控えます。

    警告

    チェックポイント — ユーザーの確認が必要です。 ローカルサーバーを起動した後、スナップショットスクリプトの実行や ブラウザーサブエージェントの起動前に、必ず一時停止してユーザーに 確認を求めてください。アプリが正しく実行され、レンダリングされているか 確認できるよう、URL とポート番号をユーザーに伝えてください。ユーザーの 確認を得るまでは、スナップショットの手順に進んではいけません。

  2. スナップショットスクリプトを実行します。

    npx tsx <SKILL_DIR>/scripts/snapshot.ts \
      --url http://localhost:5173 \
      --output .stitch/home.html \
      --wait 2000
    
  3. 複数のページ — ルートごとに 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
    
  4. 開発サーバーを終了します。 スナップショットの抽出専用にローカル開発サーバーを起動した場合は、抽出が完了したら、サーバープロセスを停止するか、バックグラウンドタスクを終了してください。

スクリプトのフラグ
フラグ既定値説明
--url(必須)キャプチャする URL
--output(必須)出力ファイルのパス
--wait1000ネットワークがアイドル状態になった後の追加待機時間(ms)。遅延読み込みを行うアプリでは値を増やしてください。
--viewport1280x800WIDTHxHEIGHT 形式のビューポートサイズ
--html-class—<html> 要素に設定するクラス(例:dark)
--remove-fixedfalse固定/スティッキー要素(Cookie バナー、チャットウィジェット)を削除
--full-heightfalseビューポートの高さをスクロール可能な全体の高さに変更
--title—ページタイトルを上書き(ルートパスを設定します。例:/dashboard または /settings/profile)
--auth-script—認証用のデフォルト async (page) => void 関数をエクスポートする JS/TS モジュールのパス
--inline-canvasfalse<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.jsSSR のハイドレーションには --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 から CSSOM を取得し(ステップ 0)、同一オリジンのアイコンフォント(@font-face)が base64 データ URI としてインライン化されていることを確認します。
Next.js の /_next/image がインライン化されないスナップショットの実行中に開発サーバーが起動していることを確認します。スクリプトは起動中のサーバーから最適化された画像を取得します。
ダークモードが適用されない--html-class dark
出力に Cookie バナーが含まれる--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) {
  // Example 1: Fill and submit a login form
  await page.type('#username', 'admin');
  await page.type('#password', 'password123');
  await page.click('#login-button');
  await page.waitForNavigation({ waitUntil: 'networkidle2' });

  // Example 2: Inject cookies/localStorage directly
  // await page.evaluate(() => {
  //   localStorage.setItem('token', 'mock-jwt-token');
  // });

  // Example 3: Call the app's own login API via module injection (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:ブラウザーサブエージェントによるキャプチャ

キャプチャ前にページを操作する必要がある場合(ボタンのクリック、フォームへの入力、タブの移動など)に使用します。ブラウザーサブエージェントでは自由に操作できますが、ページが大きいと出力が途中で切れる場合があります。

ワークフロー
  1. アプリをローカルで起動します。

  2. ブラウザーサブエージェントを使って移動します。

  3. 必要に応じて操作します(クリック、スクロール、フォームへの入力)。

  4. DOM を抽出します:document.documentElement.outerHTML

    警告

    ページが大きいと出力が途中で切れる場合があります。対処するには:

    • 抽出前に <style> タグを削除します:document.querySelectorAll('style').forEach(el => el.remove())
    • スタイルを静的に再追加します(Tailwind CDN リンク、ソース CSS)
  5. ファイルに保存します。


付録:静的フォールバック(MockPage.jsx)

メモ

この方法は、アプリをローカルで実行できない場合(依存関係の破損、バックエンドの不足、回避手段のない認証制限)の最終手段です。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 のルール
  1. レイアウト全体を含める — ヘッダー、サイドバー、フッター(最初に App.js を読み込みます)
  2. すべての条件分岐をフラット化する — 状態を1つ選び、三項演算子と && ガードをすべて削除します
  3. すべてのデータをハードコードする — {variable} を具体的な値に置き換え、.map() ループを展開します
  4. ロゴを保持する — ローカルパスを指定した <img> を使用します(後処理でインライン化されます)
  5. フローティング要素を削除する — Cookie バナー、チャットウィジェット、フィードバックボタン
後処理

ローカル画像をインライン化します:

npx tsx <SKILL_DIR>/scripts/post_process.ts \
  .stitch/Page.html --base-dir <app-directory>

コメント