#2018/3/15追記
先日リリースされたwebpack 4で、CommonsChunkPluginは廃止され、代わりにoptimization.splitChunks
が追加されました。
詳細は記事に書きましたのでwebpack 4を利用している方は以下をご覧ください。
webpack 4 の optimization.splitChunks の使い方、使い所 〜廃止された CommonsChunkPlugin から移行する〜
それ以前のバージョンでは有用なツールだと思いますので、まだ移行できない方はご覧いただければと思います。
はじめに
webpackのプラグインであるCommonsChunkPluginに関しての基本的な使い方、使い所に関しての記事です。
CommonsChunkPlugin
を利用する機会はそこそこあると思うですが、日本語の解説記事をほとんど見かけなかったため本記事を書きました。
解説に利用しているコードの最終形態はGitHub上にあります。
soarflat-sandbox/webpack-commons-chunk-plugin-example
webpackの基礎知識を習得したい方はwebpack 入門をご覧ください。
CommonsChunkPlugin
とは
複数のエントリーポイント間で利用している共通モジュールをバンドルしたファイルを出力するプラグイン。
バンドルした共通ファイルは**チャンク(chunk)やコモンズチャンク(commons chunk)**とも言う。
通常の出力とCommonsChunkPlugin
を利用した出力の違い(イメージ図)
以下は通常の出力
以下はCommonsChunkPlugin
を利用した出力
なぜCommonsChunkPlugin
を利用するのか
上記のイメージ図のように共通ファイルを出力するため、以下のようなメリットがあるから。
- 複数のエントリーポイントが1つの共通ファイルを利用するため、全体のファイルサイズが小さくなる
- 共通ファイルなのでキャッシュを活用できる(1度読み込めば別ページに遷移しても読み込みが不要、共通ではないファイルだけ読み込めば良い)
CommonsChunkPlugin
を利用してみる
CommonsChunkPlugin
を利用して共通のモジュールをバンドルしたファイルを出力してみる。
ディレクトリ構成
.
├── package.json
├── public
│ ├── index.html
│ ├── index2.html
│ ├── index3.html
│ └── js
│ ├── app.bundle.js
│ ├── app2.bundle.js
│ ├── app3.bundle.js
│ └── vendor.bundle.js
├── src
│ └── js
│ ├── app.js
│ ├── app2.js
│ └── app3.js
└── webpack.config.js
各ファイルの詳細
package.json
{
"name": "webpack-commons-chunk-plugin-example",
"version": "1.0.0",
"devDependencies": {
"webpack": "^3.7.1"
},
"dependencies": {
"jquery": "^3.1.1",
"velocity-animate": "^1.3.1"
}
}
上記のpackage.json
が存在する階層で以下のコマンドを実行すれば、必要なパッケージ(モジュール)をインストールできる。
npm install
webpack.config.js
const webpack = require('webpack');
const path = require('path');
module.exports = {
entry: {
app: './src/js/app.js',
app2: './src/js/app2.js',
app3: './src/js/app3.js',
},
output: {
filename: '[name].bundle.js',
path: path.join(__dirname, 'public/js'),
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
chunks: ['app', 'app2'],
}),
],
};
new webpack.optimize.CommonsChunkPlugin
にname
オプションchunks
オプションを指定する。
name
共通モジュールをバンドルしたファイル(コモンズチャンク)の名前。今回は'vendor'を指定したため、出力されるファイル名はvendor.bundle.js
になる。
chunks
コモンズチャンクと関連付けたいエントリーポイント。
今回はapp
とapp2
を指定したため、それらのエントリーポイントで共通のモジュールがバンドルされて出力される。
app.js
、app2.js
エントリーポイントであるapp.js
、app2.js
は以下の処理を記述している。
- セレクタを指定し、文字列を挿入。
- 指定したセレクタをフェードアウトする。
var $ = require('jquery');
var velocity = require('velocity-animate');
var text = 'app';
$('body').html(text);
velocity($('body'), 'fadeOut', {
duration: 1000
});
var $ = require('jquery');
var velocity = require('velocity-animate');
var text = 'app2';
$('body').html(text);
velocity($('body'), 'fadeOut', {
duration: 1000
});
どちらも外部モジュールであるjquery
とvelocity-animate
を利用している。
そのため、jquery
とvelocity-animate
は複数のエントリーポイント間で利用されている共通のモジュールである。
app3.js
エントリーポイントであるapp3.js
は以下の処理を記述している。
console.log('app3!');
consoleを出力しているだけのため、モジュールは何も利用していない。
webpackコマンドでバンドルしたファイルを出力
上記構成のwebpack.config.js
が存在する階層でwebpack
コマンドを実行すれば、バンドルしたファイルが出力される。
今回の設定だと以下のファイルがpublic/js/
に出力される。
app.bundle.js
app2.bundle.js
app3.bundle.js
vendor.bundle.js
webpack
# 以下のような出力がされる
Hash: cd5130224985ddc17931
Version: webpack 1.14.0
Time: 297ms
Asset Size Chunks Chunk Names
app.bundle.js 331 bytes 0 [emitted] app
app2.bundle.js 332 bytes 1 [emitted] app2
app3.bundle.js 1.41 kB 2 [emitted] app3
vendor.bundle.js 458 kB 3 [emitted] vendor
[0] ./src/js/app.js 226 bytes {0} [built]
[0] ./src/js/app2.js 227 bytes {1} [built]
[0] ./src/js/app3.js 21 bytes {2} [built]
+ 2 hidden modules
それぞれのエントリーポイントから出力されたファイルを読み込んでいるHTMLは以下の通り。
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>webpack tutorial</title>
</head>
<body>
<script src="js/vendor.bundle.js"></script>
<script src="js/app.bundle.js"></script>
</body>
</html>
index2.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>webpack tutorial</title>
</head>
<body>
<script src="js/vendor.bundle.js"></script>
<script src="js/app2.bundle.js"></script>
</body>
</html>
index3.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>webpack tutorial</title>
</head>
<body>
<script src="js/app3.bundle.js"></script>
</body>
</html>
全て正常に動作していることがわかる。
CommonsChunkPlugin
を利用して出力したファイル(コモンズチャンク)は、chunks
オプションでで指定したエントリーポイントから出力したファイルより先に読み込む必要がある。
そのため、今回の場合、vendor.bundle.js
より先にapp.bundle.js
かapp2.bundle.js
を読み込むとエラーが発生し動作しないため注意。
app3
はchunks
オプションで指定していないため、app3.bundle.js
より先にvendor.bundle.js
を読み込む必要がなく、正常に動作する。
CommonsChunkPlugin
を利用しないとどのようなファイルが出力されるのか
通常の出力と何が違うのかを理解するために、CommonsChunkPlugin
を利用しないとどのようなファイルが出力するのか見てみる。
以下はnew webpack.optimize.CommonsChunkPlugin
の記述を削除したwebpack.config.js
。
const webpack = require('webpack');
const path = require('path');
module.exports = {
entry: {
app: './src/js/app.js',
app2: './src/js/app2.js',
app3: './src/js/app3.js',
},
output: {
filename: '[name].bundle.js',
path: path.join(__dirname, 'public/js'),
},
};
上記の設定でwebpack
コマンドを実行すると以下のようなファイルが出力される。
webpack
# 以下のような出力がされる
Hash: 64b29a7731b74e4a80d1
Version: webpack 1.14.0
Time: 309ms
Asset Size Chunks Chunk Names
app.bundle.js 456 kB 0 [emitted] app
app2.bundle.js 456 kB 1 [emitted] app2
app3.bundle.js 1.41 kB 2 [emitted] app3
[0] ./src/js/app.js 226 bytes {0} [built]
[0] ./src/js/app2.js 227 bytes {1} [built]
[0] ./src/js/app3.js 21 bytes {2} [built]
+ 2 hidden modules
このファイルでもブラウザ上で正常に動作するが、モジュールを利用しているapp.js
とapp2.js
のそれぞれにjquery
とvelocity-animate
がバンドルされて出力されてしまっている。
そのため、先ほどCommonsChunkPlugin
を利用して出力したファイルと比べ、以下の問題点がある。
- エントリーポイントを更新する度に
jquery
とvelocity-animate
もバンドルしたファイルが更新されるため、キャッシュを活用できない -
app.bundle.js
とapp2.bundle.js
のファイルサイズが大きくなってしまう - それぞれのエントリーポイントに同じモジュールを読み込んでいるため、無駄にファイルサイズが大きくなっている
ということでCommonsChunkPlugin
を利用すればキャッシュを活用しやすく全体のファイルサイズを少なくできる。
CommonsChunkPlugin
の様々な使い方
上記は最低限の利用方法のため、利用シーンに応じた使い方をいくつか紹介する。
CommonsChunkPlugin
でバンドルする共通モジュールを指定する
設定をしなければ、CommonsChunkPlugin
は自動で共通するモジュールをバンドルして出力をする。
今回はjquery
とvelocity-animate
が共通で利用されているため、それらがバンドルされて出力される。
自動で共通モジュールをバンドルする機能は便利だが、その機能がこちらの意図に削ぐわない時もある。
上記の構成にユーザーが作成したmoduleA.js
を追加するシーンを想定してみる。
ディレクトリ構成は以下のようになる。
.
├── README.md
├── package.json
├── public
│ ├── index.html
│ ├── index2.html
│ ├── index3.html
│ └── js
│ ├── app.bundle.js
│ ├── app2.bundle.js
│ ├── app3.bundle.js
│ └── vendor.bundle.js
├── src
│ └── js
│ ├── app.js
│ ├── app2.js
│ ├── app3.js
│ └── modules
│ └── moduleA.js
└── webpack.config.js
moduleA.js
の内容は以下の通り。
var moduleA = function () {
console.log('moduleA!!!');
console.log('moduleA!!!');
console.log('moduleA!!!');
console.log('moduleA!!!');
console.log('moduleA!!!');
};
module.exports = moduleA;
上記モジュールを以下のようにapp.js
とapp2.js
で読みこんで利用する。
var $ = require('jquery');
var velocity = require('velocity-animate');
var moduleA = require('./modules/moduleA');
var text = 'app';
$('body').html(text);
velocity($('body'), 'fadeOut', {
duration: 1000
});
moduleA();
var $ = require('jquery');
var velocity = require('velocity-animate');
var moduleA = require('./modules/moduleA');
var text = 'app2';
$('body').html(text);
velocity($('body'), 'fadeOut', {
duration: 1000
});
moduleA();
この状態でwebpack
コマンドを実行するとjquery
、velocity-animate
、moduleA.js
がバンドルされたvendor.bundle.js
が出力される。
自動で共通のモジュールがバンドルされたが、moduleA.js
が頻繁に更新されるモジュールの場合、更新をする度にvendor.bundle.js
が更新されてしまうためキャッシュをあまり活用できない。
jquery
、velocity-animate
のような頻繁に更新しない外部モジュールのみをバンドルしたい場合、以下のようにwebpack.config.js
に記述を追加する。
const webpack = require('webpack');
const path = require('path');
module.exports = {
entry: {
// エントリーポイント名はCommonsChunkPluginのnameと同じものを指定
// バンドルしたい共通のモジュールのみを記述
vendor: ['jquery', 'velocity-animate'],
app: './src/js/app.js',
app2: './src/js/app2.js',
app3: './src/js/app3.js',
},
output: {
filename: '[name].bundle.js',
path: path.join(__dirname, 'public/js'),
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
chunks: ['app', 'app2'],
// Infinityを指定すると、エントリーポイントに記述したモジュール以外はバンドルされない
minChunks: Infinity,
}),
],
};
new webpack.optimize.CommonsChunkPlugin
のname
オプションにしているvendor
という名前のエントリーポイントを追加し、minChunks
オプションを追加した。
この状態でwebpack
コマンドを実行すると以下のようにjquery
、velocity-animate
がバンドルされたvendor.bundle.js
が出力される。
また、moduleA.js
がapp.js
とapp2.js
のそれぞれにバンドルされて出力される。
webpack
# 以下のような出力がされる
Hash: b177faee7f6dd193f928
Version: webpack 1.14.0
Time: 312ms
Asset Size Chunks Chunk Names
app.bundle.js 601 bytes 0 [emitted] app
app2.bundle.js 602 bytes 1 [emitted] app2
app3.bundle.js 1.41 kB 2 [emitted] app3
vendor.bundle.js 458 kB 3 [emitted] vendor
[0] ./src/js/app.js 220 bytes {0} [built]
[0] ./src/js/app2.js 221 bytes {1} [built]
[0] ./src/js/app3.js 21 bytes {2} [built]
[0] multi vendor 40 bytes {3} [built]
[3] ./src/js/modules/moduleA.js 202 bytes {0} {1} [built]
+ 2 hidden modules
minChunks
オプションがないと以下のように全ての共通モジュールがバンドルされたvendor.bundle.js
が出力される。
app.js
とapp2.js
のそれぞれにバンドルはされない。
webpack
# 以下のような出力がされる
Hash: 8bf8503da57ed4342c23
Version: webpack 1.14.0
Time: 308ms
Asset Size Chunks Chunk Names
app.bundle.js 319 bytes 0 [emitted] app
app2.bundle.js 320 bytes 1 [emitted] app2
app3.bundle.js 1.41 kB 2 [emitted] app3
vendor.bundle.js 458 kB 3 [emitted] vendor
[0] ./src/js/app.js 220 bytes {0} [built]
[0] ./src/js/app2.js 221 bytes {1} [built]
[0] ./src/js/app3.js 21 bytes {2} [built]
[0] multi vendor 40 bytes {3} [built]
[3] ./src/js/modules/moduleA.js 202 bytes {3} [built]
+ 2 hidden modules
バンドルする共通モジュールを指定するうえでの注意点
今回のmoduleA.js
のようなサイズが小さいモジュールであれば、それぞれのエントリーポイントにバンドルしても問題ない。
しかしファイルサイズが大きい場合、全体のファイルが大きくなるデメリットなどもあるため、状況に応じて指定をする。
共通ファイルのグローバル変数名を変更する
共通ファイルのグローバル変数名はデフォルトではwebpackJsonp
である。
デフォルトのままだと、以下のようにグローバル変数が衝突してエラーが発生する可能性がある。
<!-- どちらのファイルもグローバル変数がデフォルトのwebpackJsonpの場合、グローバル変数が衝突してエラーが発生する -->
<!-- 自分が設定したwebpack.config.jsのCommonsChunkPluginで出力したファイル -->
<script src="js/vendor.bundle.js"></script>
<script src="js/app.bundle.js"></script>
<!-- 自分が設定したものとは異なるwebpack.config.jsのCommonsChunkPluginで出力したファイル -->
<script src="js/option.vendor.bundle.js"></script>
<script src="js/option.bundle.js"></script>
上記を防ぐために、以下のようにwebpack.config.js
にjsonpFunction
を指定する。
const webpack = require('webpack');
const path = require('path');
module.exports = {
entry: {
// エントリーポイント名はCommonsChunkPluginのnameと同じものを指定
// バンドルしたい共通のモジュールのみを記述
vendor: ['jquery', 'velocity-animate'],
app: './src/js/app.js',
app2: './src/js/app2.js',
app3: './src/js/app3.js',
},
output: {
filename: '[name].bundle.js',
path: path.join(__dirname, 'public/js'),
jsonpFunction: 'vendor',
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
chunks: ['app', 'app2'],
// Infinityを指定すると、エントリーポイントに記述したモジュール以外はバンドルされない
minChunks: Infinity,
}),
],
};
今回はvendor
を指定したため、グローバル変数はvendor
になる。
終わり
個人的には利用する機会が多いプラグインです(もしかして他に上記を解決できる手段があるかもしれないです)。
状況に応じて利用していきましょう。
お知らせ
Udemy で webpack の講座を公開したり、Kindle で技術書を出版しています。
Udemy:
webpack 最速入門(10,800 円 -> 2,000 円)
Kindle(Kindle Unlimited だったら無料):
React Hooks 入門(500 円)
興味を持ってくださった方はご購入いただけると大変嬉しいです。よろしくお願いいたします。