浅析Rollup打包原理
一、开始
本次Rollup
打包原理分析是v0.3.1版本,因其代码精简,易读性高,所以用它来初探打包器的工作原理。
在Rollup
中,一个文件就是一个Module
(模块),每一个模块都会生成AST
语法抽象树,Rollup
会对每个AST
节点进行分析。
二、AST
Rollup
打包的核心是对AST
的解析和处理,要想深入理解打包原理,必须深入理解AST
。
Rollup
是通过acorn这个库进行AST
的生成的,acorn
的生成结果遵循estree规范。
下面是一个通过acorn
生成的AST
例子,来直观感受下。
生成的AST
结果:
AST
的基本结构是一个个Node
节点,其必包含type
和loc
(start/end
)信息。type
为Program
的Node
节点表明这是段程序,通常是脚本的最外层,body
是其属性。
这里有几个网站可以在线查看AST
结构:AST Explorer、esprima。
三、V0.3.1的打包原理
入口文件是src/rollup.js,其实例化了一个Bundle
,调用其build
方法后,返回了一个对象,包含generate
和write
方法。
注意这个版本的Rollup
的使用方式举例如下:
可以看出其主要分为两步,第一步是通过build
构建,第二步是generate
或者write
进行输出。genenrate
是把code
返回,write
是把code
写入文件中。
1. build
在bundle.build方法中,调用了this.fetchModule,其主要是读取文件(模块)内容,然后实例化一个Module
。
在Module的构造函数中,对传入的code
(文件内容)通过acorn.parse
进行了解析,获取了AST
,然后调用了this.analyse
。
在module
的analyse方法中对ast
的body
进行了遍历,判断如果存在import
或者export
语句,将其放到this.imports
和this.exports
中。
然后调用了ast
目录下的analyse方法,为每个node
节点增加了_scope
(作用域)、_defines
(定义变量)、_modifies
(修改的)、_dependsOn
(依赖)等变量。
回到bundle.build
方法中,在调用fetchModule
获取到entryModule
(入口模块)后,然后调用了entryModule.expandAllStatements方法。其遍历了ast.body
,调用expandStatement
去收集所有的语句。
expandStatement根据节点的_dependsOn
属性,找到其依赖,然后依次调用define
方法。define方法中根据导入模块的路径去调用fetchModule
,实现了递归引入依赖,构建了一个大的模块树。
这样所有的声明都被收集到了bundle.statements
中,然后调用this.deconflict解决命名冲突。
bundle.build
的整体逻辑就是这样,在其过程中会有许多对AST
的判断,以区分不同的类型的节点,来进行不同的处理。
2. generate
bundle.generate的方法是相对独立的,其核心思想是对this.statements
的拼接。因为所有的语句都已经收集到了statement
中,那么拼接起来返回就行了。
在拼接过程中过滤了export
语句,并处理了一些import
语句,然后返回了code
。
四、流程图
画了一张流程图,来加深理解:
五、总结
上面就是Rollup
基本的打包原理,简单的总结下就是从入口文件开始构建AST
、收集依赖,最后拼接所有语句,输出bundle
。
现在的最新版本V2.59.0
比其多出的功能包括:watch
、plugin
、命令行参数等。
Rollup
的插件系统其实就是在build
和generate
的不同生命周期阶段额外做的一些事情,其英文文档非常详细,建议直接阅读文档。