In this guide, we'll dive into some of the best practices and utilities for building a production site or application.
This walkthrough stems fromTree Shaking andDevelopment. Please ensure you are familiar with the concepts/setup introduced in those guides before continuing on.
The goals ofdevelopment andproduction builds differ greatly. Indevelopment, we want strong source mapping and a localhost server with live reloading or hot module replacement. Inproduction, our goals shift to a focus on minified bundles, lighter weight source maps, and optimized assets to improve load time. With this logical separation at hand, we typically recommend writingseparate webpack configurations for each environment.
While we will separate theproduction anddevelopment specific bits out, note that we'll still maintain a "common" configuration to keep things DRY. In order to merge these configurations together, we'll use a utility calledwebpack-merge. With the "common" configuration in place, we won't have to duplicate code within the environment-specific configurations.
Let's start by installingwebpack-merge and splitting out the bits we've already worked on in previous guides:
npminstall --save-dev webpack-mergeproject
webpack-demo |- package.json |- package-lock.json- |- webpack.config.js+ |- webpack.common.js+ |- webpack.dev.js+ |- webpack.prod.js |- /dist |- /src |- index.js |- math.js |- /node_moduleswebpack.common.js
+ const path = require('path');+ const HtmlWebpackPlugin = require('html-webpack-plugin');++ module.exports = {+ entry: {+ app: './src/index.js',+ },+ plugins: [+ new HtmlWebpackPlugin({+ title: 'Production',+ }),+ ],+ output: {+ filename: '[name].bundle.js',+ path: path.resolve(__dirname, 'dist'),+ clean: true,+ },+ };webpack.dev.js
+ const { merge } = require('webpack-merge');+ const common = require('./webpack.common.js');++ module.exports = merge(common, {+ mode: 'development',+ devtool: 'inline-source-map',+ devServer: {+ static: './dist',+ },+ });webpack.prod.js
+ const { merge } = require('webpack-merge');+ const common = require('./webpack.common.js');++ module.exports = merge(common, {+ mode: 'production',+ });Inwebpack.common.js, we now have setup ourentry andoutput configuration and we've included any plugins that are required for both environments. Inwebpack.dev.js, we've setmode todevelopment. Also, we've added the recommendeddevtool for that environment (strong source mapping), as well as ourdevServer configuration. Finally, inwebpack.prod.js,mode is set toproduction which loadsTerserPlugin, which was first introduced by thetree shaking guide.
Note the use ofmerge() calls in the environment-specific configurations to include our common configuration inwebpack.dev.js andwebpack.prod.js. Thewebpack-merge tool offers a variety of advanced features for merging but for our use case we won't need any of that.
Now, let's modify our npm scripts to use the new configuration files. For thestart script, which runswebpack-dev-server, we will usewebpack.dev.js, and for thebuild script, which runswebpack to create a production build, we will usewebpack.prod.js:
package.json
{ "name": "development", "version": "1.0.0", "description": "", "main": "src/index.js", "scripts": {- "start": "webpack serve --open",+ "start": "webpack serve --open --config webpack.dev.js",- "build": "webpack"+ "build": "webpack --config webpack.prod.js" }, "keywords": [], "author": "", "license": "ISC", "devDependencies": { "css-loader": "^0.28.4", "csv-loader": "^2.1.1", "express": "^4.15.3", "file-loader": "^0.11.2", "html-webpack-plugin": "^2.29.0", "style-loader": "^0.18.2", "webpack": "^4.30.0", "webpack-dev-middleware": "^1.12.0", "webpack-dev-server": "^2.9.1", "webpack-merge": "^4.1.0", "xml-loader": "^1.2.1" } }Feel free to run those scripts and see how the output changes as we continue adding to ourproduction configuration.
Many libraries will key off theprocess.env.NODE_ENV variable to determine what should be included in the library. For example, whenprocess.env.NODE_ENV is not set to'production' some libraries may add additional logging and testing to make debugging easier. However, withprocess.env.NODE_ENV set to'production' they might drop or add significant portions of code to optimize how things run for your actual users. Since webpack v4, specifyingmode automatically configuresprocess.env.NODE_ENV for you throughDefinePlugin:
webpack.prod.js
const { merge } = require('webpack-merge'); const common = require('./webpack.common.js'); module.exports = merge(common, { mode: 'production', });Technically,NODE_ENV is a system environment variable that Node.js exposes into running scripts. It is used by convention to determine dev-vs-prod behavior by server tools, build scripts, and client-side libraries. Contrary to expectations,process.env.NODE_ENV is not setwithin the build scriptwebpack.config.js automatically when running webpack. Thus, conditionals likeprocess.env.NODE_ENV === 'production' ? '[name].[contenthash].bundle.js' : '[name].bundle.js' won't work in webpack configurations unless you specifyNODE_ENV explicitly withNODE_ENV=production through CLI.
If you're using a library likereact, you should actually see a significant drop in bundle size after addingDefinePlugin. Also, note that any of our local/src code can key off of this as well, so the following check would be valid:
src/index.js
import { cube } from './math.js';++ if (process.env.NODE_ENV !== 'production') {+ console.log('Looks like we are in development mode!');+ } function component() { const element = document.createElement('pre'); element.innerHTML = [ 'Hello webpack!', '5 cubed is equal to ' + cube(5) ].join('\n\n'); return element; } document.body.appendChild(component());Webpack v4+ will minify your code by default inproduction mode.
Note that while theTerserPlugin is a great place to start for minification and being used by default, there are other options out there:
If you decide to try another minification plugin, make sure your new choice also drops dead code as described in thetree shaking guide and provide it as theoptimization.minimizer.
We encourage you to have source maps enabled in production, as they are useful for debugging as well as running benchmark tests. That said, you should choose one with a fairly quick build speed that's recommended for production use (seedevtool). For this guide, we'll use thesource-map option in theproduction as opposed to theinline-source-map we used in thedevelopment:
webpack.prod.js
const { merge } = require('webpack-merge'); const common = require('./webpack.common.js'); module.exports = merge(common, { mode: 'production',+ devtool: 'source-map', });Avoidinline-*** andeval-*** use in production as they can increase bundle size and reduce the overall performance.
It is crucial to minimize your CSS for production. Please see theMinimizing for Production section.
Many of the options described above can be set as command line arguments. For example,optimization.minimize can be set with--optimization-minimize, andmode can be set with--mode. Runnpx webpack --help=verbose for a full list of CLI arguments.
While these shorthand methods are useful, we recommend setting these options in a webpack configuration file for more configurability.