Vue.js エンジニアなら webext-vitesse で Chrome 拡張を作っちゃおう


# この記事の超概要
- テンプレートの構成を通して Chrome 拡張で何が作れるかを説明します。
- webext-vitesse の開発体験の良さを知って欲しいです。

# [webext-vitesse](https://github.com/antfu/vitesse-webext) とは
vita + Vue.js + typescript + WindiCSS で Chrome 拡張を作れるテンプレートです。

# セットアップ
下記のスクリプトを実行

```bash
npx degit antfu/vitesse-webext my-webext
cd my-webext
pnpm i
pnpm dev
```

Chrome > 拡張機能を管理 > パッケージ化されていない拡張機能を読み込む

で `my-webext/dist/extension` フォルダを選択すれば OK です。

# src/ 以下の主要ディレクトリの役割
### popup
拡張のアイコンをクリックしたときに表示される画面。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474068/6cb99845-f34a-97f8-227d-857f0b6e4a10.png)

### optionPage
設定情報などを表示するための画面。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474068/0ffb1256-c3f7-d397-225a-d6a1382533e2.png)

### contentScripts
html に差し込んで表示する要素。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474068/0395b873-513d-5577-fd72-f8f3ecbfd71e.png)

### background
中心となる処理を書く場所。Web アプリケーションでいうところの API サーバに近い。他の 3 つと異なり、CORS などの制約が無い。ブラウザの様々な機能を利用可能。

# 機能間の通信
前述した 4 機能は独立しており、通信には `chrome.runtime API` を使う必要があります。
`webext-vitesse` には `chrome.runtime API` をラップした `webext-bridge` というライブラリが含まれており、簡単に、型安全に通信処理を書くことが出来ます。

### データを送るとき
```src/background/main.ts
browser.tabs.onActivated.addListener(async({ tabId }) => {
  console.log('previous tab', tab)
  sendMessage('tab-prev', { title: tab.title }, { context: 'content-script', tabId })
})
```

### データを受け取る時
```src/contentScripts/index.ts
import { onMessage } from 'webext-bridge'  

onMessage('tab-prev', ({ data }) => {
  console.log(`[vitesse-webext] Navigate from page "${data.title}"`)
})
```

### 型定義
```shim.d.ts
import { ProtocolWithReturn } from 'webext-bridge'

declare module 'webext-bridge' {
  export interface ProtocolMap {
    'tab-prev': { title: string | undefined }
    'get-current-tab': ProtocolWithReturn<{ tabId: number }, { title?: string }>
  }
}
```

# データの保管
Chrome 拡張のストレージの説明は下記が分かりやすかったです。
https://qiita.com/dhun/items/cf18e43cb0376fcff302

`webext-vitesse` では `src/composables/useStrageLocal.ts` に定義された `useStrageLocal` 関数を使ってより簡単に `strage.local` にアクセスすることが出来ます。

```src/options/Options.vue
<script setup lang="ts">
import { useStorageLocal } from '~/composables/useStorageLocal'

const storageDemo = useStorageLocal('webext-demo', 'Storage Demo', { listenToStorageChanges: true })
</script>

<template>
  <input v-model="storageDemo" class="border border-gray-400 rounded px-2 py-1 mt-2">
</template>
```

# 実際に作ってみたもの
Gitlab と Clockify を連携する拡張を作りました。自分用に作ったのでストアに公開はしていません。[ソースコード](https://github.com/punkshiraishi/gitrack)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/474068/9dce5bf3-7a9c-7b43-1b16-67e36d1aa913.png)


# webext-bridge を使っているオススメ拡張
### [Chikamichi](https://chrome.google.com/webstore/detail/chikamichi-quickly-find-a/gkhobepjbiepngbeikhbpnfgjcjgmgha)
履歴、ブックマーク、開いているタブを fuzzy 検索できる。[ソースコード](https://github.com/kawamataryo/chikamichi) も大いに参考にさせて頂きました。

# 最後に
普段 Vue.js をよく触る方で、Chrome 拡張を作りたい人は是非使ってみてください。爆速で開発できます。

`webext-vitesse` は最新の vite 周りの技術セットがふんだんに使われているので、そのあたりも非常に参考になると思います。

# モチベーション(裏話)
前述したように複数の Web サービスを連携させて業務効率アプリを作ろうとしました。どちらのサービスも API を公開しています。連携させるには、それぞれのサービスのアクセストークンを取得する必要がありました。アプリケーション側ではアクセストークンを管理したくありません。ユーザのローカル環境に保存できると嬉しいです。

最初は GAS で作っていましたが、GAS だとどうしてもクレデンシャル情報をサーバサイドに送る必要がありました。

この問題を解決するために Chrome 拡張を作ろうと思ったのがきっかけでした。