前言
本文根据5月30日京东前端架构师汤小丽在绿盟京东专场直播活动分享的《京东在ReactNative上的实践》主题演讲整理而成。专家主要从JDRN业务现状、RN官方在性能提升上的优化、JDRN基于RN的优化、RN新架构四方面为大家介绍。
一、JDRN业务现状
议题开始,专家展示了京东超市、京东到家、京东售后、看病买药4块业务从用户点击业务入口,到业务全部渲染出来的视频,可以看出比较流畅。这4块业务各有特点,业务包的大小各异:京东超市全包size是2.6M,拆分包size是1.3M;京东到家全包size达到了7M,拆分包size是5.6M;京东售后全包size是5M,拆分包是3.7M;还有看病买药全包size是3.8M,基于Taro开发没有拆分包。业务加载的性能与业务包的大小,有一定的关系。但是基于对RN引擎的优化,即使有些业务包大,在最终实践过程中,性能表现却优于别的模块。
二、RN官方在性能提升上的优化
1.RAM和内联引用
RN的官方是Facebook,官方在性能提升上做了哪些事情?首先为大家介绍RAM。RAM相对来说是比较老的能力,2017年的版本RN就已经引入了RAM能力。引入目的是为了提升RN业务首包加载效率。RAM能力是根据业务自定义的配置,将业务首屏中的一些依赖配置到文件中提前加载。RAM适用场景是一个APP内一个大的RN包,多个子业务之间耦合在一个包中。像京东是把每个业务做了一些拆分,每个业务有独立的JS-bundle包。在测试过程中,发现性能可能不仅没有提升,反而会有一个降低的结果。
第二个是内联引用,现在的业务也会经常用到,是为了避免直接在JS代码中import某个class或component,在用到的地方判断是否为空。如果为空就require对应的JS文件。优点是延迟模块或文件的加载,按需加载减少前期不必要的解析;其次是避免引入三方库中不需要的组件,减小包大小。
2.替换JS引擎为Hermes
Hermes 引擎是 Facebook研发,在 ReactNative Android 端用于替换JavaScript Core 的 JavaScript 引擎。在iOS端,iOS系统自带的JS Core,但在安卓端不是自带的,需要APP独立引用。在性能方面,iOS端JS Core的性能还算是优秀,但在安卓端JS Core性能会差一些。大概是2019年7月,Facebook仅给安卓端引入了Hermes引擎,2021年3月,Hermes引擎引入到了iOS端。React Native 0.70 是第一个默认启用Hermes 的版本。Hermes最主要的优势是可以通过预编译(AOT)的模式,而不是其他JS引擎所采用的(JIT)模式,将JavaScript源代码编译成字节码,极大的提高的应用启动效率。
下图是(引擎未优化的前提下)JSC+JS与Hermes+HBC在安卓中端机上的对比数据。从性能提升方面来看,业务加载性能提升30-60%左右,内存占用降低 20%-30% 左右。加载时长及内存占用和包大小成正比,但是Hermes受手机设备性能及包大小影响更小。
三、JDRN基于RN的优化
在官方的基础上我们还能做哪些事情来进一步提升性能?
1.拆分包-基础库拆分
为什么要去做拆分包?最开始做拆分包的目的是因为很多RN业务内置在APP里面,APP因为不断新增的RN业务模块导致APP体积逐渐变大。如果想要给业务包瘦身的话,就想到了将其中某些公共的部分给拆出来的想法。
那哪部分可以拆出去?如react、react-native、 jdreact-core-lib、jdreact-core-scripts等公共的部分可以从业务包剥离出去。剥离出去后,要拆到哪里去?如果业务包中某些基础依赖被剥离出去了,那么它是无法被JS引擎正常加载的。我们想到的方案:把基础的这一部分,单独打成一个基础包,把这个基础包内置到APP中。然后将需要内置的业务模块以拆分包的形式内置到APP中。我们的初衷是为了给APP瘦身,但随着业务的发展,发现拆分包还有明显性能提升的好处。
那要如何拆分?下面介绍RN构建产物包的详细流程。首先是需要从git仓库将代码克隆到打包服务器上,然后用npm install方式安装依赖。安装完依赖后,就可调用RN 构建入口工具类react-native/local-cli/cli/js,将打包参数给到react-native/local-cli/cli/js。react-native/local-cli/cli/js再去调用真正的打包服务,RN用到的打包服务是metro,metro的打包流程基本顺序是:先解析,再转化,最后序列化生成最终代码。3-1是做命令参数解析,用bundle.js调用bundleCommandLineArgs.js做参数解析。参数包括业务的入口文件,平台类型参数,构建产物、asset资源、sourcemap文件存放路径等。解析完参数后,就到了3-2启动Metro Server。buildBundle工具类主要用于加载Metro Server默认的配置文件以及自定义配置文件。控制解析、转化、生成的流程是在metro/src/server.js,先去创建3-3metro/src/IncrementalBundler.js实例。metro/src/IncrementalBundler.js主要用于JS代码解析、转换。从JS入口文件开始一层层解析依赖关系,最终生成依赖图谱。然后利用babel做代码转换。最后利用3-4metro/src/DeltaBundler/Serializers/baseJSBundle解析依赖图谱,生成产物包。
生成依赖图谱过程比较复杂,做改动会有依赖图谱的错乱情况。最终选择在解析依赖图谱时,去做特殊定制,那到底如何定制?
1、调用baseJSBundle解析依赖图谱的接口有两个参数,一个是processModuleFilter,一个是createModuleId。
2、制定基础包和业务拆分包的模块id规则:0~5000,>5000。
3、基础包构建时保存所有模块path和id映射关系。
4、hook processModuleFilter和createModuleId方法,在createModuleId中,根据当前模块path判断模块是否在基础包中,如果在替换成对应的id,如果不在就从5000开始累加;在processModuleFilter中判断是否在基础包中,如果不在就将模块加到bundle中。
包瘦身结果:业务包减少1.3M,热更包(压缩)减少0.4M。
以上就是《京东在ReactNative上的实践》(上篇),后续还会发布《京东在ReactNative上的实践》(下篇),敬请期待!