3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Chrome拡張機能をReact + typescript で作る(manifest v3)

Posted at

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",
    });
  }
});

できた

こんな感じでcssを指定すると
image.png

googleが気持ち悪い赤になる
image.png

image.png
ポップアップで有効化/無効化ができる。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?