- 1. Webpack 4 course – part one. Entry, output and ES6 modules
- 2. Webpack 4 course – part two. Using loaders to handle scss, image files and transpile JS
- 3. Webpack 4 course – part three. Working with plugins
- 4. Webpack 4 course – part four. Code splitting with SplitChunksPlugin
- 5. Webpack 4 course – part five. Built-in optimization for production
- 6. Webpack 4 course – part six. Increasing development experience
- 7. Webpack 4 course – part seven. Decreasing the bundle size with tree shaking
- 8. Webpack 4 course – part eight. Dynamic imports with prefetch and preload
In this part of the Webpack 4 course, we will focus on increasing the experience of users of your application by making our output smaller. This will mean handling the code in a different way in the production environment. Today we will explain the built-in optimization in Webpack achieved by the mode parameter, so let’s go!
Webpack 4 course: why optimize code?
First, let’s answer the question of why would you want to optimize your code at all. If you follow good practice, you probably aim for your code to be easily readable, therefore you add a lot of whitespaces (tabs, spaces, newlines) and comments. While it improves your code, it makes your files bigger. On the other hand, sacrificing readability for the sake of user experience is not the way to go. Doing it manually would be cumbersome. Because of that, there are solutions that you can just pick up and use in your project.
Mode: production
The mode is a parameter that Webpack 4 introduced. The configuration requires specifying it ever since. Not doing it will cause a warning and its value will fall back to the default value, which is production. If you use mode: "production", Webpack will set some configuration for you. As a result, your output code will better fit the production. We will now walk through what it exactly does for us.
UglifyJsPlugin
Setting your mode value to production will add the UglifyJsPlugin to your configuration. It can make your code shorter and faster by compressing and minifying it. From tasks as simple as shortening your variable names and removing whitespaces to deleting redundant code. By default, it will parse every .js file. In this article, we will go through the most basic configuration of the UglifyJSPlugin. Even though Webpack 4 runs the optimization for you depending on the chosen mode, you can still configure it through the optimization property.
webpack.config.js
1 2 3 4 5 6 7 8 9 10 11 12 |
const UglifyJsPlugin = require('uglifyjs-webpack-plugin'); module.exports = { mode: "production", // using mode: "production" attaches the following configuration: optimization: { minimize: true, minimizer: [ new UglifyJsPlugin() ] }, } |
The most crucial property that you can pass to the UglifyJsPlugin is called uglifyOptions. It has a lot of default configuration. One of the most noteworthy parts is the compress property.
1 2 3 4 5 6 7 |
new UglifyJsPlugin({ uglifyOptions: { compress: { /*(...)*/ } } }) |
It is responsible for configuring a lot of heavy lifting done by the UglifyJsPlugin regarding making your code shorter and lighter. For a complete list of possible options check out the official list. It also marks the default values.
Another important property of the UglifyJsPlugin configuration is the output.
1 2 3 4 5 6 7 8 9 10 |
new UglifyJsPlugin({ uglifyOptions: { compress: { /*(...)*/ }, output: { /*(...)*/ } } }) |
The code generator tries to output the shortest code by default. You can change that behaviour by changing the output configuration. You might not be tempted to change most of the default configuration, but a property worth considering is the drop_console, which is set to false by default. Changing it to true will erase all of your console.log calls. If you would like to know more of the output properties, check out the complete list.
The UglifyJsPlugin has a lot more configuration possible. You can read it all in its documentation on Github.
DefinePlugin
This plugin allows you to create global constants resolved at compile time. If you use mode: "production", webpack will set "process.env.NODE_ENV": JSON.stringify("production") by default:
webpack.config.js
1 2 3 4 5 6 7 8 9 |
module.exports = { mode: "production", // using mode: "production" attaches the following configuration: plugins: [ new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") }), ] } |
Note that due to direct text replacement, the value given to the property must contain actual quotes. This can be done by JSON.stringify("production") or '"production"'.
Resolving it at the compile time means that if you ever use process.env.NODE_ENV in your code, it will be replaced by the value production.
1 2 3 4 |
console.log(process.env.NODE_ENV); if(process.env.NODE_ENV === 'production') { console.log('this is production!') } |
Keep in mind that the process.env.NODE_ENV value is not kept after compiling the code. Running the code above in with webpack will result in:
1 2 3 4 |
console.log("production"); if(true) { console.log("this is production!") } |
After minification done by UglifyJSPlugin, it can be simplified.
1 2 |
console.log("production"); console.log("this is production!") |
NoEmitOnErrorsPlugin
Using this plugin will help you deal with errors during the compilation. For example, there might be a situation in which you try to import a file that Webpack can’t resolve. In this situation, Webpack creates a new version of the application with the information about the error. With the usage of NoEmitOnErrorsPlugin, this version is not created at all.
webpack.config.js
1 2 3 4 5 6 7 8 9 |
const webpack = require('webpack'); module.exports = { mode: "production", // using mode: "production" attaches the following configuration: plugins: [ new webpack.NoEmitOnErrorsPlugin(); ] } |
ModuleConcatenationPlugin
By default, Webpack wraps each module in your bundle in individual function closure. These wrapper functions will make it a bit slower for your JavaScript to execute. Check out this example:
one.js
1 2 |
const dog = 'Fluffy'; export const one = 1; |
two.js
1 2 |
const dog = 'Fluffy'; export const two = 2; |
index.js
1 2 3 4 5 |
import { one } from './one'; import { two } from './two'; const dog = 'Fluffy'; console.log(one, two); |
Without the ModuleConcatenationPlugin, the output bundle will look like that:
main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
(function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony import */ var _one__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); /* harmony import */ var _two__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2); const dog = 'Fluffy'; console.log(_one__WEBPACK_IMPORTED_MODULE_0__["one"], _two__WEBPACK_IMPORTED_MODULE_1__["two"]); /***/ }), /* 1 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "one", function() { return one; }); const dog = 'Fluffy'; const one = 1; /***/ }), /* 2 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "two", function() { return two; }); const dog = 'Fluffy'; const two = 2; /***/ }) /******/ ]); |
When you set your mode to production, the plugin starts doing all the work. Thanks to this, the output bundle is now in one scope. Fewer functions mean less runtime overhead.
Note, that I am not using any minification in this example. Thanks to the fact that minimizer now knows about inter-module dependencies, it could do a better job.
main.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
(function(module, __webpack_exports__, __webpack_require__) { "use strict"; // CONCATENATED MODULE: ./src/one.js const dog = 'Fluffy'; const one = 1; // CONCATENATED MODULE: ./src/two.js const two_dog = 'Fluffy'; const two = 2; // CONCATENATED MODULE: ./src/index.js const src_dog = 'Fluffy'; console.log(one, two); /***/ }) /******/ ]); |
If you find it interesting, check out the article on the webpack blog that announces this feature for more information.
Summary
Today we’ve learned about the built-in optimization that Webpack can do with mode: 'production'. This can make your application load faster and perform better. It is achieved by undergoing a set of processes configured to suit the production needs. In the next part of the course we will cover the development configuration of the mode, so stay tuned!
for the DefinePlugin, if you only config ‘process.env.NODE_ENV’. it is not needed in webpack 4. set mode value will do it for you.
https://webpack.js.org/concepts/mode/#mode-development
https://webpack.js.org/concepts/mode/#mode-production
This is exactly what I’ve meant. I will rephrase it a bit differently so that it is clearer. Thanks!
It seems that Webpack 5 will favor Terser over Uglify, due to better performance and package maintainability
drop_console
is a property of Compress options, not Output options.