Chrome拡張のpopupをReactで作るサンプル

## はじめに
この記事は「[【マイスター・ギルド】本物のAdvent Calendar 2021](https://adventar.org/calendars/6579)」13日目の記事です。
何かと頻繁に利用するGoogle Chrome。
今後も長いお付き合いになるので、使いやすくしたいってことで拡張機能の開発に興味を持ちました。
[Extensions - Chrome Developers](https://developer.chrome.com/docs/extensions/)
本記事ではReactを使用してGoogle拡張のpopupを作るところまでにしたいと思います。(ほんとに入門です)
Create-React-Appで簡単に環境を作れるのが便利ですが、今回は使用せずシンプルにいきます。
## 本編
### 1.Reactのインストールからビルドまで
プロジェクトのルートフォルダに移動し、`yarn init -y`で初期化します。(これで`package.json`が作られます)
`src`, `public`フォルダも作ります。

```
$ yarn init -y
$ mkdir src public
```
`public`に`manifest.json`を作ります
このファイルは、Chrome拡張の色々な設定などを記述するものです。
参考：[Manifest file format - Chrome Developers](https://developer.chrome.com/docs/extensions/mv3/manifest/)

```json-doc:manifest.json
{
  "name": "Getting Started Example",     // Extensionの名前
  "description": "Build an Extension!",  // Extensionの説明
  "version": "1.0",                      // Extensionのバージョン
  "manifest_version": 3,                 // manifestのバージョン
  "action": {
    "default_popup": "popup.html"        // popupに表示するhtmlファイル
  }
}
```
`src`に`popup.html`を作成します。

```html:popup.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="react-target"></div>
  </body>
</html>
```
#### Reactをインストールする
```
$ yarn add react react-dom
```
`src`に`popup.jsx`を作成します。

```jsx:popup.jsx
import React from "react";
import { render } from "react-dom";
const Popup = () => {
  return (
    <div>
      <h1>Hello, world!</h1>
      <p>This is a simple popup.</p>
    </div>
  );
}
render(<Popup/>, document.getElementById("react-target"));
```
#### webpackをインストールする
参考：[Installation | webpack](https://webpack.js.org/guides/installation/)

```
$ yarn add --dev webpack webpack-cli
```
プロジェクトのルートに`webpack.config.js`を作成します。

```javascript:webpack.config.js
const path = require("path");
module.exports = {
  entry: {
    // ここに読み込ませるファイルを記述する
    popup: "./src/popup.jsx",
  },
  output: {
    // 出力先とファイル名を指定する
    path: path.resolve(__dirname, "dist"),
    filename: "[name].js",
  }
};
```
`package.json`に追記します。

```json:package.json
{
  "scripts": {
    "build": "webpack --config webpack.config.js"
  }
}
```
`--config`: 設定ファイルを指定します
これで、`yarn build`と打つことで`webpack.config.js`をもとにwebpackが動いてくれるようになりました。
参考：[Command Line Interface | webpack](https://webpack.js.org/api/cli/)
#### babelをインストールする
このままではブラウザがjsxファイルに対応できないので、jsファイルにコンパイルする必要があります。
[babel-loader](https://webpack.js.org/loaders/babel-loader/)、[@babel/core](https://babeljs.io/docs/en/babel-core)、[@babel/preset-env](https://babeljs.io/docs/en/babel-preset-env)、[@babel/preset-react](https://babeljs.io/docs/en/babel-preset-react)をインストールし、`webpack.config.js`に追記します。

```
$ yarn add --dev babel-loader @babel/core @babel/preset-env @babel/preset-react
```
```javascript:webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.(js|jsx)$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
          options: {
            presets: ["@babel/preset-env", "@babel/preset-react"],
          }
        }
      },
    ],
  },
}
```
#### webpackプラグインをインストールする
これでビルドできるようになりましたが、ビルドフォルダ`dist`に`popup.html`と`manifest.json`が含まれていません。
##### ビルドにhtmlファイルを含める
[HtmlWebpackPlugin](https://webpack.js.org/plugins/html-webpack-plugin/)をインストールして、`webpack.config.js`に追記します。

```
$ yarn add --dev html-webpack-plugin
```

```javascript:webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
  plugins: [
    new HtmlWebpackPlugin({
      template: "./src/popup.html",
      filename: "popup.html",
    })
  ],
};
```
これでビルドフォルダに`popup.html`が含まれるようになります。
##### ビルドにその他ファイルを含める
[CopyWebpackPlugin](https://webpack.js.org/plugins/copy-webpack-plugin/)をインストールして、`webpack.config.js`に追記します。

```
$ yarn add --dev copy-webpack-plugin
```
```javascript:webpack.config.js
const CopyPlugin = require("copy-webpack-plugin");
module.exports = {
  plugins: [
    new CopyPlugin({
      patterns: [{ from: "public" }],
    }),
  ],
};
```
### 2. 自作した拡張をChromeに読み込む
ウィンドウ右上の拡張機能から「拡張機能を管理」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/782908/67d0284a-e2ee-e0ac-e52d-54da0561f59e.png)
「デベロッパーモード」をクリックし、「パッケージ化されていない拡張機能を読み込む」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/782908/d2201806-1a51-dc5e-fe5c-e8da616b367d.png)
`manifest.json`のあるフォルダを指定すると、読み込み完了です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/782908/6a7d023b-6ae6-d444-766e-ccbfcc6c6602.png)
新しいタブを開き、`chrome-extension:// {extensionのid} / {ファイル名}`と打ち込むことでも確認できるようです。この場合はChrome DevToolsも使えます。
(例) `chrome-extension://cgbcaliplbidocbnjgkgfmacnedolee/popup.html`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/782908/752fa420-82b2-6e31-553d-39efd90c16ca.png)
(読み込んだ拡張機能にIDが記載されています)
### 3. 少しだけ開発しやすくする
最後に、webpackの設定をdevelopment, productionにそれぞれ分けます。
[webpack-merge](https://github.com/survivejs/webpack-merge)をインストールします。


```
$ yarn add --dev webpack-merge
```
プロジェクトのルートに`webpack.dev.js`, `webpack.prod.js`を作成します。

```javascript:webpack.dev.js
const { merge } = require("webpack-merge");
const config = require("./webpack.config.js");
module.exports = merge(config, {
  mode: "development",
  devtool: "inline-source-map"
});
```
```javascript:webpack.prod.js
const { merge } = require("webpack-merge");
const config = require("./webpack.config.js");
module.exports = merge(config, {
  mode: "production",
});
```
`package.json`を書き換えます。

```json:package.json
{
  "scripts": {
    "dev": "webpack --watch --config webpack.dev.js",
    "build": "webpack --config webpack.prod.js"
  }
}
```
これで、production用には`yarn build`、development用には`yarn dev`というコマンドを用意できました。
`--watch`：ファイルの差分を感知したときにビルドする
## おわりに
今回はpopupのみでしたが、とても簡単にChrome拡張を作ることが出来ました。
初めてwebpackに触れたので、この調子で触りながら知見を広げていきたいと思います。
良い感じのChrome拡張機能ができたらまた記事にしたいです。
12月も残りわずか、、
今年もやり切って、よいお年を迎えましょう！

## 参考
[Create React Appで始めるChrome拡張 - Qiita](https://qiita.com/takakobem/items/64c37a4f35cadd712907)
[Build a Chrome Extension With React & Webpack - YouTube](https://www.youtube.com/watch?v=8OCEfOKzpAw&ab_channel=TraversyMedia)