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

ウェブパックでバンドルをスプリットする

More than 1 year has passed since last update.

この記事で「バンドルスプリッティング」というのを紹介します。2つの簡単な例をあげて、それぞれの価値を説明します。ソースコードがこちらです。

optimization.splitChunks

optimization.splitChunksを使ってwebpackが作成するmain.jsを別のファイルに分けることができます。よく使うケースの一つは、ベンダーとローカルアセットを別のファイルに分けることです。

  • ベンダーアセット:外部ライブラリー。例えば、node_modulesからimportしたモジュール。だいたい変わらないコード
  • ローカルアセット:アプリのために書いたコード。開発しながらよく変わるアセット。

簡単な例を見ましょう。Vueの「ハローワールド」アプリを作っていて、パフォーマンスのためにベンダーとローカルアセットを別のバンドルにしたいです。

環境準備

echo {} >> package.jsonを実行してpackage.jsonを作ります。そしてwebpackとVueをインストールします:

npm install webpack webpack-cli vue --save

webpack設定ファイルとエントリーポイントを作ります:

touch webpack.config.js
mkdir src 
touch src/index.js 
touch src/create-app.js 

src/create-app.jsでVueアプリを作ります:

import Vue from "vue"

export default function createApp() {
  const el = document.createElement("div")

  el.setAttribute("id", "app")

  document.body.appendChild(el)

  new Vue({
    el: "#app",
    render: h => h("div", "Hello world")
  })
}

create-appsrc/index/jsimportして呼び出します:

import createApp from "./create-app"

document.addEventListener("DOMContentLoaded", () => {
  createApp()
})

そしてミニマルなwebpack設定ファイルを追加します:

const webpack = require("webpack")

module.exports = {
}

npx webpack --mode developmentを実行してバンドルします:

Hash: 6aca10b38e4ad1df98f1
Version: webpack 4.12.0
Time: 355ms
Built at: 2018-06-09 15:56:50
  Asset     Size  Chunks             Chunk Names
main.js  236 KiB    main  [emitted]  main
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 489 bytes {main} [built]
[./src/create-app.js] 246 bytes {main} [built]
[./src/index.js] 110 bytes {main} [built]
    + 4 hidden modules

main.js236 KiB!「ハローワールド」だけのために大きなバンドルです。ローカルで書いたコードは10行くらいだけでした。自分が書いたコードとベンダー(この例ではvue.js)を2つのファイルに分けられます。

バンドルを2つに分ける

webpack.config.jsを更新します:

const webpack = require("webpack")

module.exports = {
  optimization: {
    splitchunks: {
      cachegroups: {
        commons: {
          test: /[\\/]node_modules[\\/]/,
          name: "vendor",
          chunks: "initial",
        }
      }
    }
  }
}

optimizationのドキュメントはこちら。結果はnode_modulesからimportするコードはvendor.jsというファイルに書き込んで、アプリのコードはmain.jsに書き込みます。

npx wepback --mode developmentをまた実行します:

Hash: 128feabada91842ba3ce
Version: webpack 4.12.0
Time: 362ms
Built at: 2018-06-09 15:57:21
    Asset      Size  Chunks             Chunk Names
  main.js  7.62 KiB    main  [emitted]  main
vendor.js   231 KiB  vendor  [emitted]  vendor
[./node_modules/webpack/buildin/global.js] (webpack)/buildin/global.js 489 bytes {vendor} [built]
[./src/create-app.js] 246 bytes {main} [built]
[./src/index.js] 110 bytes {main} [built]
    + 4 hidden modules

今回は、main.jsは6.62 KiBだけになりました。--mode productionでバンドルするとサイズがもっと小さくなります。vendor.js、あるいはvue.jsと他には使うnode_modulesをCDNに載せたら、サイトが早くロードするし、自分のサーバのロードが減ります。

index.htmlを作って、バンドルした2つのファイルを使ってみます。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title></title>
</head>
<body>
</body>
<script src="/dist/main.js"></script>
<script src="/dist/vendor.js"></script>
</html>

python -m SimpleHTTPServerを実行してlocalhost:8000にアクセスします。"Hello World"が表示されるはずです。DevToolsのネットワークタブを開いて、2つのバンドルが見えます。

name        size
main.js     7.6 kb 
vendor.js   231 kb

ローカルコードを分ける

アプリコードも別のファイルにわかることもできます。3つの簡単な関数を作って、別のファイルに分けます。

touch src/my-module-1.js

my-module-1.jsを更新します:

export default function greetingOne() {
  const el = document.createElement("div")

  el.innerText = "Hello from my module!"

  document.body.appendChild(el)
}

2回コピーします:

cp src/my-module-1.js src/my-module-2.js
cp src/my-module-1.js src/my-module-3.js

src/index.jsも更新します:

// import createApp from "./create-app"
import firstGreeting from "./my-module-1"
import secondGreeting from "./my-module-2"
import thirdGreeting from "./my-module-3"

document.addEventListener("DOMContentLoaded", () => {
  // createApp()

  console.log("Loaded")
  firstGreeting()
  secondGreeting()
  thirdGreeting()
})

npx webpack --mode developmentを実行します:

Hash: dc9e548302c4e4708a02
Version: webpack 4.12.0
Time: 111ms
Built at: 2018-06-09 16:00:29
  Asset      Size  Chunks             Chunk Names
main.js  6.48 KiB    main  [emitted]  main
[./src/index.js] 298 bytes {main} [built]
[./src/my-module-1.js] 162 bytes {main} [built]
[./src/my-module-2.js] 162 bytes {main} [built]
[./src/my-module-3.js] 162 bytes {main} [built]

my-moduleは、コンパイルした後に162 bytesとなります。my-moduleが3つあって、AggressiveSplittingPluginを使ってそれぞれのファイルに分けてみましょう。

// ...

module.exports = {
  plugins: [
    new webpack.optimize.AggressiveSplittingPlugin({
      minSize: 100,
      maxSize: 200,
    })
  ],

  // ...

}

minSizemaxSizebytesです。1個のmy-moduleは162 bytesなので、maxSize200にすると、maxSizeを超えないように一個ずつのファイルに分けられます。npx webpack --mode developmentを実行して:

Hash: d70d8ef6e435320e0d10
Version: webpack 4.12.0
Time: 114ms
Built at: 2018-06-09 16:03:27
Asset       Size  Chunks             Chunk Names
 0.js    7.2 KiB       0  [emitted]
 1.js  719 bytes       1  [emitted]
 2.js  719 bytes       2  [emitted]
 3.js  719 bytes       3  [emitted]
[./src/index.js] 298 bytes {0} [built]
[./src/my-module-1.js] 162 bytes {1} [built]
[./src/my-module-2.js] 162 bytes {2} [built]
[./src/my-module-3.js] 162 bytes {3} [built]

4つのファイルに分けました。my-moduleが3つあって、そしてsrc/index.js。分けたら全体のファイルサイズが少し大きくなるので、こんなに小さいモジュールを分ける価値があまりないですが、大きなプロジェクトなら価値があります。例えば、SPAのページを一個ずつバンドルして、ページにアクセスするところで動的にロードできます。そうすると、最初のバンドルをできるだけ小さくして、コードが必要となる時だけにロードできます。

まとめ

この記事で学んだこと:

  • ベンダー、ローカルコードを分けること
  • AggressiveSplittingPluginを使ってローカルコードを分けること
  • コードを分ける価値と使う場合

改善

改善できるところがあります:

  • ベンダーバンドルをCDNに載せて、速さとサーバロードを比較
  • my-moduleを動的にロードする。必要でなければ、ロードしません
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
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  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