这个主题也许已经写烂了(哈哈哈哈),但是还是想自己写一遍。
之前在做脚手架相关内容,对rollup感兴趣,就去看看源码,值得写写。考虑到早期版本才是它最核心功能。因此本文是基于rollup-init分支解读。
做为一个bundler,最核心的功能是,将多个文件打包成单个文件。而文件之间引用和被引用关系其实是一个图的数据结构(但是rollup 1.0的版本并没有用这种数据结构。)
rollup刚开始重点推出是它的tree shaking功能,所以1.0的很多代码逻辑以及数据结构的设计都是为了实现这个功能。
数据结构
在阅读源码前,先说明几个数据结构:
其中大写对类,小写代表该类的实例
【Module】--- 一个文件对应一个module,基本上是处理的最小单元
【Statement】 --- 1个module有多个statement。其实就是将文件用ast解析以后,每个node节点都会生成一个statement,statement会包含node,以及当前的module。statement记录的是node 和 module的关系
【Declaration】 --- 根据statement的node属性,使用ast解析declaration。declaration中包括该declaration所在node, declaration的name以及statement。这里举个例子,比如你的代码中有如下语句
var hello =1
或者
export function world() {
}
而且同时它是export出去,那么这条语句对应一个Declaration 实例(declaration)
【Reference】--- 根据statement的node属性,使用ast找到a.b 类似调用语法,reference包含调用方名字(比如这里的a),以及statement,以及一个被调用方的declaration(比如,这里a对应的declaration)
【Scope】 --看到现在scope的作用,还是没有搞清楚,不过这个数据结构没有那么重要,可忽略。
几个数据结构之间的关系,如下图所示:
流程图
主要流程
initModule流程
bindReference 流程
tree-shaking 流程
生成最后字符串
当然,最后生成字符串的时候,并不是通过ast树的node反向编译成代码的。因为statement中会有这个node的position,利用 MagicString+ position这个库,来去掉无效代码。
最后
看完1.0版本的代码以后,个人觉得是有优化空间的。
首先Module这个对象真的需要记录所有的引用的文件对应的module吗?个人觉得没有必要。因为整个的数据结构是图,所以,可以用图的数据结构来记录,引用和被引用的关系。
整体看一下,rollup处理的重心在处理import语句,export语句,以及根据ast解析树来寻找代码中引用的代码,比如: "a.b"(reference)以及收集定义语句"function a = 123"(declartion)这种。同时建立 reference和 declartion 之间的关系。因为declartion 和statement也会彼此有关联,因此,将declartion 对应的 statement 打标,从来达到 tree-shaking的目的。
后面我又看了一下master版本,发现其实核心流程没有发生什么变化,只不过代码结构变好了,以及增加了很多性能优化代码(比如: 增加cache,以及队列),对这些优化功能我并没有什么太大的兴趣,这里就不分析啦。
这可能是一系列的文章,下来可能想分析打包常见的--对图片/css文件/babel等插件源码解析以及webpack 热更新原理。