作者 | 魔笛
责编 | 伍杏玲
自2015年Facebook发布React Native以来,目前已经是跨平台开发的主流产品,但是随着业务复杂度越来越高,单一的JSBundle越来越大,已经不能满足用户需求,需要拆分业务包&基础包。
在拆包之前先了解一下React Native 打包流程,以及JSBundle的结构
RN 官方从 0.46.0 版本开始,出于对打包速度、可靠性和更好的可拓展性等方面的考虑,将 RN 的打包模块 packager 从 RN 项目中分离,重新命名为 Metro Bundler。 详细参考
此图参考react-native打包
react-native bundle --entry-file index.js --platform ios --dev false --bundle-output ./index.ios.bundle --sourcemap-output ./index.ios.map --assets-dest ./asserts
entry-file:即入口文件,打包时以该文件作为入口,一步步进行模块分析处理。
platform:用于区分打包什么平台的 bundle (ios or android, 默认ios)
dev:用于区分 bundle 使用环境,非 dev 时,会对代码进行 minified
bundle-output:打包产物输出地址,即打包好的 bundle 存放地址
sourcemap-output:打包时生成对应的 sourcemap 文件存放地址,在跟踪查找错误或崩溃时,能帮助开发快速定位到代码
assets-dest:bundle 中使用的静态资源文件存放地址
--verbose: 打印详情
--reset-cache: 清除缓存文件
--config: CLI文件路径
Metro Bundler 在进入正式拆包前,先在 js 解释器上挂载 global.DEV 变量,该变量主要用于区分打包执行环境,同时定义了模块系统函数,包括 __d(即 define)函数、require 函数,这样 RN 也就有了自身的模块系统。另外,RN 对部分 es6、es7 的方法 Polyfill 支持,包括 Array、Object 和 Number 等。
以最基础的RN项目的 Bundle 为例,可以看到 Bundle 文件中大致定义了四个模块:
var 声明的变量,对当前运行环境的定义,Bundle 的启动时间、Process进程环境相关信息
(function() { })() 闭包中定义的代码块,其中定义了对 define(__d)、 require(__r)、clear(__c) 的支持,以及 module(React Native及第三方dependences依赖的module) 的加载逻辑
__d 定义的代码块,包括RN框架源码 js 部分、自定义js代码部分、图片资源信息,供 require 引入使用
__r 定义的代码块,找到 __d 定义的代码块 并执行
总结以上,Bundle 文件大致包含三部分:
Polyfills:最先执行的一些 function,ES6特性支持,定义模块声明方法等
modules:模块声明,以 __d开头,定义各式各样的module,其中包括了React Native 的module(StatusBar、View、Text ...),引入的第三方 module 等
require:执行 InitializeCore 和 Entry File,最后一行执行 require(0)
__d 的执行对应于 nodule_modules / metro / lib / polyfills / require.js 文件中的 define 方法
require 的执行对应于 nodule_modules / metro / lib / polyfills / require.js 文件中的 metroRequire 方法
了解了JSBundle的打包过程以及JSBundle的结构,下面我们讨论一下拆包。
随着业务不断增加,模块化自然是首要考虑的问题。
然后业务1,2,3分别用RN容器加载,但是这样的结构有显而易见两个问题:
每个业务界面打开是会有明显的白屏
每个业务中jsbundle会有很多重复模块
根据上面JSBundle的结构, 可以将JSBundle拆分成以下模块:
base module,是可复用基础包,里面包括React Native模块,view, text, button和一些可复用的三方模块。
业务1,2,3 module,是纯js业务代码,可动态加载。
目前推荐官方提供Metro Bundle打包服务基础上做拆包,如果团队人员充沛,可考虑自研打包服务,Metro Bundle 配置参数(https://facebook.github.io/metro/docs/en/configuration)
主要是序列化的时候:
配置createModuleIdFactory函数生成自定义的module id, 生成module id可以用文件绝对路径,也可以做一次hash或者md5,保证唯一。
因为 metro 打包时,会以递增的方式给每个模块分配一个ID,在文件调用时直接调用对应的ID号。在拆分 bundle 后,如果我们的基础包有依赖模块的变动,整个模块调用的3, ID都会错位。所以要采用更加稳定健壮的ID生成方式。
配置processModuleFilter 过滤包,函数传入的是module信息,返回是boolean值,如果是false就过滤不打包, 例如业务不需要打node_module中的包。
静态资源处理。因为打包生成的静态资源根目录是固定的assets,为了方便灵活组织资源内容,我们添加对自定义静态资源根目录的功能支持。
多 bundle 静态资源最终会合并到一个目录下去,这是最节约资源的一种结构,防止重复资源出现
|- assets/
|- base/
|- node_modules/
|- bundle1_resource/
|- bundle2_resource/
具体可参考, react-native-multibundler
业务(1,2,3)发布一个版本,push tag触发CI server;
CI 触发 WBRNpackage服务;
WBRNpackage服务根据base.js合成公用的base.js;
分别打出对应业务的JSBundle, 基础base.jsbundle, 并合成静态资源文件
总结
截止目前我们解决了几个问题:
模块业务分离,每个业务是一个单独的包
由于之前加载了基础包,原生容器打开对应的业务包,可以达到秒开的效果
在多容器的情况下,共用一个基础包,防止bundle资源重复
利用CI自动化自动部署JSBunlde和静态资源
作者简介:魔笛,从事移动端开发7年多的经验,目前在某互联网金融公司作为iOS端leader工作。
热 文 推 荐
☞找工作时单位普遍要求35岁以下,那35岁以上的人都干嘛去了?
☞8年后重登王座,Python再度成为TIOBE年度编程语言
print_r('点个好看吧!');
var_dump('点个好看吧!');
NSLog(@"点个好看吧!");
System.out.println("点个好看吧!");
console.log("点个好看吧!");
print("点个好看吧!");
printf("点个好看吧!");
cout << "点个好看吧!" << endl;
Console.WriteLine("点个好看吧!");
fmt.Println("点个好看吧!");
Response.Write("点个好看吧!");
alert("点个好看吧!")
echo "点个好看吧!"
点击“阅读原文”,打开 CSDN App 阅读更贴心!
React Native使你能够在Javascript和React的基础上获得完全一致的开发体验,构建世界一流的原生APP。
React Native着力于提高多平台开发的开发效率 —— 仅需学习一次,编写任何平台。(Learn once, write anywhere)
Facebook已经在多项产品中使用了React Native,并且将持续地投入建设React Native。