In this guide, we'll explore some of the best practices and utilities for building a production site or application.
[!TIP] This walkthrough builds on Tree Shaking and Development. Make sure you're familiar with the concepts and setup from those guides before continuing.
The goals of development and production builds differ greatly. In development, we want strong source mapping and a localhost server with live reloading or hot module replacement. In production, our goals shift toward minified bundles, lighter-weight source maps, and optimized assets to improve load time. Because of this clear separation, we generally recommend writing separate webpack configurations for each environment.
While we'll split out the production- and development-specific bits, we'll still keep a "common" configuration to stay DRY. To merge these configurations together, we'll use a utility called webpack-merge. With the common configuration in place, we won't have to duplicate code in the environment-specific configurations.
Let's start by installing webpack-merge and splitting out the work from previous guides:
npm install --save-dev webpack-mergeIn webpack.common.js, we now have our entry and output configuration along with any plugins required by both environments. In webpack.dev.js, we've set mode to development, added the recommended devtool for that environment (strong source mapping), and configured our devServer. Finally, in webpack.prod.js, mode is set to production, which loads the MinimizerPlugin first introduced in the tree shaking guide.
Note the merge() calls in the environment-specific configurations, which pull in our common configuration in both webpack.dev.js and webpack.prod.js. webpack-merge offers a variety of advanced merging features, but we won't need any of them for this use case.
Now let's update our npm scripts to use the new configuration files. The start script, which runs webpack-dev-server, will use webpack.dev.js, and the build script, which runs webpack to create a production build, will use webpack.prod.js:
{
"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": "^7.1.3",
"csv-loader": "^3.0.5",
"express": "^5.2.1",
"html-webpack-plugin": "^5.6.6",
"style-loader": "^4.0.0",
"webpack": "^5.105.0",
"webpack-cli": "^7.0.0",
"webpack-dev-middleware": "^8.0.3",
"webpack-dev-server": "^5.2.3",
"webpack-merge": "^6.0.1",
"xml-loader": "^1.2.1"
}
}Feel free to run these scripts and watch how the output changes as we continue building out our production configuration.
Many libraries key off the process.env.NODE_ENV variable to decide what to include. For example, when process.env.NODE_ENV is not set to 'production', some libraries add extra logging and testing to ease debugging; when it is set to 'production', they may drop or add significant portions of code to optimize for your real users. Since webpack v4, specifying mode automatically configures process.env.NODE_ENV for you via DefinePlugin:
import { merge } from 'webpack-merge';
import common from './webpack.common.js';
export default merge(common, {
mode: 'production',
});[!TIP] Technically,
NODE_ENVis a system environment variable that Node.js exposes to running scripts. By convention, server tools, build scripts, and client-side libraries use it to determine dev-versus-prod behavior. Contrary to expectations,process.env.NODE_ENVis not set automatically within the build scriptwebpack.config.jswhen running webpack. As a result, conditionals likeprocess.env.NODE_ENV === 'production' ? '[name].[contenthash].bundle.js' : '[name].bundle.js'won't work in webpack configurations unless you setNODE_ENVexplicitly, for example withNODE_ENV=productionvia the CLI.
If you use a library like react, you should see a significant drop in bundle size after adding DefinePlugin. Also note that any of our local /src code can key off this as well, so the following check is valid:
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+ minifies your code by default in production mode.
While the MinimizerPlugin is a great starting point and is used by default, other options exist:
If you decide to try another minification plugin, make sure your choice also drops dead code as described in the tree shaking guide, and provide it via optimization.minimizer.
We encourage you to enable source maps in production, as they're useful for debugging as well as for running benchmark tests. That said, choose one with a reasonably quick build speed that's recommended for production use (see devtool). For this guide, we'll use the source-map option in production, as opposed to the inline-source-map we used in development:
import { merge } from 'webpack-merge';
import common from './webpack.common.js';
export default merge(common, {
mode: 'production',
+ devtool: 'source-map',
});[!TIP] Avoid
inline-***andeval-***in production, as they can increase bundle size and reduce overall performance.
It's crucial to minimize your CSS for production. See the Minimizing 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, and mode can be set with --mode. Run npx 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 greater configurability.