Monday, 13 August, 2018 UTC


Summary

In this part of our webpack 4 course, we will take the idea of optimization even further. We will learn what the tree shaking is and how to use it. You will find out what requirements you need to meet in order for the Webpack 4 tree shaking to work and how can it benefit you. Let’s go!
Webpack 4 tree shaking
First, let’s answer a question what the tree shaking is and how can it benefit us. We often use named imports on files that have a lot of other exports. It might create a case in which we don’t import all the things, but webpack would include a whole module anyway. This is where tree shaking comes in handy because it can help eliminate this dead code for us. Thanks to that, our bundle size can decrease greatly.
If you would like to know more about imports and exports, check out the first part of this course: Entry, output and ES6 modules
For the tree shaking to take place, you need to meet a set of requirements. First of all, ES6 modules have to be used, instead of, for example, the CommonJS. This can already cause you trouble if you are using Babel. It is due to the fact, that its presets are set to transpile any module type into CommonJS by default. You can easily fix it by using 
modules: false
, either through the .babelrc or the webpack.config.js file.
.babelrc
{
  "presets": [
    ["env",
      {
        "modules": false
      }
    ]
  ]
}
webpack.config.js
module: {
    rules: [
        {
            test: /\.js$/,
            exclude: /(node_modules)/,
            use: {
                loader: 'babel-loader',
                options: {
                    presets: ['env', { modules: false }]
                }
            }
        }
    ]
},
If you would like to read more about the babel-loader or the loaders in general, check out the second part of the course.
You need to use UglifyJsPlugin. By default, it is added with the 
mode: "production"
. If you prefer not to use 
mode: "production"
, you can add UglifyJsPlugin manually.
If you are not familiar with the UglifyJsPlugin, check out the fifth part of the course: Built-in optimiziation for production
One more thing to remember is that you need to turn on optimization.usedExports. It is also added with 
mode: "production"
. It tells webpack to determine used exports for each module. Thanks to it, webpack will add additional comments in your bundle such as 
/* unused harmony export */
 that UglifyJsPlugin can later interpret.
Harmony is a code name for ES6 and ES2015
Let’s examine such a case:
utilities.js
export function add(a, b) {
    return a + b;
}
export function subtract(a, b) {
    return a - b;
}
index.js
import { add } from './utilities';

console.log(add(1,2));
console.log(add(3,4));
Running this code without a proper configuration gives us output such as this one:
/*(...)*/

/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "add", function() { return add; });
/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "substract", function() { return substract; });
function add(a, b) {
    return a + b;
}
function subtract(a, b) {
    return a - b;
}

/***/ })
/******/ ]);
As you can see, webpack didn’t tree-shake our bundle! We have both the add and subtract functions here. We will experiment a little and use such configuration:
webpack.config.js
const webpack = require('webpack');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const UglifyJS = require('uglify-es');

const DefaultUglifyJsOptions = UglifyJS.default_options();
const compress = DefaultUglifyJsOptions.compress;
for(let compressOption in compress) {
    compress[compressOption] = false;
}
compress.unused = true;

module.exports = {
    mode: 'none',
    optimization: {
        minimize: true,
        minimizer: [
            new UglifyJsPlugin({
                uglifyOptions: {
                    compress,
                    mangle: false,
                    output: {Damn i
                        beautify: true
                    }
                },
            })
        ],
    }
}
I’ve turned off most of the UglifyJsPlugin options so that we see clearly what happens with our code. Running the above gives us such output:
/* (...) */

/* 0 */
/***/ function() {
    "use strict";
    // CONCATENATED MODULE: ./src/utilities.js
        function add(a, b) {
        return a + b;
    }
    // CONCATENATED MODULE: ./src/index.js
    console.log(add(1, 2));
    console.log(add(3, 4));
    /***/}
/******/ ]);
Thanks to the usage of the optimization.usedExports and the unused option of the UglifyJsPlugin, unnecessary code was removed. Please note, that it is a default behavior in the UglifyJsPlugin, so using it with default configuration will also remove dead code (aside from running many other compressing processes).
Tree shaking libraries
If you intend to tree shake libraries, you need to remember the first thing noted in the previous paragraph: using ES6 modules, which is still not obvious in the libraries. A great example here is lodash. If you look into the code it ships, you can clearly see that it does not use ES6 modules.
Imagine wanting to use the debounce function that lodash provides.
index.js
import _ from 'lodash';

console.log(_.debounce);
Now you have the whole lodash library in your output. No way to avoid that when using 
import _ from 'lodash'
. Don’t worry though! Someone gave it a thought and created a package called lodash-es. It provides the lodash library exported as ES6 modules.
import { debounce } from 'lodash';

console.log(debounce);
Unfortunately, webpack will still fail to tree shake. According to the ECMAScript spec, all child modules need to be evaluated because they could contain side effects. I recommend reading a good explanation by Sean Larking on Stack Overflow (he is a member of the webpack core team). If a package author wants to give notice that his library does not have such side effects, he can do so in the package.json file of the library. If you look at the package.json in the lodash repository, you can see that it has 
"sideEffects": false
. So what is the problem here?
Webpack ignores the sideEffect flag by default and to change that behavior we need to set optimization.sideEffects to true. You can do it manually or through
mode: "production"
.
webpack.config.js
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
    mode: 'none',
    optimization: {
        minimize: true,
        minimizer: [
            new UglifyJsPlugin()
        ],
        usedExports: true,
        sideEffects: true
    },
    plugins: [
        new HtmlWebpackPlugin()
    ]
}
Now the lodash library will be tree-shaken by webpack!
Summary
There are quite a lot of requirements that you must meet for the tree shaking to work. It is a very useful feature to have and it is certainly worth learning. Hopefully thanks to this article you now know how to use it, because it can make your bundle a lot smaller. Just keep in mind that you need to use ES6 modules and the UglifyJsPlugin. Also, remember to configure the optimization configuration, setting usedExports and sideEffects to true.
The post Webpack 4 course – part seven. Decreasing the bundle size with tree shaking appeared first on Marcin Wanago Blog - JavaScript, both frontend and backend.