この記事で「バンドルスプリッティング」というのを紹介します。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-app
をsrc/index/js
にimport
して呼び出します:
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.js
は236 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,
})
],
// ...
}
minSize
とmaxSize
はbytes
です。1個の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]
4つのファイルに分けました。my-module
が3つあって、そしてsrc/index.js
。分けたら全体のファイルサイズが少し大きくなるので、こんなに小さいモジュールを分ける価値があまりないですが、大きなプロジェクトなら価値があります。例えば、SPAのページを一個ずつバンドルして、ページにアクセスするところで動的にロードできます。そうすると、最初のバンドルをできるだけ小さくして、コードが必要となる時だけにロードできます。
まとめ
この記事で学んだこと:
- ベンダー、ローカルコードを分けること
-
AggressiveSplittingPlugin
を使ってローカルコードを分けること - コードを分ける価値と使う場合
改善
改善できるところがあります:
- ベンダーバンドルをCDNに載せて、速さとサーバロードを比較
-
my-module
を動的にロードする。必要でなければ、ロードしません