Chrome拡張を簡単に作れるテンプレとライブラリ造ったので紹介

Chrome拡張を長年造ってきたなかで
手法や自作ライブラリが充実してきたのでこの辺で公開します。
Chrome拡張は本当に効果的に改善できて、すばらしい仕組みです。
これで皆さんが簡単に現場改善が進んだら嬉しい限りです。

(なお、これは 2017-08-05 Builderscon Tokyo 2017 でのトーク用に作成した資料です)

# わりと **ウケた** 改善

どんなことができるのか、ざっと見てみましょう。

[サンプルページ](https://waterada.github.io/chrome-ex-template/test/example.html)

※各項目ごと、「拡張を有効化」をクリックすることで拡張が入った状態を見ることができます。


# Chrome拡張を作るのに必要な **環境**

必要な環境はシンプルです。

- Google  Chrome
- Text Editor or IDE(UTF-8 が扱えるなら何でもいい)
- （公開するなら）Zip 圧縮ができるソフトウェア
- （公開するなら）クレジットカード

これだけ。

あとは、ソース履歴管理用に下記があると良い。

- [推奨] に Git (サーバとつながって無くてもいい)


# Chrome拡張を作る **手順**

どうやって作り、どうやって配布するのか説明します。

## 1. 雛形をダウンロード、解凍

[雛形置き場](https://github.com/waterada/chrome-ex-template) からダウンロードして解凍。
下記のような構成になっている。

```
README.md
src/                      … Chrome拡張本体
    manifest.json         … Chrome拡張の設定ファイル
    jquery-2.2.0.min.js   … 書きやすくするためのjQuery
    ChEx.js               … 書きやすくするための秘伝のタレ
    content_script.js     … Chrome拡張のスクリプト本体
    option.html           … オプション画面のhtml
    option_script.js      … オプション画面用のJS
    ope-storage.js        … Storage管理しやすくする秘伝のタレ
test/                     … 単体テストなど
.gitignore
```


## 2. `manifest.json` を更新

```json
{
  "name": "Chrome拡張テンプレート",
  "description": "テンプレです。変更してください。",
  "version": "0.1",
  "manifest_version": 2,
  "content_scripts": [{
    "matches": ["*://example.com/*"],
    "js": ["jquery-2.2.0.min.js", "ChEx.js", "content_script.js"]
  }],
  "options_page": "options.html",
  "permissions": [
    "*://example.com/",
    "storage"
  ]
}
```

- `"name"`, `"description"` の値を自身の拡張名、説明に書き換える。
- `"matches"` の URL をこの拡張を有効にしたい URL に置き換える。
- `"options_page"` の値はオプションページ（拡張機能の画面で「オプション」というリンクをクリックして表示される画面）への静的なリンクを指す。不要なら消してもいいい。
- `"permissions"` の値は Storage (ブラウザに内容を保存する) 機能を使う場合は必要（使わないなら消していい）。これがあることで Storage 機能が使えるようになり、インストールするユーザにも使いますよという承諾が行われる。


## 3. Chrome で動かしてみる

### 3-1. Chrome 拡張を Chrome に取り込む

- [chrome://extensions/](chrome://extensions/) にアクセス。
- 「デベロッパー モード」を ON に。すると下記のようなボタンが現れる。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/29899/3667ce3e-b854-d0de-e240-59b6bcd9ba8c.png)

- 「パッケージ化されていない拡張機能を読み込む...」ボタンを押すと、フォルダ選択ダイアログが開く。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/29899/17cf06f2-d73b-50a7-4e71-7a6b046b1f4b.png)

- src フォルダを選択して取り込む。成功すれば下記のようなものが追加される。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/29899/9721f0e8-7a35-c5a5-9f1f-3ccc1e74728d.png)


### 3-2. 対象のURLにアクセス

成功なら Title の頭に `◆` と `■` が交互に表示されて回ってるようになる。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/29899/50176d8f-c972-a9be-ea8d-308f8fee30d8.png)
また、F12 で「デベロッパーツール」を開くと Console に下記のように文言が出力される。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/29899/0231caa0-1b32-7ebe-a2bb-cf71b9ddb5cf.png)

```js
$(function () {
    //↓疎通確認コード：タイトルの■が回転するはず
    console.log('Chrome拡張の疎通確認コードが動いています。動作確認ができたら削除してください。');
    let flag = false;
    setInterval(function () {
        document.title = (flag ? "■" : "◆") + document.title.replace(/^[■◆]/, '');
        flag = !flag;
    }, 1000);
    //↑疎通確認コード
});
```

## 4. (できれば) Git でソース管理

Git で、ここで First Commit しておく。この後の自身で行った変更が解りやすくなるので。


## 5. 実装する

- content_script.js に実装 (詳細なコード例は後述)

- Chrome 拡張でリロード
  ![image.png](https://qiita-image-store.s3.amazonaws.com/0/29899/9721f0e8-7a35-c5a5-9f1f-3ccc1e74728d.png)
  の「リロード (Ctrl+R)」をクリックする。

- 実URLでリロード (これで反映されるはず)
- [Git管理するなら] 適宜 Git で Commit

## 6. デバッグ

「デベロッパーツール」を開いて行う。

### 6-1. Console

- 何かエラーがあれば Console に表示される。
  たとえば
  ![image.png](https://qiita-image-store.s3.amazonaws.com/0/29899/eb755e6e-d87c-46d1-6abd-3815337357d4.png)
  のように表示される。

### 6-2. ステップ実行

- Chrome拡張内のソースも通常のソースと同様に Sources タブから確認/ステップ実行できる。
  ![image.png](https://qiita-image-store.s3.amazonaws.com/0/29899/43d56333-ea53-577c-eaed-af47bd1f4266.png)
  
  Sources を表示したら、左ペインの「Content Scripts」を選ぶことで、Chrome拡張のソースにアクセスできる。

## 7. 初配布

### 7-1. バージョンをインクリメント

・manifest.js のバージョンをインクリメント
`"version": "0.1",`
↓
`"version": "0.2",`

### 7-2. Git でバージョン番号を Commit

- 例えば `v0.2` をコメントに、この manifest.js の変更だけを Commit する。(あとでどの機能がどのバージョンで入ったのかが追跡しやすくなる。)

![image.png](https://qiita-image-store.s3.amazonaws.com/0/29899/2bd424b6-a086-0599-6212-9d647242c190.png)


### 7-3. src.zip 作成

- `src` を Zip 圧縮 （`src.zip` ができる）

### 7-4. Google デベロッパー登録（初めて拡張をアップする場合のみ）

- Google デベロッパー登録する ($5≒570円の手数料。一回きり)
- やり方を紹介するサイトは沢山あるので、そちらをみてください。
例: http://oxynotes.com/?p=8983
- 配布/バージョンアップが圧倒的にしやすくなる。$5 は安いと思うはず。
- クレジットカード登録して支払いすれば、すぐさま Chrome 拡張を公開できる。
- 以下の画面がデベロッパー ダッシュボード。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/29899/f95c215e-f1d4-9179-ca9c-7780bb112347.png)
黄色い背景でなにやら警告が表示されるが Chrome 拡張は「アプリ」ではないので無視して大丈夫。

### 7-5. デベロッパー ダッシュボードにアップロード

- 画面の下の方に下図のような「新しいアイテムを追加する」ボタンがある。このボタンでアップロードする。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/29899/338a2069-5031-b2b8-b90c-8283fad79643.png)

- 押すと下図の画面になる。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/29899/449634cd-3b0a-4257-4954-a9c17ba065dc.png)
「ファイル選択」で Zip 圧縮した `src` フォルダを選択し、「アップロード」でアップする。

- アイテムの設定画面はほぼそのままで良いのだが、次の点を設定する。
    - カテゴリ。未設定だとエラーになるので何か選ぶ。
    ![image.png](https://qiita-image-store.s3.amazonaws.com/0/29899/fb61e7a6-46e6-3eac-7864-1926a1da0039.png)
    - 地域。とりあえず「日本」だけが ON の状態にする。
    ![image.png](https://qiita-image-store.s3.amazonaws.com/0/29899/a3c2471f-6363-5411-a75e-dab1df50d3dd.png)
    - 言語。とりあえず「日本語」にする。
    ![image.png](https://qiita-image-store.s3.amazonaws.com/0/29899/a78d4637-34c8-bf2d-e383-b9732d7ab5f6.png)
    - 公開設定オプション。とりあえず「限定公開」にする。「限定公開」ならリンクを配った人しかインストールしないので社内公開には丁度よい。
    ![image.png](https://qiita-image-store.s3.amazonaws.com/0/29899/bea594e9-2064-6617-df10-f129e5ed0b3b.png)
    - 「変更を公開」ボタンを押す。
    ![image.png](https://qiita-image-store.s3.amazonaws.com/0/29899/7462e5ba-cc01-8b61-168e-515574795d93.png)

- 30分ほど待つ。

注意：「変更を公開」ボタンを押すと、プレビュー画面が表示されるが、この画面をいくらリロードしても（公開されたとしても）、公開済みの画面にはならない（URLを見ると解るが、公開待ち専用の画面が表示されているため）。

- 公開されたら、URL を教える。

## 8. バージョンアップ

・src を Zip 圧縮 （src.zipができる）
・デベロッパー ダッシュボードにアップして30分ほど待つ

以上。
利用者側はこれで勝手にバージョンアップされる。


# ブラウザに保存させる機能を使わずに、拡張をシンプルにする

この手順は必須ではありません。消すことで、自作の拡張を若干軽くできます。
ブラウザに保存させる機能を使わないなら、`manifest.json` の `"permissions"` をまた、オプションページも不要なら `"options_page"` も消すことができます。

（とはいえ、将来使う可能性があるなら残しておいたほうが何かと便利です。）

```json:削除前
{
  "name": "Chrome拡張テンプレート",
  "description": "テンプレです。変更してください。",
  "version": "0.1",
  "manifest_version": 2,
  "content_scripts": [{
    "matches": ["*://example.com/*"],
    "js": ["jquery-2.2.0.min.js", "ChEx.js", "content_script.js"]
  }],
  "options_page": "options.html",
  "permissions": [
    "*://example.com/",
    "storage"
  ]
}
```

```json:削除後
{
  "name": "Chrome拡張テンプレート",
  "description": "テンプレです。変更してください。",
  "version": "0.1",
  "manifest_version": 2,
  "content_scripts": [{
    "matches": ["*://example.com/*"],
    "js": ["jquery-2.2.0.min.js", "ChEx.js", "content_script.js"]
  }]
}
```

- `"options_page"` を消すなら、`src` ディレクトリの中にある `options.html` と `options_script.js` を消していい。
- Storage を使わないなら、`ope-storage.js` (オプション画面からメンテする機能を提供する) も削除していい。


# Chrome拡張を作るべく、未知の画面仕様を調べる **アプローチ**

- まずはやりたいことのイメージ
    - ここにボタン置いて自動入力させようとか
    - この内容を書き換えようとか
- Chrome 開発ツールでDOMを調べる
    - とにかく付近のDOMを見る
    - `$$('')`でアクセスしてみる


# ソースの解説

[サンプルページ](https://waterada.github.io/chrome-ex-template/test/example.html?presentation=1)


# ハマりどころ

### 1. 拡張は一度造ったら消せない

非公開にはできますが、消すことはできません。
ググると消し方がヒットしますが、試しても上手く行きませんでした。
ですので、名前を変えるなどして使いまわしてます。


### 2. 拡張名に `customize` という文字列は使ってはいけない

使うと謎のハマり方します。詳細は [こちら](http://qiita.com/waterada/items/680257a45ad7bc9e5b23) で。


### 3. 拡張のバージョンは一旦上げると下げられない

拡張のバージョンは自動配布の都合上だと思いますが、一度上げて公開すると、もう下げることはできません。
うっかり間違って、すごい大きなバージョン番号にして公開しまったら、もうそこからインクリメントしてくしかないです。
公開前によく確認しましょう。


### 4. 違う拡張を更新アップロードしてもエラーにならない

まったく違う拡張を更新アップロードしても、無事（拡張名ごと）アップロードできてしまうため、違和感に気づかずそのまま公開してしまったことがありました。
複数の拡張を管理している場合は、`src` ディレクトリの場所に「拡張名.txt」みたいにファイルを置いておくと、アップする際のディレクトリ選択画面で気づきやすくなるのでよいでしょう。


### 5. もともと入っている onchange 等のイベントを発火させることはできない

Chrome拡張はもともと入っている JavaScript を触ることができません。

```js:拡張側で、こう書いても発火できない
$('#something').change();
```

これはセキュリティ的な理由で、実に妥当な仕組みです。
ただし、超える方法があります。

例えば、下記のボタンを配置すれば元々入っている onchange イベントを発火させることができます。

```html:拡張側で、このボタンを配置すれば発火可能
<input type="type" value="発火" onclick="$('#something').change()">
```

なぜこれで発火するのかというと、`onclick` はもともとのソース側に属しており、それをクリックしたというユーザの動作を伴って発火するためです。