Published on

性能优化篇(七) : Tree Shaking

翻译至: Tree Shaking

此系列文章:

性能优化篇(一) : 分包

性能优化篇(二) : 压缩Javascript

性能优化篇(三) : 动态导入

性能优化篇(四) : Prefetch

性能优化篇(五) : Preload

性能优化篇(六) : PRPL模式

Tree Shaking

有时,我们可能会向应用包中添加没有使用过的代码。可以消除这些无效代码,以减少包的大小,并防止不必要地加载更多数据。

将死代码从应用包中消除它的过程被形象的称为"摇树"。

尽管树摇动适用于像 math.js 这样的简单模块,但在某些情况下树摇动可能会很棘手。

概念

Tree Shaking 的目的是删除最终 JavaScript 包中永远不会使用的代码。如果做得正确,它可以减少 JavaScript 包的大小并缩短下载、解析和(在某些情况下)执行时间。

对于大多数使用模块打包工具(例如 webpackRollup)的现代 JavaScript 应用程序,模块打包工具都可以做到。

将您的应用程序及其依赖项视作抽象语法树(我们希望“摇动”语法树以优化它)。树中的每个节点都是为您的应用程序提供功能的依赖项。在 Tree shake 中,输入的文件被视为"图"。图中的每个节点都是一个顶级的声明语句,在代码中称为“part”。 Tree Shaking 是一种图的遍历算法,从入口点开始并标记所有遍历的路径。

每个组件都可以有声明符号、引用符号并依赖其他文件。甚至“parts”也被标记为是否有副作用。例如,语句 let firstName = 'Jane' 没有副作用,因为如果没有任何地方用到firstName,则可以删除该语句而不会有任何影响。

但是语句 let firstName = getName() 有副作用,因为在不改变代码语义的情况下不能删除对 getName() 的调用,即使没有什么需要firstName。⁣

Imports

只有使用 ES2015 模块语法( importexport )定义的模块才能进行树摇动。导入模块的方式决定模块是否可以进行树摇动。

Tree Shaking 首先访问具有副作用的入口文件的所有部分,然后继续遍历图的边缘,直到到达新的部分。

遍历完成后,JavaScript 包仅包含遍历期间所到达的部分。其他部分被省略。 ⁣假设我们定义了以下 utilities.js 文件:⁣⁣

export function read(props) {
  return props.book
}

export function nap(props) {
  return props.wink
}

然后我们有以下index.js文件:

import { read } from 'utilities';

eventHandler = (e) => {
  read({ book: e.target.value })
}

在此示例中, nap() 并没有使用,因此不会包含在Bundle包中。

副作用

当我们导入 ES6 模块时,该模块会立即执行。尽管我们没有在代码中的任何地方引用模块的导出,但模块本身可能会在执行时影响全局范围(例如,polyfills 或全局样式表)。

这称为副作用。虽然我们没有引用模块本身的导出,但如果模块一开始就有导出值,则由于导入时的特殊行为,该模块无法进行树摇动。

Webpack 文档对 tree-shaking 提供了清晰的解释。