🔨

Vite + TypeScript で作る Chrome 拡張(MV3) Step2

に公開

開発用の監視/ホットリロードと、最小の UI 調整

Step 2 では「快適に回せる dev 体制」と「最低限の見た目&操作」の 2 点を整えます。
以下の順にそのまま進めてください。

1. 監視ビルド(ほぼリアルタイム反映)

依存を追加(並列実行用)。

npm i -D concurrently

package.json の scripts を更新:

  "scripts": {
    "dev": "concurrently -n typecheck,build \"npm:typecheck:watch\" \"vite build --watch\"",
    "build": "tsc && vite build",
    "preview": "vite preview"
  },

起動:

npm run dev
  • dist/ が変更され続けるので、chrome://extensions で 一度だけ dist を読み込み、以後は
    • 変更 → dist/ 自動更新 → 拡張詳細の リロード をクリック(またはショートカット/拡張リローダーを使う)
    • ページは通常のリロード(Cmd/Ctrl+R)で OK
  • ソースマップを有効にしてあるので(Step1 設定)、DevTools から TS 行へジャンプできます。

メモ:CRXJS の dev モードでも運用できますが、ここでは確実に動く watch ビルド + 拡張のリロード に統一しています。

2. Popup の UI 調整

ここからは色々確認をするために簡単な機能を実装していきます。

src/popup.html(置き換え)

<!DOCTYPE html>
<html lang="en">
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>Popup</title>
  <body
    style="min-width: 280px; font: 14px/1.5 system-ui, sans-serif; padding: 12px;"
  >
    <h1 style="font-size:16px;margin:0 0 8px;">Vite TS MV3 Starter</h1>
    <div id="app" style="margin-bottom:10px;">Popup is working 🎉</div>

    <div style="display:flex; gap:8px; flex-wrap:wrap">
      <button id="btn-open-options">Open Options</button>
      <button id="btn-ping-bg">Ping BG</button>
      <button id="btn-log-tab">Log Active Tab</button>
    </div>

    <div id="msg" style="margin-top:10px;color:#555;"></div>

    <script type="module" src="./popup.ts"></script>
  </body>
</html>

src/popup.ts(置き換え)

const $ = <T extends HTMLElement>(sel: string) =>
  document.querySelector(sel) as T | null;
const msg = (text: string) => {
  const el = $("#msg");
  if (el) el.textContent = text;
};

const main = async (): Promise<void> => {
  // ポップアップのボタンからオプションページを開く
  $("#btn-open-options")?.addEventListener("click", () => {
    chrome.runtime.openOptionsPage();
  });
  // Backgroundへメッセージ送信してレスポンス表示
  $("#btn-ping-bg")?.addEventListener("click", async () => {
    const res = await chrome.runtime.sendMessage({ type: "PING" });
    msg(`BG replied: ${JSON.stringify(res)}`);
  });
  // activeTab 権限で現在アクティブなタブ情報を取得(ユーザー操作後に限定)
  $("#btn-log-tab")?.addEventListener("click", async () => {
    const [tab] = await chrome.tabs.query({
      active: true,
      currentWindow: true,
    });
    console.log("[popup] active tab:", {
      id: tab?.id,
      url: tab?.url,
      title: tab?.title,
    });
    msg(`Active tab: ${tab?.title ?? "N/A"}`);
  });
};

main().catch((err) => console.error(err));

保存すると自動でビルドされるので、拡張機能画面の更新ボタンを押すと反映されます。
現時点ではポップアップの UI の変更が確認出来るはず。(PING BG ボタンは Background 側未実装のためエラー)

3. Background に疎通ハンドラを追加

ポップアプの[PING BG]ボタン押下時に送信されるメッセージを受信する処理を追加します。

src/background.ts(追記または置き換え)

chrome.runtime.onInstalled.addListener((d) => {
console.log("[bg] onInstalled:", d.reason);
});

// Popup からの簡易 Ping
chrome.runtime.onMessage.addListener((msg, \_sender, sendResponse) => {
if (msg?.type === "PING") {
sendResponse({ ok: true, ts: Date.now() });
return true; // async 応答を許可する場合に true
}
});

4. Options の UI 調整(見た目+遷移確認)

src/options.html(置き換え)

<!DOCTYPE html>
<html lang="en">
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>Options</title>
  <body
    style="max-width:720px;margin:16px auto;font:14px/1.5 system-ui,sans-serif;"
  >
    <h1 style="font-size:18px;margin:0 0 12px;">Options</h1>
    <p>
      ここは設定ページです。Step 3 以降で
      <code>chrome.storage</code> を使った保存UIを追加します。
    </p>
    <button id="go-popup">Open Popup</button>
    <script type="module" src="./options.ts"></script>
  </body>
</html>

src/options.ts(置き換え)

const btn = document.getElementById("go-popup") as HTMLButtonElement | null;
btn?.addEventListener("click", () => {
  // ブラウザ UI のポップアップはプログラムで直接開けないため、軽い誘導。
  alert("拡張アイコンをクリックして Popup を開いてください。");
});

5. 動作確認

  1. npm run dev を起動(ターミナルは開いたまま)
  2. Chrome で拡張を dist から読み込み(初回のみ)
  3. 以後、ソースを保存 → dist/ が更新 → 拡張をリロード
  4. 確認 :
    1. アイコンをクリック → Popup 表示
    2. Popup の各ボタン:
      1. Open Options → 拡張の Options ページが開く
      2. Ping BG → 下部メッセージに BG replied: { ok: true, ts: ... }
      3. Log Active Tab → コンソールにタブ情報
      4. Option 画面は静的だからリロードが必要?

お疲れ様でした。それでは Step 3: 「chrome.storage の“汎用ラッパ”を作る」に続きます。
https://github.com/Igusaya/chrome-extension-start/tree/vite%2Bts_chrome_ex_2

Discussion