GCPコンソールの色を変えるChrome拡張作った👨‍💻

## はじめに

### 記事の内容

仕事でもプライベートでも利用頻度の高いGoogle Cloud（旧称：GCP）を便利に利用するため、プロジェクト毎にコンソールのヘッダカラーを変更できるChrome拡張を作りました。
本番環境だけ色を変えたりできればオペミスが減ってハッピーですよね。作ったのはコレです。

https://github.com/kawamou/gcp-benri-extension

作る上ではこちらを大変参考にさせていただきました。

https://github.com/yfuruyama/gcp-console-colorize

https://addsict.hatenablog.com/entry/2017/02/05/030557

### 想定読者

- GCPを便利化するものは作りましたが、記事の内容はGCPと一切関係ありません
- あくまでChrome拡張の入門的なものを読みたい方にオススメです

## Chrome拡張
### 概要
説明するまでもないですが、ブラウザに独自の機能を追加することができる仕組みです。

https://developer.chrome.com/docs/extensions/mv3/overview/

主にHTML / CSS / JavaScript / 画像を組み合わせて実装します。

### manifest.json

Chrome拡張には`manifest.json`という設定ファイルが必須です。
`manifest.json`に、「こういう処理が入ったら`foo.js`を呼び出してね」「ブラウザ右上の拡張機能のアイコン押されたら`bar.html`を呼び出してね」といった決まりを書いておくと、その通りにブラウザが解釈し各ファイルを呼び出してくれるようになります。
`manifest.json`が**Chrome拡張の処理のエントリーポイントになっている**わけです。重要。

様々なフィールドや用語がありますが、基本を抑える上では[こちらの記事]((https://yamakisso.hatenablog.com/entry/2022/02/23/080234))が大変分かりやすかったです🙇‍♂️

[Chrome拡張機能をmanifest v3で開発した簡易記録とハマりポイントの解消方法](https://yamakisso.hatenablog.com/entry/2022/02/23/080234)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287774/a49535fa-03b6-dcfd-e16f-6449a47ae2d7.png)

### manifest.json、v2 or v3問題

`manifest.json`の仕様（Chrome拡張の仕様）は今までv2だったのですが、[2022年1月以降、Webストアでは新仕様であるv3しか受け付けられなくなりました](https://zenn.dev/katoaki/articles/4e7548b533d7b3)。
「仕様が変更になりどうなったの？」については、`manifest.json`の記述方法が変更になったり、呼び出すことができる機能が制限されたり追加されたりしました。

https://developer.chrome.com/docs/extensions/mv3/intro/mv3-migration/

今後は特段理由がなければv3で作るのが良いと思います。

### 処理をどこに書くか

Chrome拡張の処理は主に`Background Script`か`Content Script`のどちらかに書きます。
`manifest.json`で言うところの`service_worker`か`content_scripts`に指定するjsファイルです。

`content_scripts`は、

- 特定のWebサイトと紐付き、かつ、DOM等に触ることもできるため、Webページ自体を弄ることができる
- 常駐せず、Webサイトが読み込まれるたびに一度呼び出される
- 一部の`chrome`APIにしかアクセスできない（[ドキュメント](https://developer.chrome.com/docs/extensions/mv3/content_scripts/)）

といった特徴があります。

一方`service_worker`は、

- バックグラウンドに常駐し、キー押下等のイベントに合わせて発火させることが可能
- `chrome`のAPIについて、そのほとんどを利用することができる
- 基本的にはDOM等に触ることができない

といった特徴があります。
そのため、イベントを拾うような処理は`service_worker`に記述し、Webページ自体を触る処理については`content_scripts`に書くのが基本方針になるかなと思います。
[公式ドキュメント](https://developer.chrome.com/docs/extensions/mv3/architecture-overview/)にも下記のように記載されています。

>The background script is the extension's event handler
>
>Extensions that read or write to web pages utilize a content script

（余談ですが、一応、無理矢理`content_scripts`内にリスナを登録する方法や`service_worker`からDOMに触ることも[可能ではあります](https://toranoana-lab.hatenablog.com/entry/2020/12/16/120000)。）

## 実装編

### 作ったもの

GCPコンソールのヘッダカラーはデフォルト`#1A73E8`の青ですが、プロジェクト毎に色を変えられるようにしました。
下記は`#000000`のピュアブラックに変更した例です。
主な利用用途としては、本番環境と開発環境の違いを視覚的に強く意識できるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287774/09c9655e-addb-4807-acb7-07c145463a90.png)

ブラウザ右上のChrome拡張アイコンから設定を行うことができます。
プロジェクト名及びカラーコードを登録する画面と、登録した一覧を表示する画面があります。
ここで登録したカラーコード通りにコンソールのヘッダカラーが変更されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287774/2f8eea8a-ee05-ba92-0738-26c1914547a4.png)

### 簡単な仕組み

`chrome.storage`にプロジェクト名とカラーコードの組（`ColorCode`）を保存します。
`console.cloud.google.com`のタブを開くたびに設定した`Content Script`が呼び出され、開いたプロジェクト名と保存済みのプロジェクト名が一致すればヘッダの色を塗り替えます。シンプル！

### 使用技術

React + TypeScript + Tailwind CSSを用いて作りました。
ビルドにはViteを使っています。

Chrome拡張は、スクリプトっぽくjs（ts）でイベントリスナとイベントハンドラを書いていくのがメインです。
Reactは、Chrome拡張アイコンをクリックしたときに出るオプション画面を作る場合にのみ利用しています。

### ディレクトリ構成とmanifest.json

```sh
...
├── src
│   ├── App.tsx
│   ├── EditPage.tsx
│   ├── ListPage.tsx
│   ├── colorCode.ts
│   ├── colorCodeRepository.ts
│   ├── colorize.ts
│   ├── index.css
│   ├── main.tsx
│   └── vite-env.d.ts
...
```

重要なのは`colorize.ts`です。
こちらを`Content Script`として設定し、GCPコンソールを開いたときに動作するようにします。
DOM要素を見て、ヘッダに該当するエレメントのカラーを書き換えています。

`tsx`系のファイルによってオプション画面のUIを構成しています。
`manifest.json`で`index.html`を指定しているので、Reactがビルドされた結果を読み込むことができています。

下記が`manifest.json`です。
`Content Script`やオプション画面が読み込まれるように設定しています。
また、`chrome.storage`上にプロジェクト名やカラーコードを保存しているため、パーミッションで`storage`を渡しています。
イベント発生はないので`Background Script`は利用していません。

```manifest.json
{
  "manifest_version": 3,
  "name": "gcp-benri-extension",
  "version": "0.0.1",
  "action": { "default_popup": "index.html" },
  "content_scripts": [
    {
      "matches": ["https://console.cloud.google.com/*"],
      "js": ["src/colorize.ts"]
    }
  ],
  "permissions": ["storage"]
}
```

今回`rollup-plugin-chrome-extension`を利用しています。
`manifest.json`を読み取ってChrome拡張用に賢くバンドルしてくれるプラグインです。
下記記事を参考にして簡単にセットアップできました。

https://dev.to/jacksteamdev/create-a-vite-react-chrome-extension-in-90-seconds-3df7

### どのようにローカル開発を進めたか

実装の内容としては特段書き立てる内容はないですが、Chrome拡張ビギナーの身からするとローカルでどのように開発を進めれば良いか当初戸惑ったので、メモ代わりにまとめます。

#### ファイルを読み込む方法

まず、Chrome拡張機能の管理画面`chrome://extensions`を開いて、デベロッパーモードをONにします。
`パッケージ化されていない拡張機能を読み込む`でmanifest.jsonが置いてあるフォルダを選択します。
自分の場合だと、`npm run build`によって`dist/`にビルド結果が吐き出されるので`dist/`を指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287774/a925e203-cb62-11b0-e190-f75a889b359b.png)

これで、ブラウザ上から開発中のChrome拡張の動作を確認することができます。
開発を進めて変更が生まれた際は、読み込みボタンをクリックすることでブラウザ側に変更を読み込むことができます。便利ですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287774/68adaeb7-0937-4826-8f00-5a7a9b028d13.png)

#### ログの確認

開発の中でチマチマ`console.log`吐いてプリントデバッグしたい場合もあるかもしれません。
`Content Script`のログは動作しているWebページ上の開発者ツールから確認することができます。

`Background Script`は[Service Worker上で動作する](https://developer.chrome.com/docs/extensions/mv3/service_workers/)ため、Webページ上にはログが表示されません。
`chrome://extensions`の`ビューを検証`から確認することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/287774/1f54d3e2-f0f5-2bb3-ff9e-dcb3c47407f8.png)

また、Chrome拡張のアイコンをクリックすると表示されるオプション画面のログも、**オプション画面上を右クリックして検証ツールを開けばログを表示することができます**。

## 終わりに

簡単なChrome拡張を作ることができました。
Cloud Shellウィンドウの最大化/最小化や複数アカウントの切り替え、BigQueryコンソールのショートカットキーの拡充等、将来的に追加したい機能はあるので、余力があればやっていきたいです🥺

## 2023-11-06追記

https://zenn.dev/nado1001/articles/plasmo-browser-extension

Plasmoなるものがあるらしい。