What is a tree – shaking

The figure above illustrates the intent of tree-shaking. In the front-end world, tree-shaking means removing unreferenced module code and reducing code size to improve page performance. In traditional programming languages, the concept of dead-code-elimination(DCE) eliminates code that will not be executed ina program, as in:

if(1<0){
    // dead code
}
Copy the code

What the front-end world calls tree-shaking is essentially DCE, eliminating useless JS as much as possible and reducing resource size.

My tree-shaking is not working

One of the first to do this is rollup. See the tree-shaking effect of rollup. After WebPack 2, we added tree-shaking to Webpack. When we thought that Webpack 666 was cool, we found that after the new update of Webpack, we set up the following: webpack.config.js

mode: 'development',
  context: path.resolve(__dirname, './'),
  entry: {
    index: ['./src/index.jsx'],
  },
Copy the code

index.js:

import { square } from "./math";
import {add} from './fer-gen/lib/user';

console.log(add(1))
Copy the code

math.js:

export function square(a) {
    return a * a;
}
export function add(a){
    return a + a
}
Copy the code

There is no tree-shaking in the bundle. Square is still there. Is Webpack a shaker? When webpack does tree-shaking, there are several conditions:

  1. The module system must be ESmodule.

  2. Identify the sideEffect field in the package.json file.

  3. Set webPack as the production environment.

Sure enough, after the above configuration, the square function was not found in the last bundle, and it worked! So why should there be these provisions, let’s talk about.

{
  "presets": [
    [ "env"]
  ]
}
Copy the code

The module system must be ESmodule

Why should it be es module system, not CommonJS? Yes, only ES module systems can, because ESModule is static and dependencies are determined at compile time. Commonjs is the default module system for the Node environment and is dynamic. Webpack only does this at compile time, so only the ES module will do it. However, in real projects, we typically process the source code with a series of loaders, including the god-like Babel-Loader, before going into Webpack optimization. We use the following Babel configuration: once the same source code as above is packaged with Webpack, we can see that tree-shaking is disabled again. Look at Babel after the conversion:

"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
exports.square = square;
exports.add = add;
function square(a) {
    return a * a;
}
function add(a) {
    return a + a;
}
Copy the code

The problem is that modules are converted to CJS by default when preset is ENV. Let’s configure Babel as follows:

{
  "presets": [
    [ "env",{modules:false}]
  ]
}
Copy the code

This way Babel will not convert our original ESmodule to another module system.

For TS projects, we usually use ts-loader to process TS files. For TS, we also need to set the module in tsconfig.json to ‘esnext’ or ‘es2015’:

{
 "compilerOptions": {
    module:'esnext'
    }
}
Copy the code

Can Webpack recognize es6+ syntax? Not before, but now. Before it was tree-shaking code obfuscation, it was UglifyJS, now it’s Terser, and the difference between the two is that Terser supports ES6 + syntax, so now webPack recognizes and packages our code even if we don’t use Babel.

side-effect

Why does WebPack require tree-shaking packages to be side-effect-free?

First, let’s review side-effect. In React, we often talk about purely functional components, such as:

function({name}){
    return <div>{name}</div>
}
Copy the code

In the above code, we “generally” say that the above component is pure and causes no side effects, and look at the following:

Function () {name} {window. The test = name; return <div>{name}</div> }Copy the code

This is where the function has a side effect, because it changes all the variables. So in a nutshell, side effects are what happens when the code runs: causing IO, changing the value of other variables, etc.

Back to webpack, we’re talking about the side effects of modules, for example, here’s a module:

Export a = 1; window.test=a;Copy the code

This module is impure, because when you introduce it, you change all the variables. Webpack does not tree-shaking, or even code obfuscation, for modules with side effects! .

In fact, the current version of WebPack’s tree-shaking is pretty good. Even if we overwrite all variables directly in a module, WebPack packs this side effect into the bundle, but other modules under that module are still removed.

math.js

export function add(a){
    return a+a
}
export function plus({a}){
    window.test=1212
    return a*a
}
plus({a:1231})
Copy the code

index.js

import { square,} from "./math";
console.log( square(1))
Copy the code

output:

[
    function(e, t, n) {
        'use strict';
        n.r(t),
            (window.test = 1212),
            console.log(
                (function(e) {
                    return e * e;
                })(1)
            );
    }
]
Copy the code

So what other modules are considered side effects in WebPack? Eval is also considered impure, even under a function:

export function(){
    eval('var a =1')
}
Copy the code

2. Assign the same attribute twice in the top-level scope:

export const name={first:'my_name'};
name.first='my_name1';
name.first='my_name1';
Copy the code

This is a very strange phenomenon. In principle, when a property is read or written, it can trigger getters and setters, which can have side effects. But why is it considered to have side effects after two setters? It’s not clear why.

Tree-shaking Related configurations

Webpack 4 began with the introduction of mode options, greatly lowering the barrier to entry for WebPack. In production, Tree-shaking is enabled for Webpack by default. The configuration for tree-shaking is as follows:

   optimization: {
        providedExports: false,
        usedExports: true,
        sideEffects: true,
        minimizer: [
            new TerserPlugin({
                cache: true,
                parallel: true
            })
        ]
    },
Copy the code

ProvidedExports and usedExports indicate which methods of a module are exported during webpack compilation. SideEffects allows you to flag files in package.json that are not used. The TerserPlugin is the ultimate removal of useless code.

Any other questions?

If we followed the preconditions above and didn’t write code with side effects, my code would have improved a lot. And then not!

Tree-shaking doesn’t work for third-party libraries

The problem is that the above optimizations only work for the code you write in your project, and the third-party library code you bring in is usually not esModule, so tree-shaking doesn’t work at all.

For library users, we can only expect library authors to provide libraries that support the ES version. For library owners, it is best to package es bundles (using rollup packaging) or add optimization to load on Demand (ANTD).

Due to historical reasons, generally the package entry file is not es module, so it would be risky to directly change the entry file to ES module file. Therefore, there is also a proposal that the es module entry can be added in package.json, and the ES module can be used for reference in the mode of ES module, so as to be compatible with the old version.

2, cannot shake a method that is not referenced in class.

Either webpack or rollup, they will only analyze keywords with esModule, that is, shake only unreferenced export, useless classMethod, or prototypeMethod. Such as: math. Js

export class MyMath{
    addOne(a){
        return a+1
    }
    square(a){
        return a*a
    }
}
Copy the code

index.js

import {MyMath} from './math'
const math=new Math();
math.add(1);
Copy the code

After the above code is packaged, Buddle still has the addOne method, Webpack is powerless to do anything about this situation. In this case, there are two solutions: 1. Change classMethod to a normal function; Let webpack support method-tree-shaking; Method 1, although it can be solved, is obviously not suitable for general projects, for library developers, can be an option.

For method 2, as mentioned above, Webpack is analyzed according to es keywords to mark which modules are used. Similarly, can we add relevant “keywords” to class methods manually? Such as:

export class MyMath{
    addOne(a){
        return a+1
    }
    /*@pure*/square(a){
        return a*a
    }
}
Copy the code

In theory, it is possible to collect this information during compilation and remove the useless method during optimization. However, it also needs the cooperation of multiple parties.

summary

As you can see, achieving a perfect tree-shaking is not an easy task, and there are still many problems that need to be solved by multiple parties.