TypeScriptで作るイマドキChrome拡張機能開発入門


Chrome拡張開発の入門資料は世の中にたくさんありますが、結構昔に書かれたものが多く、イマドキ風(といってもそんなに新しくもないですが笑)にTypeScriptを使った場合の開発方法を記しました。
なお、初心者の方向けに、簡単ではありますがChrome拡張の仕組みから説明していきます。

## Chormeの拡張の仕組み

以下の3つの登場人物を覚えればOKです。

### Content Scripts

ページのDOMを操作(取得・追加・更新・削除)できるscript。(3つの登場人物の中では唯一)
ただし、アクセスできるChromeのAPIは一部に制限されている。
これを使って特定のページに独自のUIを埋め込んだりできる。

### Browser Action(Page Action)

アドレスバー右に並んでいる拡張機能のアイコンをクリックしたときのアクション。
様々なChromeのAPIにアクセスできる。

### Event Page(Background Page)

Chromeが動いている間に裏で動いているスクリプト。
Browser Action同様、様々なChromeのAPIにアクセスできる。
Background Pageは非推奨。

![Chrome拡張の構成](https://qiita-user-contents.imgix.net/http%3A%2F%2Fwww.plantuml.com%2Fplantuml%2Fpng%2F06-i402mFR3fSuLyXHXcs-V2ASo95DZxcH22Uv16i3tpmfWDyCBecIQacpIcqxIK5uW7_a435ymWLf8G7ZlmsdJr5NAfoZqUbOMAIdbIQs_tFhGv8PPKGufVRJJEQU8J4Y7OJJKWJAutGJ1kHrqa1Sqj4g5GAMkWaNk8btMyJhLUWjT_mVGIhHrXX9ww_N85qAxL1AQS-Fo70Tmnxlq8i29CagPRC-3NjfmwMyMhfY2a0EYD_PHmldAmWJ8fbq2ybWCu5NGS045RW0lvjAG-GuKEr1im76JeBQpfVN3nb9bwbJrYabO2ttwButAKCQrpY3tf473P1R1NlWmbGkFyDJDjNEpx2FgTbwkgeRSZnDmCLyaR-Ti_9sZtyR9wctMkVEblUZ-ukOG9BYzYzPq0CwlqOu41sCAvuWtX-8__Ap07hAWXw28hk5OKCKMJs3JIEwjSjc48w_WdYzq7XBER8WgJ-vOsF_gbabpNavrghRlWB0grnXkvuWSya9y12naajy6QvIr5s_3xDE6yQGZoHdlmb1TnWHMVlIcyAylte2BsmwSLx1fjYo9uOin5Gcfkt9nNs_lvrLHBL-yGehpwzeWgFihkzG8I2_Cc36qjXe0kRt1RWg2XzaRT0aZdm_OQPkEc9zjW15rY79LWTlvEzpzsntoa8Zw6EijayI4hd-zxz1W00?ixlib=rb-1.2.2&auto=compress%2Cformat&gif-q=60&s=294da40d8597c2d2fd77da9ce0f919de)

[Chrome拡張機能の構造のーと](https://qiita.com/nononote/items/66e297aa070f33ae1185)より

三者はそれぞれメッセージでやり取りできます。

## 最小労力で作るChrome拡張

[外部に公開しないミニマムなchrome拡張機能を作るのは1時間も使わずにできる](https://qiita.com/Ancient_Scapes/items/822409167ae3a0b76dbe)

を見て、後は公式ドキュメントを見てください、以上。。
ではありますが、今回はちょっぴりナウい環境にしてみます。
条件はこんな感じです。

- TypeScriptを使いたい
- Promiseを使いたい

### TypeScriptでChrome拡張を開発する

- [generator-chrome-extension-kickstart-typescript](https://github.com/mazamachi/generator-chrome-extension-kickstart-typescript)→あまりメンテされておらず不安
- [chrome-extension-typescript-starter](https://github.com/chibat/chrome-extension-typescript-starter)→jQueryとmomentはいらないかな、、

最終的にこれぐらいなら自分で作っちゃおうというノリになりました。

必要なパッケージをインストール

```bash
yarn add webextension-polyfill-ts # Chrome APIでPromiseを使えるpolyfill
yarn add -D typescript webpack webpack-cli copy-webpack-plugin ts-node ts-loader @types/webpack @types/copy-webpack-plugin
```

一通りinit

```
yarn init
yarn tsc --init
```

ディレクトリ構成

```
├── package.json
├── public
│   ├── icon_32.png
│   └── manifest.json
├── src
│   └── content_scripts.ts
├── tsconfig.json
├── webpack.config.ts
└── yarn.lock
```

ビルド設定

```typescript
import { ConfigurationFactory } from 'webpack'
import path from 'path'
import CopyWebpackPlugin from 'copy-webpack-plugin'

const config: ConfigurationFactory = () => {
  return {
    entry: {
      content_scripts: path.join(__dirname, 'src', 'content_scripts.ts')
    },
    output: {
      // distディレクトリにcontent_scripts.jsを吐く
      path: path.join(__dirname, 'dist'),
      filename: '[name].js'
    },
    module: {
      rules: [
        {
          test: /.ts$/,
          use: 'ts-loader',
          exclude: '/node_modules/'
        }
      ]
    },
    resolve: {
      extensions: ['.ts', '.js']
    },
    plugins: [
      // publicディレクトリにあるファイルをdistディレクトリにコピーする
      new CopyWebpackPlugin([
        { from: 'public', to: '.' }
      ])
    ]
  }
}

export default config
```

データをstorageに読み書きするcontent_scriptsのサンプル

```typescript:content_scripts.ts
import { browser } from 'webextension-polyfill-ts'

const execute = async () => {
  const value = await browser.storage.local.get('date')
  console.log(value.date || '日時が記録されていません')

  await browser.storage.local.set({ date: new Date().toString() })
  console.log('現在の日時を記録しました')
}

execute()
```

あとは`yarn webpack`してdistディレクトリをChromeで読み込むだけ！

[ソース](https://github.com/masaki-koide/chrome-extension-typescript)

## 【余談】chrome.storage APIについて

拡張機能でデータを永続化したい場合、ブラウザのlocalStorageを使うこともできますが、
localStorageのスコープの問題がありややこしいです。
chromeのAPIとしてstorageが用意されており、key-value形式のデータを保存することができます。

[chrome.storage](https://developer.chrome.com/apps/storage)

ちなみに保存されたデータは`~/Library/Application Support/Google/Chrome/Default/Local Extension Settings/{ExtensionのID}`で見ることができます。
(余談の余談ですが、拡張機能のコードは`~/Library/Application Support/Google/Chrome/Default/Extensions/{ExtensionのID}/{Extensionのversion}`から見れます)

```
├── 000003.log
├── CURRENT
├── LOCK
├── LOG
└── MANIFEST-000001
```

000003.logを見ればなんとなくデータが書き込まれているのが確認できます。(LevelDBという形式らしいですが、僕はよくわかりません、、)

また、`chrome.storage.local`でローカルのファイルにデータを保存することもできますが、`chrome.storage.sync`でデータを同期することもできちゃいます！

## 【余談】jQuery不要説

Chrome拡張機能といえば、jQueryが使って開発されている例が非常に多く見られるのですが、個人的に昨今はjQueryの必要性がどんどん薄れてきているのかなと感じています。ECMAScriptの仕様が新しくなって配列操作など便利な機能や構文がどんどん増えています。

DOMのLoadを待ってからスクリプトを実行するといったような実行タイミングの制御も、Content Scriptsの`run_at`というフィールドを設定すれば簡単にできます。詳しくは公式ドキュメントを御覧ください。

https://developer.chrome.com/extensions/content_scripts#run_time