前置定義
Bundle コード包 Chunk コード塊
インストール
npm install webpack —save-dev
コード分割
コード分割は、webpack の最も魅力的な機能の一つです。これにより、コードを必要に応じてロードできる様々なコード包に分割できます。例えば、ユーザーがルートを切り替えたときや、ユーザーがイベントを発生させたときなどです。これにより、小さなモジュールを使用し、リソースのロード優先度を制御できるようになります。適切に使用すれば、アプリケーションのロード時間に大きな影響(短縮)を与えることができます。
キャッシュと並列ロードのためのリソース分割
サードパーティコードの分割
典型的なアプリケーションは、多くのサードパーティのフレームワークやライブラリファイルに依存しています。アプリケーションコード自体とは異なり、これらのサードパーティコードの変更はそれほど頻繁ではありません。 これらのライブラリをアプリケーションコード自体から分離し、独自のコード包に保持することで、ブラウザのキャッシュ戦略を利用して、これらのコードを長期間にわたってエンドユーザーのマシンにキャッシュできます。
この効果を達成するためには、アプリケーションコードがどのように変更されても、サードパーティコードのベンダー包のハッシュ部分は不変である必要があります。 CommonsChunkPlugin
を使用してベンダー/ライブラリコードを分割する方法を学びましょう。
CSS 分割
スタイルファイルをアプリケーションロジックから独立した単一の包に分割することもできます。これにより、スタイルファイルのキャッシュ性が向上し、ブラウザがアプリケーションコードをロードする際にスタイルファイルを並列でロードできるようになるため、FOUC(スタイルが適用されていないコンテンツが一時的に表示される現象)を回避できます。
ExtractTextWebpackPlugin
を使用して CSS を分割する方法を学びましょう。
オンデマンドコード分割
上記のリソース分割では、ユーザーが設定ファイルで事前に分割点を指定する必要がありますが、アプリケーションコード内で動的な分割点を作成することも可能です。
この機能は、例えばアプリケーションの各ルートやユーザーの予測される行動に基づいて、非常に細かい粒度のコード塊が多数ある場合に非常に役立ちます。これにより、ユーザーは必要なリソースをオンデマンドでロードできます。
require.ensure()
を使用したコード分割
require.ensure
は、CommonJS スタイルの非同期リソースロード方法です。require.ensure([<fileurl>])
を追加することで、コード内に分割点を定義できます。Webpack は、この分割点に含まれるすべてのコードを含むコード包を作成できます。 require.ensure()
を使用してコードを分割する方法を学びましょう。
TODO System.import()
コード分割 - CSS
webpack では、css-loader
を使用して JavaScript で CSS ファイルをインポートすると、CSS ファイルは JavaScript ファイル内にバンドルされます。これには欠点があり、ブラウザの非同期並列ロード能力を CSS で利用できません。代わりに、ページは JavaScript ファイル全体のロードが完了するまで待機し、その後スタイルファイルのロードが完了します。Webpack は extract-text-webpack-plugin
と css-loader
を使用してスタイルファイルを分離することで、この問題を解決できます。
css-loader
の使用
CSS を JavaScript にインポートするには、css-loader
を使用して webpack の設定ファイルを構成する必要があります。
//webpack.config.js
modules.exports = function(env){
entry: '..',
...
module: {
rules: [{
test: /\.css$/,
exclude: /node_modules/,
loader: 'css-loader'
}]
}
...
}
extract-text-webpack-plugin
- ExtractTextPlugin の使用
インストール:
npm I --save-dev extract-text-webpack-plugin
ExtractTextPlugin
を使用するには、webpack.config.js
で2つのステップを設定する必要があります。
loader 内で
以前の css-loader
から適応させるには、ExtractTextPlugin
を次のように追加する必要があります。
loader: ExtractTextPlugin.extract('css-loader?sourceMap') //Can be used without sourcemaps too.
plugin 内で
new ExtractTextPlugin({ filename: 'bundle.css', disable: false, allChunks: true })
この2つのステップにより、すべての CSS モジュールを含む新しいコード包が生成され、それらが index.html
の head
に追加されます。ExtractTextPlugin でその API について詳しく学ぶことができます。
完全な設定ファイルは次のとおりです。
var ExtractTextPlugin = require("extract-text-webpack-plugin");
module.exports = function () {
return {
entry: "./main.js",
output: {
path: "./dist",
filename: "bundle.js",
},
module: {
rules: [
{
test: /\.css$/,
exclude: /node_modules/,
loader: ExtractTextPlugin.extract({
loader: "css-loader?sourceMap",
}),
},
],
},
devtool: "source-map",
plugins: [
new ExtractTextPlugin({
filename: "bundle.css",
disable: false,
allChunks: true,
}),
],
};
};
コード分割 - ライブラリファイル
典型的なアプリケーションは、フレームワークや機能サポートを提供するために多くのサードパーティに依存しています。プロジェクトで使用される固定バージョンのライブラリ/フレームワークファイルのコードは通常変更されませんが、アプリケーション自体のビジネスロジックコードは頻繁に変更されます。
アプリケーションコードとライブラリファイルのコードを一緒にバンドルすることは、非常に非効率的です。これは、ファイルの内容が変更されていない場合、ブラウザがキャッシュヘッダーに基づいてこれらのリソースファイルをローカルにキャッシュし、毎回サーバーや CDN にリクエストを送信して再取得する必要がないためです。この恩恵を享受するためには、アプリケーション自体のコードがどのように変更されても、サードパーティファイルのハッシュを不変に保つ必要があります。
アプリケーションコードとサードパーティコードを分離することによってのみ、このような効果を達成できます。
ここでは、一般的な時間フォーマットライブラリである momentjs
を使用するシンプルなアプリケーションを考えてみましょう。
moment
のインストール:
npm install --save moment
Index
ファイルは moment
を依存関係として参照し、現在の時刻を出力します。
Index.js
var moment = require("moment");
console.log(moment().format());
このアプリケーションは、以下の設定ファイルを使用してバンドルできます。
Webapck.config.js
module.exports = function (env) {
return {
entry: "./index.js",
output: {
filename: "[chunkhash].[name].js",
path: "./dist",
},
};
};
webpack
コマンドを実行し、バンドルされたファイルを調べると、moment
と index.js
の両方が bundle.js
にバンドルされていることがわかります。
これは良い解決策ではありません。index.js
が変更されると、このバンドルファイルは再構築され、ブラウザはファイルを再ロードする必要があります。たとえ moment.js
ファイルが何も変更されていなくてもです。
複数のエントリ
この問題を緩和するために、moment
に vendors
という新しいエントリを追加しましょう。
Webpack.config.js
module.exports = function (env) {
return {
entry: {
main: "./index.js",
vendor: "moment",
},
output: {
filename: "[chunkhash].[name].js",
path: "./dist",
},
};
};
ここで webpack
コマンドを実行すると、2つのバンドルファイルが生成されます。コードを調べると、moment
のコードが両方のコード包に同時に含まれていることがわかります。
この問題を解決するには、CommonsChunkPlugin
を使用する必要があります。
CommonsChunksPlugin
これはかなり複雑なプラグインです。基本的に、異なるコード包からすべての共通モジュールを抽出し、それらを共通のコード包に追加することを可能にします。この共通コード包が存在しない場合は、新しく作成されます。
CommonsChunksPlugin
を使用するように webpack の設定ファイルを変更できます。
Webpack.config.js
var webpack = require("webpack");
module.exports = function (env) {
return {
entry: {
main: "./index.js",
vendor: "moment",
},
output: {
filename: "[chunkhash].[name].js",
path: "./dist",
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "vendor", // Specify the common bundle's name.
}),
],
};
};
これにより、moment
のコードは vendor
コード包にのみ含まれるようになります。
マニフェストファイル
しかし、アプリケーションのコードを変更して再度 webpack
コマンドを実行すると、vendors
ファイルのハッシュがやはり変化していることがわかります。vendor
と main
のコード包を分離したにもかかわらず、アプリケーションコードが変更されると vendor
が変化してしまいます。これは、ブラウザのキャッシュによる恩恵を享受できないことを意味します。なぜなら、再コンパイルのたびに vendors
のハッシュ値が変更されるからです。
この問題は、コンパイルのたびに webpack がその作業を助けるための webpack ランタイムコードを生成するためです。単一のコード包が存在する場合、ランタイムはその中に存在します。しかし、複数のコード包が生成されると、ランタイムコードは共通モジュール、つまりこの vendor
ファイルに抽出されます。
これを防ぐには、ランタイムを分離されたマニフェストファイルに抽出する必要があります。これにより、もう一つのコード包が作成されますが、そのコストは、vendor
ファイルで得られる長期キャッシュによる恩恵によって相殺されます。
Webpack.config.js
var webpack = require("webpack");
module.exports = function (env) {
return {
entry: {
main: "./index.js",
vendor: "moment",
},
output: {
filename: "[chunkhash].[name].js",
path: "./dist",
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
names: ["vendor", "manifest"], // Specify the common bundle's name.
}),
],
};
};
上記のこの設定ファイルを使用すると、vendor
、main
、manifest
の3つのコード包が生成されることがわかります。このようにすることで、アプリケーションコードが変更されても、再バンドル後に変更されるのは main
と manifest
のみとなります。manifest
が変更されるのは、生成されたファイルのハッシュ値への参照が含まれているためです。
コード分割 - RequireJS の使用
この章では、webpack が require.ensure()
を介してコードを分割する方法について説明します。
require.ensure()
Webpack は、ビルド時にコード内の require.ensure()
に静的解析を行い、モジュールを分離されたコード塊に追加します。この新しいコード塊は、必要に応じて webpack によって jsonp を介してインポートされます。
その構文は次のとおりです。
require.ensure(dependencies: String[], callback: function(require), chunkName: String)
依存関係 (dependencies)
これは文字列の配列で、コールバック関数が実行される前に事前にロードされ、利用可能である必要があるすべてのモジュールを宣言します。
コールバック関数 (callback)
すべての依存関係がロードされた後、webpack によって一度実行されるコールバック関数です。Require
オブジェクトの実装がこのコールバック関数に引数として渡されます。これにより、必要な依存関係や実行する必要のある他のモジュールをさらに require
できます。
コード塊名 (chunkName)
コード塊名は、require.ensure()
を介して作成されるコード塊に名前を付けるために使用されます。異なる require.ensure()
で作成されたコード分割点から分割されたコード塊に同じ名前を付けることで、すべての依存関係が同じコード塊にバンドルされることを保証します。
以下の構造を持つプロジェクトを見てみましょう。
\\ file structure
|
js --|
| |-- entry.js
| |-- a.js
| |-- b.js
webpack.config.js
|
dist
// entry.js
require("a");
require.ensure([], function (require) {
require("b");
});
// a.js
console.log("***** I AM a *****");
// b.js
console.log("***** I AM b *****");
// webpack.config.js
module.exports = function (env) {
return {
entry: "./js/entry.js",
output: {
filename: "bundle.js",
path: "./dist",
},
};
};
webpack
コマンドを実行すると、webpack が bundle.js
と 0.bundle.js
という2つの新しいコード包を作成したことがわかります。
entry.js
と a.js
は bundle.js
にバンドルされました。
b.js
は 0.bundle.js
にバンドルされました。
require.ensure()
の落とし穴
パラメータとしての空の配列
require.ensure([], function (require) {
require("./a.js");
});
上記のコードは、分割点が作成され、a.js
が webpack によって個別にファイルとしてバンドルされることを保証します。
パラメータとしての依存関係
require.ensure(["./a.js"], function (require) {
require("./b.js");
});
上記のコードでは、a.js
と b.js
は一緒にバンドルされ、メインコード包から分離されます。しかし、b.js
の内容のみが実行されます。a.js
の内容は利用可能ですが、実行されません。a.js
を実行するには、require('./a.js')
のように同期的な方法で require
する必要があります。そうすれば JavaScript がそれを実行できます。
依存関係管理
Ø es6 module
Ø Commonjs
Ø Amd
式による依存関係 (require with expression)
式を介してモジュールをインポートすると、コンテキストが作成されるため、コンパイル時に正確なモジュールがどれであるかを特定できません。
例:
require("./template/" + name + ".ejs");
Webpack は require()
の呼び出しを解析し、いくつかの情報を抽出します。
Directory:./template
Regularexpression:/^.*\.ejs$/
コンテキストモジュール (context module)
コンテキストモジュールが生成されます。これには、このフォルダ内で上記の正規表現に一致する可能性のあるすべてのモジュールへの参照が含まれます。コンテキストモジュールには、リクエストをモジュール ID に解釈するマップが含まれています。 例:
{
"./table.ejs": 42,
"./table-row.ejs": 43,
"./directory/folder.ejs": 44
}
コンテキストモジュールには、このマップにアクセスするためのランタイムコードも含まれています。
これは、動的な参照がサポートされることを意味しますが、使用される可能性のあるすべてのモジュールが最終的なコード包にバンドルされることになります。
require.context
require.context()
メソッドを使用して独自のコンテキストを作成できます。これにより、クエリするフォルダ、サブフォルダを再帰的に検索するかどうかを決定するフラグ、およびファイルに一致する正規表現を渡すことができます。
Webpack は、コードバンドル時に require.context()
を解析します。
その構文は次のとおりです。
require.context(directory, (useSubdirectories = false), (regExp = /^\.\//));
例:
require.context("./test", false, /\.test\.js$/);
// a context with files from the test directory that can be required with a request endings with `.test.js`.
```
```javascript
require.context("../", true, /\.stories\.js$/);
// a context with all files in the parent folder and descending folders ending with `.stories.js`.
```
## コンテキストモジュール API (context module API)
コンテキストモジュールは、1つの引数(リクエストの内容)を受け取るメソッドを公開します。
公開される関数には3つのプロパティがあります:`resolve`、`key`、`id`
• `resolve` は、実行後に解決されたリクエスト内容のモジュール ID を返す関数です。
• `keys` は、実行後に配列を返し、コンテキストモジュールによってリクエストされる可能性のあるすべてのモジュール ID を含みます。
正規表現でフォルダ内のすべてのモジュールをインポートしたい場合に非常に役立ちます。
```javascript
function importAll (r) {
r.keys().forEach(r);
}
importAll(require.context('../components/', true, /\.js$/))
```
````javascript
var cache = {};
function importAll (r) {
r.keys().forEach(key => cache[key] = r(key));
}
importAll(require.context('../components/', true, /\.js$/));
// At build-time cache will be polulated with all required modules.
```
• `id` は、コンテキストモジュールによって生成されたモジュール ID です。`module.hot.accept` を使用する際に非常に役立ちます。
この記事は 2017年1月17日 に公開され、2017年1月17日 に最終更新されました。3183 日が経過しており、内容が古くなっている可能性があります。