Cordova に webpack を真に統合するプラグイン「cordova-plugin-webpack」を作った

## はじめに

Cordova ユーザーの皆さん！以下の記事をご覧になったでしょうか？
「Cordova つらい」が言語化されており、それに対しての対応についてが記載されている良記事です。
わかりみが深い、大変共感できる内容のため是非一読することをお勧めします。

[Cordovaをdisる人類全員に読んでほしい「Cordovaつらいを考える」｜榊原昌彦｜note](https://note.mu/rdlabo/n/n1e8e71228e21)

> 6. プラグインのコールバック地獄

の対応が、[以前に筆者が投稿した記事](https://qiita.com/kotarella1110/items/bfb7972d25676f784c3d)と関連がありますので是非ご覧ください。

上に記載されている内容以外にも、まだ個人的に「Cordova つらい」と感じる部分があります。
詳細は以下に記載します。

## Cordova つらい

個人的に「Cordova つらい」と感じる点は以下の2点です。

1. Cordova のワークフローに webpack が統合されていない
2. LiveReload による開発が出来ないため開発スピードが遅い

### Cordova のワークフローに webpack が統合されていない

シングルページアプリケーション（以下、SPA）の開発をしている方の殆どが、モジュールバンドラーを使用していると思います。
モジュールバンドラーの中でも webpack を使用している方が殆どでしょう。

Cordova で webpack を使用する場合、webpack のビルドで SPA を `www` ディレクトリへ展開した後、Cordova のビルドで各プラットフォームへ展開するといった、2ステップの作業が必要になります。
npm-scripts 等のタスクランナーでこの2ステップの作業を定義していれば、それほど面倒と感じることはありません。

しかし、もし Cordova のビルドの過程に webpack のビルドが組み込まれていたら、Cordova のビルドのみで作業が完結します。

### LiveReload による開発が出来ないため開発スピードが遅い

Cordova で Android と iOS の開発する際、どのように動作確認をしていますか？
アプリケーションを修正して、ビルドして、デバイスやエミュレーターで実行して…動作確認をしていますか？
わざわざビルドしなくてもアプリケーションに変更した内容を即座に確認したい、いわゆる LiveReload による開発がしたいとは思いませんか？

筆者はこれまで、LiveReload による開発をするために [cordova-plugin-browsersync](https://github.com/nparashuram/cordova-plugin-browsersync "nparashuram/cordova-plugin-browsersync: BrowserSync Plugin for Cordova") を利用していました。
しかし、このプラグインはアーカイブされ、もうメンテナンスされていません…。
また、このプラグインの作り上、アプリケーションを修正し保存してから変更した内容が反映されるまで若干時間がかかります。

余談にはなりますが、Ionic では標準機能として LiveReload をサポートしています 泣

### cordova-plugin-webpack のすヽめ

上に記載した「Cordova つらい」を解決するために、Cordova に webpack を真に統合するプラグイン「[cordova-plugin-webpack](https://github.com/kotarella1110/cordova-plugin-webpack "kotarella1110/cordova-plugin-webpack: Integrate webpack into your Cordova workflow.")」を作りました。
是非、このプラグインをインストールして使用してみてください。

## cordova-plugin-webpack について

<img src="https://github.com/kotarella1110/cordova-plugin-webpack/raw/master/media/logo/logo.png?raw=true" width="200">

以下は、[`README.md`](https://github.com/kotarella1110/cordova-plugin-webpack/blob/master/README.md "cordova-plugin-webpack/README.md at master · kotarella1110/cordova-plugin-webpack") の内容に基づいて記載しています。

### 動機

webpack を始めとしたモジュールバンドラーは、シングルページアプリケーションの開発に必要不可欠です。
しかし、残念なことに、モジュールバンドラーは Cordova の標準機能としてワークフローに統合されていません。

このプラグインをインストールするだけで、[webpack](https://webpack.js.org "webpack") を Cordova ワークフローに簡単に統合できます。

### デモ

デモについては、[こちら](https://github.com/kotarella1110/cordova-plugin-webpack#demo) をご覧下さい。

### 特徴

* Corova アプリケーションのビルドの過程に webpack のビルドが組み込まれます
* LiveReload ([Hot Module Replacement](https://webpack.js.org/concepts/hot-module-replacement "Hot Module Replacement | webpack")) による開発が可能で、デバイスやエミュレータ上のアプリケーションに変更した内容が即座に反映されます

### インストール

```shell
$ cordova plugin add cordova-plugin-webpack
```

### CLI リファレンス

#### 構文

```shell
$ cordova { prepare | platform add | build | run } [<platform> [...]]
    [-- [--webpack.<option> <value> | --livereload]]
```

| オプション | 説明 | デフォルト | エイリアス |
|---|---|---|---|
| `--webpack.<option>` | 指定したオプションは [webpack-cli のオプション](https://webpack.js.org/api/cli/)または [webpack-dev-server のオプション](https://webpack.js.org/configuration/dev-server/)へ渡されます。 例: `--webpack.config example.config.js` <br> **注意: [Stats オプション](https://webpack.js.org/api/cli/#stats-options)や [Watch オプション](https://webpack.js.org/api/cli/#watch-options) などのいくつかのオプションはまだサポートしていません。** | | `-w` |
| `--livereload` | LiveReload (HMR) を有効にします。 | `false` | `-l` |

### Examples

#### Build

Cordova の `before_prepare` フックで webpack のビルドを実行します。

```shell
$ cordova prepare
$ cordova build -- --webpack.config path/to/dir/webpack.config.js
$ cordova build android -- --webpack.mode=production
$ cordova build ios -- --webpack.env.prod
```

#### Live Reload (HMR)

Cordova の `after_prepare` フックで Webpack Dev Server を起動し、デバイスやエミュレータから接続できるようにします。

```shell
$ cordova prepare -- --livereload
$ cordova run ios -- -w.config path/to/dir/webpack.config.babel.js -l
$ cordova run android -- --livereload --webpack.port=8888 --webpack.watch-content-base=false
```

### 使い方

1. [このプラグインを追加します。](#インストール)

2. プロジェクトルートのディレクトリに webpack の設定ファイル（`webpack.config.js`）を作成します。

    ```js
    const path = require('path');

    module.exports = {
      mode: 'development',
      entry: './src/index.js',
      output: {
        path: path.resolve(__dirname, 'www'),
        filename: 'index.bundle.js',
      },
      devtool: 'inline-source-map',
    };
    ```

3. CLI を実行します

    ```shell
    $ cordova build
    $ cordova run -- --livereload
    ```

<details><summary>一から Cordova アプリケーションを作成する場合はこちら</summary><div>

1. Cordova アプリケーションを作成します。

    ```shell
    $ cordova create cordova-plugin-webpack-example cordova.plugin.webpack.example CordovaPluginWebpackExample
    ```

2. プラットフォームを追加します。

    ```shell
    $ cd cordova-plugin-webpack-example
    $ cordova platform add android ios
    ```

3. [このプラグインを追加します。](#インストール)

4. JavaScript ファイル（[エントリーポイント](https://webpack.js.org/concepts/entry-points/ "entry points")）を作成します。

    ```shell
    $ mkdir src
    $ mv www/js/index.js src/index.js
    ```

5. プロジェクトルートのディレクトリに webpack の設定ファイル（`webpack.config.js`）を作成します。

    ```js
    const path = require('path');

    module.exports = {
      mode: 'development',
      entry: './src/index.js',
      output: {
        path: path.resolve(__dirname, 'www'),
        filename: 'index.bundle.js',
      },
      devtool: 'inline-source-map',
    };
    ```
6. HTML ファイル（`www/index.html`）を修正します。

    ```diff
    -         <script type="text/javascript" src="js/index.js"></script>
    +         <script type="text/javascript" src="index.bundle.js"></script>
    ```

7. CLI を実行します。

    ```shell
    $ cordova build
    $ cordova run -- --livereload
    ```

</div></details>

---

**注意**

Android 9（APIレベル28）以降、HTTP 通信を許可する設定はデフォルトで無効になっています。
そのため、Android 9 以上のデバイス及びエミュレータでは LiveReload が機能しません。

v0.3.1 までは[プラグインの方で自動的に HTTP 通信を許可する設定を有効](https://github.com/kotarella1110/cordova-plugin-webpack/blob/v0.3.1/plugin.xml#L9-L13 "cordova-plugin-webpack/plugin.xml at v0.3.1 · kotarella1110/cordova-plugin-webpack")にしていましたが、

* `config.xml` で `AndroidManifest.xml` の `application` 要素を変更しようとするとコンフリクトが発生する
* HTTP 通信を許可する設定はユーザー側で定義出来た方が良い

という理由からプラグインの方で自動的に有効にするのをやめました。
詳しくは、[こちらの Issue](https://github.com/kotarella1110/cordova-plugin-webpack/issues/9#issuecomment-495048614 "<edit-config> changes in this plugin conflicts with <edit-config> changes in config.xml · Issue #9 · kotarella1110/cordova-plugin-webpack") をご覧下さい。

Android 9.0 以上のデバイス及びエミュレータで LiveReload を機能させたい場合は、 `config.xml` を以下のように修正する必要があります。

1. `widget` ルート要素に `xmlns:android="http://schemas.android.com/apk/res/android"` を追加します。

    ```xml
    <widget id="cordova.plugin.webpack.example" version="1.0.0" xmlns="http://www.w3.org/ns/widgets" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:cdv="http://cordova.apache.org/ns/1.0">
    ```

2. `AndroidManifeet.xml` の `application` 要素に `android:usesCleartextTraffic` が有効になるように `edit-config` 要素を定義します。

    ```xml
    <platform name="android">
        <edit-config file="AndroidManifest.xml" mode="merge" target="/manifest/application">
            <application android:usesCleartextTraffic="true" />
        </edit-config>
    </platform>
    ```

---

### webpack の設定をカスタマイズ

基本的に、あなたが作成した webpack の設定ファイルに従って動作します。
一部オプションはこのプラグインによるデフォルト値が設定されています。
このプラグインのデフォルト値を上書きしたい場合は、`webpack.config.js` を自由に修正して下さい。

```js
...
module.exports = {
  ...
  mode: 'production',
  entry: './src/index.js',
  output: {
    path: path.resolve(__dirname, 'www'),
    filename: 'bundle.js',
  },
  plugins: [
    new HtmlWebpackPlugin(),
  ],
  ...
  devServer: {
    contentBase: path.join(__dirname, 'public'),
    host: 'localhost',
    port: '8000',
    hot: false,
  },
  ...
};
```

| オプション | デフォルト |
|---|---|
| `devServer.contentBase`  | `www` |
| `devServer.historyApiFallBack` | `true` |
| `devServer.host` | `0.0.0.0` |
| `devServer.port` | `8080` |
| `devServer.watchContentBase` | `true` |
| `devServer.hot` | `true` |

例えば、モジュールの更新（HMR）の代わりにページ全体の更新（LiveReload）が必要な場合は、 `devServer.hot` オプションを `false` に指定します。

```js
...
module.exports = {
  ...
  devServer: {
    hot: false,
  },
  ...
};
```

## さいごに

「Cordova つらい」と感じているユーザーが、このプラグインによって「Cordova 最高」と感じることを願ってます。