去哪儿低代码平台跨端渲染方案及落地
作者介绍:
去哪儿网目前的低代码平台已经搭建了上万个活动页面,包含小程序、touch和 APP 多个平台。去哪儿低代码平台是基于 Shark 框架开发。Shark 是一款有着跨平台(一套代码支持跨端渲染)、按需加载(仅加载页面配置所需代码文件)等特性的类 React 框架。有着缓存、消息中心等多种能力。Shark 和低代码平台的无缝结合,给现在低代码平台带来了跨端、“所见即所得”得等多种特性。而“所见即所得”,就是一种动态加载的功能:我们在低代码平台上配置一个页面所需组件和对应的各种属性,可以及时的在各个端上看到。
随着低代码平台的推广应用,接入了越来越多的业务的核心流程,对于加载性能上的要求越来越高。在当前阶段,低代码平台在 APP 端是利用 H5 的方式来渲染页面。但是这种方式首先需要加载 WebView ,然后才会去绘制页面,导致白屏时间比较久。
去年遇到了一个契机,门票业务在对主流程进行了大改版,当时人力相对比较紧张,而且业务侧希望页面的组件是可配置的,对于这个挑战,结合低代码平台进行了思考,代码平台天然是可视化配置的,也支持多端运行,美中不足的是在端内是以 H5 方式运行的,如果在端内支持 RN 运行,补齐性能的短板,整体来讲将会是一个很好的方案。
布局名称、方式和 RN 区别较大
语法树标签主要是 View,可以看到交互和文字展示都是 View ,但在 RN中是不同标签 标签属性的不同,在 RN 和 Shark 中点击事件不同等
......
布局名的嵌套
单位的不同
属性名和RN不同
JS文件
JS 文件主要处理包括语法树(标签的替换、布局抹平、标签属性替换等)和JS( document 等的处理)两个部分。针对其中几个主要的问题展开讨论
标签以及属性的转换:
Shark 中绝大部分标签都是 View ,但是在 RN 中不同,RN 中不同的标签会承担不同的功能。比如在 Shark 里 View 还可以接收点击事件,但是在 RN 中只能是 TouchableOpacity 等少数组件。针对这一情况我们根据一些属性,当发现是一些特殊组合的时候就会在代码中替换组件。通过下面的映射表,我们将不同标签和属性的组合映射到 RN 中对应的标签和属性。
shark组件 | RN组件 |
---|---|
View | View、 TouchableOpacity(当有onClick时转换为此组件) |
Input | TextInput |
Text | Text |
Image | Image Image + TouchableOpacity (当有onClick时转换) |
ScrollView | ScrollView |
嵌套布局的抹平:
整个嵌套算是 Shark 和 RN 上分歧最大的地方,布局上要将Shark多种写法统一成 RN 的写法,其次就是要将 Shark 嵌套的布局在 RN 上抹平。过程如下图所示。
对于不同的 class 或者 style 写法,在 babel 中都是不同的节点要单独处理,对于不同的节点我们应用不同的规则。我们收集到统一的格式之后,就可以运用一个规则去处理抹平。在 scss 文件处理的过程中,嵌套文件拿到的最后的属性名都是多层拼接完成的,比如 styles. 层级1_层级2_层级3,但是在 js 文件中处理完的都是 styles. 属性名,这就引出了嵌套布局抹平的问题。我们维护了一个当前布局层级的栈,我们在每一层 View 进入的时候入栈,记录一次布局名称,每一层 View 结束的时候作为出栈。在前面处理 scss 文件时,我们拿到了所有布局的嵌套关系,根据这个栈和我们拥有的嵌套关系去遍历,去匹配是否有布局嵌套,如果有就替换如果没有则进行下一次匹配。通过这种方式我们来解决嵌套布局的问题。
Scss文件
scss 文件和 RN 使用的布局属性,其实差异不大。我们最重要的是处理类型名的嵌套,整个的转换我们分为两步,每一步去处理不同的问题。
第一步:将 scss 文件转换为 css 文件。在转换的同时,我们将单位 rem 删除、嵌套的类名抹平,这时得到了我们想要的中间文件 .css 文件。
第二部:将 css 文件转换为 RN 的布局文件即 style.js 。在这一过程中,我们要记录所有嵌套布局的路径给将来index.js去处理嵌套布局。同时为了解决属性上的问题,我们通过配置文件将不支持的属性删除,并替换不同属性值的问题。通过这个方式我们获取到 RN 可使用的一个 object 对象并保存为一个 style.js 的文件。
整体 scss 文件的转换思路如下图所示
所有的 AST 根节点都是 Program 节点,从上图中我们可以看到解析生成的 AST 的结构的各个 Node 节点都很细微,https://github.com/babel/babylon/blob/master/ast/spec.md (不过这个文档并没有说明具体输出的样式,有时同一个节点,输入不同参数输出的代码可以差距非常大,尤其是在格式化时这点就非常重要)这个文档对每个节点类型都做了详细的说明,你可以对照各个节点类型在这查找到所需要的信息。通过https://astexplorer.net/ 可以有效的观察代码对应的节点,以及节点的各种属性关系。熟悉了 AST 之后,就可以通过 Visitor 来遍历节点,更改我们想要的代码。
Visitor
在 visitor 中引入了 path 的概念,它中包含了节点的信息以及节点和所在的位置,以供对特定节点进行操作。不仅包含了当前节点的信息,也有当前节点的父节点的信息,同时也包含了添加、更新、移动和删除节点有关的其他很多方法。具体地,Path 对象包含的属性和方法主要如下:
整个 visitor 的过程,可以简述为通过修改 path 来改变 AST 语法树的过程。
如上所示,我们在修改的过程中针对 path 对替换或者修改,生成新的节点,就可以达到我们的目标。以我们这次的代码为例子,当发现 onClick 属性时,我们要将 View 标签替换为 TouchableOpacity 标签,onClick 替换为 onPress 。
通过语法树分析,onClick 在语法树中的层级是 JSXOpeningElement → attributes → JSXAttribute → JSXIdentifier → name。这时才找到了 name = "onClick"。
此时我们在 visitor 中找到 JSXIdentifier 并通过 path 找到 name
我们找到了对应的节点后,问题就是要替换成什么。通过上一步分析语法树的方式我们可以知道,onClick 转换为 onPress ,语法树上只是对应的 name 有变化。此时我们需要生成一个新的 JSXIdentifier 类型的节点。对应到我们的插件里如下图所示:
这样,我们就达到了转换的目的。通过总结不同的节点的替换和修改,就能达到修改语法树,转换代码的目的。
message
request
jump
logger
……
(图1)
(图2)
针对我们开始提到的性能问题,根据TTI监控指标,我们可以看到,P90 和 P50 的平均时间都在 2 秒之内的,这样看整体的方案是达到了我们最初的目标。