前言
鉴于大家可能对 rust 不是很了解,就尽量不贴代码了,用图和文字来描述整个过程。
由于我的 rust 水平也是一坨大便,如果有讲的不对的地方,谢谢大哥们指正。
正文
前置条件
首先做 tree-shaking 前,我们的模块关系肯定是梳理完毕的,比如 a 模块中引用了模块 b,那么 b 就是 a 的 dependence,基于这个条件,我可以把整个 modules 看出一个图,里面也包括了循环依赖啊,一个模块被多次引用啊这些玩意。
Shake步骤
大致分为以下5个步骤。
1. 排序 sort modules
为什么要 sort modules?
- 找到所有模块的引入顺序,保证在该模块做 tree shaking 的时候,以它作为 dependence 的模块都已经 tree shaking 完毕。
就像这个图,比如 B 模块中 import 了 D 模块中的 name,C 模块中 import 了 D 模块中的 age ,那么在 D 模块做 shake 的时候,我们必须保证 B 和 C 已经 shake 过了,不然我们不知道谁使用了 D 模块中的内容。
- 找到模块关系中的循环依赖的环,因为循环依赖的模块是不做 shake 处理的。
怎么做
- 首先对模块图进行深度优先遍历(先输出子元素,再输出父元素),记录下访问过的模块的 id,重复的模块不再重新访问,如果在访问的时候发现循环依赖,则把这几个模块加入到记录循环模块的数组中。
-
将 sort 后的 modules 数组反转
原因 :图中反转后的顺序就变成了 [a, d, h, i, j, c, g, b, f, e] ;
观察 f 在上图中,反转前,其实是 b 先引用的 f ,反转后,由于 c 后面没有 f,但是 shake c 的时候,我们就能知道 c 引用了 f 中的内容,所以轮到 f 模块的时候,我们已经能知道 c 和 b 中都引用了哪些内容,就可以进行 shake 了。
-
将循环依赖链路中的 module 都标记为 side-effects: true ,后续不进行 shake。
2. 中间操作
- 把所有的入口文件(index.tsx)标记为 side-effects,不做 shake
- 把非 js|jsx 模块,和外部引入的模块(external)排除,不做 shake。
3. 为每个需要 tree shake 的 module 生成一个 treeModule
根据 module 已有的一些信息,生成一个专门用于 tree shake 的结构体
yaml
{
module_id: 模块id,
side_effects: 模块是否有副作用,
module_system: esm 或者 cjs,
stmt_graph:
记录模块de ast 中 语句(statement) 和 变量(ident) 的关系,cjs 为空,因为不做 shake,
used_exports:
模块中被使用的导出,side_effects 为 true 的,记为全部导出被使用(不做 shake),不然初始值
为空数组(后续遍历时记录被使用的 exports 然后添加进来)
}
4. 遍历 treeModule,开始进行 shake
-
如果 module 不是 esm ,那么把 module 中所有引入的 module 的 used_exports 也记为 ALL,不做 shake
-
如果 module 是 esm,但是 side_effects 为 true,则根据引入模块的语句,按需把引用的内容作为 used_exports 的标记。
比如:
import b from 'b';
就把 b 模块标记为 used_exports::ALL;
import { b } from 'b';
就把 b 的 tree shake 结构体中的 used_exports 数组中加入 b。 -
如果 module 是 esm 且 side_effects 为 false,就开始正常的 shake 操作。
- 先根据前面当前模块 used_exports 中的标记和 ast 树的语句分析,找出被使用过的语句,然后通过对语句中的变量分析,反推出使用了那些引入的依赖,得到 used_import 和 used_export
- 然后和上文的步骤一样,根据引入的依赖标记引入模块中的 used_eports。
-
把一部引入的模块都标记 side_effects 为 true
5. 删除所有导出都没有被使用的模块
如何删除无用的语句
简单介绍介绍 shake 步骤中第四步中,是如何删除 useless 的语句的。
-
首先在生成 treeModule 的时候,把文件中的每个语句都分别进行初始化,生成一个对应的 stmt_id 和对应的语句信息,包括语句的 export、import 信息,语句中使用了哪些变量,定义了哪些变量,是否是自执行(比如 for,while)。
-
通过收集到的 usedExports 可以知道哪些 export 的内容被使用了,通过语句中收集的变量,可以知道那些语句是被使用了的,然后依次反推,哪些变量被使用,又可以得知哪些语句被使用了,最后就能得到 used_statement,然后删除没有被使用到的语句。
-
最后统计下变量中使用的 import 语句中的内容,标记引入模块中的 usedExport,继续遍历下一个 Module。
盗图:
由于我的画图水平也是一坨大便,就偷一下 pshu 老师的图来帮助理解一下,如果通过 export 找到被使用过的语句。