阅读 245

react-native 打包流程分析(metro)

命令行

运行 react-native bundle 打包命令会发生什么?

从命令的入口 react-native/package.json/bin 层层深入(five minutes later...),发现最终会用 @react-native-community/cli 中的 buildBundle 方法进行打包。

有意思的是这个方法内部并没有调用 metro 对外暴露的 打包 API,而是直接粗暴使用 metro 源码进行打包(我™直呼内行)。

metroreact-native0.45 版本后使用的 js 和静态资源打包工具。

接下来进入打包主流程。

主流程

打包后输出的包可以分为普通 bundle RAM bundle,这里主要分析打包普通 bundle 的流程。

RAM bundle 可以理解为按需执行(但不能做到按需IO),在 iOSAndroid 上的实现各有不同。

metro主流程.png

在打包时,metro 会启动一个服务器 Server,它可以做一些打包、缓存和文件热替换工作。

其中,构建器 Bundler 会遍历文件并分成一个个模块,随后将这些模块导入 Serializer 流程进行合并,得到一个完整的打包文件 bundle.js

从源码层面上看并没有所谓的 ResolverSerializer 类,这里指官方文档所述的打包阶段

流程:Bundler

Bundler 类中,Resolver 负责管理依赖树和模块,Transformer 负责编译 js 和静态资源,它们是并行工作的。

metro Bundler.jpg

DeltaCalculator: 主要负责计算依赖变化。

WorkerFarm: 使用单、多线程(根据配置)调用 babel 执行编译操作。

简单来说,DeltaCalculator 会将打包入口路径传给 Transformer,后者通过 babel 遍历编译得出 graph (astcode 和依赖树),然后返回到 DeltaCalculator 中保存。

这个过程会执行多次,因为除了业务代码,还有一些 polyfill 需要打包成模块,所以整个流程为:

1.根据业务入口,编译出一份依赖表和 n 个模块对象(一个文件就是一个模块)。

2.根据 polyfill 依赖入口,编译出另一份依赖表和多个模块,和步骤一结果进行合并。

3.进入 Serializer 流程合并所有模块,输出完整代码。

额外知识: DeltaCalculator 内部维护 deletedFilesmodifiedFiles 两个对象,通过 node-haste 去监听依赖文件的变化,进行增量刷新。

node-haste 是一个用于node.js静态资源的依赖项管理系统,在 react-native 里单独维护了一个专用版本,详见 github

流程:Serializer

这块应该是最没有技术难度的部分。负责处理模块整合,核心代码是 baseJSBundlebundleToString

首先编译生成的模块代码用的是类 AMD 模块化。

// 每个模块 code 如下
// __d() 表示定义一个模块
__d(function(g,r,i,a,m,e,d){r(d[0]),r(d[1])},0,[1,356]);
复制代码

polyfill 的代码比较特殊,是段立即执行的函数,在合包时放在最前。 其余模块将各自的 code 相加,如下:

// 我是 polyfill
!(function(r){})(); // ....
// 我是模块
__d(function(g,r,i,a,m,e,d){r(d[0]),r(d[1])},0,[1,356]);
__d(function(g,r,i,a,m,e,d){r(d[2]),r(d[3])},0,[1,111]);
// ...
复制代码

看到这里我也有点无语,这字符串相加合包是真的简单粗暴。但是这段代码总觉得缺少一点什么。没错,现在各个模块都声明好了,却没有入口去调用,这就把入口代码补上。

// 我是 polyfill
!(function(r){})(); // ....
// 我是模块
__d(function(g,r,i,a,m,e,d){r(d[0]),r(d[1])},0,[1,356]);
__d(function(g,r,i,a,m,e,d){r(d[2]),r(d[3])},0,[1,111]);
// ...
// 我是入口
__r(0);
复制代码

到这里,一个完整的 bundle 字符串已经生成好了,接下来将字符串写入文件就是整包 bundle.js

最后从之前打包出的模块列表中,找出谁是静态资源,拷贝到相应的静态资源路径下,结束整个打包流程。

结束语

react-native bundle 命令算是比较简单的打包流程,如果涉及到监听文件进行热替换、缓存,复杂度会上升,留待下次梳理吧~。

文章分类
前端
文章标签