// a.ts 初次构建时 Mako 发现引用 a 的模块根本没用到 b,所以把 b.ts 从 module_graph 中删掉了
export { default as b } from './b';
// a.ts 二次构建时加了条 console,但 b 已经无了所以会导致 chunk 生成失败
export { default as b } from './b';
console.log(balabala);
记录 module 被引入的 symbol,便于后续增量 tree-shaking 时确认范围 from @stormslowly
新方案不能破坏 generate 的 transform 的并行,否则性能会变差 from @sorrycc @stormslowly
statement id 记录及优化应用的方案需要再思考一下,因为 cjs 处理会影响 id 的正确性 from @stormslowly
a. 思路1:把 generate 阶段对 ast 的操作挪到 tree-shaking apply 之后去做,同时要解 entry 变并行(chunk 内的模块是串行生成的) from @sorrycc
b. 思路2:用 span 替代 id,但要看 span 是否会发生变化 from @stormslowly
背景
目前 Mako 的 tree-shaking 仅支持 build,在 dev 阶段下会有如下问题:
社区的竞品例如 Farm、Webpack 都是支持 dev 阶段做 tree-shaking 的。
面临的问题
目前已知最大的问题是 #396 ,在初次构建时 tree-shaking 会过滤 module_graph 中的 module 以及 module 中的 AST 语句,而在增量构建时需要重新 parse 修改的文件为 AST,其引用的模块有可能已经被删掉了,最终导致构建失败,举个例子:
这个问题本质是增量构建时 parse 的 AST 没有做 tree-shaking,它所需要的模块关系和 module_graph 中存在的关系不是一回事,那能不能在增量构建 parse AST 以后做一遍 tree-shaking 呢?
的确是可以的,但实现起来却没这么简单,tree-shaking 需要感知的副作用可能是向外扩散的,修改的文件 AST 的副作用可能由引入它的祖先或依赖文件决定,那么增量 tree-shaking 的范围就不太好控制了,根据 @stormslowly 的调研,Farm 就是采用类似思路,在增量构建时去搜索到当前改动模块最小的拓扑图,单独对这个拓扑做一次 tree-shaking,再更新回 module_graph。
不过,这个方案的代价是,我们要么需要存储两份 module_graph 的 edge 及两份 AST(tree-shaking 前后各一份)确保最小拓扑的准确性,要么需要在增量构建的时候基于修改模块重新生成一份最小 graph,前者会增加内存消耗,后者会增加时间消耗,Mako 既然站在巨人的肩膀上,就希望能试着找到一个更加均衡的方案。
实现思路
既然问题核心是 module_graph 的修改不可逆而 tree-shaking 又依赖原始信息,那倘若我们让 module_graph 变得可逆呢?
在新流程中,无论首次还是增量构建,tree-shaking 都不再直接修改 module_graph,而是在 module info 和 graph edge 上做标记,直到 generate 阶段再复制一份 ast 来应用 tree-shaking 的改动,这样就能做到 module_graph 的信息始终与磁盘文件一致,不用再去磁盘上读没有修改过的模块,而在 generate 阶段的拷贝 ast 内存占用也是即用即释放的(也会做方法级别的缓存避免做重复 tree-shaking),不会常驻内存。
方案实现
module_graph 改造
主要有 3 点,用来记录 tree-shaking(或者说 optimization)需要的信息并且改造读取的方法:
Dependency
增加is_used
标记,它目前作为 edge 上的信息储存在 module_graph 里ModuleInfo
增加optims
字段,值是枚举值,本期只支持OptimsType::UselessStmt(id)
一种,用来记录 tree-shaking 要删除的语句is_used
为false
的 edge,让 Code Splitting 的逻辑尽量不感知 module_graph 的改造farm_tree_shake 改造
目前是分析 + 删除逻辑一体的,需要把逻辑拆成 optmize 和 generate 两个阶段:
Dependency
和ModuleInfo
把要优化的内容都放进 module_graph 里,标记要支持缓存,没改过的 AST 就不需要重新标记了,避免 fullbuild 有多余的消耗generate 改造
这块目前还没完全理清楚,可能还缺一些细节,目前的大致的思路:
generate_hot_update_chunks
里根据updated_modules
和 module_graph 里的已有标记信息计算增量 tree-shaking 的最小范围任务拆分
WIP