ブラウザ機能拡張でnpmパッケージを使う

ブラウザの機能拡張を作っていて、少し凝ったことをしようとすると npm のパッケージを使いたくなる。[公式チュートリアル](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/What_are_WebExtensions)ではそこまで書いていないので、これが1つ目の壁になるのかな。まあ

https://github.com/mdn/webextensions-examples/tree/main/webpack-modules

これが答えなんだけど、HowTo 的にまとめてみた。前提条件として MDN のチュートリアルは一通り終わっていることを想定している。

# 手順
## 最初の設定
プロジェクト用のディレクトリを作り、npm 用の初期化をする。

```shell
mkdir sampleext
cd sampleext
npm init -y
```

すると構成は以下のような状態になる。

```shell
$ tree
./
└── package.json

0 directories, 1 file
```

package.jsonを変更する

```json:./package.json(変更前)
{
  "name": "sampleext",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
```

```json:./package.json(変更後)
{
  "name": "sampleext",
  "version": "0.0.1",
  "description": "Sample Firefox extension using npm packages",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "webpack"
  },
  "license": "ISC"}
```

Webpackをインストールする

```shell
npm install --save webpack webpack-cli
```

この例では出力先のディレクトリは `dist` にする。dist ディレクトリを作って、`dist/manifest.json` を作る。

```json:dist/manifest.json
{
    "manifest_version": 3,
    "name": "Webpack Example",
    "version": "0.0.1",
    "description": "A minimal example of how to use npm modules from within a WebExtension.",
    "icons": {
        "32": "icons/leftpad-32.png"
    },
    "browser_action": {
        "default_icon": "icons/leftpad-32.png",
        "default_title": "Left Pad",
        "default_popup": "popup/left-pad.html",
        "browser_style": true
    },
    "background": {
        "scripts": [
            "background_scripts/index.js"
        ]
    }
}
```

解説しておくと
- manifest_version: マニフェストのバージョン。最新は3
- name: Extension の名前
- version: Extension のバージョン
- description: Extention の解説。MDNの記事ではちゃんと書くことを推奨している。
- icons: アイコン画像。必須ではない
- browser_action: ブラウザの拡張機能ボタンの挙動を定義する
   - default_icon: 拡張機能メニューを呼び出すメニューボタンの画像
   - default_title: 拡張機能メニューの名前
   - default_popup: ボタンを押したときに表示されるポップアップのHTML
   - browser_style: 独自のCSSを使用する場合は`true`
- background: (条件に該当する)ページが読み込まれたときに自動的にinjectされ実行されるスクリプト。この場合 `background_scripts/index.js` になるが、この `background_scripts/index.js` は Webpack によって自動生成される。

次に `webpack.config.js` を作る

```javascript:./webpack.config.js
const path = require("path")
module.exports = {
    entry: {
        background_scripts: "./background_scripts/background.js",
        popup: "./popup/left-pad.js"
    },
    output: {
        path: path.resolve(__dirname, "dist"),
        filename: "[name]/index.js"
    },
    mode: 'none',
}
```
同じく解説すると
- entry: Webpack が必要とするソースファイル
    - background_scripts: 上記の `manifest.js` でいう `background_scripts/index.js` のソースファイル。
    - popup: 上記の `manifest.js` でいう `popup/left-pad.html` から呼び出される `popup/index.js` のソースファイル。
- output: Webpack の出力先
    - path: 出力先の basepath。この場合は `./dist` になる。
    - filename: 出力されるファイル名。ここの `[name]` は entry のエントリ名を指している。この例では `entry:{}` の中にある、`background_scripts` と `popup` がそれぞれエントリ名になり、出力されるファイルは以下のようになる。
        - エントリ名: `background_scripts`
            - ソース: `background_scripts/background.js`
            - 出力先: `dist/background_scripts/index.js`
        - エントリ名: `popup`
            - ソース: `popup/left-pad.js`
            - 出力先: `dist/popup/index.js`
## ソースファイルの作成
この時点でツリーは以下のようになっている。
```shell
./
├── dist/
│   └── manifest.json
├── node_modules/
│   └── (多数のファイル)
├── package-lock.json
├── package.json
└── webpack.config.js
```
ここにソースファイルを追加していく。ソースファイルを追加すると、ツリーは以下のようになる。△ がこれから作成するファイル。
```shell
./
├── background_scripts
│   └── △ background.js
├── dist/
│   ├── icons/
│   │   └── △ leftpad-32.png
│   ├── manifest.json
│   └── popup/
│       ├── △ left-pad.html
│       └── △ style.css
├── node_modules/
│   └── (多数のファイル)
├── package-lock.json
├── package.json
├── popup/
│   └── △ left-pad.js
└── webpack.config.js
```
それぞれ以下のような内容になる
- `background_scripts/background.js`: https://github.com/mdn/webextensions-examples/blob/main/webpack-modules/background_scripts/background.js
- `dist/popup/left-pad.html`: https://github.com/mdn/webextensions-examples/blob/main/webpack-modules/addon/popup/left-pad.html
- `dist/popup/style.css`: https://github.com/mdn/webextensions-examples/blob/main/webpack-modules/addon/popup/style.css
- `popup/left-pad.js`: https://github.com/mdn/webextensions-examples/blob/main/webpack-modules/popup/left-pad.js

## 構築
ビルドすると、`dist/`以下にブラウザに読み込み可能なコードが出力される

```shell
npm install
npm run build
```

Firefoxブラウザのロケーションバーに `about:debugging` と入れる
左のメニューから [この Firefox] を選択
[一時的なアドオンを読み込む] をクリックし、`dist/manifest.js` を指定するとプラグインが動作するようになる

## (オプション)難読化
本番用にビルドするときはWebpackで難読化することもできる。

`webpack.config.js` の `mode` を `production` に変更する
```javascript:./webpack.config.js
const path = require("path");

module.exports = {
    entry: {
        background_scripts: "./background_scripts/background.js",
        popup: "./popup/left-pad.js"
    },
    output: {
        path: path.resolve(__dirname, "dist"),
        filename: "[name]/index.js"
    },
    mode: 'production',
};
```
変更したら再度ビルドする
```shell
npm run build
```