设计无侵入性的低代码编辑器

低代码(此处的低代码是广义的低代码,也包含零代码场景,后面都以低代码代称)是当前技术圈话题点比较多的一项技术。一些是商业化地APaaS或者SaaS等系统给客户使用的,一些是给团队内部提效使用的,虽然大同小异,但是场景不同针对点也有所不同。本篇文章主要针对开发团队内部使用的低代码编辑器来进行展开。

动机

之前碰到团队是一个多业务、多端、多组件库、多框架、多平台的大前端团队,在一些场景有大量的同质化页面,开发每次复制粘贴虽然能解决问题,但是对开发同学的成长没有什么帮助,沟通成本也非常高。

希望可以通过一些方案解放开发,进行提效,用低代码构建页面是我能想到的方案之一。

低代码这个想法开始于21年初,选用低代码的核心思路其实主要是想让需求的沟通链变短,让参与需求的开发尽可能少,最好做到不需要开发。

预研

当时市面上的低代码编辑器为了更好地把控编辑的过程,通常将画布中要展示的组件和编辑器本身用一个技术栈,且是编辑器所支持的组件库来实现的,有一定的封闭性。

也因此你想要选择一个好用的第三方低代码编辑器,就要跟他使用同一个技术栈,甚至同一套组件库,那如果原有的项目不是匹配的技术,就需要对自己原有的技术进行改造会支持。

而即使使用了同一套技术栈同一套组件库,因为要支持低代码编辑器的使用,可能还要在组件代码本体中进行声明和标注等操作满足编辑器的圈选等定制逻辑。

这样的设计在一定程度上降低了开发时的知识成本,更讲究开发的约定来让业务、组件、编辑器三位一体,更好掌控,比较适合一个技术栈规范严谨的团队闭环,但是对于外部团队,或者业务模块或技术栈较多的团队用起来就比较痛苦,也因为组件库和编辑器的联动导致了必然的侵入性。

也可能正是因为这种原因,业内出现了低代码的百家争鸣,主流大厂基本人手一个,有的厂子甚至有一堆低代码相关的建设,大家都在以自己的思考和自己擅长的方式输出了各种类型的低代码编辑器,像阿里也在AI到低代码上有了较深度的实践,让人钦佩。

但这样的低代码编辑器满足不了团队的多样性场景,总不能每个场景都做一个低代码编辑器吧。

那如果要自己做一个低代码编辑器,满足需求,又要做成一个什么样的东西呢:

  • 需要一个编辑器支持所有场景
  • 需要跨技术栈
  • 不希望对原有的技术做改造

提出假设

仔细分析上面的问题和矛盾点会发现,如果让业务、组件、编辑器三项内容分别解耦,然后通过某种约定再将他们相连,是不是就可以满足上面的要求了,这似乎正应该是一个在多元化团队中低代码编辑器该有的样子。

低代码编辑器只是个工具,简单来看主要做了拖拉拽组件元素,生成布局,修改配置,产生页面这几步。

不论你是什么样的团队,要做怎样的编辑器,只要需要人工介入,这几步都是避免不了的,而如果想让开发尽可能不介入,只要在这个前提下做好体验就可以了(当然做好这个体验也是个非常艰难的工程,本篇文章先不做该角度的讨论)。

那是不是可以就针对这个流程做编辑器,跟流程不直接相关的内容都分离出去,以接入的方式接入到低代码编辑器里面。

当前,阿里(lowcode-engine)腾讯(tmagic-editor)各有一个类似的方案已经开源,虽然我的解决方案并不相同,但是大方向的思考相似,大家有兴趣可以对比了解下。

画布设计

先来从下图看下低代码通常的基本结构和行为:

从图中可以了解到:

  • 页面中有三个主要元素,组件、画布、配置
  • 组件和配置都是输入,画布中主要是输出
  • 每次有输入行为的变化都有可能让画布重新渲染

前面有说过,之所以现有的低代码编辑器有侵入性,主要还是在于对组件库中的组件有干预,做这些干预的主要目的,就是为了能便捷地拖拽、点选、配置组件。

这些行为其实也主要是发生在画布上的,如果我能让画布和组件实体产生解耦,那么无侵入的设计就迈出了关键的一步。

实际上低代码侵入业务的原因也是多样的,一些低代码平台也会从打包甚至业务流程上侵入,只是当编辑器做到了无侵入的时候,其他环境的侵入就能做到更多的选择。
最容易想到的方式是让低代码部分和传统开发的部分完全走两条线,而这不是本文的重点,就不再过多展开。

此时我想到了现在手机的触摸操作,在触摸手机进行操作的行为时,其实也不是在直接操作手机的显示屏本身,而是在操作电容屏上的导电膜,然后将信号传递给各种程序再转换出画面的。

受到这个思路的启发,我想低代码编辑器也可以以这个思路进行设计。

和手机屏幕的结构对应,我也做了一些类似的分层:

  • 手机上的触点就像是我在低代码上要点选的元素,我称之为编辑层
  • 产生触发或者交互行为就会有行为信号传递给系统,也就是低代码编辑器
  • 手机系统回家行为信号处理成机器语言给到应用;而编辑器则对行为信号进行解析,变成约定的领域语言给到渲染引擎
  • 应用和渲染引擎根据对应的语言情况,进行渲染,就会产生用户能看到的结果

在这个分层中,其实已经将组件与画布本身进行了一定程度地解耦。

想接入这样低代码编辑器的团队,预计只需要了解DSL并开发渲染引擎即可,其他环节由低代码编辑器进行管理,也就能在低代码中想手机引入各种软件一样,引入渲染引擎和组件库了。

同时低代码的结构和操作流程也会变成下面的样子。

现在解耦的设计有了,那具体要怎么让开发团队接入呢?DSL和渲染引擎又是什么?

DSL

领域特定语言(Domain Specific Language),在低代码领域中几乎已经成为标配,通常用来作为中间语法的设计,来抹平不同技术间的差异性,做到一码多用。因此我也选用了DSL做低代码编辑器和组件库及画布之间的媒介。

以一个图片(Image)的节点进行举例:

这就是一个节点的基本结构,而开发者就是要做一个将这个DSL节点解析成对应技术栈布局节点的引擎,比如前端就是要解析成<img width=”100%” src=”url” />并渲染到浏览器中。DSL节点根据前面图片中的流程,是需要编辑器来生成的。这需要用户对画布操作的时候,可以将对组件进行的操作转变为节点的DSL,现在设计的目标是编辑器可以和组件库解耦,那这里就不能让编辑器直接去操作组件。因此可以使用声明的方式,将组件的描述给到编辑器。

通过以上的操作,就能做到编辑器生产这套节点信息,渲染引擎也就是开发者消费、解析、渲染这套信息,让上面画布的设计成为可能。

上图就是触发画布编辑后,而后借助编辑器中的DSL转换器,将用户的操作信号转为DSL并将其给到渲染引擎,而渲染层则会根据这段描述结合真实的组件画出页面,至此一套无侵入性的低代码编辑器就算是有了雏形了。
复制代码

下面再看看渲染层和编辑层是如何设计的。

渲染层

渲染层最主要的职责就是给渲染引擎和组件库一个容器,根据页面描述渲染出对应的页面内容。

从细节来看,还需要具备:

  • 组件的使用
  • 组件相关技术栈的运行时
  • 组件解析
  • 页面的渲染

按照这样的设计,渲染层相当于一个沙箱将组件库和页面渲染的行为隔离在了自己的容器中。

而关于沙箱,我的目标是尽可能避免低代码编辑器和渲染层的互相影响,可能的影响会有:

  • 样式影响
  • 脚本影响
  • 显示元素的影响

因此,可选的有ShadowDOM、iframe两种方案,在尝试了两种方案以后,发现如果要编辑对话框之类的组件,往往会有一个蒙版,而这个蒙版往往会去尝试让整个页面被遮盖,而ShadowDOM的方案并不能很好的对这个问题进行控制,因此我最终选择了iframe方案。

相对的,组件本身的使用也完全不感知编辑器本身,两边无法因为环境因素互相影响,只能通过主动的操作让两边进行通信。通过这个设计,甚至可以让这个渲染层画出与生产环境完全相同的页面,达到真正的所见即所得。

低代码编辑器的职责是构建和编辑页面描述,现在把绘制出来的可视内容隔离了,那怎么编辑内容呢?这就要靠编辑层了。

编辑层

从前面的流程图可以看出操作后的页面描述同时给了渲染层和编辑层,也就是说,编辑层也要绘制一个页面的模型,只不过这个模型是一个只有标注和布局的触发器。

从上图可以更形象地看出编辑层和渲染层的关系,其实就是将组件的布局和大小映射到了编辑层,让用户操作组件的时候感觉是点击到了渲染后的组件,其实用户点击的是编辑层的透明触点,以此来进行页面内容编辑行为的触发。

如果想让点击选框的时候准确的识别到对应的组件,就需要编辑层的选框和对应的组件完全贴合,想要做到这个贴合,只需要将组件的布局信息进行收集,传递给低代码编辑器,再由低代码编辑器给到编辑层按照坑位进行布局即可,而确定一对一的关系,只要在页面描述生成的时候给每个组件节点一个唯一的ID即可。

因此就可以通过传统低代码编辑器的开发能力对页面进行编辑,完整的编辑器运作流程如下:

再说无侵入性

讲到这里,再回顾一下最开始的动机,这套设计能不能解决问题。

多样性的问题,前端部分比较典型的就是有多个组件库。

当前的设计,团队不同业务模块的组件库各自包装一层组件库的声明,再加上对应的渲染引擎,就可以把一个低代码编辑器多用的流程跑起来了。

有同学看到这里可能下意识觉得做多个渲染引擎的成本似乎很高,而在实践中也发现,其实React同一个技术栈只用一套渲染引擎就可以了,因为解析的方式都是相同的。

而想用小程序这种也可以扩展类似Taro这种React的引擎就可以进行接入了。

引擎的大部分代码其实也都趋于源生,在Vue上也有一定的复用性,甚至可以做到SSR,因此随着实践这样的成本将会越来越低,总归是比建设多个低代码编辑器好太多了。

抛砖引玉

整体设计简单来说就是,给传统的低代码编辑器加一层渲染层。

在原编辑器产生编辑行为后生成的页面描述给到渲染层的容器,只要渲染层能解析上层的描述,就可以渲染任何的组件库。基于这一句话,可以说当前任何一个低代码编辑器都可以进行跨技术栈跨组件库的改造。

而理论在实践后将不出意外地会遇到一系列的挑战:

  • 编辑层如何和渲染层的布局进行同步
  • 如何让渲染层和低代码编辑器很好的进行隔离
  • 隔离后是否可以完全像传统的方式进行开发编辑

当前我正在尝试以这样的模式写一个可以快速接入到团队的低代码拖拽框架,当前还只是demo阶段,欢迎交流。

whimsical: https://github.com/gaofeiyu/whimsical