查看原文
其他

美团外卖终端容器无关化研发框架

正浩 宝石 彭震 美团技术团队 2022-05-14


总第480

2021年 第050篇

2019年9月,美团外卖技术团队联合多个研发部门正式推出了React2X,面向所有的前端研发人员,特别是按业务领域划分的团队,为大家提供一个完整的、开放的多终端容器无关化(Containerless)研发框架。

研发同学可以通过React2X框架快速创建、开发、构建、部署项目,在人力消耗最小的前提下,以期在不同终端上达到相对最佳的性能体验,并且能大幅降低因容器升级带来的替换和改造成本,让代码同构的复用率最大化。
  • 一、前言

  • 二、目标与场景

    • 2.1 核心目标

    • 2.2 应用场景

  • 三、挑战与优势

    • 3.1 业界调研

    • 3.2 技术挑战

    • 3.3 项目优势

  • 四、技术全景

    • 4.1 底层基础框架

    • 4.2 应用场景同构

    • 4.3 落地场景与效果

  • 五、展望与总结

  • 作者简介

  • 招聘信息

| 终端容器无关化(Containerless):与服务无关化(Serverless)的概念类似,即在保持顶层业务研发语言不变更的情况下,在下层可以兼容性地升级、替换终端容器的能力,让用户无需关心终端容器的运维,只要将精力聚焦到业务逻辑上的技术。

一、前言

React2X是一款面向多终端、跨平台、容器无关化研发框架。在整个美团前端技术栈日益规范的趋势下,React技术栈在我们技术体系环节中的地位变得越来越重要。在广告、营销这些推广属性的业务上,在各个终端(包括美团App、美团外卖App、大众点评App,以及站外的微信小程序、百度小程序、头条&抖音小程序等其他终端)实现“一次开发,同步需求上线”的业务诉求也变得越来越多。在这样的背景下,我们定义了React2X应用的核心场景:
  • 面对美团内部丰富多样的技术容器体系(Mach、MRN、Titans、MTFlutter、MMP等),如何保证跨容器开发体验的一致性,以及建设跨容器应用开发的生态能力,是我们需要解决的问题。
  • 公司内丰富的终端容器化技术蓬勃发展,而因业务升级带来的改造成本也比较大,亟待一款高扩展性设计的顶层框架作为技术抓手。
  • 跨容器动态化能力覆盖,逐步成为各个业务方越来越重视的基础能力,可以大幅缩短需求交付的周期,提高上线发版的效率,并能有效地解决包体积大小的问题,提升业务的敏捷性
  • 多场景下的同构诉求,例如在各种推广页、模块化、游戏、轻量布局差异的PC/App同构场景下,可以节省多端研发的人力。
最终我们的核心痛点围绕在了美团系·小程序美团系·App矩阵上的同一个需求的多次开发运维上,为了解决研发人力瓶颈问题,我们需要一款“一次研发,多终端容器复用”的研发框架来提升研发效率
调研整个前端领域,我们找到了一些业界的解决方案,像是美团最早的mpvue、腾讯的Wepy、滴滴的Chameleon、京东的Taro等等。在经过比较与试用之后,我们最终基于投入产出比的价值判断,选择站在巨人的肩膀上研发定制一款满足美团技术、业务场景的研发框架——React2X后面简称R2X)。从R2X第一个版本发布到现在,已经接受了来自于公司各个业务两年多的考验。所以我们希望通过本文帮助大家对R2X有一个大致的了解。

二、目标与场景

2.1 核心目标

为了解决业务需求在多端容器需要重复开发的难题,通过代码复用实现开发提效,我们确定了以下的目标:
  • 解决公司内部多终端容器开发痛点:实现Webview容器、小程序容器、MRN容器、Mach容器、游戏容器、部分运营推广场景PC容器的代码同构复用,统一开发规范,抹平开发差异,并提供对其他容器的扩展能力。
  • 建设跨容器动态化能力:跨容器动态化能力的缺失,导致产品不能够通过快速迭代来验证需求的效果,这个问题严重限制了业务的发展。跨容器动态化能力可以解决美团外卖业务端上发版和包体的问题,帮助业务实现快速发版上线、线上问题热修复、以及容灾的能力。
  • 建设容器无关的开发生态体系:R2X最终要解决的是容器差异性,进行统一的技术生态能力建设,为多终端容器开发场景提升生产和运维效率。

2.2 应用场景

R2X开发框架主要期望能最终面向多终端应用的终端容器,用于场景化研发:

图 1
即:
  • 业务项目基于React语法为技术框架基础。
  • 业务方有在多终端/多容器(包括MRN容器、Webview容器、MP容器、Flutter容器、Mach容器、PC浏览器容器)运行的需求。
  • 业务方有特定的场景化诉求,包括推广页、模块化、小游戏、PC/App同构等等。

三、挑战与优势

3.1 业界调研

针对上述核心目标和应用场景,我们对市面上的跨容器框架进行了调研。由于美团外卖的技术栈统一是React为主,所以我们的必备要求是:一款以React为DSL语言的复用框架,能快速融入美团的技术生态
根据下表的对比,如果以React为DSL语言出发,当时就只有Taro一家能满足我们的业务诉求,但它的生态环境并不适合在美团体系内使用。基于多方面因素的考虑,我们决定结合各大主流框架之所长,然后开发出一款属于美团外卖的跨容器复用框架。
注:前期调研时间截止到2019年05月,可能与当前数据存在一定的出入。

3.2 技术挑战

当我们决定要打造一款属于美团外卖的跨容器复用框架之后,在实现的过程中主要遇到了以下挑战:
① 各个容器之间差异性适配成本
  • 语法语义:MRN/小程序/Webview在DSL上就有着完全不同的语法语义。
  • 端能力:同一容器在不同端上表现也存在不少差异,比如外卖App中MRN容器和美团App中MRN容器分别有定制的Native模块以及各类桥协议等。
② 业务接入的使用成本
  • 首次成本:作为一个新定义的框架如何让新业务方快速上手,如何从存量业务线进行迁移。
  • 边际成本:如何融合美团的基建生态,让业务方快速复用平台能力。
③ 顶层架构的合理设计
  • 高可维护性、高度扩展性:如何快速升级、替换、新增一款底层容器。
注:以上问题我们会在下文“技术全景”章节中给予解答。

3.3 项目优势

3.3.1 功能特点对比

目前,业界以小程序作为跨端目标平台的框架较多,但大多都是使用Web前端技术栈作为基础,但同时兼顾React Native的技术栈就比较少。下表中列出的是支持以React、类React作为DSL的相关框架进行对比。
综上所述,从目前的业务形态来看,R2X在容器的匹配程度以及美团生态支持程度上看,是现阶段的最佳方案。R2X相较于业界其他框架来说,拥有更加完善的适用于美团外卖团队的本地化实现。

3.3.2 性能数据对比

基于业界跨平台框架和美团内部的跨平台框架,我们针对性能也进行了Benchmark测试,最终对比结果如下。
小程序性能对比
结论:可以看到,在小程序Benchmark测试结果中,R2X-MP是领先于Remax和Taro-MP。
与React Native性能对比
结论:在React Native的Benchmark测试结果中,R2X-MRN和MRN基本持平,但都低于纯React Native的性能表现。

3.3.3 同构场景对比

除了支持了基本的React Native、小程序和Webview容器同构场景之外,R2X还实现了在MTFlutter、Mach、小游戏(Webview游戏、微信小游戏&小程序、美团小游戏)、PC浏览器等容器上的同构能力扩展,相比于业内的其他跨容器开发框架的生态也更加丰富和健全。

四、技术全景

图 2
上图为R2X的架构全景图,整体架构可以按照从下到上,从左到右的视角进行解读:
  • 最下层是R2X的生态环境建设,在R2X内部去实现公司生态的常用SDK以及业务中的各项专题能力;并通过搭建物料市场/插件市场,以业务共建的形式丰富R2X生态。
  • 再上层是R2XCore的根基,通过解析Command命令来执行唤起构建器,并实现了类似Webpack的插件系统,以插件化的形式组织驱动整个核心构建流程,便于维护以及扩展。
  • 再往上是跨端容器层,它是整个跨端能力的核心,通过实现了不同的容器插件来将R2X代码编译成各端可执行代码,并通过运行时能力对组件/API进行对齐。
  • 最上层是承载的App端,目前有美团外卖、大众点评、美团等多款移动App终端。
  • 最右边是R2X在研发、发布、运维以及用户文档上做的一些建设。
因为R2X覆盖了美团内部大部分的主流容器场景,所以技术体系较为复杂和庞大,大家可以根据自身的业务形态,选择性地去了解对应场景的同构方案。

4.1 底层基础框架

4.1.1 R2X-CLI的设计

CLI作为R2X项目驱动器,它不仅包含了命令行的启动,更重要的,它是各个编译构建的核心。
在早期,CLI执行build命令时,我们通过--type来区分不同的构建容器,从而加载不同的编译逻辑。通过指定构建容器的形式来实现同一套代码能够构建出不同的容器产物。但经过长时间的业务迭代,我们发现了这种结构存在的问题:
  • 整体流程冗长且复杂,长时间迭代会变得越来越难以维护。
  • 每个容器的构建流程相互独立,且构建逻辑各不一致,有多处重复的逻辑处理。
  • 编译流程缺少统一的关键节点,编译时无法进行业务方的定制扩展。
针对以上问题,我们考虑对CLI进行了一次全新的重构,引入插件化能力(关于插件化能力的具体实现会在下文详细描述)。CLI整体结构变成如下图所示:

图 3
整个CLI模块只需要关心参数的解析以及插件的加载执行,不需要再实现各个容器的具体编译逻辑。通过Hooks的形式,将编译的各个时机暴露给插件,插件基于这些Hook进行编译能力的实现,最终输出产物给CLI模块。这种形式带来了以下几个好处:
  • CLI结构变得清晰,只需要维护配置解析、插件解析等功能。
  • 扩展性增强,可通过插件化的形式新增或删减容器/编译能力,保证代码独立维护功能的单一性。
  • 编译流程可梳理,无论什么容器的编译流程都基于编译器暴露的时机执行并串联,整体流程清晰明了。

4.1.2 组件及API的设计

R2X的目的是希望通过一套代码能够在多端上运行,但是由于多端差异的存在,我们需要设计一套统一的标准规范来进行对齐。在运行时部分,主要分为组件/接口的对齐。
多端差异
在开始讲述实现之前,我们先来看看各端之间的差异到底在哪些地方。
组件(标签)差异
  • Webview标签采用的是XML写法,所提供的基础标签有<div/><a/>等等。
  • 小程序采用的是WXML(WeiXin Markup Language)标签语言,也提供了一套完整的基础标签,但是和Webview有着较大的差异。
  • React Native则是采用的JSX(JS-XML)语法,虽然和XML很接近,但是又有着很多的不同点,同时它也有自己的一套基础组件,和Webview、小程序又截然不同。
API差异
  • 接口差异:在不同端中都提供了相同或近似的功能,但是其实现方式以及调用参数可能存在着很大的差异,比如数据缓存Storage,小程序中用wx.setStorage/wx.setStorageSync,Webview则用localStorage.setItem,而MRN中AsyncStorage.setItem,几乎每一个功能点都有着多多少少的差异。
  • 容器差异:各个端所提供的API都是各自的容器量身打造的,比如小程序的各业务接口类 API,完全是针对小程序所处的微信环境打造的,这类功能与其他端并不相关。
  • 能力差异:各个端之间的差异我们可以通过定制的手段来适配,然而并不是所有的功能点在各个端上都能够实现。比如在Webview中就无法做到像小程序、React Native中提供很多原生能力,像是文件保存读取等等,这一类差异性在适配过程中都属于不可抗拒、不可抹平的差异。
样式差异
小程序的WXSS和Webview的CSS在参数属性上其实是几乎一致的,但是在层级关系上有着很大的差别,小程序分为全局样式与局部样式,各个组件之间的样式也是不会相互影响(默认配置下)。而对比React Native采用的StyleSheet,是用Inline Style的方式,不支持全局样式,不支持标签样式,并且属性有诸多限制,如只能使用Flex布局等等。
如何适配
从以上章节我们已经了解到了各个端之间有着非常大的差异点,那我们应该如何克服这些困难呢?

图 4
由于各端对组件和API的支持程度不同,我们选定了一端为基础标准,定义好各个组件的属性以及接口参数,通过TypeScript的Interface进行实现。然后在各个端分别基于以上的接口进行功能对齐实现,对于端能力限制的功能进行了一定取舍,对高优功能进行了SDK底层实现适配。最终,我们基于已有的功能封装实现了一套完整的基础组件@r2x/components和基础API@r2x/r2x

4.1.3 开放式插件能力

随着R2X的在美团内部的应用越来越多,大家对于R2X模式的认可度也在不断提高,我们从业务方中经常听到以下这些问题:“是否可以增加支持某某功能/容器”,“我们业务架构比较特殊,能否做出一些调整”。业务方对R2X会有更多功能/容器的诉求,也会有更多定制化的需求出现。
所以,我们决定实现一套完整的开放式插件能力,提供一种相对比较简单的方式,让大家能够自己来定制这些特殊需求。在最新的版本中,我们将R2X的编译时进行了重构,在新的编译时架构中引入了基于Tapable的插件系统。开发者可以通过编写插件的方式为R2X拓展更多功能,或者为自身业务线定制更多的个性化功能。
在插件类型分为两类:
  • 容器插件,用于封装R2X所支持的容器的核心编译能力。
  • 功能插件,基于已有的容器插件,在此基础上进行某种特定功能的自定义实现。
插件能力的整体架构如下:

图 5
借助开发式插件能力,我们将之前编写了若干个平台容器插件,开发者安装后即可使用:
  • 小程序容器插件:@r2x/plugin-container-wxapp。
  • MRN容器插件:@r2x/plugin-container-mrn。
  • Titans容器插件:@r2x/plugin-container-h5。
除了扩展新的容器平台,我们还可以通过继承现有的容器插件,来编写一些特殊的定制化功能插件。
1. 对代码进行预处理
基于开放式插件能力,我们可以像Babel插件一样,通过对AST语法的修改对代码源文件进行编译前后的修改。比如:修改文件引用路径、插入代码片段、处理本地图片等等。
2. 对文件产物进行修改
在编译产出生成时,我们可以对编译文件的内容、文件路径、文件结构进行修改。结合自身业务的定制化,CLI可以将R2X项目和现有的原生项目进行结合改造。
除了以上功能,插件化能力为用户在编译时提供了极大的自由度。如果你想体验的话,欢迎加入美团外卖技术团队。

4.1.4 特性能力-多态能力

为什么需要多态能力?
多态能力是用于提供跨端时各端组件及API的统一解决方案。基于多态能力,开发者可以定制自己的跨端组件。而R2X具备了完善的跨端能力,能够覆盖多终端和容器,为什么还需要多态?
业务研发为了满足各自场景的要求需要一定的灵活性。同时,Webview/小程序/React Native容器存在端上的差异,需要开发者人为进行环境判断。逻辑一复杂、跨端数量一多,代码可读性变低、维护成本起飞,这不是我们的本意。
基于这样的背景,R2X提供了扩展性良好的多态能力。
R2X多态能力介绍
对于多态能力的支持,我们分为两类:
  • 多态组件/API,R2X根据文件后缀区分编译目标端。

图 6
  • 差异化代码,R2X提供getEnv环境方法用于判断当前语句编译目标端类型。
<View style={{color: R2X.getEnv() === "WEAPP" ? "green" : "blue" }} />
通过差异化代码可轻松满足端差异诉求。

4.2 应用场景同构

4.2.1 页面级容器场景同构

页面级容器场景同构我们借鉴了业内Taro1.x、Rax等优秀跨端框架的做法,结合美团内部容器、基建特点做了很多本地化实现和定制,在Webview、MRN、小程序三种容器上通过不同的编译时方案进行预处理,并且引入了React运行时,保证了对React DSL支持的完整程度和代码同构率,编译时转化流程方案和运行时结构如下图所示:

图 7
R2X2.0相比于R2X1.0,运行时方案能够解决编译时方案带来的语法限制问题:
  1. 不能在包含JSX 元素的map循环中使用if表达式 ✅
  2. 不能使用Array#map之外的方法操作JSX数组 ✅
  3. 不能在JSX参数中使用匿名函数 ✅
  4. 暂不支持在render()之外的方法定义JSX ✅
  5. 不允许在JSX 参数(props)中传入JSX元素 ✅
  6. 不能在JSX参数中使用对象展开符 ✅
同时也支持大部分React第三方生态库,目前已支持使用原生react-redux。彻底抹平各端的差异,无论是MRN、Flutter、小程序、Webview的自定义组件都可以直接当成React组件引入,小程序原生自定义组件也无需配置usingComponents。

4.2.2 模块级容器场景同构

在模块级同构方案上,我们在App上依赖Mach容器;在小程序容器中,我们克服了Mach容器渲染机制的约束(运行时与虚拟DOM的使用限制),单独在小程序上设计了Mach容器渲染方案,实现了R2X-模块化(R2X-Module)在客户端和小程序上99%以上的代码同构率。
整体方案

图 8
  1. 核心驱动包,容器驱动的核心,针对渲染能力、解析能力、缓存能力、性能监控四个方面进行了实现,达到动态化驱动效果。
  2. 业务容器自定义,基于SDK提供的驱动能力,针对不同展位特性进行了容器自定义功能扩展配置,让业务方可根据实际业务场景自行扩展。
  3. 分环境构建,主要实现了将类React语法进行AST编译解析,根据构建平台分别编译成对应的Bundle产物。
  4. 自动化构建部署,将构建能力接入Talos(美团内部自研的构建部署工具),再结合低代码业务工具实现一键部署,将编译产物根据配置项上传至DD(美团内部自研移动端动态下发平台)。
模板驱动方案
目前,R2X-Module在客户端和小程序容器的同构率在99.3%以上,在性能方面首次渲染时长和模板渲染时长的TP50时间分别是185ms144ms,比较优秀但还存在优化空间。同时提供R2X-Module SDK供业务方选择。R2X-Module SDK初始化以及模板加载渲染流程如下图所示:

图 9

4.2.3 PC/App适配同构

在移动互联网发展已经高度成熟的今天,移动端的PV流量占比绝大数,以外卖广告商家端为例,PC端仅仅占有很少比例,其中PC流量占比在我们部分业务上已经不及5%。因此在某些场景下实现PC/App的同构方案能够解放一部分人力,对提高开发效率来说是十分必要的。目前,外卖广告商家端的一些轻量布局差异的页面,已经完成了PC/App同构的方案设计和落地。
样式同构适配

图 10

图 11
端能力扩展
R2X的基础能力支持Webview/MRN/小程序三端,缺少对PC微前端子项目的支持。要实现PC/APP多端同构需要对R2X的端能力进行扩展。PC端本质上也属于Web端,因此PC微前端的端能力扩展可以复用大部分的Webview的端能力。整体架构图、技术设计要点、扩展流程图如下所示:

图 12
平台代码处理
在项目同构开发中,不可避免地会出现跟平台强相关的代码或者业务逻辑,比如某些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多端生效的模块。

4.2.4 小游戏容器场景同构

实现react2x-game同构方案主要做的两点:渲染层的兼容、业务层的兼容。
  1. 渲染层的兼容:实现游戏引擎在多端环境下渲染能力的兼容(Canvas、WebGL)。
  2. 业务层的兼容:实现基础API、项目流程、公共模块的兼容,制定游戏差异的个性化定制规范。
渲染层兼容
在上文,我们提到过“无论是Webview游戏、小程序、小游戏、美团小游戏都为我们提供了Canvas、WebGL控件”,很大程度地降低了我们兼容渲染层的复杂度。下面表单,是各端对于语法以及Canvas、WebGL、Document、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。

图 13
多端兼容构建
在构建层面,我们通过集成的多种个性化插件工具,对多端代码进行差异处理。如:环境变量注入、各端适配代码的混入、规范检测、代码解析和转化等。针对小游戏、小程序代码和执行环境的特殊性,制作wx-build-plugin、lwxapp-build-plugin等用于处理小游戏和小程序的打包工作。结合上文中提到的各类差异的处理方案,制作add-wxml-loader、transfrom-loader、wxss-loader等工具协助完成项目构建。如下图14所示,构建之初会注入本次构建的环境变量,读取和分析配置文件,集成和初始化构建工具集合,为项目构建做准备。然后在构建环节,针对各端的差异进行差别处理,分析层针对不同文件进行解析,并在转换层进行转换和构建,最终生成各端需要的最终产物。

图 14

4.3 落地场景与效果

R2X-推广页容器场景同构


R2X-模块化容器场景同构


R2X-小游戏容器场景同构

效果收益

R2X在美团外卖业务中得到了广泛的应用。截止2021年10月,R2X累计在美团内部已有二十多个部门在使用或者在调研中,总计落地了上百个工程、页面,框架下载量达百万次,页面平均代码同构率达90%以上。R2X生态体系在容器代码复用与运维层面,累计为美团节省成本上千人/日,并提升动态化页面转化5%-8%的成功率。

五、展望与总结

综上所述,在美团外卖多元化业务形态和容器多样性的情况下,跨容器复用成为了发展的必经之路。而R2X在经历了两年的迭代下也取得了阶段性的成果,在美团各个业务场景都完成了业务的落地覆盖,针对公司的生态环境接入也做出了不少的基础建设。我们相信跨容器多端代码复用依旧是当前缩减项目交付周期,减少研发成本,提升研发效率的重要一环。但目前我们在很多复杂的业务场景下做的不够完美,因此还有许多工作待完善,例如:
  • 开发体验优化,目前想接入或正在接入的兄弟部门已经越来越多,如何减少接入成本,丰富基础建设,优化开发体验,帮助大家快速迁移接入,将是下一阶段的重要课题。
  • 渲染性能优化,在美团外卖场景下性能优化一直是我们在兼顾高效生产的另一个重要指标。特别在小程序场景下,低端机型的性能体验一直是业界瓶颈,如何突破这一难关将会是同构方案全面推广的“敲门砖”。
最后,感谢各个相关研发团队对R2X建设过程中的鼎力支持,R2X的发展离不开所有参与者日以继夜的投入和贡献,我们会持续基于R2X在终端容器领域进行更多探索。如果大家觉得R2X还不错,或者对美团的R2X框架比较感兴趣,欢迎跟我们一起交流探讨。

作者简介

正浩、宝石、彭震,均为美团外卖终端团队研发工程师。
----------  END  ----------
招聘信息
美团外卖长期招聘Android、iOS、FE、Java高级/资深工程师和技术专家,欢迎有兴趣的同学投递简历到:lizhenghao@meituan.com
也许你还想看
  | 美团外卖iOS多端复用的推动、支撑与思考
  | 美团民宿跨端复用框架设计与实践
  | 外卖客户端容器化架构的演进

阅读更多

---
前端 |  算法 | 后端 | 数据
安全 | Android | iOS  | 运维 | 测试

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存