Tree shaking(摇树)你必须要知道的事情

2,012 阅读5分钟

简单地说,摇树意味着从包中删除无法访问的代码(也称为死代码)

“你可以把你的应用想象成一棵树。您实际使用的源代码和库代表了树的绿色、有生命的叶子。死代码代表秋天消耗的树的棕色枯叶。为了摆脱枯叶,你必须摇晃树,让它们倒下。”

“You can imagine your application as a tree. The source code and libraries you actually use represent the green, living leaves of the tree. Dead code represents the brown, dead leaves of the tree that are consumed by autumn. In order to get rid of the dead leaves, you have to shake the tree, causing them to fall.”

这个词最早是由Rollup 团队在前端社区普及的。但是所有动态语言的作者从很早以前就一直在努力解决这个问题。摇树算法的想法至少可以追溯到 1990 年代初期。

在 JavaScript 领域,自从 ES2015 中的 ECMAScript 模块 (ESM) 规范(以前称为 ES6)以来,tree-shaking 已经成为可能。从那时起,大多数打包器默认启用摇树,因为它们在不改变程序行为的情况下减少了输出大小。

ES Modules Vs CommonJS

CommonJS 比 ESM 规范早了几年。它旨在解决 JavaScript 生态系统中缺乏对可重用模块的支持。CommonJS 有一个require()函数,它根据提供的路径获取外部模块,并在运行时将其添加到作用域中。

require是一个function,像其它函数一样,它很难评估在编译时间的运行结果(因为是函数)。最重要的是,require可以在代码中的任何位置进行调用,包括:包装在另一个函数调用中、在 if/else 语句中、在 switch 语句中等等。

通过广泛学习 和反思CommonJS 架构,ESM 规范确定了这种新架构,其中模块通过各自的关键字importexport. 因此,不再有函数调用。ESM 也只允许作为顶级声明——不可能将它们嵌套在任何其他结构中,因为它们是静态的:ESM 不依赖于运行时执行。

Scope And Side Effects

const pure = (a:number, b:number) => a + b

const impure = (c:number) => window.foo.number + c

Bundlers(捆绑器)通过尽可能多地评估提供的代码来确定模块是否纯。但是在编译时或构建时的代码无法进行评估。因此,假设具有副作用的包无法正确消除,即使它们没有访问。

因此,捆绑器现在接受模块package.json文件中的一个键,允许开发人员声明模块是否没有副作用。这样,开发人员可以选择不进行打码评估并提示打包程序;如果没有可访问的导入或require链接到特定包中的语句,则可以消除特定包中的代码。这不仅使包更精简,而且可以加快编译时间。

{

"name": "my-package",

"sideEffects": false

}

所以,如果你是一个包开发者,在发布之前认真使用sideEffects,当然,每次发布时都要修改它,以避免任何意外的破坏性更改。

除了根sideEffects键之外,还可以通过/*@__PURE__*/在方法调用中注释内联注释 ,逐个文件地确定纯度。

const x = */@__PURE__*/eliminated_if_not_called()

Avoid Premature Transpiling

不幸的是,有一个特定问题相当普遍,并且可能对摇树造成破坏性影响,就是过早的进行转移优化。简而言之,当您使用特殊的加载器,将不同的编译器集成到您的打包器时,就会发生这种情况。常见的组合是 TypeScript、Babel 和 Webpack——以及它们各种之间的相互组合。

Babel 和 TypeScript 都有自己的编译器,它们各自的加载器允许开发人员使用它们,以便于集成。这就是隐藏的问题。

这些编译器代码优化之前到达您的代码。无论是默认情况下还是错误配置,这些编译器通常会输出 CommonJS 模块,而不是 ESM。正如在上一节中提到的,CommonJS 模块是动态的,因此无法正确评估以消除死代码

随着“同构”应用程序(即在服务器端和客户端运行相同代码的应用程序)的增长,这种情况现在变得更加普遍。因为 Node.js 还没有对 ESM 的标准支持,当编译器针对node环境时,它们会输出 CommonJS。

因此,请务必检查您的优化算法正在接收的代码

Tree-Shaking Checklist

既然您已经了解了捆绑和 tree-shaking 工作原理的来龙去脉,让我们为自己画一个清单,您可以在重新访问当前的实现和代码库时将其打印在方便的地方。希望这将节省您的时间,并让您不仅可以优化代码的感知性能,甚至可以优化管道的构建时间!

  1. 使用 ESM,不仅要在您自己的代码库中使用,还要支持将 ESM 作为其消耗品输出的包。
  2. 确保您确切知道哪些(如果有)依赖项尚未声明sideEffects或将它们设置为true.
  3. 在使用具有副作用的包时,使用内联注释来声明方法调用。
  4. 如果您正在输出 CommonJS 模块,请确保转换导入和导出语句之前优化您的包。