AngularでChrome拡張を作ってみよう

# はじめに
Chrome拡張は、Webページと同じくHTML、JavaScript、CSSから構成されるため、各種フレームワークやライブラリを使用することが可能です。そこでこの記事では、Angularを使って、現在のタブの情報を表示するだけのごく簡単なChrome拡張を作る手順についてまとめてみます。

なお、この記事ではあくまでも開発手順に焦点を当てているため、Angularの踏み込んだ開発やChrome拡張のWebストアへの公開は対象外としています。Chrome拡張を公開する手順については、以下の記事が参考になりますのでチェックしてみてください。

[超最低限の Chrome エクステンションを開発しウェブストアで公開するまでの手順](https://qiita.com/otchy/items/e29bf2e377c5b7ddc2f7)

また、この記事では、Node.jsとnpmがあらかじめインストールされていることを前提として進めていきます。まだインストールしていない場合は、あらかじめインストールをお願いします。

# Chrome拡張の開発
それでは、実際にAngularを使ったChrome拡張の開発手順について見てみましょう。

## Angular CLIのインストール
[Angular CLI](https://cli.angular.io)は、Angularのソースコードやビルド環境、テスト環境などを自動的に生成してくれる非常に優秀なコマンドラインツールです。この開発手順でも、Angular CLIを使用します。

まずAngular CLIをインストールするために、ターミナルで次のコマンドを実行してください。

```
$ npm install @angular/cli -g
```

Angular CLIをインストールしたら、 `ng -v` を実行してAngular CLIが正しくインストールされたことを確認してみましょう。次のようにAngular CLIのテキストがでかでかと表示されればOKです。

```
$ ng -v
    _                      _                 ____ _     ___
   / \   _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
  / △ \ | '_ \ / _` | | | | |/ _` | '__|   | |   | |    | |
 / ___ \| | | | (_| | |_| | | (_| | |      | |___| |___ | |
/_/   \_\_| |_|\__, |\__,_|_|\__,_|_|       \____|_____|___|
               |___/
@angular/cli: 1.4.4
```

## Angularの環境構築
Angular CLIをインストール後、ターミナルで開発用のディレクトリに移動します。ここでは`~/dev`というディレクトリを使用すると仮定します。

```
$ cd ~/dev
```

開発用ディレクトリに移動したら、Angular CLIを使用してAngularの開発環境を構築します。環境の構築には[`ng new`](https://github.com/angular/angular-cli/wiki/new)を使います。ターミナルで次のコマンドを実行して、`chrome-ext`というディレクトリに環境を構築しましょう。

```
$ ng new chrome-ext
```

上記コマンドを実行すると、指定したディレクトリの中にAngularの環境が構築され、必要なnpmパッケージも自動的にダウンロードされます。`chrome-ext`ディレクトリの中に、以下のような構成で各種ファイルが生成されていることを確認しましょう。

```
dev
|- chrome-ext
   |- src
   |  |- ...
   |
   |- e2e
   |  |- ...
   |
   |- README.md
   |- karma.conf.js
   |- package-lock.json
   |- package.json
   |- protractor.conf.js
   |- tsconfig.json
   |- tslint.json

# 一部省略
```

## マニフェストファイルの追加
Chrome拡張を作るには、拡張の内容を定義するマニフェストファイル`manifest.json`が必要となります。そのため、`src`ディレクトリの中に`manifest.json`を追加し、以下の内容をコピペしてください。

```json:src/manifest.json
{
  "name": "Chrome Ext",
  "manifest_version": 2,
  "description": "タブの情報を表示します",
  "version": "1.0",
  "browser_action": {
    "default_popup": "index.html"
  },
  "permissions": [
    "tabs"
  ]
}
```

今回はタブの情報を取得する必要があるため、`permissions`に`tabs`を定義しています。なお、ここではマニフェストファイルの詳細については割愛しますので、詳しく知りたい方は[公式のドキュメント](https://developer.chrome.com/apps/manifest)を確認してみてください。

## .angular-cli.jsonの変更
`src`に追加した`manifest.json`ファイルは、ビルド時に他のファイルと共に`dist`ディレクトリへと出力する必要があります。そこで、`manifest.json`を出力対象とするため、`.angular-cli.json`を開き、以下のように`assets`に`manifest.json`を追加してください。

```json:.angular-cli.json
{
  ...
  "apps": [
    {
      ...
      "assets": [
        "assets",
        "favicon.ico",
        "manifest.json"
      ],
    }
  ]
}
```


## @types/chromeのインストール

Angular + TypeScriptで開発を行うにあたって、Chromeの型を定義したパッケージ `@types/chrome`をnpmで事前にインストールしておきます。これをインストールしておくことで、Visual Studio Codeなどを使用したときのオートコンプリーションが効くようになり、開発が非常に楽になります。

![chrome.gif](https://qiita-image-store.s3.amazonaws.com/0/12152/f229290c-6d51-7fc9-becb-c36b5343036f.gif)

`@types/chrome`をインストールするために、ターミナルで`chrome-ext`ディレクトリに移動し、以下のコマンドを実行してください。

```
$ npm install @types/chrome --save-dev
```

これによって、`@types/chrome`がインストールされ、`package.json`に以下のようなdevDependenciesが追加されます。

```json:package.json
{
  ...

  "devDependencies": {
    "@types/chrome": "0.0.48",

    ...
}
```

次に、`src`ディレクトリにある`tsconfig.app.json`を開いて、`compilerOptions.types`に`chrome`を追加してください。

```json:src/tsconfig.app.json
{
  "compilerOptions": {
    ...
    "types": [
      "chrome"
    ]
  }
}
```

## ソース編集
ここからは、実際にAngularのファイルを編集していきます。

### AppComponentの変更
まず、`src/app`ディレクトリに定義されている`AppComponent`の内容を以下のように変更します。

```html:src/app/app.component.html
<div style="text-align:center">
  <h1>{{tab.title}}</h1>
  {{tab.url}}
</div>

<pre style="background-color:#e0e0e0">
  {{tab | json}}
</pre>

```


```ts:src/app/app.component.ts
import { Component, OnInit, ChangeDetectorRef } from '@angular/core';


@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  tab: chrome.tabs.Tab;

  constructor(private ref: ChangeDetectorRef) { }

  ngOnInit() {
    // 現在のタブのオブジェクトを取得する
    chrome.tabs.query({ 'active': true, 'lastFocusedWindow': true }, (tabs) => {
      this.tab = tabs[0];

      // 変更があったことを通知する
      this.ref.detectChanges();
    });
  }
}
```

ここでは、`chrome.tabs.query()`を使用して、現在開かれているタブの情報を取得しています。また、コールバック関数はAngularの管理対象外から呼び出されるため、`ChangeDetectorRef`の`detectChanges()`を使用して値に変更があったことを通知します。

### CSSの変更
`src/styles.css`を開き、以下のようにbodyのサイズを指定します。

```css:src/styles.css
body {
  width: 400px;
  height: 400px;
}
```

このようにbodyのサイズを指定することで、ポップアップされる領域のサイズを決めることができます。

## ビルド
最後に、作成したコードをAngular CLIでビルドします。ターミナルから以下のコマンドを実行してください。

```
$ ng build -aot
```

このコマンドで`dist`ディレクトリが生成され、必要なファイルが配置されます。[^aot]

## Chrome拡張の登録
最後に`dist`に生成されたファイルをChrome拡張として登録します。

ChromeのURLバーに`chrome://extensions`を入力して、Chrome拡張の管理画面を開きます。Chrome拡張画面右上にある**デベロッパーモード**(Develper mode)にチェックを入れ、**パッケージ化されていない拡張機能を読み込む**(Load unpacked extension)ボタンをクリックします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/12152/69adf55f-1766-b327-989d-66eff75fa34e.png)

ボタンをクリックするとディレクトリが指定可能となるので、先程のビルドによって生成された`dist`ディレクトリを指定してファイルを読み込みます。

正常に読み込まれると、URLバーの隣に「C」という拡張のアイコンが表示されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/12152/a35ad1b8-57ed-631d-c1f9-6c38dfd57697.png)

これをクリックすると、AngularのコードがChrome拡張の中で実行され、このように現在のタブの情報が表示されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/12152/0644fe5e-05de-6fb3-b560-31dfab7313ba.png)

## 修正したコードの反映
以降、コードの修正を繰り返しつつ実際にChrome拡張を動作させながら開発を進めていくことになります。修正したコードの反映は、以下のステップで行うことができます。

1. ビルド (`ng build -aot`)
2. 拡張の再読み込み

![image.png](https://qiita-image-store.s3.amazonaws.com/0/12152/79bc4e54-05a3-2d24-6160-ae05b903ff9d.png)

# 最後に
以上が、AngularによるChrome拡張の一連の手順です。Angular CLIを使うと簡単にAngularによるChrome拡張が簡単に作れるので、暇すぎて死に絶えそうな時に試してみましょう。

ちなみに、ここで使用したコードは[GitHub](https://github.com/Tsuyoshi84/AngularExtensionDemo)で公開しています。

# 参考
[Build Your Own Chrome Extension Using Angular 4.](https://medium.com/@PardeepJain/build-your-own-chrome-extension-using-angular-4-ecedbd7404cc)
[Build a simple Emoji Chrome Extension with Angular CLI.](https://medium.com/@_JoshSommer/build-a-simple-emoji-chrome-extension-with-angular-cli-84937a1ca640)
[Using chrome extension apis in typescript](https://stackoverflow.com/questions/28844406/using-chrome-extension-apis-in-typescript)

# 注釈
[^aot]: ここで指定している`-aot`オプションは、[Ahead of Time](https://angular.io/guide/aot-compiler)を意味し、事前にコンパイルを行う役割を持ちます。(デフォルトではJust in Timeのコンパイルとなります)　このオプションを指定しなければ、Chrome拡張実行時にJust in Timeで実行されるコンパイラの動作がエラーとなってしまいます。