KDE BLOG

Webデザインやコーディングについて書いています

【webpack速習】vol.7: Production

下記ページの要点をまとめていきます。

webpack.js.org

本番サイトまたはアプリケーションを構築するためのベストプラクティスとユーティリティについて説明。

セットアップ

開発用のビルドと本番用のビルドの目標は大きく異なっている。 - 開発用では、強力なソースマッピング、ライブリロードもしくはHMRを伴う開発用localhostサーバが必要 - 本番用では読み込み時間を短縮させるためにバンドルファイルの最小化、軽量のソースマップ、最適化されたアセットが必要

そのため通常は開発用と本番用の別々のwebパック構成を作成するのが望ましい。
固有部分に関しては切り離すが、共通箇所についてはDRY原則にのっとって、共通設定を維持するのが良い。
これらの共通設定と固有設定をマージするために webpack-merge というユーティリティのプラグインを使用する。
共通構成が整っていれば環境固有の構成内でコードを複製する必要がない。

インストール

yarn add webpack-merge --dev

ディレクトリ構成

project
  |- package.json
- |- webpack.config.js
+ |- webpack.common.js
+ |- webpack.dev.js
+ |- webpack.prod.js
  |- /dist
  |- /src
    |- index.js
    |- math.js
  |- /node_modules

▼ webpack.common.js

const path = require('path');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

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

▼ webpack.dev.js

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

module.exports = merge(common, {
  mode: 'development',
  devtool: 'eval-source-map',
  devServer: {
    contentBase: './dist'
  },
});

▼ webpack.prod.js

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

module.exports = merge(common, {
  mode: 'production',
});

webpack.common.js で エントリーポイントとアウトプットの設定をし、開発、本番用の両方で必要なプラグインを含めている。

webpack.dev.js で modeを development にし、その環境に推奨されるソースマップ、シンプルなdevServer 構成を追加。

webpack.prod.js で mode は Tree shakingで必要な TerserPlugin をロードする production に設定している。

npm scripts

npm scriptを新しい構成に再設定する。

▼ package.json

{
  // 略
  "scripts": {
-    "start": "webpack-dev-server --open",
+    "start": "webpack-dev-server --open --config webpack.dev.js",
-    "build": "webpack"
+    "build": "webpack --config webpack.prod.js"
  },

それぞれのコマンドを実行し、意図した結果になっていればOK。

webpack.common.js で設定済みのプロパティに、webpack.prod.js あるいは webpack.dev.js で追加するには

例えば本番ビルド時だけに使いたい plugin を追加する場合、下記のように特に何も考えずにそのまま plugin プロパティに記述するだけで、webpack-merge がうまくマージしてくれる。

▼ webpack.common.js

module.exports = {
  // 略
  plugins: [
    new CleanWebpackPlugin('dist'),
    new HtmlWebpackPlugin({
      title: 'Production'
    }),
  ],
}

▼ webpack.dev.js

module.exports = {
  // 略
  plugins: [
    new webpack.DefinePlugin({
      hoge: JSON.stringify('hogehoge')
    })
  ]
}

Specify the Mode(モード指定)

多くのライブラリは process.env.NODE_ENV 変数をキーオフしてライブラリに何を含めるべきかを決定する(たとえば、本番環境ではないときには、デバッグを容易にするためにロギングやテストを追加するライブラリなど)。

ただし、process.env.NODE_ENV === 'production' を使用すると、実際のユーザーの動作を最適化するためにコードのかなりの部分を削除または追加する可能性がある。
webpack v4以降、modeを指定すると DefinePlugin が自動的に設定される。

厳密には、NODE_ENVはNode.jsが実行中のスクリプトに公開するシステム環境変数。これは、慣習的に、サーバーツール、ビルドスクリプト、およびクライアントサイドライブラリによってdev vs prodの動作を決定するために使用いる。
予想に反して、process.env.NODE_ENV はビルドスクリプトwebpack.config.js 内で production に設定されていない

この process.env.NODE_ENV 変数は、ローカルの /src コード内で使えるので、下記のようなコードは有効。

▼ /src/index.js

import { cube } from './math.js';

+ if (process.env.NODE_ENV !== 'production') {
+   console.log('Looks like we are in development mode!');
+ }

function component() {
 // 略

Minification(圧縮)

webpack v4 +はデフォルトで production モードでコードを圧縮する。

TerserPlugin は圧縮化にはじまり、デフォルトで使用されるのに良いプラグインだが、他にも人気なものがある。

  • BabelMinifyWebpackPlugin
  • ClosureCompilerPlugin

別のプラグインを試す場合、tree shaking が機能するか確認して、optimize.minimizer に指定する。

Source Mapping(ソースマッピング

ソースマップは、ベンチマークテストの実行だけでなくデバッグにも役立つので、本番環境で有効にしておくことを推奨している。
とは言っても、本番環境での使用にはかなり早いビルド速度のものを選ぶべき。
このガイドでは、開発で使用した inline-source-map とは対照的に、production では source-map を使用(別ファイルで **.map ファイルが生成される)。

※バンドルサイズが大きくなり、全体的なパフォーマンスが低下する可能性があるため、production での inline-xxx および eval-xxx の使用は避ける。

Minimize CSSCSSの圧縮)

本番環境でCSSを最小化することは非常に重要。
実運用環境での最小化のセクションを参照
MiniCssExtractPlugin | webpack

CLI Alternatives(CLIの選択肢)

上記で説明したもののいくつかは、コマンドラインからも実行できる。
たとえば、--optimize-minimize フラグは、裏で TerserPlugin を含む。
--define process.env.NODE_ENV="production"は、上記のDefinePlugin インスタンスに対しても同じことを行う。
そして、webpack -pprocess.env.NODE_ENV"production" に設定する。

CLIで行うのは簡単ではあるが、通常は config ファイル上で行うのが理解しやすいのでベター。