业务背景
低代码由于研发效能和交付的优势变得越来越普及,在降本提效的同时也带来了很多黑盒逻辑。现有的低代码平台普遍缺乏面向用户的调试能力,当用户在低代码搭建遇到问题时,排查和解决问题强依赖平台的客服答疑或浏览器原生的调试能力,导致非前端用户使用低代码平台的成本很高。因此我们需要提供更适合低代码平台的调试能力,降低低代码平台的使用成本。
技术难点
在阿里低代码引擎的产品技术体系中,一方面我们需要提供低代码平台的基础调试能力,适用于大多数低代码的调试能力,需要考虑非前端用户的使用习惯和学习成本;另一方面由于低代码平台的目标用户类型差异较大,不同低代码平台所需的调试能力也是不一样的,除了基础的调试能力之外,我们需要通过提供扩展定制能力,帮助不同的低代码平台快速建设适合的调试能力。
技术细节
在技术架构上,我们通过分层设计,将调试体系分为 Client、Server、Protocol 等不同层来承担不同的职责,向下做好协议的统一收敛,与低代码引擎的协议和渲染体系对接,向上也能支持不同分层下的高度定制。在产品设计上,我们在低代码引擎标准协议的基础上完成了日志查看、错误码定位、审查元素、数据源查看与修改等低代码调试能力上的探索。具体实践和分析:低代码引擎体系下的调试能力在集团内的中后台平台 A 落地了半年,活跃用户总量为 300,占比平台总月活的 10%,并大幅度降低了答疑中调试相关问题的占比。
低代码引擎介绍
低代码引擎是一款为低代码平台开发者提供的,具备强大扩展能力的低代码研发框架。由阿里巴巴终端委员会(原阿里巴巴前端委员会)、钉钉宜搭联合出品。使用者只需要基于低代码引擎便可以快速定制符合自己业务需求的低代码平台。同时,低代码引擎还在标准低代码设计器的基础上提供了简单易用的定制扩展能力,能够满足业务独特的功能需要。
开源地址: https://github.com/alibaba/lowcode-engine
官网: http://lowcode-engine.cn/#/
低代码调试背景介绍
该低代码平台月活跃用户有大概 4000 人左右,这 4000 人里面有前端、后端、测试开发等。其中大概只有 30% 的使用者是前端研发,而剩下 70% 的使用者都是非前端研发人员。为了帮助这 4000 人使用该低代码平台,该低代码平台提供了两个前端全职进行答疑,帮助该平台的使用者解决他们遇到的问题。
我们可以看到,答疑同学一个月大概需要解决 400 多个答疑工单,这些工单里面有大量的使用问题和应用调试问题。
那我们来假设一下,如果这个低代码平台没有前端同学来进行答疑,那是不是有至少 400 多个使用者有可能无法使用我们的低代码平台。我认为是非常有可能的,在这种情况下,如果他们遇到问题,找不到渠道来解决问题的,因此对低代码平台产生不好的印象,他们就很容易抛弃掉这个低代码平台,甚至抛弃掉所有的的低代码平台也是有可能的。
这样我们可以看出来这个低代码平台有两个问题:
- 答疑成本高,对于非前端研发同学来说,十分依赖答疑同学提供的支持,这样会导致低代码平台用户量越多,答疑成本就越高,
- 学习门槛高:就算有足够的答疑支持,非前端研发使用低代码平台也有较高的学习成本,这就导致低代码平台很难进行更大范围的推广。
因此,我们需要去减少答疑的成本,为了找到减少答疑的解决方案,我们先来分析一下答疑的案例。
这是一个答疑同学在答疑过程中和低代码平台的使用者沟通的过程。
在最开始的时候,用户会跟答疑人员说遇到了一个报错,需要答疑人员帮忙看看问题在哪里。这时候一般都需要用户提供可以让答疑人员复现问题的步骤。比如说,需要提供报错页面的地址。在大多数情况下,这些页面都有权限管控,需要找对应的权限管理员来添加权限。甚至有的项目因为添加权限太麻烦,只能通过截图或者视频来查看和定位问题。
所以这个案例暴露出来的第一个问题是权限管控会让答疑时间更长,也让查看和定位问题的难度更高了。
我们继续看第二部分,当权限添加完成之后,用户和答疑人员往往还要沟通复现路径,由于用户和答疑人员沟通过程往往存在信息差,所以需要反复沟通才能明确复现的步骤,答疑人员才能复现出报错场景,才能找到到问题。
我们可以看出来,这里暴露的第二个问题是复现问题沟通耗时且繁琐。
找到问题之后,发现这些问题很有可能是一些对于前端很简单的问题,比如说标点符号使用错误,中英文符号使用错误等。因为大多数低代码平台的使用者是非前端开发,他们没有前端相关的基础。因此对于前端来说很简单的问题没有办法自己解决,就只能依赖答疑人员的帮助。那如果缺少了答疑,没有前端基础的使用者想要解决类似的问题,学习成本还是非常高的
首先我们来看一下该低代码平台调试的现状。这里我们设计了一个页面,这个页面在设计器中是没有问题的,但是当我们在某一个组件中配置了一个错误的表达式,我们在预览的时候就会看到页面出现报错。对于前端来说,可以打开控制台查看报错信息,报错信息就像图中展示的这样。
这个报错信息能帮助低代码开发师找到问题吗?实际上,不管是对于前端研发人员来说,还是对于非前端研发来说,都是很难直接定位到问题的。
一方面,这个页面很有可能有几百上千个节点,每一个节点都有很多个配置。这个报错信息没有告诉我们这个报错的代码是配置在哪个组件节点里面的。也不能让用户打开每一个节点来检查每一个配置。
另外一个方面,低代码平台可以配置代码的地方很多,这样就导致我们的代码是非常散乱的。我们无法通过现有信息知道报错代码配置的方式,配置的地方。也就是说,就算是前端,也很难找到报错代码的。
为了让低代码平台的使用者能自己解决这类调试问题,我们就需要提供一个调试工具,让用户可以
- 直观的知道如何查看报错的内容
- 可以根据报错内容,知道报错的代码在哪里
- 在找到报错的代码之后,知道如何去修改代码,修复问题。
有了这个工具之后,低代码平台的使用者就可以自己解决问题,也就大大减少对答疑人员的依赖了。
对于第一个问题,我们需要让低代码平台的使用者知道“如何查看报错”,我们提供的解决方案是,在遇到报错的时候,我们通过一个显眼的标识来告诉用户,你开发的页面出现错误了,其中错误的个数有多少个。用户看到这个标识之后,就会去点击这个标识。当用户点击之后,就会在页面中出现低代码调试工具,来帮助用户查看报错相关的信息。
在这个调试工具中,我们通过展示日志的方式,来让用户更加直观的找到跟错误有关的信息。并且,慢慢的用户就知道我们提供了一个工具来帮助他们开发低代码页面。之后遇到其他问题也会自己打开低代码调试工具,相当于起到了锻炼用户开发习惯的作用。
在用户知道如何查看报错之后,接下来的问题就是如何通过日志输出的能力,让用户知道错误的代码在哪里。
对于这个问题,我们通过给不同类型的报错提供对应的错误码,来帮助用户找到报错代码。
- 首先对于不同类型的错误,我们会提供一个单独的错误码,并且还会提供对应的上下文信息,比如组件 ID 是多少,用户配置的表达式内容是什么;
- 当然有了这些信息还是不够的,我们还需要将上下文信息用更语义化的方式展示给用户。因此为了更好的组织这些错误码、对应的上下文信息以及展示给用户的描述文案,我们利用错误码管理后台来管理这些信息。
有了这些信息,我们在低代码调试工具的日志面板中就可以展示出非常详细的信息来帮助用户找到报错的代码。
比如:之前我们在控制台中看到的错误,在日志面板中展示的内容就变成了:某某某组件表达式执行出现错误,并给出了错误的原因,给出了错误的表达式内容,错误的组件 ID 等信息。用户通过组件 ID 就可以在低代码平台中进行搜索,就可以找到对应的组件了。也就能找到报错的代码了。
对于非前端研发来说,我们帮助用户找到错误的代码还不能完全解决问题,我们还需要告诉他们如何修改代码来解决错误。
为了解决这个问题,我们在错误日志的基础上,增加了一个外链功能,用户可以点击这个外链,打开一个错误码对应的文档,这个文档通过图文的方式详细的描述了问题的解决步骤。这样对于一些简单的问题,用户就可以找到解决方案。
当然,对于一些相对复杂的报错案例,用户可能还是无法自己解决问题,还是需要答疑人员的帮助。为了减少用户和答疑人员来回沟通的成本,我们还支持用户上传页面日志,日志中会携带页面的关键信息,比如说日志内容、搭建产生的 JSON 源码,环境信息等等。这样就可以减少工单处理的时长。
第二个用户会遇到的问题是?对于大多数低代码产品,他们的在页面设计时所看到的效果,和实际预览的时候看到的效果在一些情况下是不一致的。比如说我们在设计这个页面的时候,给某个组件配置了表达式,而这些表达式在画布中是不会进行计算的。这样会导致一个问题。设计时看到的效果和实际展示看到的效果是不一致的。
比如这里的按钮展示的就不一样。那么当我们在预览的时候,如果效果和我们的期望的不一样,我们就想知道
- 这个组件的配置表达式是什么?
- 这个组件展示的文案是怎么计算出来的?
之前为了解决这些问题,用户需要回到设计器中,找到对应的组件,查看这个组件的配置才能解决。而对于一些复杂的配置,比如三元表达式,用户为了找到问题,还需要在很多地方添加 Console,查看数据源的值。因此为了帮助用户快速的定位到单个组件配置的问题,我们提供了组件审查功能。
它的效果是这样的,当切换到组件审查面板的时候,用户可以点击页面中的元素,或者点击大纲树中对应的组件,就可以看到这个组件的详细信息。
比如这里有一个低代码开发的页面,我们需要查看页面中某一个按钮的展示逻辑。
- 我们就可以点击这个按钮,就可以看到这个组件的详细信息
- 首先展示的详细信息,是这个组件根据配置计算出来的值,我们可以看到这个按钮展示的文案来源是 content 这个参数的值。
- 第二个展示的信息是这个组件配置的原始内容,这里我们就可以看到 content 这个参数值实际上配置的是一个表达式,也就是 content 的值是根据 state.text 的表达式计算来的。
- 第三个我们展示的信息是这个组件可以访问到的上下文信息,比如 this.state 的值是多少,比如 this.utils 下有哪些函数等等。
这样用户就可以通过这个工具,一步步查看组件参数的配置,也可以推导出它的计算过程,如果不符合预期也可以快速的发现并知道如何解决问题。
除了刚刚提到的这两个工具之外,我们还提供了数据源面板和 schema 面板两个调试工具。
- 数据源面板: 数据源面板可以让用户查看页面数据源的值,并且为了方便低代码平台的用户对数据源进行调试,数据源面板还支持修改数据源的值。
- schema 面板:对于低代码平台来说,所有的可视化操作和配置,实际上操作的都是一个 json 对象,这个 json 对象我们把它叫做 schema,我们也提供了 schema 面板来查看页面渲染时使用的 schema 源码。
以上就是我们针对该低代码平台建设的低代码调试能力。但是它只能解决一个低代码平台的问题,为了帮助更多的低代码平台解决类似的问题,我们还需要提供低代码调试扩展能力。
在阿里,将近有200多个低代码平台,而刚刚我们提到的低代码平台只是这里面中的一个。那么其他低代码平台需要调试能力吗?肯定也是需要的,不过他们需要的不只是我们之前提到的调试能力,他们还需要根据自己平台的特点,来定制适合自己平台的调试能力。
比如,
- 有的平台的用户是前端,就期望能通过浏览器插件来进行调试,这样更符合前端用户开发调试的习惯;
- 而有的平台,提供的是表单搭建能力,比如说宜搭。就更需要表单相关的调试功能;
- 有的平台就想在调试能力上加一个按钮,来帮助用户找到答疑入口。
为了满足不同低代码平台对调试能力的诉求,我们需要在低代码调试能力的基础上提供扩展能力,让低代码平台可以定制对应的调试能力。
- 插件化: 首先我们需要将调试能力进行插件化,插件化的意思是将每一个调试能力都抽象为一个插件。这样可以方便不同的低代码平台选择不同的调试能力,并且可以对调试能力进行插拔,平台需要什么调试能力就可以引用对应的插件。
- 基础调试能力:在将调试能力插件化之后,我们就可以将之前建设的调试能力作为基础调试能力,并且沉淀成基础调试能力插件,比如日志调试能力插件、元素审查调试能力插件等等。
- 调试扩展能力:当基础调试能力不满足低代码平台的诉求时,低代码平台就需要利用我们提供的调试扩展能力,来开发自定义调试插件。
- 低代码平台开发者:当我们有了基础调试能力和调试扩展能力之后,低代码平台的开发者就可以基于这两个能力开发平台的调试能力了。
首先低代码平台开发者可以看看现有的基础调试能力是否适合自己的低代码平台,并且选择适合自己低代码平台的基础调试能力。
基础调试能力无法满足的部分,低代码平台就需要开发对应的调试能力插件。自定义调试插件开发完成之后,就可以和基础调试能力组合到一起。这样,低代码平台就拥有了更适合自己平台的调试能力。
比如:
- 低代码平台 A,就选择了元素审查的基础调试能力,并且基于自己平台的搭建特点,开发了表单调试能力。
- 低代码平台 B,就选择的是日志的基础调试能力,并开发了图表调试能力。
这样不同的低代码平台就可以拥有更适合自己平台的调试能力,这个就是我们最终期望实现的低代码调试扩展能力,
接下来的问题是:我们要如何实现低代码调试扩展能力,并且它还能用于几十、上百个低代码平台。
幸运的是,在我们建设低代码调试扩展能力之前,阿里集团大部分低代码平台都是基于低代码引擎来建设的。也就是阿里内部 200 个低代码平台中,有将近 130 个低代码平台是基于低代码引擎来实现低代码搭建和渲染的。
有了这个前提,我们就可以站在巨人的肩膀上,通过给低代码引擎这个体系提供标准的低代码调试能力,以及低代码调试扩展能力,来服务越来越多的低代码平台。
正如我们之前提到的,大多数低代码平台,可视化拖拽和配置实际上都是在操作一份 json 文件。低代码引擎也不例外,并且低代码引擎还对这个 json 文件制定了一份标准协议,也就是「低代码引擎搭建协议」。因此通过低代码引擎建设的低代码平台,在可视化操作之后,都会产生一份符合搭建协议的 json schema。
这份 json schema 浏览器是无法直接进行解析渲染的,因此还需要依托于「渲染引擎」来解析 schema,将低代码页面绘制到浏览器中。那么这个渲染引擎是不是只有一套呢?并不是。由于渲染引擎在绘制页面的时候,还会用到大量的基础组件或者业务组件,这些组件用到的技术栈可能是不一样的,为了让不同技术栈的物料都能渲染到浏览器中,渲染引擎也需要根据不同的技术栈实现多套。
因此,我们有多套渲染引擎。例如 React 渲染引擎、Rax 渲染引擎、以及未来还会有 Vue 渲染引擎等。
介绍了那么多关于渲染引擎的信息,那渲染引擎和调试能力是什么关系呢?
调试能力需要通过渲染引擎才能获取到页面信息。为什么这么说呢,我们先看一下低代码页面绘制的过程。
- 首先低代码平台通过可视化配置,会产生一份描述页面的 json schema。
- 在页面渲染的时候,渲染引擎会根据 schema 的内容绘制页面,并且除了 json schema 之外,渲染引擎还需要知道,json schema 中描述的组件和插件是什么样的。所以也需要把整个页面会用到的组件、第三方插件等信息给到渲染引擎。
- 渲染引擎有了这些信息之后,才会在浏览器上绘制出来页面。
在整个绘制过程中,浏览器知道 schema 的内容吗?以及知道每一个组件的参数值吗?实际上,浏览器是不知道的,因为这些执行和解析的过程都是由渲染引擎完成的。那么调试能力需要知道这些信息,就只能通过渲染引擎来获取。
- 比如说需要通过渲染引擎来获取页面的 schema
- 比如说需要跟渲染引擎来获取单个组件的参数值。
因此调试能力需要通过某种方式和渲染引擎进行通信,比如通过 API 来进行通信。但是正如之前我们提到的,渲染引擎根据不同的技术栈实现了多套,而每一套渲染引擎提供的 API 都有可能是不一致的。因此我们不能基于渲染引擎的 API 来开发调试能力。
这里为什么说不能呢?想象一下,如果有这样一个低代码平台,它即支持PC页面的搭建也支持 H5 页面的搭建,但是 PC 页面使用的可能是 React 版本的渲染引擎,而 H5 页面使用的是 Rax 版本的渲染引擎。那么同样的调试能力就需要根据两个不同版本的渲染引擎 API 来做兼容,或者说可能需要开发两套。这肯定是不合理的,那调试能力和渲染引擎之间就需要一个沟通的标准。
这个通信的标准就是低代码调试协议。它通过定义渲染引擎和低代码调试能力之间通信的标准,来抹平不同版本渲染引擎在 API 和实现上的差异。比如:
- 它定义了渲染引擎如何主动通知调试能力;
- 它也定义了调试能力如何主动通过渲染引擎获取到相关的信息。
这样的好处是,
- 对于低代码平台的开发者来说,他们在开发低代码调试能力的时候可以不用关注页面用的是什么技术栈的渲染引擎。也不需要做任何兼容和适配的工作;
- 对于低代码引擎来说,不同技术栈得渲染引擎在实现了低代码调试协议之后,就可以支持所有已经开发好的调试能力。
接下来让我们看看调试协议是如何进行定义的。
首先在定义协议之前,我们再来明确一下我们定义协议的目的是什么。它的主要的目的还是用来完成调试能力的开发。而调试能力开发需要的主要分为两个方面:
1.schema 获取和操作类型
第一个是 schema 的获取或者修改相关的操作。比如获取整个页面的 schema,获取单个组件节点的参数和状态。这些操作本质上都是在获取或者操作 schema。
基于低代码引擎建设的低代码平台,产生的 schema 是根据低代码引擎搭建协议规范生成的。因此我们操作 schema 就是操作搭建协议描述的结构。为了让调试能力更方便的操作搭建协议,我们将搭建协议分成了三层。
- 第一层是搭建协议的顶层结构,为了操作这一层结构,我们在调试协议中定义了 schema 调试域。这样就可以通过 schema 调试域来获取整个页面的 json schema 或者说修改整个页面的 json schema。
- 第二层是搭建协议中的容器结构,容器是一类特殊的组件,在组件能力基础上还增加了一些特殊能力,比如说它有单独的自定义方法和数据源。因此我们定义了 Container 调试域来操作容器这一层结构。这样我们就可以在调试能力中修改容器的数据源或者调用容器中的自定义方法了。
- 第三层就是搭建协议中的单个组件结构,在调试中肯定需要对单个组件进行操作,比如说需要知道组件计算后的参数值,组件的原始配置,获取组件的 json schema 等等。因此我们定义了 Node 调试域来操作这一层结构。
2.辅助类型
除了获取或者修改 schema 相关的操作之外,在调试的时候,我们还需要一些辅助类型的操作。比如说,调试能力想打开一个新的浏览器页面,就需要在浏览器窗口中执行对应的表达式。这些需要执行的表达式我们就用 Runtime 这个调试域来进行通信。比如,当我们审查元素的时候,需要页面出现遮罩,来标识用户正在查看的组件是哪个。因此我们定义了 Overlay 调试域来进行相关通信。
这样我们的低代码调试协议就定义好了。接下来我们看一下根据低代码调试协议,我们实际上在调试过程中调试工具和渲染引擎通信的示例。
辅助类型 - Runtime 调试域
第一个例子,是一个辅助类型的域,也就是 Runtime 调试域。当调试能力期望在新的浏览器窗口中打开一个新的页面时,就会发送一个 Runtime 域的 JSON,这个 JSON 中的 expression 字段就是定义了需要执行的表达式。当渲染引擎接收到这个 JSON 之后,通过解析,就知道了它的意图,就会执行 JSON 中的表达式,执行完成之后,也会发送一个 JSON 通知调试面板表达式执行完成。
schema 操作类型 - Schema 调试域
第二个例子,是关于操作低代码引擎搭建协议顶层结构的域,也就是 Schema 域,当 Schema 变化的时候,渲染引擎就会发送图中的 JSON 给到调试工具,调试面板就能知道当前页面的 Schema 变化了,页面渲染的内容也就变化了,调试能力就需要执行相关的操作,比如更新 schema 调试面板展示的内容,更新审查面板的大纲树等等。
低代码调试协议目前基本上介绍的差不多了,我们再考虑一个问题,之前我们提到这个协议由渲染引擎来发送,但是这样是不是合适呢?实际上是不合适的,因为对于低代码平台来说,线上环境和开发环境中用的渲染引擎都是同一个,在线上环境有那么多冗余的代码是有问题的。另外这种实现方案也会让渲染引擎有较大的改动,同时也会让使用旧版本渲染引擎的页面无法使用调试能力。
那么由谁来发送调试协议呢,以及它又通过什么方式来和渲染引擎进行通信呢?
为了解决这个问题,我们新增了一个代理人的角色,这样:渲染引擎通知调试能力的方式就变成了,渲染引擎通过事件让代理人知道要发送调试协议了,代理人就发送对应的调试协议。调试工具想获取 schema 的内容就变成了,调试工具发送的调试协议,由代理人来接收,接收之后代理人就会调用渲染引擎的 API 来获取 schema 的内容。得到结果之后代理人再将结果通过调试协议发送给低代码调试工具。
增加代理人的好处是:
- 渲染引擎只需要提供相关的 API,而不需要为了调试能力进行大规模的改动。
- 代理人只有在开发低代码平台页面时才存在,这样就不会影响到我们的线上环境。
我们看一下实际通信的例子,
- 首先在页面初始化的时候,代理人会调用渲染引擎的 API,来注册日志回调事件,相当于跟渲染引擎说,有日志了记得通知我;
- 当渲染引擎出现日志的时候,就会调用代理人注册的回调函数,相当于通知代理人,有日志了,日志内容是某某某。
- 代理人收到通知之后,就会发送对应的调试协议,也就是图中的 Json 对象。
- 低代码调试能力就会接收到对应的协议,也就可以执行相关的操作,这里就是将接收到的新日志展示出来。
我们已经定义好了低代码调试协议,也实现了渲染引擎和调试能力之间的通信能力。
接下来我们还需要提供一个容器来展示基础插件和自定义插件。
根据展示的类型,我们将插件分为两种:
- Tab 类型:比如之前的日志面板。对应图中的黄色区域。
- Action 类型:当点击的时候,它只会执行一些操作,比如上传日志,比如跳转到答疑页面等等。这些插件会展示在图中的红色区域。
用户可以根据自己需要的调试能力来选择开发不同类型的插件。
我们现在基本上已经实现基本的调试扩展能力了,为了让低代码平台可以更便捷的开发低代码平台调试能力,我们还提供了调试相关研发的工具链,让用户可以初始化开发插件,调试插件以及整合插件。除了工具链之外,我们还提供了更方便的 API 来操作调试协议,这样用户开发调试能力的时候就不用写 JSON 了。
这样我们的低代码调试扩展能力就建设完成了。
在阿里的这款中后台低代码平台中,使用调试能力的月活跃用户有 300 人,平台的答疑量降低了 10% 左右。
其中一些简单的答疑基本上已经消失了,比如说由于跨域,一些接口请求会出现错误,之前也有很多同学因为这类问题找到我们答疑同学,现在之类答疑已经没有了,并且由于组件表达式配置错误,而导致的页面报错,这种类型的答疑也少了很多。
此外,阿里对外的商业化低代码平台,钉钉宜搭,由于大多数搭建场景是在搭建表单页面,他们也根据表单页面的特点,扩展了对应的调试能力。可以支持查看表单数据和错误请求。
目前低代码调试能力还处于刚刚萌芽的阶段,还需要不断探索和建设。就像浏览器提供给前端研发丰富的调试能力一样,低代码领域未来还会有非常丰富的调试能力,这些调试能力可以帮助越来越多的低代码开发师解决低代码搭建过程中遇到的问题。
下一步我们还会建设更多的基础调试能力:
- 比如用户在低代码中写的 JS 代码,我们可以在调试工具中查看和调试对应的 JS 代码。而不是需要回到设计器中才能查看。
- 比如可以在调试工具中查看页面的数据源请求信息。
- 比如可以查看页面或者容器生命周期的执行等等。
此外,我们还会提供更多的低代码调试容器:
- 比如 Web 容器,适合提供给非前端研发同学来使用。
- 比如浏览器插件容器,适合提供给前端研发来使用,更适合前端研发同学的调试习惯。
- 比如说客户端调试容器,提供给一些特殊调试场景来使用,比如用于移动端调试场景。
最后,我们也需要让更多的渲染引擎支持低代码调试能力,比如我们低代码引擎中不同技术栈的渲染引擎,React 版本的渲染引擎和 Rax 版本的渲染引擎,以及未来由社区支持的 Vue 渲染引擎。