0%

tree shaking

tree shaking如何工作的呢?

虽然 tree shaking 的概念在 1990 就提出了,但直到 ES6 的 ES6-style 模块出现后才真正被利用起来。

在ES6以前,我们可以使用CommonJS引入模块:require(),这种引入是动态的,也意味着我们可以基于条件来导入需要的代码

但是CommonJS规范无法确定在实际运行前需要或者不需要某些模块,所以CommonJS不适合tree-shaking机制。在 ES6 中,引入了完全静态的导入语法:import。

因为tree shaking只能在静态modules下工作。ECMAScript 6 模块加载是静态的,因此整个依赖树可以被静态地推导出解析语法树。所以在 ES6 中使用 tree shaking 是非常容易的。

tree shaking的原理是什么?

  • ES6 Module引入进行静态分析,故而编译的时候正确判断到底加载了哪些模块

  • 静态分析程序流,判断哪些模块和变量未被使用或者引用,进而删除对应代码

  • webpack中,tree-shaking的实现一是先标记出模块导出值哪些没有被用过,二是使用Terser删除这些没被用到的导出语句,标记过程分为三个步骤:

    (1)Make阶段,收集模块导出变量记录到模块关系依赖图ModuleGraph变量中

    (2)Seal阶段,遍历ModuleGraph标记模块导出变量有没有被使用

    (3)生成产物时,若变量没有被其他模块使用则删除对应导出语句

使用

将文件标记为无副作用(side-effect-free)

在一个纯粹的 ESM 模块世界中,识别出哪些文件有副作用很简单。然而,我们的项目无法达到这种纯度,所以,此时有必要向 webpack 的 compiler 提供提示哪些代码是“纯粹部分”。

这种方式是通过 package.json 的 "sideEffects" 属性来实现的。

1
2
3
4
{
"name": "your-project",
"sideEffects": false
}

如同上面提到的,如果所有代码都不包含副作用,我们就可以简单地将该属性标记为 false,来告知 webpack,它可以安全地删除未用到的 export 导出。

「副作用」的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。举例说明,例如 polyfill,它影响全局作用域,并且通常不提供 export。

压缩输出

通过如上方式,我们已经可以通过 importexport 语法,找出那些需要删除的“未使用代码(dead code)”,然而,我们不只是要找出,还需要在 bundle 中删除它们。为此,我们将使用 -p(production) 这个 webpack 编译标记,来启用 uglifyjs 压缩插件。

注意,--optimize-minimize 标记也会在 webpack 内部调用 UglifyJsPlugin

从 webpack 4 开始,也可以通过 "mode" 配置选项轻松切换到压缩输出,只需设置为 "production"

webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
const path = require('path');

module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
- }
+ },
+ mode: "production"
};

注意,也可以在命令行接口中使用 --optimize-minimize 标记,来使用 UglifyJSPlugin。为了学会使用 tree shaking,你必须……

  • 使用 ES2015 模块语法(即 importexport)。

  • 在项目 package.json 文件中,添加一个 “sideEffects” 入口。

  • 引入一个能够删除未引用代码(dead code)的压缩工具(minifier)(例如 UglifyJSPlugin)。

  • 配置optimization对象的usedExports为true

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // webpack.config.js
    module.exports = {
    entry: "./src/index",
    mode: "production",
    devtool: false,
    optimization: {
    usedExports: true,
    },
    };