Configure Webpack for Production Build — Part II

Sherry Hsu
5 min readMay 31, 2018

To learn about Auto-versioning, please see Configure Webpack for Production Build — Part I

In the production build, we want to make sure the application loads as fast as possible for great user experience. ie. good performance!

A few techniques we could use to enable our applications to load faster:

  1. Minification
  2. Tree Shaking
  3. Bundle Splitting
  4. Code Splitting

Minification

The UglifyJSPlugin is one of the most commonly used Webpack Plugin for minifying source code. It could be added via explicitly requiring and defining the plugin or simply by adding -p in the CLI.

// package.json{
"scripts": {
"build": "NODE_ENV=development webpack -p
}

Tree Shaking

Tree shaking is a technique used for eliminating dead code. The dead code is found through static analysis of ES6 import/ export where the imported yet unused modules will be “shook off”. Tree shaking is done automatically by UglifyJSPlugin when ES6 import/ export is used.

To examine the change in the code size before and after tree-shaking, we can use webpack-bundle-analyzer
https://www.npmjs.com/package/webpack-bundle-analyzer

// install 
> npm install -D webpack-bundle-analyzer
// produce the static analysis profile and run webpack-bundle-analyzer to show the bundle size visually
> npm run analyse

// package.json
{ "scripts": { "analyse": "NODE_ENV=production webpack --config webpack.prod.js --profile --json > stats-prod.json && webpack-bundle-analyzer stats-prod.json dist/"
}
}

One great example of tree shaking is the removal of all the unused Lodash modules after tree shaking.

Before tree shaking, main.js = 2.85mb

After tree shaking, the bundle size has got a lot smaller with main.js shrinks to 1.26MB. Lodash has gone from 527KB to 70 KB.

After tree shaking, the bundle size has got a lot smaller!

Bundle Splitting

Bundle splitting is also an effective way of reducing the main application bundle size by separating vendor code into a separate file. Since the vendor files don’t change as often as our code, users can cache vendor files for longer and request less when updating cache of our code.

One common way to do bundle splitting is to downsize main.js by separating vendor.js, main.css, manifest.js and source maps.

Vendor.js

In Webpack 3, we use CommonsChunkPlugin to separate vendor.js. The equivalent in Webpack 4 is SplitChunksPlugin. CommonsChunkPlugin looks at all entry points and check for common dependencies between them and separate them out into own bundles. In CommonsChunkPlugin, the minChunks option decides what modules are added to the newly created bundle. If minChunks = 2, it means the dependency must be used in at least 2 chunks to be made into a common dependency. minChunks also accepts a function, that takes in each individual module as an argument. We can use the function to check whether the module resides inside node_modules, ie. whether it is a vendor module and place the module in vendor.js accordingly.

Manifest.js

To extract the manifest file, we use CommonsChunkPlugin with a name that is not define in the entry chunk. Once the Webpack bootstrap logic is extracted into a separate manifest file, the bundle hash of main.js will only get updated when there is an actual change in the application code.

// 1. put everything inside node_modules inside vendor.js
// 2. create the manifest file

module.exports = {
plugins:[
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: module => module.context && module.context.indexOf('node_modules') !== -1
}),
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
})
]
}

main.css

To extract the css files out into a separate file, we can use ExtractTextPlugin on the CSS rules. In my project where I used SCSS for application code and external CSS libraries, I could define 2 functions with ExtractTextPlugin, one to extract the main.css and another one to extract vendor.css. We can also minify the extracted CSS file by setting the minimize option to true.

Source Maps

One thing to remember to separate is the source maps. Source map maps the transformed JS code to the original code so that we can debug the original code on the browser. There are 2 type of source maps: inline source map and separate source map.

Inline source maps — faster, but also make bundle size larger. Suits Development.

Separate source maps — slower, but keep the bundle size smaller. Suits Production. Source maps might not even be needed in Production.

// developmentmodule.exports={
devtool: 'cheap-module-eval-source-map'
}
// production
module.exports={
devtool: 'source-map'
}

https://webpack.js.org/configuration/devtool/#devtool

Code Splitting

Code splitting is similar to bundle splitting. According to Webpack documentation, it can be done in 3 different ways:

  1. Manually Setting the entry points
  2. CommonChunkPlugin ( this is like bundle splitting)
  3. Dynamic Import. This enables us to load Components later when user performs an action or when we predict user is going to perform an action. This speeds up initial loading as not all resources are loaded at once, some resources may not even be loaded (lazy loading!)

The benefit of code splitting in Dynamic Import is Lazy Loading. Not all codes need to be loaded at once initially. Some can load later when user performs an action or some may never be loaded.

However, it can not be done automatically by Webpack. We need to exercise judgement and perform manual loading in places where it makes sense.

A few examples can be seen in:

--

--

Sherry Hsu

A software engineer passionate about learning and growth