Tree-Shaking深入解析

1,721 阅读4分钟

一、什么是Tree-Shaking

Tree-Shaking 是一种基于 ES Module 规范的 Dead Code Elimination 技术,他会在打包过程中通过静态分析模块之间的依赖关系从而确定哪些模块导出/引入的值没有被使用,从而将它删除。Tree-Shaking 较早由 Rich_Harris 的 rollup 实现,后来,webpack2 也增加了tree-shaking 的功能。

二、CommonJs 和 ES Module

首先来思考一个问题,为什么“Tree-Shaking是一种基于ES Module规范的 Dead Code Elimination 技术”,用CommonJs不行吗?

要搞清楚这个原因还得从 ES Module 和 CommonJs 的特性出发,我们知道对于 CommonJs 他的导入导出都是动态的难以预测,如下所示:

if(isTrue){
	module = require("./a")
}else{
  module = require("./b")
}

CommonJs 在实际运行之前是无法确定哪些模块是需要的哪些是不需要的,所以 CommonJs 是不适合 tree-shaking 的,而 ES Module 则从规范层面规避了这些问题,他要求所有模块的导入导出只能出现在模块的顶层,所以ES Module下的模块之间的依赖关系时高度确定的,webpack只需要对所有模块进行静态分析,就能够推断出那些模块的导出没有被其他模块使用。

三、开启Tree-Shaking模式

在webpack中开启tree-shaking需要满足以下条件:

  • 使用 ESM 规范编写模块代码
  • 在 webpack.config.js 中配置 optimization.usedExports 为 true,启动标记功能
  • 在生产环境中启动代码压缩
    • 配置 mode = "production"
    • 配置 optimization.minimize = true
  • 禁止 Babel 对模块的导入导出进行转译

Babel 是一个非常流行的 JavaScript 代码转译器,它能够帮我们将高版本的 js 代码转换成兼容性更强的低版本代码,使得前端开发者可以使用最新的语言特性开发出兼容旧版本浏览器的代码。

但是如果我们用 babel 对模块的导入导出进行转译的话就会使 tree-shaking 功能失效,因为 babel 会将 ES Module 语句转移成 CommonJs 风格的语法,这就导致webpack 不能对其进行静态分析。 这里我们看到当我们使用babel对模块的导入导出进行转译之后,webpack并没有将 “c” 标记为 unused ,作为对比下面这张图是没有使用babel转译得到的结果,很明显看到函数“c”被标记为 unused harmony export 。

四、实现原理

Tree-Shaking 的实现首先是标记出模块导出值中哪些是没有被用过的,然后使用Terser删除掉这些没有用过的代码。

标记分类

  • 所有的 import 标记为 /* harmony import */
  • 所有被使用过的 export 标记为 /* harmony export ([type]) */,其中 [type] 和 webpack 内部有关,可能是binding,immutable等等。
  • 没有被使用过的 export 标记为 /* unused harmony export [FuncName] */,其中 [FuncName] 为 export 的方法名称。

五、处理sideEffect

定义

副作用的定义是在导入时会执行特殊行为代码,而不仅仅是暴露出一个或多个 export,比如我们导入一个全局的css文件,他是不提供 export 的。

webpack认为这些有副作用的文件不应该被tree-shaking掉,因为这将会破坏整个应用程序,但是webpack的tree-shaking在处理这些文件时稍微逊色了点,他只能简单的判断变量后续是否被引用、修改,所以对于很多有可能产生副作用的代码,他只能采取保守的策略--不删除。

配置参数

在项目的 package.json 文件中,添加 "sideEffects" 属性。package.json 有一个特殊的属性 sideEffects,就是为处理副作用而存在的,它有三个可能的值:

  • true是默认参数,表示所有的文件都是有副作用的,也就是说没有一个文件可以进行tree-shaking。
  • false表示不存在有副作用的文件,即所有的文件都是可以tree-shaking的。
  • [...]表示具有副作用的文件集,除了数组里面的文件之外其他文件都是可以进行tree-shaking的。

pure标记副作用

除了在sideEffect中配置之外,开发者可以在调用语句前添加 PURE 备注,明确告诉 Webpack 该次函数调用并不会对上下文环境产生副作用,例如: 带上 PURE 的代码最终被删除掉。

传送门