花椒低代码可视化编辑平台的设计与实践
一. 项目由来
19年夏天的一次小组头脑风暴会议上,成员们在讨论下半年创新项目时提出了这样一个想法,大致思路是左侧预览,右侧填充数据,数据保存后触发页面更新,为此我们还做了一个类似的DEMO进行演示。因为当时我们已经有Blade模板平台可以使用,所以该项目的优先级被排在了后面,惨遭搁置。
2020年以来因为疫情的原因,我们公司相关的营销业务越来越多,活动形式大同小异,多是单期,而Blade平台只能做到整页定制,无法复用,就导致营销活动占用了更多的开发资源,挤压了其他项目,所以我们就又想起了上面那个想法。我们需要一个能更精细化定制,可自由组合,实时预览,方便使用的模板平台去解决这个问题。
二. 明确需求
2.1 定制的颗粒度
该模板平台的主要使用对象是运营人员,开发人员负责开发组件,所以颗粒度不能太细,否则运营人员的学习成本和开发人员的开发成本都会非常高昂。每个组件最好只能配置少量参数,逻辑全部由开发人员在组件内部实现;
2.2 组件的UI
由于该模板平台生成的页面是 to C
的,所以必须是强UI表现,需由开发人员自己编写,像生成企业OA、后台那种的定制UI组件或者不支持自定义组件的就得PASS掉;
2.3 自由组合
对于这个需求,一般的可视化搭建框架都能实现。由于我们的业务都是面向移动端用户,不像PC那样需要嵌套,所以采用非嵌套组件层级规则,每个组件占宽100%,可调整先后顺序即可;
2.4 实时预览
因为之前的Blade模板平台,只有发布到测试/正式环境才能查看实际配置后的效果,遇到不确定的配置项可能得重发好几次才能符合最终需求。该需求一般的框架都能满足,所以开发排期排到最后。
三. 技术调研
本着不重复造轮子的想法,先调研一下有没有现成的解决方案。
1. amis
amis
是百度开源的一个低代码前端框架,它使用 JSON 配置来生成页面,功能很强大,能实现很多复杂的功能,但是有点不适合我们的业务。amis的目标是 toB 用户,组件很简洁(就是丑),不适合toC,并且配置项太多,学习成本很高。而我们新的模板发布平台打算是提供给运营人员使用,考虑到运营人员的技术水平约等于无,所以只能PASS,如果后续有后台的需求,我们会考虑用这个。
2. 鲁班H5
鲁班H5
是基于Vue2.0开发,通过拖拽的形式,生成页面的工具。它支持自定义组件、脚本系统、拖拽等功能,能满足我们的业务需求,但是对于一些复杂组件要实现自己的编辑器,这点让我们很犹豫。试想一下自己做完一个很复杂的组件,还要去做一个相应的好用的组件属性编辑器,还要考虑各种校验等等一系列问题,令人望而却步。
3. pipeline-page
pipeline-page
是 github 上开源的一套页面可视化搭建框架。它将整个系统分为了三部分:编辑器,实际页面,后台。编辑器通过iframe与实际页面关联,编辑器编辑属性之后发给后台,后台构建页面,刷新之后即可实时预览,通过这种方式
1.实现了编辑器和组件库前端框架的分离;2.实现了编辑器和组件库各组件的分离;3.避免了预览页面的逻辑和样式污染编辑器环境。
这样的方案理论上无论采用什么技术栈,升级组件库前端框架,都可以做到成本最小化。编辑器页面只提供数据,后台负责存储组件信息、组件属性值和页面的数据,渲染页面只需要根据获取的页面和组件数据进行展示,三者解耦。另外pipeline-page
采用了 JSON Schema
的方案来实现组件属性表单的生成,并且带有校验,不用再考虑去写编辑器了,完美解决了 鲁班H5
的痛点。
看到 pipeline-page
之后就没有再去调研其他的可视化搭建框架了,这套方案的架构十分适合我们的业务,但是它的实现不太符合我们现有的业务,于是我们决定按照 pipeline-page
的思想,根据花椒前端的实际情况自己实现一套。
四. 技术架构
花椒低代码可视化编辑平台也是分为三个部分: 前端,服务端,组件库+预览页。
4.1 前端部分
前端采用的是D2Admin的模板搭建了一个简易的管理系统,根据花椒自己的实际业务增加了团队管理,站点管理(预埋,还未实现),页面管理,组件分类等功能。在编辑页面我们在左侧按照不同分类展示组件,点击可弹出浮层介绍组件详情,方便运营同学了解使用方法,点击添加即可在中间预览区看到组件;右侧为属性编辑区,组件的逻辑和样式由组件内部封装,前端部分通过组件信息中的 JSON Schema
去渲染表单,运营人员根据开发人员提供的简单的预设属性来填入信息即可。使用组件搭建出一套活动页面,例子: https://web.huajiao.com/jimu/3/index.html
。
前端部分与 pipeline-page
不同的是我们在编辑组件属性时,不仅将组件及页面信息发送给后台服务端,而且同时将这些信息通过 window.postMessage
的方式传输给iframe中的预览页;预览页本身集合了所有业务组件,收到信息后通过 动态组件
的方式实时渲染,方便了使用人员及时看到配置后的效果,不需要后台构建,比 pipeline-page
的预览更加快速及时。所以在页面未发布之前,不会生成真正的页面,它只是存在服务器端中的一段数据。
其中 page
字段是对页面本身的一些描述信息,componentList存储的是页面使用的组件中的数据,预览页使用 cname
指定渲染的组件,style
字段为组件容器的样式(用于调整组件的显示区间等不影响组件功能的样式),props
就是组件根据业务需求需要填入的属性值(VUE Compoent中的props字段需要传入的值)。
4.2 服务端部分
服务器负责存储前端提交的组件数据、页面数据以及组件信息,在收到用户发起的发布请求后,通过调用gitlab的API来触发pipeline构建和发布页面。
技术架构:
1.node框架基于可维护可扩展的考虑,采用的是nestjs框架。2.使用管道、DTO验证参数,摒弃if-else,使参数验证更加优雅。3.用户注册登录是基于passport-jwt实现,再配合nest的路由守卫方便进行用户信息的验证。4.使用typeorm连接数据库,可以让前端专注于业务逻辑,不用过度担心数据存储。
服务器在以上技术的基础上,实现了和各个平台的联动。详情如下
4.3 预览+编译部分
我们的实现方案和 pipeline-page
最大的不同就是在 预览+编译部分,我们觉得 pipeline-page
方案关于组件这部分太过割裂,使用起来不顺畅,用户还需要打包并将资源转移到 pipeline-resources
中,所以我们依托花椒的GITLAB CI/CD 实现了一套"预览+编译"功能。
组件库目录结构:
组件库和平时开发的单页项目没有任何区别,开发人员切新分支后在 components/xxx
分类目录下添加组件,组件可以引用公共资源。
分类的目录结构
class.config.json 标识子分类信息
autoplay-video/index.vue
为模板文件
这个和平常开发一个业务组件是没有任何区别的,props 中标明了组件所需数据,每个属性上面必须写好注释,因为我们写了一个脚本工具,执行 yarn generate:schema
命令即可将 props
通过脚本自动转化为渲染表单的JSON Schema => autoplay-video/index.schema.json
, 当然这个文件开发人员也可以选择不自动生成,自己手写也可以;
然后开发人员在 constants/devConfig.js
中编辑 componentList
即可实现本地调试;
开发测试完毕之后合并到master分支,此时通过CI执行添加组件脚本 node scripts/updateComponent.js
,将组件信息提交给服务端,对开发流程无侵入,和平时开发无区别。
components_update:
image: registry.huajiao.com/gitlab-ci/dplt-core:0.1.5
stage: components_update
script:
- node scripts/updateComponent.js
only:
changes:
- src/js/components/**/*
refs:
- master
except:
- triggers
添加组件之后需要将新的组件集合到预览页中,同样是通过CI文件执行新的脚本构建和发布新的预览页。
build_preview:
stage: build
script:
- PREVIEW_MODE=true yarn build
artifacts:
name: "$CI_COMMIT_SHA" # 每次提交一个功件
paths:
- dist
when: on_success
only:
refs:
- master
except:
- triggers
deploy_preview:
image: registry.huajiao.com/gitlab-ci/dplt-core:0.1.5
stage: deploy
dependencies:
- build_preview
script:
- PREVIEW_MODE=true node scripts/deploy.js
only:
refs:
- master
except:
- triggers
待发布完毕之后,在编辑器页面调用新组件的时候就可以显示出来了。
if(PREVIEW_MODE === 'true') {
window.addEventListener("message", (event) => {
console.log('onMessage', event.data);
if(
typeof event.data === 'string'
&& event.data !== ''
) {
const message = JSON.parse(event.data);
if(message.identifier === 'jimu_preview') {
if(message.type === 'updateView') {
safeStart(); // 这里是清理组件重新初始化VUE
loadConfig(message.data); // 这里是加载上面的预览信息
}
}
}
}, false);
}
发布生成真正的文件同样是依托 gitlab CI 功能,代码如下
build_template:
stage: build
script:
- yarn build:trigger
artifacts:
name: "$CI_COMMIT_SHA" # 每次提交一个功件
paths:
- dist
when: on_success
only:
- triggers
deploy_template:
image: registry.huajiao.com/gitlab-ci/dplt-core:0.1.5
stage: deploy
dependencies:
- build_template
script:
- node scripts/deploy.js
only:
- triggers
当用户点击发布时,服务端调用 https://git.huajiao.com/api/v4/projects/772/trigger/pipeline
接口,将构建页面所需信息传输过去,CI接收到信息后将其进行格式化并组装成自己所需信息,然后构建页面并发布到测试环境/正式环境。
if(process.env.DEPLOY_CONFIG) {
// 从环境变量取传入配置
const DEPLOY_CONFIG = JSON.parse(process.env.DEPLOY_CONFIG);
config = {
componentList: DEPLOY_CONFIG.components.map(item => {
return {
id: item.cname,
style: JSON.parse(item.style),
props: JSON.parse(item.props)
}
}),
page: {
id: DEPLOY_CONFIG.pageId,
name: DEPLOY_CONFIG.pageName,
...JSON.parse(DEPLOY_CONFIG.props),
}
};
}
前端可以通过服务端转接的 gitlab 接口轮询 pipeline 状态,给用户显示发布进度信息。
{
"id": 161337,
"sha": "50e37a230e1f221ae1fdd9b28cf27d3c7eb1f9bb",
"ref": "master",
"status": "pending", // 这里的状态
"web_url": "https://git.huajiao.com/frontend/cli/jimu/pipelines/161337",
"before_sha": "0000000000000000000000000000000000000000",
"tag": false,
"yaml_errors": null,
"user": {
"id": 18,
"name": "yyy",
"username": "zzzxxx-hj",
"state": "active",
"avatar_url": "https://secure.gravatar.com/avatar/39fe51fea0fca93e688cbae09d68b30f?s=80&d=identicon",
"web_url": "https://git.huajiao.com/zzzxxx-hj"
},
"created_at": "2021-07-01T09:29:00.210Z",
"updated_at": "2021-07-01T09:29:00.332Z",
"started_at": null,
"finished_at": null,
"committed_at": null,
"duration": null,
"coverage": null
}