| 终端容器无关化(Containerless):与服务无关化(Serverless)的概念类似,即在保持顶层业务研发语言不变更的情况下,在下层可以兼容性地升级、替换终端容器的能力,让用户无需关心终端容器的运维,只要将精力聚焦到业务逻辑上的技术。
React2X是一款面向多终端、跨平台、容器无关化研发框架。在整个美团前端技术栈日益规范的趋势下,React技术栈在我们技术体系环节中的地位变得越来越重要。在广告、营销这些推广属性的业务上,在各个终端(包括美团App、美团外卖App、大众点评App,以及站外的微信小程序、百度小程序、头条&抖音小程序等其他终端)实现“一次开发,同步需求上线”的业务诉求也变得越来越多。在这样的背景下,我们定义了React2X应用的核心场景:
最终我们的核心痛点围绕在了美团系·小程序和美团系·App矩阵上的同一个需求的多次开发运维上,为了解决研发人力瓶颈问题,我们需要一款“一次研发,多终端容器复用”的研发框架来提升研发效率。
调研整个前端领域,我们找到了一些业界的解决方案,像是美团最早的mpvue、腾讯的Wepy、滴滴的Chameleon、京东的Taro等等。在经过比较与试用之后,我们最终基于投入产出比的价值判断,选择站在巨人的肩膀上研发定制一款满足美团技术、业务场景的研发框架——React2X(后面简称R2X)。从R2X第一个版本发布到现在,已经接受了来自于公司各个业务两年多的考验。所以我们希望通过本文帮助大家对R2X有一个大致的了解。
为了解决业务需求在多端容器需要重复开发的难题,通过代码复用实现开发提效,我们确定了以下的目标:
R2X开发框架主要期望能最终面向多终端应用的终端容器,用于场景化研发:
即:
针对上述核心目标和应用场景,我们对市面上的跨容器框架进行了调研。由于美团外卖的技术栈统一是React为主,所以我们的必备要求是:一款以React为DSL语言的复用框架,能快速融入美团的技术生态。
根据下表的对比,如果以React为DSL语言出发,当时就只有Taro一家能满足我们的业务诉求,但它的生态环境并不适合在美团体系内使用。基于多方面因素的考虑,我们决定结合各大主流框架之所长,然后开发出一款属于美团外卖的跨容器复用框架。
对比项 | mpvue | Taro 1.3 | Chameleon | WePY | UniApp |
---|---|---|---|---|---|
DSL | Vue | 类React(Nerv) | 类Vue | Vue | Vue |
是否支持 React Native | 否 | 是,但支持效果不佳 | Weex | 否 | 否 |
兼容 API | 无 | 有(API支持程度不一) | 自研多态协议 | 无 | 是 |
跨端组件库 | 无 | 有 | 有 | 无 | 无 |
美团生态 | 有 | 无 | 无 | 无 | 无 |
语法校验 | 无 | ESLint | 自研 | 无 | 有 |
TypeScript | 有 | 有 | 无 | 有 | 有 |
定制化扩展 | 无 | 可自研Plugin | 无 | 无 | 有 |
编译扩展 | 无 | 无 | 无 | 无 | 有 |
调研结论 | 不匹配 | 部分满足 | 部分满足 | 不匹配 | 不匹配 |
注:前期调研时间截止到2019年05月,可能与当前数据存在一定的出入。
当我们决定要打造一款属于美团外卖的跨容器复用框架之后,在实现的过程中主要遇到了以下挑战:
① 各个容器之间差异性适配成本 - 语法语义:MRN/小程序/Webview在DSL上就有着完全不同的语法语义。 - 端能力:同一容器在不同端上表现也存在不少差异,比如外卖App中MRN容器和美团App中MRN容器分别有定制的Native模块以及各类桥协议等。
② 业务接入的使用成本 - 首次成本:作为一个新定义的框架如何让新业务方快速上手,如何从存量业务线进行迁移。 - 边际成本:如何融合美团的基建生态,让业务方快速复用平台能力。
③ 顶层架构的合理设计 - 高可维护性、高度扩展性:如何快速升级、替换、新增一款底层容器。
注:以上问题我们会在下文“技术全景”章节中给予解答。
目前,业界以小程序作为跨端目标平台的框架较多,但大多都是使用Web前端技术栈作为基础,但同时兼顾React Native的技术栈就比较少。下表中列出的是支持以React、类React作为DSL的相关框架进行对比。
R2X | Taro 1.3 | Taro 3.0 | Rax | Remax | |
---|---|---|---|---|---|
原理 | R2X 1.0重编译时,R2X 2.0重运行时 | 重编译时 | 重运行时 | 重运行时 | 重运行时 |
容器重点 | 以MRN,小程序,WebView为主,同时支持MTFlutter、Mach、游戏、PC浏览器 | 以小程序、Web为主,React Native支持不多 | 以小程序、Web为主,React Native交给58团队支持 | 小程序、Web、Flutter | 小程序、Web |
API | 支持KNB桥&对多平台API进行了统一 | 对多平台API进行了统一 | 对多平台API进行了统一 | 多平台不统一 | 多平台不统一 |
跨端组件库 | 有 | 有TaroUI,但不支持React Native | 有TaroUI,但不支持React Native | 无 | 无 |
业务组件扩展 | 提供扩展方案 | 参考TaroUI | 参考TaroUI | 提供扩展方案 | 提供扩展方案 |
美团内部生态支持 | 已支持埋点监控等 | 无 | 无 | 无 | 无 |
模块化能力 | 支持 | 不支持 | 不支持 | 不支持 | 不支持 |
编译插件扩展 | 支持 | 不支持 | 支持 | 支持 | 支持Webpack配置 |
综上所述,从目前的业务形态来看,R2X在容器的匹配程度以及美团生态支持程度上看,是现阶段的最佳方案。R2X相较于业界其他框架来说,拥有更加完善的适用于美团外卖团队的本地化实现。
基于业界跨平台框架和美团内部的跨平台框架,我们针对性能也进行了Benchmark测试,最终对比结果如下。
小程序性能对比
框架 | 创建 | 更新 | 添加 | 交换 | 删除 |
---|---|---|---|---|---|
R2X-MP | 947.6 | 586.8 | 1467.2 | 1355.2 | 82.2 |
Remax | 2798.2 | 1872.6 | 5162.2 | 4818.2 | 86.4 |
Taro-MP | 1653.4 | 976.4 | 2483.2 | 2256.6 | 65.2 |
结论:可以看到,在小程序Benchmark测试结果中,R2X-MP是领先于Remax和Taro-MP。
与React Native性能对比
框架 | 创建 | 更新 | 添加 | 交换 | 删除 |
---|---|---|---|---|---|
R2X-MRN | 309.875 | 83.75 | 384 | 191.875 | 82.125 |
MRN | 297.625 | 105.25 | 400.125 | 231.625 | 65.875 |
Taro-RN | 209.5 | 77.5 | 246.25 | 85.125 | 17.125 |
结论:在React Native的Benchmark测试结果中,R2X-MRN和MRN基本持平,但都低于纯React Native的性能表现。
除了支持了基本的React Native、小程序和Webview容器同构场景之外,R2X还实现了在MTFlutter、Mach、小游戏(Webview游戏、微信小游戏&小程序、美团小游戏)、PC浏览器等容器上的同构能力扩展,相比于业内的其他跨容器开发框架的生态也更加丰富和健全。
React Native | 小程序 | Webview | Flutter | 模块级容器 | 小游戏容器 | PC浏览器(PC/App同构) | |
---|---|---|---|---|---|---|---|
R2X | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 |
Taro | 支持 | 支持 | 支持 | 不支持 | 不支持 | 不支持 | 不支持 |
Rax | 支持 | 支持 | 支持 | 不支持 | 不支持 | 不支持 | 不支持 |
Remax | 支持 | 支持 | 支持 | 不支持 | 不支持 | 不支持 | 不支持 |
上图为R2X的架构全景图,整体架构可以按照从下到上,从左到右的视角进行解读:
因为R2X覆盖了美团内部大部分的主流容器场景,所以技术体系较为复杂和庞大,大家可以根据自身的业务形态,选择性地去了解对应场景的同构方案。
CLI作为R2X项目驱动器,它不仅包含了命令行的启动,更重要的,它是各个编译构建的核心。
在早期,CLI执行build命令时,我们通过–type来区分不同的构建容器,从而加载不同的编译逻辑。通过指定构建容器的形式来实现同一套代码能够构建出不同的容器产物。但经过长时间的业务迭代,我们发现了这种结构存在的问题:
针对以上问题,我们考虑对CLI进行了一次全新的重构,引入插件化能力(关于插件化能力的具体实现会在下文详细描述)。CLI整体结构变成如下图所示:
整个CLI模块只需要关心参数的解析以及插件的加载执行,不需要再实现各个容器的具体编译逻辑。通过Hooks的形式,将编译的各个时机暴露给插件,插件基于这些Hook进行编译能力的实现,最终输出产物给CLI模块。这种形式带来了以下几个好处:
R2X的目的是希望通过一套代码能够在多端上运行,但是由于多端差异的存在,我们需要设计一套统一的标准规范来进行对齐。在运行时部分,主要分为组件/接口的对齐。
多端差异
在开始讲述实现之前,我们先来看看各端之间的差异到底在哪些地方。
组件(标签)差异
API差异
样式差异
小程序的WXSS和Webview的CSS在参数属性上其实是几乎一致的,但是在层级关系上有着很大的差别,小程序分为全局样式与局部样式,各个组件之间的样式也是不会相互影响(默认配置下)。而对比React Native采用的StyleSheet,是用Inline Style的方式,不支持全局样式,不支持标签样式,并且属性有诸多限制,如只能使用Flex布局等等。
如何适配
从以上章节我们已经了解到了各个端之间有着非常大的差异点,那我们应该如何克服这些困难呢?
由于各端对组件和API的支持程度不同,我们选定了一端为基础标准,定义好各个组件的属性以及接口参数,通过TypeScript的Interface进行实现。然后在各个端分别基于以上的接口进行功能对齐实现,对于端能力限制的功能进行了一定取舍,对高优功能进行了SDK底层实现适配。最终,我们基于已有的功能封装实现了一套完整的基础组件@r2x/components
和基础API@r2x/r2x
。
随着R2X的在美团内部的应用越来越多,大家对于R2X模式的认可度也在不断提高,我们从业务方中经常听到以下这些问题:“是否可以增加支持某某功能/容器”,“我们业务架构比较特殊,能否做出一些调整”。业务方对R2X会有更多功能/容器的诉求,也会有更多定制化的需求出现。
所以,我们决定实现一套完整的开放式插件能力,提供一种相对比较简单的方式,让大家能够自己来定制这些特殊需求。在最新的版本中,我们将R2X的编译时进行了重构,在新的编译时架构中引入了基于Tapable的插件系统。开发者可以通过编写插件的方式为R2X拓展更多功能,或者为自身业务线定制更多的个性化功能。
在插件类型分为两类:
插件能力的整体架构如下:
借助开发式插件能力,我们将之前编写了若干个平台容器插件,开发者安装后即可使用:
除了扩展新的容器平台,我们还可以通过继承现有的容器插件,来编写一些特殊的定制化功能插件。
1. 对代码进行预处理
基于开放式插件能力,我们可以像Babel插件一样,通过对AST语法的修改对代码源文件进行编译前后的修改。比如:修改文件引用路径、插入代码片段、处理本地图片等等。
2. 对文件产物进行修改
在编译产出生成时,我们可以对编译文件的内容、文件路径、文件结构进行修改。结合自身业务的定制化,CLI可以将R2X项目和现有的原生项目进行结合改造。
除了以上功能,插件化能力为用户在编译时提供了极大的自由度。如果你想体验的话,欢迎加入美团外卖技术团队。
为什么需要多态能力?
多态能力是用于提供跨端时各端组件及API的统一解决方案。基于多态能力,开发者可以定制自己的跨端组件。而R2X具备了完善的跨端能力,能够覆盖多终端和容器,为什么还需要多态?
业务研发为了满足各自场景的要求需要一定的灵活性。同时,Webview/小程序/React Native容器存在端上的差异,需要开发者人为进行环境判断。逻辑一复杂、跨端数量一多,代码可读性变低、维护成本起飞,这不是我们的本意。
基于这样的背景,R2X提供了扩展性良好的多态能力。
R2X多态能力介绍
对于多态能力的支持,我们分为两类:
<View style={{color: R2X.getEnv() === "WEAPP" ? "green" : "blue" }} />
通过差异化代码可轻松满足端差异诉求。
页面级容器场景同构我们借鉴了业内Taro1.x、Rax等优秀跨端框架的做法,结合美团内部容器、基建特点做了很多本地化实现和定制,在Webview、MRN、小程序三种容器上通过不同的编译时方案进行预处理,并且引入了React运行时,保证了对React DSL支持的完整程度和代码同构率,编译时转化流程方案和运行时结构如下图所示:
R2X2.0相比于R2X1.0,运行时方案能够解决编译时方案带来的语法限制问题:
同时也支持大部分React第三方生态库,目前已支持使用原生react-redux。彻底抹平各端的差异,无论是MRN、Flutter、小程序、Webview的自定义组件都可以直接当成React组件引入,小程序原生自定义组件也无需配置usingComponents。
在模块级同构方案上,我们在App上依赖Mach容器;在小程序容器中,我们克服了Mach容器渲染机制的约束(运行时与虚拟DOM的使用限制),单独在小程序上设计了Mach容器渲染方案,实现了R2X-模块化(R2X-Module)在客户端和小程序上99%以上的代码同构率。
整体方案
模板驱动方案
目前,R2X-Module在客户端和小程序容器的同构率在99.3%以上,在性能方面首次渲染时长和模板渲染时长的TP50时间分别是185ms和144ms,比较优秀但还存在优化空间。同时提供R2X-Module SDK供业务方选择。R2X-Module SDK初始化以及模板加载渲染流程如下图所示:
在移动互联网发展已经高度成熟的今天,移动端的PV流量占比绝大数,以外卖广告商家端为例,PC端仅仅占有很少比例,其中PC流量占比在我们部分业务上已经不及5%。因此在某些场景下实现PC/App的同构方案能够解放一部分人力,对提高开发效率来说是十分必要的。目前,外卖广告商家端的一些轻量布局差异的页面,已经完成了PC/App同构的方案设计和落地。
样式同构适配
端能力扩展
R2X的基础能力支持Webview/MRN/小程序三端,缺少对PC微前端子项目的支持。要实现PC/APP多端同构需要对R2X的端能力进行扩展。PC端本质上也属于Web端,因此PC微前端的端能力扩展可以复用大部分的Webview的端能力。整体架构图、技术设计要点、扩展流程图如下所示:
平台代码处理
在项目同构开发中,不可避免地会出现跟平台强相关的代码或者业务逻辑,比如某些API调用的是App的底层能力,只能在React Native中使用,在Web端肯定是不支持的。或者由于产品需求的原因,某些交互或者展示差异较大等等。而项目针对某一端进行编译、打包时,其他不相关的端代码是无用、多余的,如果保留的话,不仅会增加代码体积,甚至会出现编译报错,因此我们需要借助平台代码处理的能力来进行优化。平台代码的处理主要包含三部分:模块导入、组件展示、业务逻辑。
主要思路是使用注释和指定平台的方式,让特定的平台代码只在特定平台生效,注释关键字%%platform%%
,比如%%RN%%
表示React Native端独有,%%MICRO%%
表示PC微前端独有,%%MICRO|Webview%%
表示PC微前端、Webview 两端生效。示例代码如下:
import A from '@r2x/r2x-a'; // %%RN%%只在React Native端保留。
import B from '@r2x/r2x-b'; // %%MICRO%% 只在MICRO端保留。
import C from '@/utils/c'; // 这是所有端生效的公共模块。
import D from '@r2x/r2x-d'; // %%MICRO|Webview%%在MICRO、Webview多端生效的模块。
实现react2x-game同构方案主要做的两点:渲染层的兼容、业务层的兼容。
渲染层兼容
在上文,我们提到过“无论是Webview游戏、小程序、小游戏、美团小游戏都为我们提供了Canvas、WebGL控件”,很大程度地降低了我们兼容渲染层的复杂度。下面表单,是各端对于语法以及Canvas、WebGL、Document、Window等基础功能的支持情况:
对象 | Webview | 微信小游戏 | 微信小程序 | 美团小游戏 |
---|---|---|---|---|
语法 | JavaScript | JavaScript | JavaScript | JavaScript |
Canvas | 支持 | 支持 | 支持 | 支持 |
Canvas(离屏) | 支持 | 支持 | 不支持 | 支持 |
WebGL | 支持 | 支持 | >2.11.0 | 支持 |
Ducument | 支持 | 不支持 | 不支持 | 不支持 |
Window | 支持 | 不支持 | 不支持 | 不支持 |
可以看出,在语法层面各端都支持了JavaScript语法,但是在执行环境以及基础功能上的差异比较大,总结来说:
执行环境:小游戏、小程序不具备DOM、BOM的能力(渲染引擎中会大量使用)。 基础功能:小程序不支持离屏Canvas,在2.11.0版本以后才开始支持WebGL。
为了解决这些问题,我们设计开发了adaptor层,用来模拟document、window的能力。使游戏引擎可以在非Webview的环境下正常的执行和调用BOM、DOM的基础功能。同时,制定离屏canvas的适配方案,用来解决小程序无法支持离屏canvas的问题。为了获取到有效离屏canvas,我们制作了 “r2x-add-wxml-loader” ,在.wxml文件的loader阶段自动注入额外的< canvas/>控件,并隐藏于手机屏幕之外,用于模拟游戏引擎中的离屏canvas。
多端兼容构建
在构建层面,我们通过集成的多种个性化插件工具,对多端代码进行差异处理。如:环境变量注入、各端适配代码的混入、规范检测、代码解析和转化等。针对小游戏、小程序代码和执行环境的特殊性,制作wx-build-plugin、lwxapp-build-plugin等用于处理小游戏和小程序的打包工作。结合上文中提到的各类差异的处理方案,制作add-wxml-loader、transfrom-loader、wxss-loader等工具协助完成项目构建。如下图14所示,构建之初会注入本次构建的环境变量,读取和分析配置文件,集成和初始化构建工具集合,为项目构建做准备。然后在构建环节,针对各端的差异进行差别处理,分析层针对不同文件进行解析,并在转换层进行转换和构建,最终生成各端需要的最终产物。
效果收益
R2X在美团外卖业务中得到了广泛的应用。截止2021年10月,R2X累计在美团内部已有二十多个部门在使用或者在调研中,总计落地了上百个工程、页面,框架下载量达百万次,页面平均代码同构率达90%以上。R2X生态体系在容器代码复用与运维层面,累计为美团节省成本上千人/日,并提升动态化页面转化5%-8%的成功率。
综上所述,在美团外卖多元化业务形态和容器多样性的情况下,跨容器复用成为了发展的必经之路。而R2X在经历了两年的迭代下也取得了阶段性的成果,在美团各个业务场景都完成了业务的落地覆盖,针对公司的生态环境接入也做出了不少的基础建设。我们相信跨容器多端代码复用依旧是当前缩减项目交付周期,减少研发成本,提升研发效率的重要一环。但目前我们在很多复杂的业务场景下做的不够完美,因此还有许多工作待完善,例如:
最后,感谢各个相关研发团队对R2X建设过程中的鼎力支持,R2X的发展离不开所有参与者日以继夜的投入和贡献,我们会持续基于R2X在终端容器领域进行更多探索。如果大家觉得R2X还不错,或者对美团的R2X框架比较感兴趣,欢迎跟我们一起交流探讨。
正浩、宝石、彭震,均为美团外卖终端团队研发工程师。
美团外卖长期招聘Android、iOS、FE、Java高级/资深工程师和技术专家,欢迎有兴趣的同学投递简历到[email protected]。