iimon TECH BLOG

iimonエンジニアが得られた経験や知識を共有して世の中をイイモンにしていくためのブログです

えぇ!?Reactをchrome拡張機能に載せてもいいんですか!?

はじめに

今年の夏も暑すぎませんかね……。

暑すぎて涼しい夜に蝉が鳴いていたり、庭に湧いたコガネムシカメムシがぶんぶん飛び交ったり。

年々、最高気温の記録が更新されすぎて暑さのボジョ○ヌーヴォ感。

夏って何ですかね……。

とまあ夏の暑さについては置いといて、、

入力チームにてフロントエンドを担当しております、入社半年の新人(だと思いたい)まつむらです。

iimonのプロダクトは主にchrome拡張機能での使用を前提に開発されています。

ですが、恥ずかしながら、今までの経歴としてはVueを用いたSPA開発が主で、chrome拡張機能の開発は入社時までゼロという……。

いい加減、感覚だけで開発するのも良くないなと思ったので、chrome拡張機能の基礎をざっくり把握していこうかなと!

ただ、モダンフレームワークを使ったSPAでの開発も忘れられない……。

そこで思ったわけですよ。

SPAが忘れられないならchrome拡張機能をReactでやっちゃえばいいんじゃね?と。

ということで、今回はReactを用いたchrome拡張機能の開発フローについて書いていこうと思います!  

というか実現できるの?

現在担当しているプロダクトがVanillaTSでDOMを拾ってオーバーレイ表示を行うような作りになっているので、そもそも論でモダンフレームワークと相性ってどうなの?使えるの?と思ったので、流行りの対話型AIに聞いてみたところ、以下のような回答が得られました。

はい、Reactを使ってChrome拡張機能を作成することは可能です。以下にその基本的な手順を説明します。

いけるんだ……。

手順もそれっぽいことを出力してくれていました。

出来そうなことは分かったので、ちゃんとした情報収集を行ったところ、有志が作成したスターターキットが存在していることが分かりました!

github.com

また、これを活用して実装していた記事も見かけたので、今回は0->1ではなく、それに倣って作成していくことにします。

chrome拡張機能について

chrome拡張機能は4つのコンポーネントに分けられるそうです。

マニフェスト

諸々を定義するファイル。

形式はjson

サービスワーカー

ブラウザのイベントを監視できる。

主にbackground.jsと言う名称で導入される。

⚠️DOMへの干渉は不可!

コンテンツスクリプト

DOM要素を読み込んだり、変更したりすることができる。

主にcontent_scripts.jsと言う名称で導入される。

ツールバーアクション

拡張機能のアイコンをクリックすることで、ポップアップを開いたり、オプションページへアクセスしたりできる。  

文字通り、ツールバーにいるアイコン専用のアクションといった位置付け。

こやつらを駆使することで、chrome拡張機能は成り立っているようです。

とりあえず先人の知恵を借りながら作ってみる。

ざっくりとchrome拡張機能についても復習もできたので、いよいよ本題に入っていこうかなと思います。

ちなみに筆者の開発環境は次の通りです。

※yarn前提のプロジェクトらしいので、npmユーザの方でもyarnを使用してください。

  1. 前述のURLからテンプレートをダウンロード・解凍します。
  2. インストールとビルドを行います。 yarn install && yarn dev
  3. ビルド成果物をchrome拡張機能へ取り込む
    chrome://extensions
    chrome://extensionsへアクセスして、 「パッケージ化されていない拡張機能を読み込み」 からビルドされたdistディレクトリを選択。
    読み込みが完了すると、welcomeページへ飛ばされます。
  4. 拡張機能の一覧からアイコンをクリックしてカウンターが表示されることを確認。
    counter

ここまで来れば、あとは魔改造カスタムし放題というわけです!

カスタムについて

ブラウザでリロードをすると、画面右下にも同じカウンターが表示されます。

counter in browser

DevToolsを開いて確認すると、my-extension-rootを起点としてこのカウンターが展開されていることが分かります。

dev-tools

ソースを辿ったところ、Content.tsxにて展開されているようです。

なので、ここをちょいと弄れば……

// As-is
import { Counter } from '../app/features/counter';

const Content = () => {
  return (
    <div className="fixed z-[999] bottom-2 right-2 shadow-xl border-[1px] bg-white bg-opacity-10">
      <div className="flex justify-center mt-2 text-base">Content Counter</div>
      <Counter />
    </div>
  );
};

export default Content;
// To-be
const Content = () => {
  return (
    <div className="fixed z-[999] bottom-2 right-2 shadow-xl border-[1px] bg-white bg-opacity-10">
      <div className="flex justify-center mt-2 text-base">Content Counter</div>
      <div> カウンターだよーん </div>
    </div>
  );
};

export default Content;

実はこのテンプレートがかなり優秀で、ホットリロードに対応しているという。。

つまりファイルを保存するだけで拡張機能の再取り込みとかせずに更新が可能!

cmd + sで保存すると……ほい、変更完了!

少し見辛いかもしれませんが、右下にあったカウンターが書き変わっているのが分かるかと思います。

よきかな〜

ただ、現状のままだと、どのサイトでも出てきてしまうので、特定のURLの時だけ表示するように修正します!

manifest.json(今回はmanifest.ts)content_scriptsの部分を次のように修正します。

// 
const manifest = defineManifest(async (env) => ({
// 〜省略〜
  content_scripts: [
    {
      // 本ブログのみを対象にする
      matches: ['https://tech.iimon.co.jp/**'],
      js: ['content/index.tsx'],
    },
  ],
// 〜省略〜
}));

export default manifest;

すると、本ブログ以外の場所では表示されなくなったかと思います。

ちなみに配列で定義できるので、matchesには複数のURLパターンが設定できます!

まとめ

実際に操作して分かったこととして、chrome拡張機能に触れてこなかったとはいえ、結構簡単に、しかもReactで実装できることが分かったので、結構良い感じなのでは!?と感じました!

何よりReactを用いることでコンポーネント化を実現できるので、

  • CSS汚染などに悩まされずにデザインを担保できる
  • ボタン等の挙動をイベントリスナーで拾わずに済むようになる=事故が減る

というのが大きな利点だと思います。

ただ、デメリットもあって、普通のVanillaTSで実装するよりもファイルが大きくなりがちなので、拡張機能の審査には時間がかかるかもしれないです。

メリットを取るか、デメリットを取るか、そこはプロジェクトによるのかなと思います。

それにしても先人の知恵って偉大!OSS文化にまた助けられました。

とはいえ、今回は実装の可否に重きを置いて記事を書いたので、正直、中身はあまり把握できていません。。勘に頼る癖ほんと良くない

なので、機会があれば続きとしてもっと深掘りした記事を書いていこうかなと思います!

最後まで読んでいただきありがとうございました!

参考URL

zenn.dev

宣伝コーナー

弊社ではエンジニアを募集しております。

ぜひカジュアル面談でお話ししましょう!

ご興味ありましたら、ご応募ください!

Wantedly / Green