🔨
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. 動作確認
- npm run dev を起動(ターミナルは開いたまま)
- Chrome で拡張を dist から読み込み(初回のみ)
- 以後、ソースを保存 → dist/ が更新 → 拡張をリロード
- 確認 :
- アイコンをクリック → Popup 表示
- Popup の各ボタン:
- Open Options → 拡張の Options ページが開く
- Ping BG → 下部メッセージに BG replied: { ok: true, ts: ... }
- Log Active Tab → コンソールにタブ情報
- Option 画面は静的だからリロードが必要?
お疲れ様でした。それでは Step 3: 「chrome.storage の“汎用ラッパ”を作る」に続きます。
Discussion