Help us understand the problem. What is going on with this article?

webpackのDLLバンドルを使ってビルドを速くする

More than 3 years have passed since last update.

How to make your Webpack builds 10x faster

「webpackビルドを10倍速くする方法」というスライドを見つけた。

内容を要約すると、こんな感じ。

  • css-loaderは0.15未満を使う
  • cacheDiretoryはデフォルトで無効だから有効にする
  • HappyPackを使う
  • DLLバンドルを使って、静的コードのバンドルを分ける

DLLバンドルは聞いたことがなかったので調べた。

DLLバンドルとは

Dll bundles doesn't execute any of your module's code. They only include modules.

モジュールをまとめただけのbundleで、scriptタグで読み込んだ時点では含まれるモジュールは実行されず、他のbundleから参照された時に実行される。

<!-- こういうイメージ -->
<script src="vendor.dll.js" />
<script src="a.bundle.js" />

DLLバンドルの作り方

DllPlugin

webpack.DllPluginを使って、DLLバンドルと、そのDLLバンドルに含まれるモジュールの情報が書かれたmanifestファイル(jsonファイル)を生成する。

サンプル

例として、reactreact-domを含んだdll bundleを作る。

まず、必要なパッケージをインストールする。webpackは全部入りなので、楽。

$ mkdir webpack-dll-example && cd webpack-dll-example
$ echo "{}" > package.json
$ npm install -D webpack
$ npm install -S react react-dom

webpack.config.jsを書く。後でもう一つwebpack.configを書くので、webpack.dll.config.jsという名前にする。

webpack.dll.config.js
const path = require('path');
const webpack = require('webpack');

module.exports = {
  entry: {
    vendor: ['react', 'react-dom']
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].dll.js',
    /**
     * output.library
     * window.${output.library}に定義される
     * 今回の場合、`window.vendor_library`になる
     */
    library: '[name]_library'
  },
  plugins: [
    new webpack.DllPlugin({
      /**
       * path
       * manifestファイルの出力先
       * [name]の部分はentryの名前に変換される
       */
      path: path.join(__dirname, 'dist', '[name]-manifest.json'),
      /**
       * name
       * どの空間(global変数)にdll bundleがあるか
       * output.libraryに指定した値を使えばよい
       */
      name: '[name]_library'
    })
  ]
};

webpackを実行すると、dist以下にDLLバンドルとmanifestファイルを出力される。

$ ./node_modules/.bin/webpack --config webpack.dll.config.js
Hash: 36187493b1d9a06b228d
Version: webpack 1.13.1
Time: 860ms
        Asset    Size  Chunks             Chunk Names
vendor.dll.js  699 kB       0  [emitted]  vendor
   [0] dll vendor 12 bytes {0} [built]
    + 167 hidden modules

$ ls dist
./                    vendor-manifest.json
../                   vendor.dll.js

manifestファイルは↓のような感じで、含まれるモジュールとそのidがkey-valueで定義されている。

cat dist/vendor-manifest.json
{
  "name": "vendor_library",
  "content": {
    "./node_modules/react/react.js": 1,
    "./node_modules/react/lib/React.js": 2,
    "./node_modules/process/browser.js": 3,
    "./node_modules/object-assign/index.js": 4,
    "./node_modules/react/lib/ReactChildren.js": 5,
    "./node_modules/react/lib/PooledClass.js": 6,
    "./node_modules/fbjs/lib/invariant.js": 7,
...

DLLバンドルの使い方

DllReferencePlugin

作ったDLLバンドルを別のバンドルから参照するには、webpack.DllReferencePluginを使う。
ビルド時にDllReferencePluginを通してDLLバンドルのmanifestファイルを読み込むと、requireの解決の際にDLLバンドルに含まれるモジュールを使ってくれるようになる。

サンプル

さっきと同じディレクトリで作業する。

まず、reactをrequireしてconsole.logに吐くだけのindex.jsを書く。

var React = require('react');
var ReactDOM = require('react-dom');
console.log("dll's React:", React);
console.log("dll's ReactDOM:", ReactDOM);

まず、DLLバンドルを参照しない、普通のwebpack.config.jsを書く。

webpack.config.js
const path = require('path');
const webpack = require('webpack');

module.exports = {
  entry: {
    'dll-user': ['./index.js']
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].bundle.js'
  }
};

webpackを実行すると、dist/dll-user.bundle.jsが出力される。700KB。

terminal
$ ./node_modules/.bin/webpack
Hash: d8cab39e58c13b9713a6
Version: webpack 1.13.1
Time: 801ms
             Asset    Size  Chunks             Chunk Names
dll-user.bundle.js  700 kB       0  [emitted]  dll-user
   [0] multi dll-user 28 bytes {0} [built]
   [1] ./index.js 145 bytes {0} [built]
    + 167 hidden modules

では、webpack.config.jsにDllReferencePluginを追加してビルドする。

webpack.config.js
const path = require('path');
const webpack = require('webpack');

module.exports = {
  entry: {
    'dll-user': ['./index.js']
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].bundle.js'
  },
  // ----ここから追加---
  plugins: [
    new webpack.DllReferencePlugin({
      context: __dirname,
      /**
       * manifestファイルをロードして渡す
       */
      manifest: require('./dist/vendor-manifest.json')
    })
  ]
  // ----ここまで追加---
};
terminal
./node_modules/.bin/webpack
Hash: 3bc7bf760779b4ca8523
Version: webpack 1.13.1
Time: 70ms
             Asset     Size  Chunks             Chunk Names
dll-user.bundle.js  2.01 kB       0  [emitted]  dll-user
   [0] multi dll-user 28 bytes {0} [built]
   [1] ./index.js 145 bytes {0} [built]
    + 3 hidden modules

2.01KB。めっちゃ軽くなった。

実際に下記のようなhtmlを書いて、react, react-domがロードできているか(ブラウザのコンソールに出力されているか)を確認。

<body>
  <script src="dist/vendor.dll.js"></script>
  <script src="dist/dll-user.bundle.js"></script>
</body>

スクリーンショット 2016-05-27 11.31.06.png

ちゃんと出力されている。うえーい。

実際どれくらい速くなるか?

react, fluxibleで作っているツールで試してみた。DLLバンドルにはreact, react-dom, fluxibleなど、npmでインストールしたモジュールを全部突っ込む。

terminal
// 導入前
$ webpack --config webpack.config.dev.js
Hash: e30bcddcf9b7d11ac9f8
Version: webpack 1.12.14
Time: 6684ms
           Asset     Size  Chunks             Chunk Names
client.bundle.js  4.02 MB       0  [emitted]  client
 login.bundle.js  2.44 MB       1  [emitted]  login
   [0] multi login 28 bytes {1} [built]
   [0] multi client 52 bytes {0} [built]
    + 624 hidden modules

// 導入後
$ webpack --config webpack.config.dev.js
Hash: 26b6e5664a6eff083097
Version: webpack 1.12.14
Time: 4478ms
           Asset     Size  Chunks             Chunk Names
client.bundle.js  1.31 MB       0  [emitted]  client
 login.bundle.js   707 kB       1  [emitted]  login
   [0] multi login 28 bytes {1} [built]
   [0] multi client 52 bytes {0} [built]
    + 277 hidden modules

2.2秒ほど速くなった。

おまけ:cacheDirectory

ついでなので、元スライドで紹介されていたcacheDiretoryも有効にしてみる。(cacheDirectoryとbabel-loaderのqueryにcacheDirectoryを追加)

webpack --config webpack.config.dev.js
Hash: 26b6e5664a6eff083097
Version: webpack 1.12.14
Time: 2248ms
           Asset     Size  Chunks             Chunk Names
client.bundle.js  1.31 MB       0  [emitted]  client
 login.bundle.js   707 kB       1  [emitted]  login
   [0] multi login 28 bytes {1} [built]
   [0] multi client 52 bytes {0} [built]
    + 277 hidden modules

更に2秒速くなった。babelのコンパイルに時間がかかっている気がするので、HappyPackを入れると更に速くなりそう。

externalsと比較

externalsを使うとglobal変数からにモジュールをrequireでき、DLLと似たようなことができる。

例えば、Reactの場合、下記をwebpack.configに書くと、require('react')window.Reactから取得するようになる。

webpack.config.js
const path = require('path');
const webpack = require('webpack');

module.exports = {
  entry: {
    'ex': ['./index.js']
  },
  output: {
    path: path.join(__dirname, 'dist'),
    filename: '[name].bundle.js'
  },
  externals: {
    // require('react')はwindow.Reactを使う
    'react': 'React',
    // require('react-dom')はwindow.ReactDOMを使う
    'react-dom': 'ReactDOM'
  }
};

下記のようにreact.min.jsとreact-dom.min.jsをバンドルより先に読みこめばよい。

<body>
  <script src="dist/react.min.js"></script>
  <script src="dist/react-dom.min.js"></script>
  <script src="dist/ex.bundle.js"></script>
</body>

dllとの違いは、

  • reactのように既にビルドされているJS(react.min.js)がある場合はexternalsでも十分だが、fluxibleのようにビルドされたJSがない場合はdllにしないとバンドルを分けることができない。
  • dllはモジュールのファイルパスも保持しているので、dllに含まれていればrequire('react/lib/React')のように、一部のモジュールだけrequireできる。

たぶんこんな感じ。externalsで済むときはそちらの方が設定が楽なのでおすすめ。

dllはcommonjsでロードできればOKなので、2つのSPAがありentryが分かれているような場合に、共通するモジュールだけdllに寄せる等の用途に使えそう。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away