136
124

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

webpackのCommonsChunkPluginの使い方、使い所 (webpack 4で廃止)

Last updated at Posted at 2017-02-28

#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を利用した出力の違い(イメージ図)

以下は通常の出力

webpack.jpg

以下はCommonsChunkPluginを利用した出力

commonChunks (1).jpg

なぜ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

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

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.CommonsChunkPluginnameオプションchunksオプションを指定する。

name

共通モジュールをバンドルしたファイル(コモンズチャンク)の名前。今回は'vendor'を指定したため、出力されるファイル名はvendor.bundle.jsになる。

chunks

コモンズチャンクと関連付けたいエントリーポイント。
今回はappapp2を指定したため、それらのエントリーポイントで共通のモジュールがバンドルされて出力される。

app.jsapp2.js

エントリーポイントであるapp.jsapp2.jsは以下の処理を記述している。

  • セレクタを指定し、文字列を挿入。
  • 指定したセレクタをフェードアウトする。
app.js
var $ = require('jquery');
var velocity = require('velocity-animate');
var text = 'app';

$('body').html(text);
velocity($('body'), 'fadeOut', {
  duration: 1000
});
app2.js
var $ = require('jquery');
var velocity = require('velocity-animate');
var text = 'app2';

$('body').html(text);
velocity($('body'), 'fadeOut', {
  duration: 1000
});

どちらも外部モジュールであるjqueryvelocity-animateを利用している。
そのため、jqueryvelocity-animateは複数のエントリーポイント間で利用されている共通のモジュールである。

app3.js

エントリーポイントである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

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>

app.gif

index2.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>

app2.gif

index3.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>
app3.jpg

全て正常に動作していることがわかる。
CommonsChunkPluginを利用して出力したファイル(コモンズチャンク)は、chunksオプションでで指定したエントリーポイントから出力したファイルより先に読み込む必要がある。
そのため、今回の場合、vendor.bundle.jsより先にapp.bundle.jsapp2.bundle.jsを読み込むとエラーが発生し動作しないため注意。
app3chunksオプションで指定していないため、app3.bundle.jsより先にvendor.bundle.jsを読み込む必要がなく、正常に動作する。

CommonsChunkPluginを利用しないとどのようなファイルが出力されるのか

通常の出力と何が違うのかを理解するために、CommonsChunkPluginを利用しないとどのようなファイルが出力するのか見てみる。
以下はnew webpack.optimize.CommonsChunkPluginの記述を削除したwebpack.config.js

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.jsapp2.jsのそれぞれにjqueryvelocity-animateがバンドルされて出力されてしまっている
そのため、先ほどCommonsChunkPluginを利用して出力したファイルと比べ、以下の問題点がある。

  • エントリーポイントを更新する度にjqueryvelocity-animateもバンドルしたファイルが更新されるため、キャッシュを活用できない
  • app.bundle.jsapp2.bundle.jsのファイルサイズが大きくなってしまう
  • それぞれのエントリーポイントに同じモジュールを読み込んでいるため、無駄にファイルサイズが大きくなっている

ということでCommonsChunkPluginを利用すればキャッシュを活用しやすく全体のファイルサイズを少なくできる。

CommonsChunkPluginの様々な使い方

上記は最低限の利用方法のため、利用シーンに応じた使い方をいくつか紹介する。

CommonsChunkPluginでバンドルする共通モジュールを指定する

設定をしなければ、CommonsChunkPluginは自動で共通するモジュールをバンドルして出力をする。
今回はjqueryvelocity-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の内容は以下の通り。

moduleA.js
var moduleA = function () {
  console.log('moduleA!!!');
  console.log('moduleA!!!');
  console.log('moduleA!!!');
  console.log('moduleA!!!');
  console.log('moduleA!!!');
};

module.exports = moduleA;

上記モジュールを以下のようにapp.jsapp2.jsで読みこんで利用する。

app.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();
app2.js
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コマンドを実行するとjqueryvelocity-animatemoduleA.jsがバンドルされたvendor.bundle.jsが出力される。
自動で共通のモジュールがバンドルされたが、moduleA.jsが頻繁に更新されるモジュールの場合、更新をする度にvendor.bundle.jsが更新されてしまうためキャッシュをあまり活用できない

jqueryvelocity-animateのような頻繁に更新しない外部モジュールのみをバンドルしたい場合、以下のようにwebpack.config.jsに記述を追加する。

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.CommonsChunkPluginnameオプションにしているvendorという名前のエントリーポイントを追加し、minChunksオプションを追加した。
この状態でwebpackコマンドを実行すると以下のようにjqueryvelocity-animateがバンドルされたvendor.bundle.jsが出力される。
また、moduleA.jsapp.jsapp2.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.jsapp2.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.jsjsonpFunctionを指定する。

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'),
    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 円)

興味を持ってくださった方はご購入いただけると大変嬉しいです。よろしくお願いいたします。

136
124
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
136
124

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?