This guide extends the examples provided in Getting Started and Output Management. Please make sure you are at least familiar with the examples provided in them.
Code splitting is one of the most compelling features of webpack. This feature allows you to split your code into various bundles which can then be loaded on demand or in parallel. It can be used to achieve smaller bundles and control resource load prioritization which, if used correctly, can have a major impact on load time.
There are three general approaches to code splitting available:
entry
configuration.CommonsChunkPlugin
to dedupe and split chunks.This is by far the easiest, and most intuitive, way to split code. However, it is more manual and has some pitfalls we will go over. Let's take a look at how we might split another module from the main bundle:
project
webpack-demo
|- package.json
|- webpack.config.js
|- /dist
|- /src
|- index.js
+ |- another-module.js
|- /node_modules
another-module.js
import _ from 'lodash';
console.log(
_.join(['Another', 'module', 'loaded!'], ' ')
);
webpack.config.js
const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
plugins: [
new HTMLWebpackPlugin({
title: 'Code Splitting'
})
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
This will yield the following build result:
Hash: 309402710a14167f42a8
Version: webpack 2.6.1
Time: 570ms
Asset Size Chunks Chunk Names
index.bundle.js 544 kB 0 [emitted] [big] index
another.bundle.js 544 kB 1 [emitted] [big] another
[0] ./~/lodash/lodash.js 540 kB {0} {1} [built]
[1] (webpack)/buildin/global.js 509 bytes {0} {1} [built]
[2] (webpack)/buildin/module.js 517 bytes {0} {1} [built]
[3] ./src/another-module.js 87 bytes {1} [built]
[4] ./src/index.js 216 bytes {0} [built]
As mentioned there are some pitfalls to this approach:
The first of these two points is definitely an issue for our example, as lodash
is also imported within ./src/index.js
and will thus be duplicated in both bundles. Let's remove this duplication by using the CommonsChunkPlugin
.
The CommonsChunkPlugin
allows us to extract common dependencies into an existing entry chunk or an entirely new chunk. Let's use this to de-duplicate the lodash
dependency from the previous example:
webpack.config.js
const path = require('path');
+ const webpack = require('webpack');
const HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
index: './src/index.js',
another: './src/another-module.js'
},
plugins: [
new HTMLWebpackPlugin({
title: 'Code Splitting'
- })
+ }),
+ new webpack.optimize.CommonsChunkPlugin({
+ name: 'common' // Specify the common bundle's name.
+ })
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
With the CommonsChunkPlugin
in place, we should now see the duplicate dependency removed from our index.bundle.js
. The plugin should notice that we've separated lodash
out to a separate chunk and remove the dead weight from our main bundle. Let's do an npm run build
to see if it worked:
Hash: 70a59f8d46ff12575481
Version: webpack 2.6.1
Time: 510ms
Asset Size Chunks Chunk Names
index.bundle.js 665 bytes 0 [emitted] index
another.bundle.js 537 bytes 1 [emitted] another
common.bundle.js 547 kB 2 [emitted] [big] common
[0] ./~/lodash/lodash.js 540 kB {2} [built]
[1] (webpack)/buildin/global.js 509 bytes {2} [built]
[2] (webpack)/buildin/module.js 517 bytes {2} [built]
[3] ./src/another-module.js 87 bytes {1} [built]
[4] ./src/index.js 216 bytes {0} [built]
Here are some other useful plugins and loaders provided by the community for splitting code:
ExtractTextPlugin
: Useful for splitting CSS out from the main application.bundle-loader
: Used to split code and lazy load the resulting bundles.promise-loader
: Similar to the bundle-loader
but uses promises.TheCommonsChunkPlugin
is also used to split vendor modules from core application code using explicit vendor chunks.
Two similar techniques are supported by webpack when it comes to dynamic code splitting. The first and more preferable approach is use to the import()
syntax that conforms to the ECMAScript proposal for dynamic imports. The legacy, webpack-specific approach is to use require.ensure
. Let's try using the first of these two approaches...
import()
calls use promises internally. If you useimport()
with older browsers, remember to shimPromise
using a polyfill such as es6-promise or promise-polyfill.
Before we start, let's remove the extra entry
and CommonsChunkPlugin
from our config as they won't be needed for this next demonstration:
webpack.config.js
const path = require('path');
- const webpack = require('webpack');
const HTMLWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: {
+ index: './src/index.js'
- index: './src/index.js',
- another: './src/another-module.js'
},
plugins: [
new HTMLWebpackPlugin({
title: 'Code Splitting'
- }),
+ })
- new webpack.optimize.CommonsChunkPlugin({
- name: 'common' // Specify the common bundle's name.
- })
],
output: {
filename: '[name].bundle.js',
+ chunkFilename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
Note the use of chunkFilename
, which determines the name of non-entry chunk files. For more information on chunkFilename
, see output documentation. We'll also update our project to remove the now unused files:
project
webpack-demo
|- package.json
|- webpack.config.js
|- /dist
|- /src
|- index.js
- |- another-module.js
|- /node_modules
Now, instead of statically importing lodash
, we'll use dynamic importing to separate a chunk:
src/index.js
- import _ from 'lodash';
-
- function component() {
+ function getComponent() {
- var element = document.createElement('div');
-
- // Lodash, now imported by this script
- element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+ return import(/* webpackChunkName: "lodash" */ 'lodash').then(_ => {
+ var element = document.createElement('div');
+
+ element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+
+ return element;
+
+ }).catch(error => 'An error occurred while loading the component');
}
- document.body.appendChild(component());
+ getComponent().then(component => {
+ document.body.appendChild(component);
+ })
Note the use of webpackChunkName
in the comment. This will cause our separate bundle to be named lodash.bundle.js
instead of just [id].bundle.js
. For more information on webpackChunkName
and the other available options, see the import()
documentation. Let's run webpack to see lodash
separated out to a separate bundle:
Hash: a27e5bf1dd73c675d5c9
Version: webpack 2.6.1
Time: 544ms
Asset Size Chunks Chunk Names
lodash.bundle.js 541 kB 0 [emitted] [big] lodash
index.bundle.js 6.35 kB 1 [emitted] index
[0] ./~/lodash/lodash.js 540 kB {0} [built]
[1] ./src/index.js 377 bytes {1} [built]
[2] (webpack)/buildin/global.js 509 bytes {0} [built]
[3] (webpack)/buildin/module.js 517 bytes {0} [built]
If you've enabled async
functions via a pre-processor like babel, note that you can simplify the code as import()
statements just return promises:
src/index.js
- function getComponent() {
+ async function getComponent() {
- return import(/* webpackChunkName: "lodash" */ 'lodash').then(_ => {
- var element = document.createElement('div');
-
- element.innerHTML = _.join(['Hello', 'webpack'], ' ');
-
- return element;
-
- }).catch(error => 'An error occurred while loading the component');
+ var element = document.createElement('div');
+ const _ = await import(/* webpackChunkName: "lodash" */ 'lodash');
+
+ element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+
+ return element;
}
getComponent().then(component => {
document.body.appendChild(component);
});
Once you start splitting your code, it can be useful to analyze the output to check where modules have ended up. The official analyze tool is a good place to start. There are some other community-supported options out there as well:
See Lazy Loading for a more concrete example of how import()
can be used in a real application and Caching to learn how to split code more effectively.