Chrome拡張機能(Vite + React + TypeScript)
内容
ブラウザで選択された文字をサイドパネルに表示する
- useStateを使いたい
- JSON形式で値を受け取りたい
お試し簡易実装のため多少バグがあると思います。
ご了承ください。
開発環境
OS : macOS Sonoma 14.2.1
node : 18.15.0
npm : 9.8.1
環境構築
terminal
Viteで作成
> npm create vite@latest
> product name : productName (任意の名前)
> React
> TypeScript + SWC
> cd productName
> npm i
ここで拡張機能に必要なものをインストール(* @crxjs/vite-pluginには@betaをつけてください)
> npm i -D @crxjs/vite-plugin@beta @types/chrome
> mkdir src/background
> touch src/background/background.ts
機能説明
chrome.scripting.executeScriptの中ではReactの関数など使用できないので注意。
background.tsを中継することでuseStateの値を更新できるようになります。
実装
manifest.jsonを作成させます。
vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import { crx, defineManifest } from "@crxjs/vite-plugin"
const manifest = defineManifest({
manifest_version: 3,
name: "chrome-extension", // 拡張機能の名前
version: "1.0.0",
icons: {
// 128: "icon-128-active.png" // 拡張機能のアイコン
},
action: {
default_popup: "index.html"
},
side_panel: {
default_path: "index.html", // サイドパネルに表示するHTML
},
permissions: [ // 権限を付与
'sidePanel',
'tabs',
'activeTab',
'storage',
'scripting'
],
host_permissions: ["<all_urls>"], // 拡張機能が使えるURLを指定(今回は全てにしています)
background: {
service_worker: "src/background/background.ts"
}
})
export default defineConfig((comand) => {
return {
server: {
port: 5173,
strictPort: true,
hmr: {
port: 5173
},
},
build: {
rollupOptions: {
input: {
},
output: {
},
},
watch: {
}
},
plugins: [react(), crx({ manifest })],
}
})
src/background/background.ts
// Chromeで更新があった場合に実行される
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
// chrome://から始まるURLでは拡張機能は使えないみたいです
if (tab.url?.startsWith("chrome://")) return
if (changeInfo.status === "complete") {
console.log(tabId, changeInfo, tab);
send(JSON.stringify({ tabId: tabId, title: tab.title, update: true }))
}
})
// Chromeのアクティブなタブが切り替わった際に実行される
chrome.tabs.onActivated.addListener((activeInfo) => {
send(JSON.stringify({ ...activeInfo, update: true }))
})
// 拡張機能を実行した際、サイドパネルを開く
chrome.sidePanel
.setPanelBehavior({ openPanelOnActionClick: true })
.catch((error) => console.error(error))
// message受け取り用
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
let json = JSON.parse(message)
if (json?.selectedText) {
send(message)
} else {
send("background receive message [Success]")
}
})
// message送信用
const send = (message: any) => {
chrome.runtime.sendMessage(message)
}
App.tsx
import { useEffect, useState } from 'react'
function App() {
const [firstInit, setFirstInit] = useState<boolean>(false)
const [tabId, setTabId] = useState<number>(null!)
const [tabTitle, setTabTitle] = useState<string>(null!)
const [selectedText, setSelectedText] = useState<string>("")
const [update, setUpdate] = useState<boolean>(false)
useEffect(() => {
init()
chrome.runtime.onMessage.addListener(receiveMessage)
}, [])
// tabIdまたはtabTitleが変更された場合
useEffect(() => {
// Chromeタブが更新された際、再度selectionchangeイベントを実行する
init()
setUpdate(false)
}, [!!firstInit && !!update ])
// background.tsからmessageを受け取る
const receiveMessage = (v: any) => {
let json = JSON.parse(v)
if (json?.title) { setTabTitle(json.title) }
if (json?.selectedText) { setSelectedText(json.selectedText) }
if (json?.update) { setUpdate(json.update) }
}
// Chromeタブ内でイベントを実行させる
const init = async () => {
setFirstInit(true)
// Chromeタブの情報を取得
let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
setTabId(tab.id!)
setTabTitle(tab.title!)
// Chromeタブに対してScriptを実行
chrome.scripting.executeScript({
target: { tabId: tab.id! },
func: () => {
/** -----------------------------------------
* ここではReactの関数など使用できないので注意してください。
------------------------------------------- */
// Chromeタブ内でのselectionchangeイベントを検知させます
document.addEventListener("selectionchange", () => {
let str = window.getSelection()?.toString()
let json = { selectedText: str }
// 検知した場合にから文字でなければbackground.ts側にmessageを送ります
str && chrome.runtime.sendMessage(JSON.stringify(json))
})
}
})
}
return (
<>
<div>
<div style={{ padding: "1rem" }}>
<div>tabID:</div>
<div style={{ background: "green" }}> {tabId}</div>
<div>tabタイトル:</div>
<div style={{ background: "rgb(0,120,154)" }}> {tabTitle}</div>
</div>
<div style={{ padding: "1rem" }}>
<div>
<textarea
name=""
id=""
cols={30}
rows={10}
value={selectedText}
onChange={(e) => setSelectedText(e.currentTarget.value)}
style={{ width: "100%" }}
/>
</div>
</div>
</div>
</>
)
}
export default App
拡張機能を実行してみる
npm run dev
またはnpm run build
を実行し、distフォルダを作成しておいてください。
- デベロッパー モードをONにします。
- パッケージ化されていない拡張機能を読み込む
- 今回作成したdistファイルを選択
- Chromeの拡張機能に追加される
- Chromeの拡張機能から実行
Chromeタブ内(chrome://以外)で選択された文字がサイドパネル上に表示されれば成功です。