Babel+Browserify を使い WebExtensions 上で React を動かす

備忘録.

WebExtensions (ブラウザのアドオン) で react を動かそうとしてハマった. 今回は babel と browserify を使って react のコードを変換することで難を逃れた. Babel の方はもしかしたら不要かもしれない.

更新のたびにハマっては調べを繰り返している気がする.

## Babel とは何か
JavaScript compiler (transpiler).
js (,jsx,..) file を読み込んで js file を出力する. 今回は jsx を js へ変換するのに使うが, 例えば ECMA 2015 で書かれたコードを読み込んで ECMA 2011 互換のコードに変換したりするのにも使える.

Babel が用意している presets の中に preset-react があり, これを使用すると jsx も処理できるようになる.

https://babeljs.io/docs/en/babel-preset-react

Babel 6 から 7 へ上がるときに色々と仕様変更があったらしい.

#### polyfill って？
有志が作成した babel から使える小技集みたいなもの？正直恩恵がまだ理解できていない. 以下の記事を参考にさせていただいた.

- babel@6 : https://aloerina01.github.io/blog/2018-03-19-1
- babel@7 : https://aloerina01.github.io/blog/2018-11-29-1

## `Uncaught ReferenceError: require is not defined` への対処
Babel の吐いたコードをブラウザ上で動かそうとするとこのエラーが出る. しかし babel での require の処理方法がわからなかった. ので browserify を使う.

https://github.com/browserify/browserify-handbook

## 変換の流れ

- 自分が書いたコード
- --> babel
- --> browserify
- --> addon で読み込むコード

という感じでやっていく.

#### 事前準備
プロジェクトは npm init などで作成しておく.
必要なライブラリを npm でインストール.

```
$ npm init
省略
$ npm install --save-dev "@babel/cli" "@babel/core" "@babel/polyfill" "@babel/preset-env" "@babel/preset-react" "browserify"
$ npm --save react
```

<details>
<summary>今回利用したバージョン</summary>
<div>

```json:package.json
{
  "devDependencies": {
    "@babel/cli": "^7.8.4",
    "@babel/core": "^7.9.0",
    "@babel/polyfill": "^7.8.7",
    "@babel/preset-env": "^7.9.5",
    "@babel/preset-react": "^7.9.4",
    "browserify": "^16.5.1"
  },
  "dependencies": {
    "react": "^16.13.1"
  }
}
```
</div>
</details>

#### 変換元のコードを記述
詳細は省くが, 今回は最終的に以下の２つのコードが得られるようにする

- popup-script.js
  - アドオンメニューのクリックなどで開く画面（html）上で動くスクリプト
  - 変換前は popup.jsx と popup-script.js の２ファイルで構成
  - DOM 要素の生成に react を利用. jsx の先頭で react を import しておく
- background-script.js
  - addon の background 処理を実施するスクリプト（ブラウザロード時に勝手に呼ばれる）
  - こちらは react を使用しないが対比のために置いておく

変換元のコードは以下３つ.

<details>
<summary>popup.jsx. React で DOM 要素生成するモジュール</summary>
<div>

```jsx:popup.jsx
import * as React from "react";

export function popup() {
  // Display a "Like" <button>
  return <div>Yeahooooooooooo!!!!!!!!!!!!!!!!!!!!!!!11111</div>;
}
```
</div>
</details>

<details>
<summary>popup-script.js. popup.jsx で部品が生成されることを確認</summary>
<div>

```js:popup-script.js
import * as popup from "./popup";

console.log(JSON.stringify(popup.popup(), null, 1));
```
</div>
</summary>

<details>
<summary>background-script.js. コードが読み込まれたことを確認.</summary>
<div>

```js:background-script.js
console.log("Load background-script.js");
```
</div></details>

構成は次のようにした.

```:bash
$ tree ./babel_src
./babel_src
├── background-script.js
└── popup
    ├── popup-script.js
    └── popup.jsx
```

#### 変換処理

- まず babel で環境ごとの互換性が解決されたコードを出力する
  - これにより react のコード (jsx) が js へ変換される
  - popup-script.js 内の `import` が `require` へと置換される

<details>
<summary>追記：先に .babelrc というファイルを作成し preset-react を設定する必要がある.
</summary>
<div>

```json:.babelrc
{
    "presets": [
        [
            "@babel/preset-react"
        ],
        [
            "@babel/preset-env"
        ]
    ]
}
```
</div>
</details>

`.babelrc` を作成したら次のようにする.

```:bash
$ babel ./babel_src -d ./babel_dst
$ tree ./babel_dst
./babel_dst
├── background-script.js
└── popup
    ├── popup-script.js
    └── popup.js
```

- Browserify で `require` (browser が解釈できない命令) を処理
  - Browserify 単独ではディレクトリ単位の処理ができないっぽいので事前にディレクトリを作成
  - これにより require が取り除かれ popup.js がインライン展開された popup-script.js が生成される

```:bash
$ rm -rf ./browse_dst && mkdir -p ./browse_dst/popup
$ browserify ./babel_dst/popup/*.js > ./browse_dst/popup/popup-script.js
$ browserify ./babel_dst/*.js > ./browse_dst/background-script.js
$ tree ./browse_dst
./browse_dst
├── background-script.js
└── popup
    └── popup-script.js
```

#### 動作確認
詳細は省くが, firefox であれば `about:debugging#/runtime/this-firefox` を開くと自分で作成したアドオンをテストすることができる.

今回は popup-script.js が吐き出した以下のログを確認することができた.

<details>
<div>

```:console
{
 "type": "div",
 "key": null,
 "ref": null,
 "props": {
  "children": "Yeahooooooooooo!!!!!!!!!!!!!!!!!!!!!!!11111"
 },
 "_owner": null,
 "_store": {}
} popup-script.js:63:11
```
</div>
</details>

#### innerHTML
Browserify が吐き出すコードには innerHTML 属性に対する変換コードが含まれている. innerHTML の利用は security 上問題になるということで, Firefox Add-ons への登録時に警告が出てしまい, 登録に失敗する.

対処法として, 今回の場合, 実際には innerHTML 属性への代入処理など使っていないので, 今は以下のスクリプトで browserify の吐き出すコードに置換処理をかけ, 誤魔化し誤魔化し使っている状態..

```bash:sed
sed -i -e 's|\([a-zA-Z\.]*\.innerHTML[[:blank:]]*=\)|/*\1*/|g' ./browse_dst/background-script.js
```

他の開発者はどうしているのだろう.

## TBD: webpack
js をコンパクトにするもの.
pack 後のコードは普通の人間には読めない（source map ファイルを使えば戻せるっぽい）. ゆくゆくは必要になるが最初から入れると理解が追いつかず混乱を招くので今回は見送り.

追記：どうも webpack は browserify の代替としても使われているらしい（2016年あたりから）. 頑張って webpack で実現すべきなのだろうが、設定ファイルが少々複雑なのでまずは CLI で順番に変換を試すのが良さそう.

<details>
<summary>上記の browserify で行っていた処理を webpack-cli (ver4) で実施すると例えばこのようになる.</summary>
<div>

```bash:webpack-cli
$ webpack-cli ./babel_dst/*.js --output ./browse_dst/background-script.js
$ webpack-cli ./babel_dst/popup/*.js --output ./browse_dst/popup/popup-script.js
```
</div>
</details>

<details>
<summary>おまけ：css import したい場合（`--module-bind` の順序に注意）.</summary>
<div>

```bash:webpack-cli
$ webpack-cli ./babel_dst/*.js --module-bind css=style-loader --module-bind css=css-loader --output ./browse_dst/background-script.js
$ webpack-cli ./babel_dst/popup/*.js --module-bind css=style-loader --module-bind css=css-loader --output ./browse_dst/popup/popup-script.js
```
</div>
</details>

webpack を利用する場合でも上記の innerHTML の問題は発生する（sed での対応も難しくなる）.

参考：

- [Browserifyユーザーがwebpackを覚え始めるには](https://qiita.com/nacam403/items/6ed71e4356ff156f4009)
- [BrowserifyからWebpackへの移行時の注意点](https://qiita.com/derui/items/5233b4ef8fbde1e80d33)

## TBD: babelify
https://github.com/babel/babelify

何をしてくれるのか理解できていない.
browserify から babel を呼ぶのだと思われるが..