ReactでChrome Extensionを作る
- Chrome Extensionの種類
- Chrome Extensionのアーキテクチャ
- create-react-appでChrome Extensionは作れる?
- webpackでChrome Extensionを作る
- content scriptでFirestoreを叩けなかった
- backgroundとcontent script間のメッセージのやりとりはLong-lived connectionsで行う
- Reactを埋め込む
エロゲー批評空間の推薦システムを作っている。推薦アイテムをFirestoreから取ってきて推薦枠を表示するChrome Extensionになる予定。この土日にChrome Extensionだけ出来上がったので、調べたことをまとめておく。
Chrome Extensionの名前はdreamyfishにした。素晴らしき日々という作品の中で主人公がエリック・サティの夢見る魚を引いていたのを思い出したから。
GitHub - roronya/dreamyfish-fe
Chrome Extensionの種類
大きく分けて2つある。
- Brouser Action
- 開いているページに関わらず動かすExtension
- Page Action
- 開いているページに特化したExtension
Chrome Extensionのアーキテクチャ
公式のOverviewのアーキテクチャの箇所が分かりやすかった。
ExtensionはJavascriptが動く箇所が3つあり、それぞれで「この場所はこういうことができますよ」というのが決まっている。そして、これらの3つのJSはMessageを送り合うことでデータを受け渡しできる。
それぞれの特性は以下のようになっている。
- popup
- URLの右側にアイコンが表示されていてクリックすると動くHTMLとそこで動くJS
- Extension独自のUIを作るときに使う
- content script
- background
公式のOverviewにあるように、backgroundでAPIを叩き、Messageでデータは受け渡し、content scriptやpopupはUIに専念する、という責務分割になっているようだ。
create-react-appでChrome Extensionは作れる?
popupであれば簡単に作れる。デフォルトで生成されるmanifest.jsonを書き換えればすぐにできるようだ。
ref: create-react-app で TypeScript × React での Chrome Extension 開発を始める - Qiita
ref: create-react-appはなぜmanifest.jsonを作るの? - an odd fellow
しかし、content scriptやbackgroundで動かしたい場合はejectしてwebpackの設定を弄る必要があったので諦めた。create-react-appは npm run build
すると index.html
をエントリーポイントに build
ディレクトリ配下にファイルを吐くので、popupの場合は都合が良い。しかし、content scriptやbackgroundはHTMLは持たない生のJSとして出力される必要があり、そういう設定はwebpackの設定を直接いじらなければならない。
webpackでChrome Extensionを作る
create-react-appに頼りきっていたのでwebpackから逃げていたが、良い機会だったので本腰を入れて勉強した。詳細は省く。結局webpackを理解せずコピペで進もうとしたら遠回りになることが多かった。
Rails本やJSの本など多数執筆している山田祥寛さんの速習シリーズのwebpackがかなり為になった。2018年の販売だが、十分新しい内容で、これで勉強すれば基礎がわかるのでwebpack4やbabel v7.4なども差分が分かってついていけるようになった。
- 作者: 山田祥寛
- 出版社/メーカー: WINGSプロジェクト
- 発売日: 2018/04/27
- メディア: Kindle版
- この商品を含むブログを見る
webpackのconfigはこのようになった。content scriptとbackgroundを作るのでentryをふたつ用意し、loaderでJSにする必要のないmanifest.jsonをCopyPluginで直接buildディレクトリにコピーしている。
dreamyfish-fe/webpack.config.js at master · roronya/dreamyfish-fe · GitHub
content scriptでFirestoreを叩けなかった
content scriptでFirestoreを叩いたら以下のようなエラーメッセージで動かなかった。
Could not reach Cloud Firestore backend. Connection failed 1 times. Most recent error: FirebaseError: [code=unavailable]: The operation could not be completed This typically indicates that your device does not have a healthy Internet connection at the moment. The client will operate in offline mode until it is able to successfully connect to the backend.
詳細な原因は分からなかった。content scriptはページのContextを引き継ぐので、Content Security Policy周りで引っかかったのだろうか。以下のようなページもあり、Content Security Policyの設定も試したがだめだった。
ref: Google Developers Japan: Chrome 拡張機能で Firebase を使う方法
データ取得はbackgroundでやれということなのだな、と理解した。
backgroundとcontent script間のメッセージのやりとりはLong-lived connectionsで行う
content scriptからbackgroundにメッセージを送り、backgroundはメッセージに即したデータをFirestoreから取得して、content scriptに返答する、という流れをやろうとした。
ref: Message Passing#Simple one-time requests - Google Chrome
この公式にあるSimple one-time requestsで行ったところ、Firestoreのデータ取得を待たずにチャンネルが閉じてしまい通信できなかった。メッセージを返答するまで長時間要する場合はLong-lived connectionsを使えとあったので、使ったらうまくいった。
ref: Message Passing#Long-lived connections - Google Chrome
Reactを埋め込む
埋め込みたい箇所にdivをぶちこんでReactDOM.renderするだけでおk。こんなかんじ。
dreamyfish-fe/content.js at master · roronya/dreamyfish-fe · GitHub