花椒低代碼可視化編輯平臺(tái)的設(shè)計(jì)與實(shí)踐
點(diǎn)擊上方 程序員成長(zhǎng)指北,關(guān)注公眾號(hào)
回復(fù)1,加入高級(jí)Node交流群
一. 項(xiàng)目由來
19年夏天的一次小組頭腦風(fēng)暴會(huì)議上,成員們?cè)谟懻撓掳肽陝?chuàng)新項(xiàng)目時(shí)提出了這樣一個(gè)想法,大致思路是左側(cè)預(yù)覽,右側(cè)填充數(shù)據(jù),數(shù)據(jù)保存后觸發(fā)頁面更新,為此我們還做了一個(gè)類似的DEMO進(jìn)行演示。因?yàn)楫?dāng)時(shí)我們已經(jīng)有Blade模板平臺(tái)可以使用,所以該項(xiàng)目的優(yōu)先級(jí)被排在了后面,慘遭擱置。
2020年以來因?yàn)橐咔榈脑颍覀児鞠嚓P(guān)的營(yíng)銷業(yè)務(wù)越來越多,活動(dòng)形式大同小異,多是單期,而Blade平臺(tái)只能做到整頁定制,無法復(fù)用,就導(dǎo)致營(yíng)銷活動(dòng)占用了更多的開發(fā)資源,擠壓了其他項(xiàng)目,所以我們就又想起了上面那個(gè)想法。我們需要一個(gè)能更精細(xì)化定制,可自由組合,實(shí)時(shí)預(yù)覽,方便使用的模板平臺(tái)去解決這個(gè)問題。
二. 明確需求
2.1 定制的顆粒度
該模板平臺(tái)的主要使用對(duì)象是運(yùn)營(yíng)人員,開發(fā)人員負(fù)責(zé)開發(fā)組件,所以顆粒度不能太細(xì),否則運(yùn)營(yíng)人員的學(xué)習(xí)成本和開發(fā)人員的開發(fā)成本都會(huì)非常高昂。每個(gè)組件最好只能配置少量參數(shù),邏輯全部由開發(fā)人員在組件內(nèi)部實(shí)現(xiàn);
2.2 組件的UI
由于該模板平臺(tái)生成的頁面是 to C 的,所以必須是強(qiáng)UI表現(xiàn),需由開發(fā)人員自己編寫,像生成企業(yè)OA、后臺(tái)那種的定制UI組件或者不支持自定義組件的就得PASS掉;
2.3 自由組合
對(duì)于這個(gè)需求,一般的可視化搭建框架都能實(shí)現(xiàn)。由于我們的業(yè)務(wù)都是面向移動(dòng)端用戶,不像PC那樣需要嵌套,所以采用非嵌套組件層級(jí)規(guī)則,每個(gè)組件占寬100%,可調(diào)整先后順序即可;
2.4 實(shí)時(shí)預(yù)覽
因?yàn)橹暗腂lade模板平臺(tái),只有發(fā)布到測(cè)試/正式環(huán)境才能查看實(shí)際配置后的效果,遇到不確定的配置項(xiàng)可能得重發(fā)好幾次才能符合最終需求。該需求一般的框架都能滿足,所以開發(fā)排期排到最后。
三. 技術(shù)調(diào)研
本著不重復(fù)造輪子的想法,先調(diào)研一下有沒有現(xiàn)成的解決方案。
1. amis
amis 是百度開源的一個(gè)低代碼前端框架,它使用 JSON 配置來生成頁面,功能很強(qiáng)大,能實(shí)現(xiàn)很多復(fù)雜的功能,但是有點(diǎn)不適合我們的業(yè)務(wù)。amis的目標(biāo)是 toB 用戶,組件很簡(jiǎn)潔(就是丑),不適合toC,并且配置項(xiàng)太多,學(xué)習(xí)成本很高。而我們新的模板發(fā)布平臺(tái)打算是提供給運(yùn)營(yíng)人員使用,考慮到運(yùn)營(yíng)人員的技術(shù)水平約等于無,所以只能PASS,如果后續(xù)有后臺(tái)的需求,我們會(huì)考慮用這個(gè)。
2. 魯班H5
魯班H5是基于Vue2.0開發(fā),通過拖拽的形式,生成頁面的工具。它支持自定義組件、腳本系統(tǒng)、拖拽等功能,能滿足我們的業(yè)務(wù)需求,但是對(duì)于一些復(fù)雜組件要實(shí)現(xiàn)自己的編輯器,這點(diǎn)讓我們很猶豫。試想一下自己做完一個(gè)很復(fù)雜的組件,還要去做一個(gè)相應(yīng)的好用的組件屬性編輯器,還要考慮各種校驗(yàn)等等一系列問題,令人望而卻步。
3. pipeline-page
pipeline-page 是 github 上開源的一套頁面可視化搭建框架。它將整個(gè)系統(tǒng)分為了三部分:編輯器,實(shí)際頁面,后臺(tái)。編輯器通過iframe與實(shí)際頁面關(guān)聯(lián),編輯器編輯屬性之后發(fā)給后臺(tái),后臺(tái)構(gòu)建頁面,刷新之后即可實(shí)時(shí)預(yù)覽,通過這種方式
1.實(shí)現(xiàn)了編輯器和組件庫前端框架的分離;2.實(shí)現(xiàn)了編輯器和組件庫各組件的分離;3.避免了預(yù)覽頁面的邏輯和樣式污染編輯器環(huán)境。
這樣的方案理論上無論采用什么技術(shù)棧,升級(jí)組件庫前端框架,都可以做到成本最小化。編輯器頁面只提供數(shù)據(jù),后臺(tái)負(fù)責(zé)存儲(chǔ)組件信息、組件屬性值和頁面的數(shù)據(jù),渲染頁面只需要根據(jù)獲取的頁面和組件數(shù)據(jù)進(jìn)行展示,三者解耦。另外pipeline-page 采用了 JSON Schema 的方案來實(shí)現(xiàn)組件屬性表單的生成,并且?guī)в行r?yàn),不用再考慮去寫編輯器了,完美解決了 魯班H5 的痛點(diǎn)。
看到 pipeline-page 之后就沒有再去調(diào)研其他的可視化搭建框架了,這套方案的架構(gòu)十分適合我們的業(yè)務(wù),但是它的實(shí)現(xiàn)不太符合我們現(xiàn)有的業(yè)務(wù),于是我們決定按照 pipeline-page 的思想,根據(jù)花椒前端的實(shí)際情況自己實(shí)現(xiàn)一套。
四. 技術(shù)架構(gòu)
花椒低代碼可視化編輯平臺(tái)也是分為三個(gè)部分: 前端,服務(wù)端,組件庫+預(yù)覽頁。
4.1 前端部分
前端采用的是D2Admin的模板搭建了一個(gè)簡(jiǎn)易的管理系統(tǒng),根據(jù)花椒自己的實(shí)際業(yè)務(wù)增加了團(tuán)隊(duì)管理,站點(diǎn)管理(預(yù)埋,還未實(shí)現(xiàn)),頁面管理,組件分類等功能。在編輯頁面我們?cè)谧髠?cè)按照不同分類展示組件,點(diǎn)擊可彈出浮層介紹組件詳情,方便運(yùn)營(yíng)同學(xué)了解使用方法,點(diǎn)擊添加即可在中間預(yù)覽區(qū)看到組件;右側(cè)為屬性編輯區(qū),組件的邏輯和樣式由組件內(nèi)部封裝,前端部分通過組件信息中的 JSON Schema 去渲染表單,運(yùn)營(yíng)人員根據(jù)開發(fā)人員提供的簡(jiǎn)單的預(yù)設(shè)屬性來填入信息即可。使用組件搭建出一套活動(dòng)頁面,例子: https://web.huajiao.com/jimu/3/index.html。

前端部分與 pipeline-page 不同的是我們?cè)诰庉嫿M件屬性時(shí),不僅將組件及頁面信息發(fā)送給后臺(tái)服務(wù)端,而且同時(shí)將這些信息通過 window.postMessage 的方式傳輸給iframe中的預(yù)覽頁;預(yù)覽頁本身集合了所有業(yè)務(wù)組件,收到信息后通過 動(dòng)態(tài)組件 的方式實(shí)時(shí)渲染,方便了使用人員及時(shí)看到配置后的效果,不需要后臺(tái)構(gòu)建,比 pipeline-page 的預(yù)覽更加快速及時(shí)。所以在頁面未發(fā)布之前,不會(huì)生成真正的頁面,它只是存在服務(wù)器端中的一段數(shù)據(jù)。

其中 page 字段是對(duì)頁面本身的一些描述信息,componentList存儲(chǔ)的是頁面使用的組件中的數(shù)據(jù),預(yù)覽頁使用 cname 指定渲染的組件,style 字段為組件容器的樣式(用于調(diào)整組件的顯示區(qū)間等不影響組件功能的樣式),props 就是組件根據(jù)業(yè)務(wù)需求需要填入的屬性值(VUE Compoent中的props字段需要傳入的值)。
4.2 服務(wù)端部分
服務(wù)器負(fù)責(zé)存儲(chǔ)前端提交的組件數(shù)據(jù)、頁面數(shù)據(jù)以及組件信息,在收到用戶發(fā)起的發(fā)布請(qǐng)求后,通過調(diào)用gitlab的API來觸發(fā)pipeline構(gòu)建和發(fā)布頁面。
技術(shù)架構(gòu):
1.node框架基于可維護(hù)可擴(kuò)展的考慮,采用的是nestjs框架。2.使用管道、DTO驗(yàn)證參數(shù),摒棄if-else,使參數(shù)驗(yàn)證更加優(yōu)雅。3.用戶注冊(cè)登錄是基于passport-jwt實(shí)現(xiàn),再配合nest的路由守衛(wèi)方便進(jìn)行用戶信息的驗(yàn)證。4.使用typeorm連接數(shù)據(jù)庫,可以讓前端專注于業(yè)務(wù)邏輯,不用過度擔(dān)心數(shù)據(jù)存儲(chǔ)。
服務(wù)器在以上技術(shù)的基礎(chǔ)上,實(shí)現(xiàn)了和各個(gè)平臺(tái)的聯(lián)動(dòng)。詳情如下

4.3 預(yù)覽+編譯部分
我們的實(shí)現(xiàn)方案和 pipeline-page 最大的不同就是在 預(yù)覽+編譯部分,我們覺得 pipeline-page 方案關(guān)于組件這部分太過割裂,使用起來不順暢,用戶還需要打包并將資源轉(zhuǎn)移到 pipeline-resources 中,所以我們依托花椒的GITLAB CI/CD 實(shí)現(xiàn)了一套"預(yù)覽+編譯"功能。
組件庫目錄結(jié)構(gòu):

組件庫和平時(shí)開發(fā)的單頁項(xiàng)目沒有任何區(qū)別,開發(fā)人員切新分支后在 components/xxx 分類目錄下添加組件,組件可以引用公共資源。
分類的目錄結(jié)構(gòu)

class.config.json 標(biāo)識(shí)子分類信息
autoplay-video/index.vue 為模板文件

這個(gè)和平常開發(fā)一個(gè)業(yè)務(wù)組件是沒有任何區(qū)別的,props 中標(biāo)明了組件所需數(shù)據(jù),每個(gè)屬性上面必須寫好注釋,因?yàn)槲覀儗懥艘粋€(gè)腳本工具,執(zhí)行 yarn generate:schema 命令即可將 props 通過腳本自動(dòng)轉(zhuǎn)化為渲染表單的JSON Schema => autoplay-video/index.schema.json, 當(dāng)然這個(gè)文件開發(fā)人員也可以選擇不自動(dòng)生成,自己手寫也可以;
然后開發(fā)人員在 constants/devConfig.js 中編輯 componentList 即可實(shí)現(xiàn)本地調(diào)試;

開發(fā)測(cè)試完畢之后合并到master分支,此時(shí)通過CI執(zhí)行添加組件腳本 node scripts/updateComponent.js,將組件信息提交給服務(wù)端,對(duì)開發(fā)流程無侵入,和平時(shí)開發(fā)無區(qū)別。
components_update:image: registry.huajiao.com/gitlab-ci/dplt-core:0.1.5stage: components_updatescript:- node scripts/updateComponent.jsonly:changes:- src/js/components/**/*refs:- masterexcept:- triggers
添加組件之后需要將新的組件集合到預(yù)覽頁中,同樣是通過CI文件執(zhí)行新的腳本構(gòu)建和發(fā)布新的預(yù)覽頁。
build_preview:stage: buildscript:- PREVIEW_MODE=true yarn buildartifacts:name: "$CI_COMMIT_SHA" # 每次提交一個(gè)功件paths:- distwhen: on_successonly:refs:- masterexcept:- triggersdeploy_preview:image: registry.huajiao.com/gitlab-ci/dplt-core:0.1.5stage: deploydependencies:- build_previewscript:- PREVIEW_MODE=true node scripts/deploy.jsonly:refs:- masterexcept:- triggers
待發(fā)布完畢之后,在編輯器頁面調(diào)用新組件的時(shí)候就可以顯示出來了。
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(); // 這里是清理組件重新初始化VUEloadConfig(message.data); // 這里是加載上面的預(yù)覽信息}}}}, false);}
發(fā)布生成真正的文件同樣是依托 gitlab CI 功能,代碼如下
build_template:stage: buildscript:- yarn build:triggerartifacts:name: "$CI_COMMIT_SHA" # 每次提交一個(gè)功件paths:- distwhen: on_successonly:- triggersdeploy_template:image: registry.huajiao.com/gitlab-ci/dplt-core:0.1.5stage: deploydependencies:- build_templatescript:- node scripts/deploy.jsonly:- triggers
當(dāng)用戶點(diǎn)擊發(fā)布時(shí),服務(wù)端調(diào)用 https://git.huajiao.com/api/v4/projects/772/trigger/pipeline 接口,將構(gòu)建頁面所需信息傳輸過去,CI接收到信息后將其進(jìn)行格式化并組裝成自己所需信息,然后構(gòu)建頁面并發(fā)布到測(cè)試環(huán)境/正式環(huán)境。
if(process.env.DEPLOY_CONFIG) {// 從環(huán)境變量取傳入配置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),}};}
前端可以通過服務(wù)端轉(zhuǎn)接的 gitlab 接口輪詢 pipeline 狀態(tài),給用戶顯示發(fā)布進(jìn)度信息。
{"id": 161337,"sha": "50e37a230e1f221ae1fdd9b28cf27d3c7eb1f9bb","ref": "master","status": "pending", // 這里的狀態(tài)"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}
五. END


“分享、點(diǎn)贊、在看” 支持一波 
