Chrome拡張機能をReact + typescript で作る(manifest v3)
指定したページを適当なcssで上書きする拡張機能が欲しかった。
これ を拡張機能でやりたいだけなんだけど、なかなかうまく動くのが無いから勉強がてら自分で作ることにした。。。
結構ハマったのでメモ
↓ 開発した拡張機能(指定したウェブサイトにCSSを埋め込む雑アプリ
css-injector
できたコードはこれ
https://github.com/m-masataka/css-injector-extension
まずは考える
chrome-extensioinでは3つの構成要素があり、それぞれに何をさせるかを考える。
- Content Scripts(ブラウザのイベントをトリガーに動くスクリプト)
- 設定したcssをページに埋め込む。多分今回のメインロジック
- Browser Action(ポップアップメニューとか)
- cssの設定と保存を行う
- Event Page(バックグラウンドで動くなにか)
- 今回はあまり役目無し
- ブラウザ起動時の表示アイコン制御に使った
↓のページがすごくわかりやすかった。
https://qiita.com/sakaimo/items/5e41d6b2ad8d7ee04b12
はじめに
とりあえずBrowser ActionはReactで書くのでcreate-appする。
yarn create react-app sample_app --template typescript
Manifest
一旦必要なmanifestは書いておく。
build後のフォルダ全体が拡張機能のパッケージになるので、このマニフェストはpublicにいれとく。
permissionsで不要なものを定義すると後々審査で怒られるので注意。開発中は必要ないもの入れても大丈夫そう。
{
"name": "CSS Adopter",
"manifest_version": 3,
"version": "0.1",
"permissions": ["tabs", "activeTab", "scripting", "storage"],
"options_page": "index.html",
"background": {
"service_worker": "background.js"
},
"action": {
"default_icon": "disabled.png",
"default_popup": "popup.html"
},
"content_scripts": [
{
"matches": ["https://*/*"],
"js": ["content.js"],
"run_at": "document_end"
}
]
}
Browser Action
今回はポップアップと設定ページの2つのページを作るのだけど、Reactでhtml2つ出力するのってどうするんだっけ?
というところでハマった。
作るページはマニフェストにも記載してる↓2つ
- "options_page": "index.html"
- "default_popup": "popup.html"
https://zenn.dev/ulcttku/articles/creating-chrome-extensions-with-react
ここのページ参考にrenderを2つ定義して、buildでゴニョゴニョすることにした。
index.tsx
ReactDOM.render(
<React.StrictMode>
<PopupPage />
</React.StrictMode>,
document.getElementById("popup") || document.createElement("div")
);
ReactDOM.render(
<React.StrictMode>
<OptionsPage />
</React.StrictMode>,
document.getElementById("root") || document.createElement("div")
);
package.json
"build": "react-scripts build && npm run rename:popup",
"rename:popup": "sed 's/root/popup/' build/index.html > build/popup.html",
jsを適用するidを変更して2つのページを生成する感じ。
後々さがしたら素晴らしくスマートなサンプルがあった....
https://github.com/chibat/chrome-extension-typescript-starter
適用するcssとサイトの設定はchrome storageに保存する。
使い方はここ
typescriptでこれを使うためにtype/chromeをインストールする必要がある。
yarn add @types/chrome
chrome.storage.(local/sync).set({key: value});
でストレージへ保存できる。
useStateへの反映タイミングと合わせて使う感じで、objectそのまま保存することもできるのでかなり楽ちん。
const [css, setCSS] = useState<CSS[]>([]);
setCSS(cs);
if (chrome.storage) {
chrome.storage.local.set({ css_adopter: cs });
}
storageからアイテムを取得するときはこんな感じ。
useEffectで読み出して画面ロード時にデータ反映する。
useEffect(() => {
if (chrome.storage) {
chrome.storage.local.get(["css_adopter_enable"], (items) => {
if (items["css_adopter_enable"]) {
let changed_enable = items["css_adopter_enable"];
setEnable(changed_enable);
}
});
}
}, []);
Content Script
ここでcssの追加を行う。
loadイベントをトリガーにすることで、なんらかページがロードされたときにcssを挟み込むことができる。
window.addEventListener("load", () => {
chrome.storage.local.get(["css_adopter"], function (items) {
.... CSSの処理
で、このcontent_scriptsが実行されるタイミングがmanifestで指定できるらしく、
run_at = document_end
でページが全部ロードされたタイミングとなるらしい。
"content_scripts": [
{
"matches": ["https://*/*"],
"js": ["content.js"],
"run_at": "document_end"
}
]
Event Page
最後にbackground script
ここはあまりやることがないけど、拡張機能のアイコンでON/OFFを表現したいときとかに使えそう。
manifest v3でbackgroundの処理はservice workerに移行するらしい。
https://developer.chrome.com/docs/extensions/mv3/intro/mv3-overview/#service-workers
ChromeのService Workerって悪いことするイメージしか無いけど大丈夫なのかな?
ここはほんとにiconを入れ替えるだけ。
chrome.action.setIconでアイコン変えられるみたい。
chrome.storage.local.get(["css_adopter_enable"], (items) => {
if (items["css_adopter_enable"]) {
let changed_enable = items["css_adopter_enable"];
chrome.action.setIcon({
path: (changed_enable ? "enabled" : "disabled") + ".png",
});
}
});