webpack にまつわるぐだぐだ。キャッシュ対策と CommonsChunkPlugin

webpack (version 2) の公式ドキュメントではバンドルファイルのブラウザキャッシュ対策として、ファイル名にハッシュ値を埋め込む方法が提案されている

こうするとアプリのコードを変更する度に、バンドルファイル名に埋め込まれたハッシュ値も更新され、ブラウザキャッシュ対策ができる、という寸法だが…。
当然ファイル名が変わるわけだから、<sript> タグも更新しなくてはならない。それを解決するには、HTML ファイルも動的に扱わなければならない。chunk-manifest-webpack-pluginhtml-webpack-plugin を導入し、HTML のテンプレーティングを行い…、となると今使っている Pug はどうなる…、となってさらにさらに掘り下げないといけなくなり、はっきり言ってソリューションとして無理がある。

閑話休題。

webpack をヘビーに使っていると、サードパーティーフレームワークやライブラリのコード(npm install でインストールしたようなモジュール)を出力ファイル(バンドルファイル)から切り離したくなる。 バンドルファイルにライブラリが直接含まれるが故に、以下のような事象に直面したことがある開発者は多いのではないだろうか。

  • 複数人で開発していて各自のライブラリのバージョンが微妙に異なり、Git経由でやり取りされるバンドルファイルに環境の差分までが含まれてしまう
  • 使っているライブラリが増えて、コンパイルがとても遅くなる

解決策として、フレームワークやライブラリはまとめて別のバンドルファイルにする、もしくは単独で配布されたものを <script> タグを読み込む、という解決方法を取ることもできる。 これを可能にするのが externals オプションで、ここで宣言された名前については、依存関係がグローバルスコープから参照されるようになる。

externals: {
  jquery: 'jQuery'
}

webpack.config.js に設定しておくと、 import jQuery from 'jquery'; というコードはグローバルスコープの jQuery を参照するようになり、バンドルファイルに jQuery のコードは含まれないようになる。 もちろんこの場合には jQuery は新たに別の <script> タグで読み込む必要ある。

よりスマートな解決策としての CommonsChunkPlugin

CommonsChunkPlugin を使うと、複数のバンドルファイルから共通の依存モジュールを切り出してくれる。 サードパーティーライブラリをアプリのコードと分割する、という使い方ももちろんできる。 公式で示されている設定は以下のようなものだ。

plugins: [
  new webpack.optimize.CommonsChunkPlugin({
    name: "vendor",
    minChunks: function(module){
      // node_modules のディレクトリ下に含まれるものだけに絞る
      return module.context && module.context.indexOf("node_modules") !== -1;
    }
  }),
  new webpack.optimize.CommonsChunkPlugin({
    name: "manifest", // 名前は "manifest" でないとダメ
    minChunks: Infinity // entryがいくつあろうと生成。省略可
  }),
]

サードパーティーコードを別にしたいだけなら vendor だけでも目的は果たせるが、manifest を入れると、webpack バンドル共通部分(ソースには webpackBootstrap とコメントが書かれている部分)が manifest.js として別ファイルに書き出される。こうするとビルドするたびに vendor のハッシュ値が変更されることを防げるとのこと。
でもこれ、先述のハッシュ値をファイル名に含めるキャッシュ対策をやっていないと、意味はなさそう。なぜならハッシュ値はバンドルファイルのコード内には現れないから。

manifest を定義した場合、以下のように manifest.js も読み込まないとコードは動かないので注意。

<script src="/js/manifest.js"></script>
<script src="/js/vendor.js"></script>
<script src="/js/bundle.js"></script>

References