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 | { |
如同上面提到的,如果所有代码都不包含副作用,我们就可以简单地将该属性标记为 false
,来告知 webpack,它可以安全地删除未用到的 export 导出。
「副作用」的定义是,在导入时会执行特殊行为的代码,而不是仅仅暴露一个 export 或多个 export。举例说明,例如 polyfill,它影响全局作用域,并且通常不提供 export。
压缩输出
通过如上方式,我们已经可以通过 import
和 export
语法,找出那些需要删除的“未使用代码(dead code)”,然而,我们不只是要找出,还需要在 bundle 中删除它们。为此,我们将使用 -p
(production) 这个 webpack 编译标记,来启用 uglifyjs 压缩插件。
注意,
--optimize-minimize
标记也会在 webpack 内部调用UglifyJsPlugin
。
从 webpack 4 开始,也可以通过 "mode"
配置选项轻松切换到压缩输出,只需设置为 "production"
。
webpack.config.js
1 | const path = require('path'); |
注意,也可以在命令行接口中使用
--optimize-minimize
标记,来使用UglifyJSPlugin
。为了学会使用 tree shaking,你必须……
使用 ES2015 模块语法(即
import
和export
)。在项目
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,
},
};