webpackの本番用設定を考えてみる

## はじめに
webpackを本格導入するために本番環境用の設定を考えてみることにします。
今回以下の観点から本番用設定をしていくことにします。

* 本番用設定と開発環境用設定を分ける
* bundle時にminifyをする
* bundle前に出力先ディレクトリをcleanする
* digest化をする
* bundleされるファイルに自動対応する(htmlのファイルを自動生成)


ソースは[こちら](https://qiita.com/sugamondo/items/7c7d54c92c0c37e2337a)を元にしています。
また、npmの代わりにyarnを使用しています。


## 本番用設定と開発環境用設定を分ける
本番と開発で分けると言っても共通部分は少なからずあるはずです。なので以下のような構成を考えます。

* webpack.common.js
* webpack.dev.js
* webpack.prd.js

それぞれどの環境で使うことを想定しているかと言いますと

| ファイル名 | 環境 |
|:--- |:--- |
| webpack.common.js | 全環境共通 |
| webpack.dev.js | local, development |
| webpack.prd.js | staging, production |

です。

それでは、実際に環境別のファイルを用意してそれぞれ出し分けていくのですが、`webpack-merge`というプラグインが必要になります。

ざっくり言いますと、webpackの設定を結合してくれるプラグインです。
今回の用途で言いますと、`webpack.common.js`と`webpack.dev.js`の設定を`webpack.common.js`と`webpack.prd.js`の設定を結合します。

※webpackをインストールするところは省略しますので、インストールして以下のような状態にした上で実行してください。

```package.json
{
  "name": "webpack-demo",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "build": "webpack"
  },
  "devDependencies": {
    "webpack": "^3.5.6"
  }
}
```

ではではプラグインをインストールします。
※もう一つ必要なプラグインがあるのでそれも追加しておきます。

```bash
$ yarn add --dev webpack-merge yarn-run-all
```

それぞれのファイルを追加するのですが、今回は[公式のチュートリアル](https://webpack.js.org/guides/production/)を参考にします。

```webpack.common.js
const path = require('path');

module.exports = {
  entry: {
    app: './src/index.js'
  },
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}
```

```webpack.dev.js
const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
  devtool: 'inline-source-map'
});
```

```webpack.prd.js
const merge = require('webpack-merge');
const common = require('./webpack.common.js');

module.exports = merge(common, {
});
```


これで環境別のファイルを用意できましたので、次にyarnのコマンド実行で本番環境であれば`webpack.prd.js`、開発環境であれば`webpack.dev.js`を実行するように設定を追加していきます。


```package.json
{
  "name": "webpack-demo",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "scripts": {
    "webpack:development": "webpack --config ./webpack.dev.js --display-error-details",
    "webpack:production": "webpack --config ./webpack.prd.js --display-error-details",
    "development": "yarn run-p webpack:development",
    "production": "yarn run-p webpack:production"
  },
  "devDependencies": {
    "webpack": "^3.5.6",
    "webpack-merge": "^4.1.0",
    "yarn-run-all": "^3.1.1"
  }
}
```

これで準備完了です。

本来であればこれをjenkinsなどのCIツールなどから環境ごとのコマンドを実行したり、Dockerのコンテナ起動時のcommandなどにセットしておいて環境の出しわけを行うのですが、今回はそこまでやらないので単にコマンドを実行していくとします。

それではまずはdevから

```bash
$ yarn run development
yarn run v1.0.1
$ yarn run-p webpack:development

> webpack-demo@1.0.0 webpack:development /xxx/xxx/xxx/webpack-demo
> webpack --config ./webpack.dev.js --display-error-details

Hash: 856e4f5bf56c21d8a61d
Version: webpack 3.6.0
Time: 91ms
        Asset     Size  Chunks             Chunk Names
app.bundle.js  7.82 kB       0  [emitted]  app
   [0] ./src/index.js 320 bytes {0} [built]
   [1] ./src/math.js 95 bytes {0} [built]
✨  Done in 2.38s.
```

package.jsonに追加したdev用scriptの`webpack:development`が実行されているのがわかります。その中で、`webpack --config ./webpack.dev.js`にあるようにdev用設定ファイルが使われています。

本番用のscriptも実行してみます。

```bash
$ yarn run production
yarn run v1.0.1
$ yarn run-p webpack:production

> webpack-demo@1.0.0 webpack:production /xxx/xxx/xxx/webpack-demo
> webpack --config ./webpack.prd.js --display-error-details

Hash: 856e4f5bf56c21d8a61d
Version: webpack 3.6.0
Time: 75ms
        Asset     Size  Chunks             Chunk Names
app.bundle.js  3.31 kB       0  [emitted]  app
   [0] ./src/index.js 320 bytes {0} [built]
   [1] ./src/math.js 95 bytes {0} [built]
✨  Done in 1.96s.
```

ちゃんと本番用のscriptが実行され、設定ファイルが使用されていることがわかります。

これで環境ごとの出しわけができました。local、development、stagingと細かくわけたい場合もこの応用でできます。

ちなみに最初に`yarn-run-all`というプラグインをインストールしましたが、これは複数のコマンドを並列に実行するプラグインです。package.jsonの中で`yarn run-p`と実行していますが、こレはこのプラグインが入っていないと実行できません。

一つのコマンドを実行する分には不要ですが、cleanしたりあれもこれも・・・となってくることを想定して入れておくと便利です。


## bundle時にminifyをする
bundleされる一つ一つのファイルサイズを小さくするために圧縮します。webpackなら簡単にできます。今回は本番環境のみで圧縮してみることとします。

`uglifyjs-webpack-plugin`をインストールしましょう。

```bash
$ yarn add --dev uglifyjs-webpack-plugin
```

次に`webpack.prd.js`を変更します。

```webpack.prd.js
const merge = require('webpack-merge');
consut UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const common = require('./webpack.common.js');

module.exports = merge(common, {
  plugins: [
    new UglifyJSPlugin()
  ]
});
```

そうしましたら`yarn run production`で本番用ビルドをします。

```bash
$ yarn run production
yarn run v1.0.1
$ yarn run-p webpack:production

> webpack-demo@1.0.0 webpack:production /xxx/xxx/xxx/webpack-demo
> webpack --config ./webpack.prd.js --display-error-details

Hash: 856e4f5bf56c21d8a61d
Version: webpack 3.6.0
Time: 143ms
        Asset       Size  Chunks             Chunk Names
app.bundle.js  784 bytes       0  [emitted]  app
   [0] ./src/index.js 320 bytes {0} [built]
   [1] ./src/math.js 95 bytes {0} [built]
✨  Done in 2.11s.
```

これで圧縮についてはやらなければいけないことは以上で、実際に出力された`dist/app.bundle.js`が圧縮されていればオーケーです。


## bundle前に出力先ディレクトリをcleanする
今まで使っていたファイルが不要になったりしてした場合、bundle先のディレクトリにずっと残ったままになってしまいます。それをビルドのたびに綺麗にすることがこの項のゴールになります。

この処理については本番も開発も関係なく出力先のディレクトリを綺麗にしたいので共通に入れます。

ではではプラグインを追加します。

```bash
$ yarn add --dev clean-webpack-plugin
```

次に共通ファイルに設定を追加していきます。

```webpack.common.js
const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');

module.exports = {
  entry: {
    app: './src/index.js'
  },
  plugins:[
    new CleanWebpackPlugin(['dist'])
  ],
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
}
```

共通処理なので本番、開発それぞれ実行して動作確認してみます。

```bash
# 開発
$ yarn run development
yarn run v1.0.1
$ yarn run-p webpack:development

> webpack-demo@1.0.0 webpack:development /xxx/xxx/xxx/webpack-demo
> webpack --config ./webpack.dev.js --display-error-details

clean-webpack-plugin: /xxx/xxx/xxx/webpack-demo/dist has been removed.
Hash: 856e4f5bf56c21d8a61d
Version: webpack 3.6.0
Time: 131ms
        Asset     Size  Chunks             Chunk Names
app.bundle.js  7.82 kB       0  [emitted]  app
   [0] ./src/index.js 320 bytes {0} [built]
   [1] ./src/math.js 95 bytes {0} [built]
✨  Done in 2.08s.

# 本番
yarn run production
yarn run v1.0.1
$ yarn run-p webpack:production

> webpack-demo@1.0.0 webpack:production /xxx/xxx/xxx/webpack-demo
> webpack --config ./webpack.prd.js --display-error-details

clean-webpack-plugin: /xxx/xxx/xxx/webpack-demo/dist has been removed.
Hash: 856e4f5bf56c21d8a61d
Version: webpack 3.6.0
Time: 214ms
        Asset       Size  Chunks             Chunk Names
app.bundle.js  784 bytes       0  [emitted]  app
   [0] ./src/index.js 320 bytes {0} [built]
   [1] ./src/math.js 95 bytes {0} [built
```

無事それぞれビルド実行できました。
試しに`dist`ディレクトリに適当なファイルを作成して実行してみると、そのファイルは削除されていました。


## digest化をする & bundleされるファイルに自動対応する
これも本番環境で運用する時は必要ですね。

digest化するだけなら本当に簡単ですが、htmlファイルなどからbundleされたファイルを読み込んでいる箇所をケアしないといけません。

これは以下の要件で追加しようと思います。

| 環境| 要件|
|:--- |:--- |
| 開発環境| htmlファイルの自動生成のみ|
| 本番環境| digest化 & htmlファイルの自動生成 |


それではまずはプラグインを入れていきます。

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

開発環境用の設定を追加していきます。

```webpack.dev.js
const common = require('./webpack.common.js');
const merge = require('webpack-merge');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = merge(common, {
  devtool: 'inline-source-map',
  plugins: [
    new HtmlWebpackPlugin({
      title: 'development'
    }),
  ]
});
```

本番環境用の設定です。

```webpack.prd.js
const path = require('path');
const common = require('./webpack.common.js');
const merge = require('webpack-merge');
const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = merge(common, {
  plugins: [
    new UglifyJSPlugin(),
    new HtmlWebpackPlugin({
      title: 'production'
    }),
  ],
  output: {
    filename: '[name]-[hash].bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
});
```

これを実行すると本番、開発環境ともに`dist/index.html`がビルドのたびにbundle結果に合わせて自動で生成されることと、本番環境のみbundle結果のファイル名がdigest化されていることがわかります。

`dist/index.html`についてはそれぞれのビルドのタイミングで自動生成されていること、
中身をみて本番であればhash付きのファイルが参照されていることをみてみてください。


## おわりに
webpackの本番運用のための設定を考えてきました。DBの設定ファイルみたく環境毎に分けることが簡単にできることが分かりました。

開発環境はゴリゴリに開発効率重視した設定、本番はファイルサイズなどパフォーマンス重視など、それぞれプロジェクトにあった設定ができそうです。