In this article, I will introduce the concept of bundle splitting, and provide two simple examples that illustrate how to implement bundle spltting and it’s benefits.
The source code for this article is here.
The optimization.splitChunks
option allows the main.js
generated by webpack by default to be split into multiple chunks - in other words, multiple files. Let’s see the most common use case: splitting initial and vendor assets.
vendor assets: assets used from external providers, for example imported from node_modules
. Code that isn’t unique to your application.
initial assets: assets which are unique to your application, such as your business logic, that you will probably change over time.
Firstly, a simple example. We are building a hello world Vue application, and want to use bundle splitting to improve the performance of our application, and reduce the load on our server.
Create a package.json
run running echo {} >> package.json
, and install wepback and vue:
npm install webpack webpack-cli vue --save
And create a webpack config and entry point:
touch webpack.config.js
mkdir src
touch src/index.js
touch src/create-app.js
In src/create-app.js
, create a new Vue app as follows:
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")
})
}
Import create-app
and execute it in src/index.js
:
import createApp from "./create-app"
document.addEventListener("DOMContentLoaded", () => {
createApp()
})
Add a minimal webpack config:
const webpack = require("webpack")
module.exports = {
}
Now run npx webpack --mode development
to bundle using the default settings:
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
- that’s a large payload, just for hello world
. We only wrote about 10 lines of code, though. The rest is all of vue.js
. We can split the vendor code (vue.js
) and our code src/index.js
.
Update webpack.config.js
:
const webpack = require("webpack")
module.exports = {
optimization: {
splitChunks: {
cacheGroups: {
commons: {
test: /[\\/]node_modules[\\/]/,
name: "vendor",
chunks: "initial",
}
}
}
}
}
More details about optimization
are here. This basically wll bundle any code from node_modules
into a file called vendor.js
, and our own code into main.js
.
Run npx wepback --mode development
again:
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
Much better. main.js
is only 6.62 KiB (and will be even smaller once bundled with --mode production
.
Now we can serve vendor.js
from a CDN, reducing the amount of code our server has to transfer, and speeding up the delivery of long term cacheable assets. Let’s make a quick index.html
to see this working:
touch index.html
And inside index.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>
Run a server with python -m SimpleHTTPServer
, and access localhost:8000
. You should see “Hello world”. Inspect the devtools > network tab, you can see the bundles:
Name Size
main.js 7.6 KB
vendor.js 231 KB
We can also split our own code into multiple pieces. Let’s make a few simple functions, and split them up.
touch src/my-module-1.js
Inside, add a greet
function:
export default function greetingOne() {
const el = document.createElement("div")
el.innerText = "Hello from my module!"
document.body.appendChild(el)
}
We want a few more chunks to split. Copy the module twice:
cp src/my-module-1.js src/my-module-2.js
cp src/my-module-1.js src/my-module-3.js
To make it clear what is going on, update src/index.js
and import the new modules we made, while commenting out create-app
:
// 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()
firstGreeting()
secondGreeting()
thirdGreeting()
})
Try building this with 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]
We can see each my-module
, after compilation, comes out at 162 bytes. Let’s split each into their own chunk with AggressiveSplittingPlugin
. Update webpack.config.js
:
// ...
module.exports = {
plugins: [
new webpack.optimize.AggressiveSplittingPlugin({
minSize: 100,
maxSize: 200,
})
],
// ...
}
minSize
and maxSize
are bytes. Each my-module
is 162 bytes. By specifying a maxSize
of 200, each my-module
will be split into it’s own chunk, in order not to exceed the maxSize
of 200. Compile again with 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]
Now each my-module
has it’s own chunk. The combined size of the separate chunks is a lot large, because of all the boilerplate webpack adds (inspect any file to see). For these tiny modules, it doesn’t make sense to split the code, but it does serve is a simple example. Simply import each chunk as a <script>
tag if you want to see this working.
In a large complex application though, splitting can be very beneficial. For example, each chunk could be separate page, which is asynchronously loaded only when a user visits that page. This is something I will discuss in a future article.
We learned how to
Some improvements could be:
The source code for this article is here.