【webpack速習】vol.10: Caching
下記ページをざっくりまとめます。
- ブラウザはキャッシュにより、不要なトラフィックを避けサイトの読み込みが高速になることがあるが新しいコードを取得するに困ることがある
- 本ページでは、内容が変更されていない限り、Webpackのコンパイルによって生成されたファイルがキャッシュされたままになることを保証するために必要な設定に焦点を当てる
Output Filenames
output.filename
の置換設定を使うことで出力ファイルの名前を定義できる- webpackは、置換と呼ばれる
[ ]
で囲まれた文字列を使ってファイル名をテンプレート化する方法を提供している [contenthash]
置換は、アセットのコンテンツに基づいて一意のハッシュを追加する- アセットのコンテンツが変わると
[contenthash]
も変わる
- アセットのコンテンツが変わると
実際に試してみる。
▼ ディレクトリ構成
project |- package.json |- webpack.config.js |- /dist |- /src |- index.js |- /node_modules
▼ webpack.config.js
const path = require('path'); const CleanWebpackPlugin = require('clean-webpack-plugin'); const HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { entry: './src/index.js', plugins: [ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ - title: 'Output Management' + title: 'Caching' }) ], output: { - filename: 'bundle.js', + filename: '[name].[contenthash].js', path: path.resolve(__dirname, 'dist') } };
これでビルドを実行すると下記のような結果が出力される。
Asset Size Chunks Chunk Names main.7e2c49a622975ebd9b7e.js 544 kB 0 [emitted] [big] main index.html 197 bytes [emitted]
中身を何も変えずに再ビルドすると、ファイル名は変わらず再度出力されるが、少しでも中身を変えると [contenthash]
部分が変更される。
これは、webpackのエントリチャンクに特定の定型句、具体的にはランタイムとマニフェストが含まれているため。
※Webpackのバージョンによって出力が異なる場合がある。 安全のために下記手順を推奨。
Extracting Boilerplate
code splitting で学んだように、SplitChunksPlugin
を使ってモジュールを別々のバンドルに分割することができる。
webpackは、optimize.runtimeChunk
オプションを使用してランタイムコードを別々のチャンクに分割するための最適化機能を提供している。
すべてのチャンクに対して単一のランタイムバンドルを作成するには、single
に設定する。
▼ webpack.config.js
// 略 module.exports = { // 略 + optimization: { + runtimeChunk: 'single' + } }
ビルド実行結果
Hash: 82c9c385607b2150fab2 Version: webpack 4.12.0 Time: 3027ms Asset Size Chunks Chunk Names runtime.cc17ae2a94ec771e9221.js 1.42 KiB 0 [emitted] runtime main.e81de2cf758ada72f306.js 69.5 KiB 1 [emitted] main index.html 275 bytes [emitted] [1] (webpack)/buildin/module.js 497 bytes {1} [built] [2] (webpack)/buildin/global.js 489 bytes {1} [built] [3] ./src/index.js 309 bytes {1} [built] + 1 hidden module
lodash
や react
などのサードパーティ製のライブラリは、アプリケーションコードよりも変更される可能性は低いので、別のチャンクに分けることを推奨している。
それによりクライアントからのリクエストを少なくすることができる。
これは、SplitChunksPlugin
の例2に示されている SplitChunksPlugin
の cacheGroups
オプションを使用して実行できる。
▼ webpack.config.js
// 略 module.exports = { // 略 optimization: { runtimeChunk: 'single', + splitChunks: { + cacheGroups: { + vendor: { + test: /[\\/]node_modules[\\/]/, + name: 'vendors', + chunks: 'all' + } + } + } } }
ビルド実行結果
Asset Size Chunks Chunk Names runtime.cc17ae2a94ec771e9221.js 1.42 KiB 0 [emitted] runtime vendors.a42c3ca0d742766d7a28.js 69.4 KiB 1 [emitted] vendors main.abf44fedb7d11d4312d7.js 240 bytes 2 [emitted] main index.html 353 bytes [emitted]
メインバンドルに node_modules ディレクトリのベンダーコードが含まれておらず、サイズが240バイトに減少していることがわかる。
Module Identifiers
print.js をプロジェクトに追加する
▼ ディレクトリ構成
project
|- package.json
|- webpack.config.js
|- /dist
|- /src
|- index.js
+ |- print.js
|- /node_modules
▼ src/print.js
export default text => { console.log(text); };
▼ src/index.js
import _ from 'lodash'; + import print from './print'; function component() { const element = document.createElement('div'); const button = document.createElement('button'); const br = document.createElement('br'); button.innerHTML = 'Click me and look at the console!!'; element.innerHTML = _.join(['Hello', 'webpack'], ' '); element.appendChild(br); element.appendChild(button); + button.onclick = print.bind(null, 'Hello, webpack!!'); return element; } document.body.appendChild(component());
これでビルドすると、main
バンドルがだけが更新されると思いきや、実際には、下記のように3つすべてが更新される。
Asset Size Chunks Chunk Names runtime.1400d5af64fc1b7b3a45.js 5.85 kB 0 [emitted] runtime vendor.a7561fb0e9a071baadb9.js 541 kB 1 [emitted] [big] vendor main.b746e3eb72875af2caa9.js 1.22 kB 2 [emitted] main index.html 352 bytes [emitted]
これは、各module.idがデフォルトで順序の解決に基づいて増分されるため。
解決の順序が変わると、IDも変わる。
まとめると下記となる。
main
バンドルは内容が更新されたため変更されたvender
バンドルはmodule.id
が更新されたため変更されたmanifest
バンドル(≒runtime
バンドル)は新しいモジュールへの参照を含むようになったため変更された
1と3は予想できる内容である。
vendor
のハッシュに関しては何とかしたいが、これを解決するために使用できるプラグインが2つある。
NamedModulesPlugin
数値の識別子ではなくモジュールへのパスを使用する。開発時に読みやすく出力するのに役立つが、実行には少し時間がかかるHashedModuleIdsPlugin
プロダクションビルドに推奨される
今回は2番目の HashedModuleIdsPlugin
を使用。
webpack.config.js でpluginsに追加する。
▼ webpack.config.js
+ const webpack = require('webpack'); // 略 module.exports = { entry: './src/index.js', plugins: [ new CleanWebpackPlugin(['dist']), new HtmlWebpackPlugin({ title: 'Caching' }), + new webpack.HashedModuleIdsPlugin() ], // 略
これにより、vendor
のハッシュは変更せずに保たれるようになる。
キャッシュに関しては複雑になる可能性があるが、そのメリットは大きい。
詳細に関しては下記のissueを参考。
Guides - Explain Hash Changes in Caching Guide · Issue #652 · webpack/webpack.js.org · GitHub