#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.jsapp2.bundle.jsapp3.bundle.jsvendor.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 円)
興味を持ってくださった方はご購入いただけると大変嬉しいです。よろしくお願いいたします。



