- Published on
性能优化篇(七) : Tree Shaking
翻译至: Tree Shaking
此系列文章:
Tree Shaking
有时,我们可能会向应用包中添加没有使用过的代码。可以消除这些无效代码,以减少包的大小,并防止不必要地加载更多数据。
将死代码从应用包中消除它的过程被形象的称为"摇树"。
尽管树摇动适用于像 math.js 这样的简单模块,但在某些情况下树摇动可能会很棘手。
概念
Tree Shaking 的目的是删除最终 JavaScript 包中永远不会使用的代码。如果做得正确,它可以减少 JavaScript 包的大小并缩短下载、解析和(在某些情况下)执行时间。
对于大多数使用模块打包工具(例如 webpack 或 Rollup)的现代 JavaScript 应用程序,模块打包工具都可以做到。
将您的应用程序及其依赖项视作抽象语法树(我们希望“摇动”语法树以优化它)。树中的每个节点都是为您的应用程序提供功能的依赖项。在 Tree shake 中,输入的文件被视为"图"。图中的每个节点都是一个顶级的声明语句,在代码中称为“part”。 Tree Shaking 是一种图的遍历算法,从入口点开始并标记所有遍历的路径。
每个组件都可以有声明符号、引用符号并依赖其他文件。甚至“parts”也被标记为是否有副作用。例如,语句 let firstName = 'Jane'
没有副作用,因为如果没有任何地方用到firstName
,则可以删除该语句而不会有任何影响。
但是语句 let firstName = getName()
有副作用,因为在不改变代码语义的情况下不能删除对 getName()
的调用,即使没有什么需要firstName
。
Imports
只有使用 ES2015 模块语法( import
和 export
)定义的模块才能进行树摇动。导入模块的方式决定模块是否可以进行树摇动。
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 提供了清晰的解释。