Chrome拡張(Content Script)をTypeScriptで作成した際の設定類【個人開発】

__Amazon.co.jpのウィッシュリストとランキングページで獲得ポイントの表示を追加__するChrome拡張__"Amazon Wishlist Point Visualization"__を作りました。セールになっている商品を見つけやすくなります。

https://chrome.google.com/webstore/detail/devola-amazon-wishlist-po/khfjbdbepipkeecalhcpcnhkdfedkcki

# この記事の概要

元々は自分向けにjQueryを使用して作成していたChrome拡張を勉強の一環+脱jQueryとして、TypeScriptで作成しなおしました。私自身がChrome拡張を作成する際に作例が大いにヒントとなったため、重要そうな部分や躓いた点について書いていきます。

プログラムそのものは各自で実装したい内容が異なると思うのであまり触れず、設定ファイル類に絞って記載します。

「__Chrome拡張をTypeScriptで作成したい人__」の助けになると嬉しいです。

Chrome拡張で作成できる機能はいくつか種類がありますが、今回作成したのは__Content Script__です。これは特定のURLでのみ実行されるスクリプトです。

> 特定のURLで動作するスクリプトで、表示されているDOM要素を読み込んだり、変更を加える事ができる。文字の大きさを変えたり、リンクを変更したりといった用途に使います。

[https://oxynotes.com/?p=8836](https://oxynotes.com/?p=8836)

## 使用した技術要素

- TypeScript
- webpack

# 作成したもの

Amazonでウィッシュリストとランキングのページはデフォルトでは金額だけが表示されていて、獲得できるポイントが表示されていません。これでは獲得ポイントが増加するセール中なのか気付けません。

なので価格の横に獲得ポイントも表示させる機能を作りました。

![ウィッシュリストの例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243414/4ea3d458-5e88-477b-674c-b820aa902d4d.jpeg "ウィッシュリストの例")

## ディレクトリ構造

```txt
├─dist               最終的な成果物を出力するディレクトリ
├─public             静的ファイルを格納するディレクトリ
│  ├─images
│  │  ├─icon16.png
│  │  ├─icon48.png
│  │  └─icon128.png
│  └─manifest.json
├─src                プログラムを格納するディレクトリ
│  ├─index.ts
│  ├─ranking.ts
│  ├─util.ts
│  └─wishlist.ts
├─package.json
├─tsconfig.json
├─webpack.config.ts
└─yarn.lock
```

## 依存関係のインストール

使用するパッケージをインストールします。

```bash
yarn add -D typescript ts-loader ts-node webpack webpack-cli copy-webpack-plugin @types/webpack @types/copy-webpack-plugin
```

| パッケージ名 | 用途 |
|-|-|
|typescript| TypeScript本体|
|ts-loader| `.ts`ファイルを`.js`に変換 |
|ts-node|`.ts`を直接nodeで実行するために使用<br>(後述する躓きのために必要)|
|webpack|複数のファイルを1つにまとめて出力|
|webpack-cli|webpackをコマンドで実行するために使用|
|copy-webpack-plugin|webpackでファイルをコピーするために使用|
|@types/webpack|TypeScript向けの定義データ|
|@types/copy-webpack-plugin|TypeScript向けの定義データ|

```json:package.json
  "scripts": {
    "build": "webpack --mode production"
  }
```



## マニフェストの作成

```json:manifest.json
{
  "manifest_version": 3,
  "name": "Devola: Amazon Wishlist Point Visualization",
  "short_name": "Devola",
  "version": "1.3.0",
  "description": "説明文",
  "default_locale": "ja",
  "icons": {
    "16": "images/icon16.png",
    "48": "images/icon48.png",
    "128": "images/icon128.png"
  },
  "content_scripts": [
    {
      "matches": [
        "https://www.amazon.co.jp/*/wishlist/*",
        "https://www.amazon.co.jp/portal-migration/bestsellers/*",
        "https://www.amazon.co.jp/gp/bestsellers/*",
        "https://www.amazon.co.jp/gp/new-releases/*",
        "https://www.amazon.co.jp/gp/movers-and-shakers/*"
      ],
      "css": [],
      "js": ["js/index.js"],
      "run_at": "document_end"
    }
  ]
}
```

`matches`にて処理を実行したいURLパターンを定義します。今回はAmazonのウィッシュリストとランキングページを指定しています。

`js`にて実行するスクリプトを指定します。`/dist`が最終出力のディレクトリになるため、`/dist/js/index.js`を実行するよう指定しています。

`run_at`にてスクリプトを実行するタイミングを指定します。今回はDOMが読み込まれたタイミングで実行したいため`document_end`を指定しています。その他のタイミングにしたい場合は[ドキュメント](https://developer.chrome.com/docs/extensions/mv3/content_scripts/#run_time)を参照してください。

## TypeScriptの設定

```json:tsconfig.json
{
  "compilerOptions": {
    "target": "es2020",
    "module": "es2020",
    "strict": true,
    "esModuleInterop": true,
    "typeRoots": [ "node_modules/@types"]
  },
  "exclude": [
    "node_modules"
  ],
  "ts-node": {
    "compilerOptions": {
      "target": "es2020",
      "module": "commonjs"
    }
  }
}
```

Chromeは新しいESにも対応しているため`"target": "es2020"`を指定しています。

`webpack.config.ts`を使用している場合`module`に`commonjs`以外を指定すると、エラーが発生します。そのため`ts-node`を使用して一旦`commonjs`としてプログラムを読み込ますことでエラーを回避しています。

## webpackの設定

```ts:webpack.config.ts
import webpack from "webpack";
import path from "path";
import CopyWebpackPlugin from "copy-webpack-plugin";

const config: webpack.Configuration = {
  entry: {
    index: path.join(__dirname, "src/index.ts"),
  },
  output: {
    path: path.join(__dirname, "dist/js"),
    filename: "[name].js",
    clean: true,
  },
  module: {
    rules: [
      {
        test: /.ts$/,
        use: "ts-loader",
        exclude: "/node_modules/",
      },
    ],
  },
  resolve: {
    extensions: [".ts", ".js"],
  },
  plugins: [
    new CopyWebpackPlugin({
      patterns: [{ from: "public", to: "../" }],
    }),
  ],
};

export default config;
```

`entry`にてバンドルする起点となるファイルを指定しています。`index.ts`を`index`という名前で定義しています。Chrome拡張を作成する際にContent Script以外も作成する場合は`index`を`content`などに書き換えたりすると出力結果が見分けやすくなると思います。

`output`にてバンドルした結果をどこに出力するかを指定しています。今回は`/dist/js/[name].js`として出力するように指定しています。`[name]`には`index`が入ります。

`module`にてバンドルの対象となるファイルを指定しています。`rule`にて一致した条件のファイルを何を用いて処理するのかを指定しています。今回は`.ts`なファイルを`ts-loader`を使用して変換することを指定しています。`node_modules`配下のファイルは変換したくないため除外しています。

`resolve`にてプログラム内でimportする際に省略する可能性のある拡張子を指定しています。

`plugins`内で`CopyWebpackPlugin`をどのように使用するかを定義しています。`/public`配下のファイルを`output`で指定したディレクトリ(`/dist`)にコピーするよう指定しています。

## ビルドする

```bash
yarn build
```

実行してみると`/dist`にファイルが出力されます。

```txt
dist
├─images
│  ├─icon16.png
│  ├─icon48.png
│  └─icon128.png
├─js
│  └─index.js
└─manifest.json
```

後は`/dist`をzip化してChrome拡張の管理画面でアップロードすることで公開が行えます。

ここまで読んでいただきありがとうございました。実装コードはGitHubにて公開しています。

https://github.com/big-mon/amazon-wishlist-pointgetter

# 参考にした記事

- [step by stepで始めるwebpack](https://qiita.com/howdy39/items/48d85c430f90a21075cd)
- [webpackの基本だけどハマりやすいentryの設定と[name]](https://qiita.com/sansaisoba/items/921438a19cbf5a31ec53)
- [webpack + ts-loader で使う tsconfig.json を動的に切り替える](https://mizchi.dev/202006101314-switch-tsconfig-on-webpack)