引言
React Native 应用默认会将我们的 JS 代码打包成一个文件,当我们的 React Native 应用变得很庞大了以后,一次性下载所有 JS 代码往往耗时很长,这时我们可能会想到可以通过按需加载来进行优化。而按需加载的首要任务就是对代码进行拆分,本文会一步步揭示 React Native 拆包的秘密。
Metro 介绍
因为 React Native 使用 Metro 来进行打包,所以我们得先来了解一下它,研究一个打包器最好的方式就是先看看它的构建产物。
构建产物分析
假设我们有如下代码:
1 | // index.js |
我们使用 Metro 对其进行打包(需要安装 metro 和 metro-core):
1 | metro build index.js --out bundle.js -z false |
其中 -z
表示是否对代码进行 minify,为了方便查看,我们选择 false
。
打包后的文件如下所示(省略掉了前面的代码):
1 | ... |
__d
__d
是 define 的意思,即定义一个模块:
1 | ;(function (global) { |
define
函数接受 3 个参数,factory
是模块的工厂方法,moduleId
是模块的 id,dependencyMap
是模块的依赖列表,里面存储的是所依赖的其他模块的 id,这个函数很简单,生成了一个 mod
,然后保存到了 modules 中而已。
__r
__r
有点复杂,不过我们顺藤摸瓜,最后会来到这个函数:
1 | function loadModuleImplementation(moduleId, module) { |
该函数通过模块 id 从 modules
中获取到该模块,然后执行模块的工厂方法,方法里面会对传进去的 exports
对象进行一些修改(比如下面这个模块在 exports
上面添加了字段 say
)。
1 | __d( |
loadModuleImplementation
最后会返回修改后的 exports
对象。
回到上面的例子,我们现在其实可以手动对其进行拆包了,方法很简单,将打包的产物分成两个文件即可:
1 | // utils.bundle.js |
使用时,我们只需要保证先加载 utils.bundle.js
,再加载 index.bundle.js
即可。那么如何自动的实现呢?我们先要了解一下 Metro 打包的一些配置才行。
配置文件
关于 Metro 的配置文件有如下这些:
1 | module.exports = { |
但是这里我们只需要了解 serializer
即可,即构建产物输出相关的配置,serializer
下的配置我们也仅需要了解 createModuleIdFactory
和 processModuleFilter
。
createModuleIdFactory
该配置用于生成模块的 id,比如当配置成如下所示时:
1 | module.exports = { |
打包出的模块将会以文件路径作为模块 id:
1 | ... |
processModuleFilter
该配置用于过滤掉模块的输出,还是用一个例子来说明:
1 | // index.js |
基于上面这个配置打包,会把 unused.js
模块从构建产物中去掉。
拆包实战
基于上面的知识,我们终于可以开始进行拆包实战了。我们还是用一个例子来说明:
1 | // utils.js |
可以看到 index1.js
和 index2.js
两个文件中都使用到了 utils.js
,我们的目的是将它单独提取出来打成一个叫做 base.js
的包,index1.js
和 index2.js
分别打包成 bundle1.js
和 bundle2.js
。加载时先加载 base.js
,然后加载 bundle1.js
和 bundle2.js
。
我们首先来打包 base.js
,其打包配置如下:
1 | const fs = require('fs') |
该配置的含义是用模块在项目中的相对路径作为模块 id,并将已打包过的模块 id 记录到 modules.txt
中,base.js
打包完成后,该文件内容为:
1 | src/utils.js |
接下来打包 bundle1.js
,其打包配置为:
1 | const fs = require('fs') |
该配置的含义是用模块在项目中的相对路径作为模块 id,并过滤掉 modules.txt
已存在的模块。bundle1.js
打包结果如下所示:
其中第一行是模块系统的一些初始化代码,这些代码在 base.js
已经存在了,所以这里需要删掉这一行,可以使用 line-replace
这个包来实现:
1 | // package.json |
到这,bundle1.js
的打包就完成了,同样的可以按照这个方式打包 bundle2.js
。
最后,验证一下打包出来的产物是否能够正常运行:
1 |
|
结语
React Native 拆包听起来是个很高端的概念,但是实操一下发现还是很简单的。不过,这只是第一步,接下来我们需要修改 Native 的代码来真正实现我们前面所说的按需加载,答案在下一篇文章揭晓。