キャッシュ(Caching)
webpackで処理された静的リソースを長期的にキャッシュ可能にするためには、以下の対応が必要です。
[chunkhash]
を使用して、コンテンツの変更に基づいたキャッシュ識別子を各ファイルに生成する- HTMLファイルでファイルを読み込む際に、コンパイル状態を使用してファイル名を取得する
- リソースをロードする前に、チャンクマニフェストJSONファイルを生成し、HTMLページに書き込む
- 起動コードを含むエントリコードブロックのハッシュ値が、その依存関係が変更されていない限り、変更されないようにする
存在する問題
コードに何か更新が必要な場合、それはサーバーにデプロイされ、クライアントによって再ダウンロードされる必要があります。ネットワークの状態が良くない場合、これは非常に非効率な作業となります。これがブラウザが静的リソースをキャッシュする理由です。
しかし、これには落とし穴があります。新しいバージョンをリリースする際にファイル名を更新しないと、ブラウザはファイルが変更されていないと認識し、クライアントが最新のリソースを取得できない可能性があります。
この問題を解決する簡単な方法は、ブラウザに新しいファイル名を伝えることです。webpackを使用しない場合、ビルドバージョンを使用して今回の更新を識別します。
application.js?build=1
application.css?build=1
webpackでもこれは簡単です。webpackのビルドごとに、ファイル名を構成するために使用できる一意のハッシュ値が生成されます。以下の設定ファイルは、ハッシュ値を持つ2つのファイル名を生成します。
// webpack.config.js
const path = require("path");
module.exports = {
entry: {
vendor: "./src/vendor.js",
main: "./src/index.js",
},
output: {
path: path.join(__dirname, "build"),
filename: "[name].[hash].js",
},
};
webpackコマンドを実行すると、以下の出力が得られます。
Hash: 55e783391098c2496a8f
Version: webpack 1.10.1
Time: 58ms
Asset Size Chunks Chunk Names
main.55e783391098c2496a8f.js 1.43 kB 0 [emitted] main
vendor.55e783391098c2496a8f.js 1.43 kB 1 [emitted] vendor
[0] ./src/index.js 46 bytes {0} [built]
[0] ./src/vendor.js 40 bytes {1} [built]
しかし、問題は、再コンパイルするたびにすべてのファイル名が変更され、クライアントが毎回アプリケーション全体のコードを再ダウンロードすることになる点です。では、クライアントが変更されたファイルのみをダウンロードするようにするにはどうすればよいでしょうか?
各ファイルに固有のハッシュを生成する
ファイルの内容が変更されていない場合、コンパイルごとにファイル名が変わらないようにするにはどうすればよいでしょうか?例えば、すべての依存ライブラリファイルを格納するためのコードバンドルなどです。
Webpackはファイルの内容に基づいてハッシュ値を生成することを許可しています。これが更新された設定です。
// webpack.config.js
module.exports = {
/*...*/
output: {
/*...*/
filename: "[name].[chunkhash].js",
},
};
この設定も2つのファイルを生成しますが、各ファイルは独自のハッシュ値を持っています。
main.155567618f4367cd1cb8.js 1.43 kB 0 [emitted] main
vendor.c2330c22cd2decb5da5a.js 1.43 kB 1 [emitted] vendor
開発環境では
[chunkhash]
を使用しないでください。これにより、コンパイル時間が長くなります。開発環境と本番環境の設定ファイルを分け、開発環境では[name].js
を、本番環境では[name].[chunkhash].js
を使用してください。
webpackコンパイル統計情報(compilation stats)からファイル名を取得する
開発環境では、HTMLにファイルを読み込むだけで十分です。
<script src="main.js"></srcipt>
しかし、本番環境では、毎回異なるファイル名が生成されます。
<script src="main.12312123t234.js"></srcipt>
HTMLで正しいファイルを読み込むためには、ビルドに関する情報を知る必要があります。これは、以下のプラグインを使用してwebpackのコンパイル統計情報(compilation stats)から抽出できます。
// webpack.config.js
const path = require("path");
module.exports = {
/*...*/
plugins: [
function () {
this.plugin("done", function (stats) {
require("fs").writeFileSync(
path.join(__dirname, "…", "stats.json"),
JSON.stringify(stats.toJson()),
);
});
},
],
};
または、以下のプラグインを使用してJSONファイルを公開することもできます。
- https://www.npmjs.com/package/webpack-manifest-plugin
- https://www.npmjs.com/package/assets-webpack-plugin
webpack-manifest-plugin
を通じて出力される簡単なファイルは以下のようになります。
{
"main.js": "main.155567618f4367cd1cb8.js",
"vendor.js": "vendor.c2330c22cd2decb5da5a.js"
}
その後の処理は、あなたのサービスによって決定されます。Railsベースのプロジェクト向けの素晴らしい例 [walk through for Rails-based projects](walk through for Rails-based projects) があります。Node.jsのサーバーサイドレンダリングを使用する場合は、webpack-isomorphic-tools を使用できます。アプリケーションがサーバーサイドレンダリングに依存しない場合、index.html
を直接生成できます。これには以下の2つのプラグインを使用できます。
- https://github.com/ampedandwired/html-webpack-plugin
- https://github.com/szrenwei/inline-manifest-webpack-plugin
決定論的ハッシュ (Deterministic hashes)
生成ファイルのサイズを圧縮するために、webpackはモジュールを識別するために名前の代わりにIDを使用します。コンパイル中にIDが生成され、コードブロック名にマッピングされて、チャンクマニフェストと呼ばれるJavaScriptオブジェクトに格納されます。これはエントリファイルに配置され、バンドルされたファイルが正常に機能するようにします。
これには以前と同じ問題があります。他の場所が変更されていなくても、どこかのファイルを変更すると、更新されたエントリにマニフェストファイルが含まれる必要があります。これにより新しいハッシュ値が生成され、エントリファイル名が変更され、長期キャッシュの恩恵を受けられなくなります。
この問題を解決するために、https://github.com/diurnalist/chunk-manifest-webpack-plugin を使用して、マニフェストファイルを個別のJSONファイルに抽出する必要があります。これが更新された設定ファイルで、chunk-manifest.json
をバンドルされたフォルダの下に生成します。
// webpack.config.js
var ChunkManifestPlugin = require("chunk-manifest-webpack-plugin");
module.exports = {
// your config values here
plugins: [
new ChunkManifestPlugin({
filename: "chunk-manifest.json",
manifestVariable: "webpackManifest",
}),
],
};
エントリファイルからマニフェストファイルを削除した後、このファイルをwebpackに手動で提供する必要があります。上記の例で manifestVariable
オプションに気づいたかもしれません。これはwebpackがマニフェストJSONファイルを検索するためのグローバル変数であり、そのためコードバンドルの前にHTMLに読み込む必要があります。HTMLにJSONファイルの内容を書き込むのは簡単で、HTMLファイルの head
部分は以下のようになります。
<html>
<head>
<script>
//<![CDATA[
window.webpackManifest = {
0: "main.3d038f325b02fdee5724.js",
1: "1.c4116058de00860e5aa8.js",
};
//]]>
</script>
</head>
<body></body>
</html>
最終的な webpack.config.js
ファイルは以下の通りです。
var path = require("path");
var webpack = require("webpack");
var ManifestPlugin = require("webpack-manifest-plugin");
var ChunkManifestPlugin = require("chunk-manifest-webpack-plugin");
var WebpackMd5Hash = require("webpack-md5-hash");
module.exports = {
entry: {
vendor: "./src/vendor.js",
main: "./src/index.js",
},
output: {
path: path.join(__dirname, "build"),
filename: "[name].[chunkhash].js",
chunkFilename: "[name].[chunkhash].js",
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
minChunks: Infinity,
}),
new WebpackMd5Hash(),
new ManifestPlugin(),
new ChunkManifestPlugin({
filename: "chunk-manifest.json",
manifestVariable: "webpackManifest",
}),
],
};
webpack-html-plugin を使用している場合、inline-manifest-webpack-plugin を使用してこれを行うことができます。
この設定を使用すると、サードパーティのコードブロック(vendors chunk)は、そのコードまたは依存関係を変更しない限り、変更されません。
シミング(Shimming)
webpackはモジュールバンドラーとして、ES2015モジュール、CommonJS、AMDを含むモジュールシステムをサポートしています。しかし、多くの場合、サードパーティライブラリを使用する際に、それらが $
や jquery
のようなグローバル変数に依存しているのを見かけます。また、公開する必要がある新しいグローバル変数を作成することもあります。webpackがこれらの非モジュール(broken modules)ファイルを理解できるようにするいくつかの異なる方法を見てみましょう。
dist
ファイル下のバンドルされていないCommonJS/AMDファイルを、バンドルされた dist
バージョンよりも優先してください。
ほとんどのモジュールは package.json
の main
フィールドで dist
バージョンを指定します。これはほとんどの開発者にとって非常に便利ですが、webpackにとっては src
ディレクトリにエイリアスを設定する方が、依存関係をより良く最適化できるため望ましいです。しかし、多くの場合、dist
バージョンを使用しても大きな問題はありません。
// webpack.config.js
module.exports = {
...
resolve: {
alias: {
jquery: "jquery/src/jquery"
}
}
};
provide-plugin
[provide-plugin](https://webpack.js.org/plugins/provide-plugin)
を使用することで、このモジュールを webpack
を通じて参照されるすべてのモジュールで利用可能な変数として提供できます。この変数を使用したときにのみ、対応するモジュールが読み込まれます。多くの古いモジュールは、jQueryの $
や jQuery
のような特定のグローバル変数を使用します。このシナリオでは、グローバルな $
識別子に遭遇するたびに、webpackで var $=require('jquery')
と事前に設定できます。
module.exports = {
plugins: [
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery",
}),
],
};
imports-loader
[imports-loader](https://webpack.js.org/loaders/imports-loader/)
は、必要なグローバル変数を従来のモジュールに挿入します。例えば、一部の従来のモジュールは this
が window
オブジェクトを指すことに依存しています。これは、モジュールがCommonJSコンテキストで実行されるときに this
が module.exports
を指すため問題を引き起こします。この場合、imports-loader
を介して this
を上書きできます。
webpack.config.js
module.exports = {
module: {
rules: [
{
test: require.resolve("some-module"),
use: "imports-loader?this=>window",
},
],
},
};
これはAMD、CommonJS、そして従来のモジュールなど、異なるモジュールタイプをサポートしています。しかし、通常、define
変数をチェックし、奇妙な(quirky)コードを使用してこれらのプロパティを公開します。この場合、define = false
を設定してCommonJSパスを強制することが役立つ場合があります。
webpack.config.js
module.exports = {
module: {
rules: [
{
test: require.resolve("some-module"),
use: "imports-loader?define=>false",
},
],
},
};
exports-loader
ライブラリファイルがグローバル変数を作成し、その利用者がそれを使用することを期待しているとします。この場合、[exports-loader](https://webpack.js.org/loaders/exports-loader/)
を使用して、CommonJSスタイルのグローバル変数を公開する必要があります。例えば、file
を file
として、helpers.parse
を parse
として公開するには:
webpack.config.js
module.exports = {
module: {
rules: [
{
test: require.resolve("some-module"),
use: "exports-loader?file,parse=helpers.parse",
// adds below code the the file's source:
// exports["file"] = file;
// exports["parse"] = helpers.parse;
},
],
},
};
script-loader
[script-loader](https://webpack.js.org/loaders/script-loader/)
は、HTMLに script
タグを追加したのと同じように、グローバルコンテキストでコードを解析します。この場合、理論的にはすべてのモジュールが正常に動作するはずです。
このファイルは文字列としてコードにバンドルされます。
webpack
によって圧縮されないため、圧縮されたバージョンを使用してください。また、この状況ではwebpackが提供する開発ツールは使用できません。
legacy.js
ファイルに以下が含まれているとします。
GLOBAL_CONFIG = {};
script-loader
の使用
require("script-loader!legacy.js");
一般的に、このような結果が得られます。
eval("GLOBAL_CONFIG = {}");
noParse
オプション
AMD/CommonJSスタイルのモジュールがなく、dist
に含める必要がある場合、このモジュールを [noParse](https://webpack.js.org/configuration/module/#module-noparse)
としてマークできます。これにより、webpack
はこのモジュールを読み込むだけで、いかなる処理も行わないため、ビルド時間を短縮できます。
ProvidePlugin
のようなASTサポートを必要とするものは機能しません。
module.exports = {
module: {
noParse: /jquery|backbone/,
},
};
ライブラリファイルの作成
webpackはアプリケーションコードをバンドルするためのツールですが、ライブラリコードをバンドルするためにも使用できます。もしあなたがJavaScriptライブラリの作者で、バンドルプロセスを合理化する方法を探しているなら、この章の内容が非常に役立つでしょう。
ライブラリの作成(Author a Library)
ここでは、数字の1から5を対応する単語に変換し、その逆も行う簡潔なラッパーライブラリがあります。これは次のようになります。
src/index.js
import _ from "lodash";
import numRef from "./ref.json";
export function numToWord(num) {
return _.reduce(
numRef,
(accum, ref) => {
return ref.num === num ? ref.word : accum;
},
"",
);
}
export function wordToNum(word) {
return _.reduce(
numRef,
(accum, ref) => {
return ref.word === word && word.toLowerCase() ? ref.num : accum;
},
-1,
);
}
ライブラリの使用方法は以下の通りです。
//ES2015modules
import*aswebpackNumbersfrom'webpack-numbers';
...
webpackNumbers.wordToNum('Two')//outputis2
...
//CommonJSmodules
varwebpackNumbers=require('webpack-numbers');
...
webpackNumbers.numToWord(3);//outputisThree
...
//Asascripttag
<html>
...
<scriptsrc="https://unpkg.com/webpack-numbers"></script>
<script>
...
/*webpackNumbersisavailableasaglobalvariable*/
webpackNumbers.wordToNum('Five')//outputis5
...
</script>
</html>
完全なライブラリ設定とコードは webpack-library-example にあります。
webpackの設定
次に、このライブラリをバンドルします。
• lodashはバンドルせず、利用者が読み込むことを期待する
• ライブラリ名を webpack-numbers
とし、変数を webpackNumbers
とする
• ライブラリは import webpackNumbers from 'webpack-numbers'
または require('webpack-numbers')
で読み込めるようにする
• script
タグで読み込んだ場合、グローバル変数 webpackNumbers
でアクセスできるようにする
• Node.jsで使用できるようにする
webpackの追加
基本的なwebpack設定を追加します。
webpack.config.js
module.exports = {
entry: "./src/index.js",
output: {
path: "./dist",
filename: "webpack-numbers.js",
},
};
これは、このライブラリをバンドルするための基本的な設定を追加します。
Loadersの追加
しかし、対応するローダーがなければコードを解析することはできません。ここでは、JSONファイルの解析を追加するために json-loader
を追加します。
webpack.config.js
module.exports = {
// ...
module: {
rules: [{
test: /.json$/,
use: 'json-loader'
}]
}
};
externals
の追加
さて、webpack
コマンドを実行すると、かなり大きなコードバンドルが生成されることに気づくでしょう。コードを調べると、lodashがコードバンドルにバンドルされていることがわかります。あなたのライブラリにとって lodash
を一緒にバンドルすることは全く必要ありません。
externals
設定を通じてこれを行うことができます。
webpack.config.js
module.exports = {
...
externals: {
"lodash": {
commonjs: "lodash",
commonjs2: "lodash",
amd: "lodash",
root: "_"
}
}
...
};
これは、利用者の環境であなたのライブラリが lodash
に依存することを期待することを意味します。
libraryTarget
の追加
このライブラリが広く使用されるためには、異なる環境で同じように動作する必要があります。例えば、CommonJS、AMD、Node.js、またはグローバル変数としてなどです。
この目的を達成するために、webpack設定に library
プロパティを追加する必要があります。
webpack.config.js
module.exports = {
...
output: {
...
library: 'webpackNumbers'
}
...
};
これにより、ライブラリが読み込まれたときにグローバル変数としてアクセスできるようになります。他の状況で使用できるようにするには、設定に libraryTarget
の値を追加し続けます。
webpack.config.js
module.exports = {
...
output: {
...
library: 'webpackNumbers',
libraryTarget:'umd' // Possible value - amd, commonjs, commonjs2, commonjs-module, this, var
}
...
};
library
が設定されていても libraryTarget
が設定されていない場合、libraryTarget
は config reference で指定されているようにデフォルトで var
になります。
最後の一歩
バンドルされたファイルを package.json
の指定されたフィールドに追加します。
package.json
{
...
"main": "dist/webpack-numbers.js",
"module": "src/index.js", // To add as standard module as per https://github.com/dherman/defense-of-dot-js/blob/master/proposal.md#typical-usage
...
}
これで、npmモジュールとして公開し、unpkg.comを通じてユーザーに配布できるようになります。
この記事は 2017年1月19日 に公開され、2017年1月19日 に最終更新されました。3182 日が経過しており、内容が古くなっている可能性があります。