issues
search
umijs
/
mako
An extremely fast, production-grade web bundler based on Rust.
https://makojs.dev
MIT License
1.9k
stars
74
forks
source link
TreeShaking 思路
#124
Closed
heden9
closed
1 year ago
heden9
commented
1 year ago
TreeShaking module
module_id 模块的ID
side_effects 是否包含副作用
stmt_graph 语句的依赖关系图
used_export 当前模块被依赖到的 export 语句,反之就是要被删除的语句
module_system 当前的模块系统类型:esm、cjs、混合
实现思路
module_graph.toposort() 拓扑排一下,主要是看看哪些模块被用到了,以及分析循环依赖
针对循环依赖的模块设置为 sideEffects
针对入口模块设置为 sideEffects
针对不是JS的模块、以及他们的依赖也设置为 sideEffects
包含了 external 的模块也设置为 sideEffects
将原始模块重新创建为 treeShaking 模块
针对不是 esm 的模块,默认全部导出(commonjs的),(没法treeShaking)
针对有副作用的模块,分析 export、import
分析 import 语句的过程:
找到当前依赖的模块
如果当前模块不是JS、或者是external的,则跳过处理
如果当前使用模块的姿势是 import “xxx”,这里有可能是 specifiers.is_empty 或者是 Namespace,则认为当前模块是 UsedExports:ALL 并且包含副作用
如果是 named export,则提取当前的 specifiers
如果是 default export,则提取当前的 specifiers 为 default
分析 export 语句的过程(这里只包含重新导出的部分,re-export)
….
分析不含副作用的模块
如果当前模块不包含任何 usedexport,则认为当前模块是可以被删除的
开始删除无用的声明
类似上面的分析过程,对删除声明后的代码进行分
动态加载的模块
入口模块设置为副作用模块,usedexport:all
删除刚刚分析出来的无用模块
sorrycc
commented
1 year ago
转一个相关资讯,来自 @fz6m 。
heden9
commented
1 year ago
Top Level Statement graph 思路
接着上述的思路,我们会先进行topo排序,倒序对每个文件进行下述分析,每个模块内的 Top Level Statement 是我们做 TreeShaking 的最小单元
Top Level Statement 所来源的类型会有多种:
Module Decl
import
export
Statement
self exec statement
variable decl
….
分析每个Statement 内部的数据,包含 Import 信息、export 信息
其中包含两部分:
import 语句产生的 defined_ident
其他声明语句产生的 defined_ident,如 const f =1; 或者 export const f = 1;这里与 export 无关,可认为所有 export 出去的一定是个defined_ident
当前 statement 内所有的 used_ident,用来分析变量使用情况。我们可以通过 sym + ident 结合起来确保每个变量都是唯一的,解决不同作用域的同名变量的问题,可以通过 equal 来匹配两个变量是否是同一个
由于我们只关心 Top Level Ident,那么在 statement 内部产生的变量定义都可以被忽略,只需要记录 used 的情况。此时通过 visit_ident 来找所有的变量,需要把当前范围内所产生的变量定义全部跳过。
Statement Graph 中的边是「使用到的变量集合」,描述的是两个statement之间的依赖关系,比如
Statement1 :export const foo = 1;
Statement2:export const bar = foo;
此时边就是 HashSet::new(foo),表达的是一种变量之间的引用关系,比如一个变量被多个地方引用,那么就会对应有多条边
heden9
commented
1 year ago
标记和删除
通过注释标记当前函数或者声明的 leading_comments 为
/*unused*/
,如果不做 minify 的话,codegen的时候会保留注释,方便排查问题
在 minify 阶段,额外删除包含了注释为
/*unused*/
的声明语句
其他的 deadcode 通过 SWC 自带的压缩器删除
heden9
commented
1 year ago
模块连接 ModuleConcatenation
我们单独做 tree shaking 往往不够,还存在于大量的模块系统本身的代码,而且因为存在大量闭包,在内存使用上也不够优秀
ModuleConcatenation 就是把一部分符合要求的模块「连接」为一个模块,从而避免上述问题。webpack 中这个功能也被称为 scope hoisting,这个功能本身和 tree shaking 是不冲突,相互补充的。
ConcatenatedModule
id 连接模块的 ID
rootModule 根模块
modules 当前连接模块内包含哪些模块
实现思路
刚刚已经 toposort 了整个 module_graph ,按照倒序来进行模块合并,将合法的模块组合为一个模块
遍历当前连接模块内所有的 statement,保留原本的顺序进行拼接,这里有几种情况:
删除当前模块有效的 import 语句,根据 graph 的边信息找到他所依赖的「依赖模块」的变量,修改为相同的变量名字(变量名称有可能因为第二条产生改变)
处理 top level defined ident,将变量名字修改为当前模块信息关联的唯一名字
处理 export statement,如果当前 statement 提供了 defined ident,那么去除 export 语句,如果当前模块仍然被无法连接的模块所依赖,则保留 export
TreeShaking module
实现思路