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

この記事で「バンドルスプリッティング」というのを紹介します。２つの簡単な例をあげて、それぞれの価値を説明します。ソースコードが[こちら](https://github.com/lmiller1990/webpack-splitting-example)です。

## `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アプリを作ります：


```js
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-app`を`src/index/js`に`import`して呼び出します：

```js
import createApp from "./create-app"

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

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

```js
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.js`は`236 KiB`！「ハローワールド」だけのために大きなバンドルです。ローカルで書いたコードは１０行くらいだけでした。自分が書いたコードとベンダー（この例では`vue.js`）を２つのファイルに分けられます。

## バンドルを２つに分ける

`webpack.config.js`を更新します：

```js
const webpack = require("webpack")

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

`optimization`のドキュメントは[こちら](https://survivejs.com/webpack/building/bundle-splitting/)。結果は`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`を作って、バンドルした２つのファイルを使ってみます。

```html
<!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`のネットワークタブを開いて、２つのバンドルが見えます。

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

## ローカルコードを分ける

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

```
touch src/my-module-1.js
```

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


```js
export default function greetingOne() {
  const el = document.createElement("div")
  
  el.innerText = "Hello from my module!"

  document.body.appendChild(el)
}

```


２回コピーします：

```
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`も更新します：



```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`が３つあって、`AggressiveSplittingPlugin`を使ってそれぞれのファイルに分けてみましょう。


```js
// ...

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

  // ...

}
```


`minSize`と`maxSize`は`bytes`です。１個の`my-module`は162 bytesなので、`maxSize`を`200`にすると、`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]
```

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

## まとめ

この記事で学んだこと：

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

## 改善

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

- ベンダーバンドルをCDNに載せて、速さとサーバロードを比較
- `my-module`を動的にロードする。必要でなければ、ロードしません