如何評(píng)價(jià)Dooring低代碼/零代碼搭建平臺(tái)?
?余秋雨說(shuō):“人的格局一大,就不會(huì)在生活的瑣碎里沉淪。真正自信的人,總能夠簡(jiǎn)單的鏗鏘有力。”
?
大家好, 我是徐小夕, 之前一直在分享可視化低代碼的一些實(shí)踐, 圍繞 H5-Dooring 零代碼搭建平臺(tái)也輸出了很多技術(shù)文章, 最近2.7.0 版本也順利迭代完成, 這里詳細(xì)分享一下 H5-Dooring 無(wú)代碼搭建平臺(tái)技術(shù)方案.
H5-Dooring 開(kāi)源版本 可視化低代碼技術(shù)集合 H5-Dooring在線體驗(yàn)
Dooring無(wú)代碼產(chǎn)品技術(shù)演進(jìn)

兩年前我設(shè)計(jì)了H5-Dooring的第一個(gè)開(kāi)源版本, 之后陸陸續(xù)續(xù)迭代了兩年, github star已達(dá)到6.5k+, 也找到了很多志同道合的小伙伴, 一起研發(fā)Dooring系的搭建產(chǎn)品, 如:
h5-dooring | 可視化搭建解決方案 mitu-editor | 開(kāi)源圖片編輯器 v6.dooring | 可視化大屏搭建平臺(tái) dooringx | 可視化搭建框架

從技術(shù)設(shè)計(jì)和產(chǎn)品規(guī)劃上, 這幾年也總結(jié)摸索出了一些經(jīng)驗(yàn)和實(shí)踐, 接下來(lái)我就和大家一起分享一下H5-Dooring 的技術(shù)架構(gòu)設(shè)計(jì)與演進(jìn).

底層搭建協(xié)議標(biāo)準(zhǔn)化
我們都知道任何低代碼或者零代碼搭建產(chǎn)品都非常注重底層搭建協(xié)議, 這些產(chǎn)品通常會(huì)設(shè)計(jì)一套向上兼容且可擴(kuò)展的DSL結(jié)構(gòu), 來(lái)實(shí)現(xiàn)頁(yè)面元件的標(biāo)準(zhǔn)化配置, 并支持元件的向上擴(kuò)展.

上面這張圖是我在設(shè)計(jì) V6.Dooring 可視化大屏搭建平臺(tái)的編輯器架構(gòu)圖, 這里的底層搭建協(xié)議可以認(rèn)為是 搭建基礎(chǔ), 也就是我們常說(shuō)的 “經(jīng)濟(jì)基礎(chǔ)決定上層建筑”.
在設(shè)計(jì)H5-Dooring 搭建平臺(tái)前, 我也參考了很多標(biāo)準(zhǔn)化軟件數(shù)據(jù)協(xié)議, 給我啟發(fā)最大的就是 ODATA, 它是微軟于2007年發(fā)起的開(kāi)放協(xié)議, 主要由以下幾部分組成:
「核心協(xié)議」: 主要定義了開(kāi)放數(shù)據(jù)協(xié)議的核心語(yǔ)義和行為

「URL規(guī)范」: 主要定義了一系列推薦(非強(qiáng)制)采用的構(gòu)建用于訪問(wèn) OData服務(wù)中的數(shù)據(jù)和模型的URL的規(guī)則

「通用格式定義語(yǔ)言(CSDL)」: 它定義了 OData服務(wù)的「EDM」模型的一種XML格式的表現(xiàn)形式

「擴(kuò)展的巴科斯范式(ABNF)」: 定義了構(gòu)建 OData請(qǐng)求和響應(yīng)URL的「巴科斯范式」

為了讓可視化搭建平臺(tái)的組件數(shù)據(jù)標(biāo)準(zhǔn)化且可擴(kuò)展, 這里我分享一下H5-Dooring的Schema設(shè)計(jì).

Schema 分兩部分:
editData 組件可編輯屬性的數(shù)組 config 組件真正消費(fèi)的數(shù)據(jù)
editData 詳解
editData 是 組件屬性可編輯項(xiàng)的數(shù)組, 每一項(xiàng)里面包含了如下字段:
key 屬性名 name: 屬性名的中文顯示 type: 屬性的可編輯類型 isCrop(可選) cropRate(可選) range(type 為'Radio'或'Select'時(shí)的選項(xiàng)數(shù)組) 后期可能會(huì)擴(kuò)展(詳細(xì)結(jié)構(gòu)可參考Dooring 開(kāi)源版本)
key和name 都可以按照組件屬性的語(yǔ)義來(lái)定, 這里值得一提的是 type. 不同屬性的值類型不同, 所以我們編輯項(xiàng)的 type 也不同, 所有的類型如下:
Upload 上傳組件 Text 文本框 RichText 富文本 TextArea 多行文本 Number 數(shù)字輸入框 DataList 列表編輯器 FileList 文件列表編輯器 InteractionData 交互設(shè)置 Color 顏色面板 MutiText 多文本 Select 選擇下拉框 Radio 單選框 Switch 開(kāi)關(guān)切換 CardPicker 卡片面板 Table 表格編輯器 Pos 坐標(biāo)編輯器 FormItems 表單設(shè)計(jì)器
更詳細(xì)的介紹可以訪問(wèn) dooring 開(kāi)發(fā)文檔
config 詳解
config 本質(zhì)上是一個(gè)對(duì)象, 也就是組件所能暴露出來(lái)的屬性集合, 和 editData 數(shù)組每一項(xiàng)的key 一致, 如下:
{
cpName: 'Header',
logoText: '',
fontSize: 20,
color: 'rgba(47,84,235,1)',
height: 60,
fixTop: false,
menuList: [
{
id: '1',
title: '首頁(yè)',
link: '/'
},
{
id: '2',
title: '產(chǎn)品介紹',
link: '/'
},
]
}
我們通過(guò)以上的設(shè)計(jì)規(guī)范, 就可以輕松制作一個(gè)可實(shí)時(shí)編輯的低代碼組件:

可以在Dooring官方文檔體驗(yàn): 低代碼組件案例
搭建模式多元化
最開(kāi)始設(shè)計(jì)H5-Dooring的時(shí)候?yàn)榱俗畲笙薅鹊慕档陀脩舻拇罱ǔ杀? 我采用了智能網(wǎng)格布局的方式來(lái)搭建頁(yè)面, 用戶只需要在二維空間像搭積木一樣選擇適合的組件就可以快速的制作頁(yè)面:

這樣雖然可以降低用戶的搭建難度, 并能滿足一部分受眾的搭建需求, 比如說(shuō)簡(jiǎn)單的官網(wǎng), 活動(dòng)頁(yè)面制作,下面是一個(gè)我搭建的比較有代表性的例子:

但是對(duì)于平臺(tái)方, 為了滿足更多場(chǎng)景的頁(yè)面深度制作, 就必須提供不同場(chǎng)景不同行業(yè)的組件物料, 這將對(duì)研發(fā)帶來(lái)巨大的壓力(雖然也一直在添加新組件).
另一方面, 目前上很多H5活動(dòng)制作平臺(tái)基本上都采用的自由布局的模式搭建, 好處就是可以最大限度的還原設(shè)計(jì)稿, 滿足更靈活的搭建需求, 缺點(diǎn)就是使用成本比網(wǎng)格布局的模式要高, 還會(huì)涉及圖層的概念.
當(dāng)然綜合評(píng)估下來(lái), 確實(shí)很有必要給一部分用戶提供自由布局的模式, 所以在技術(shù)層我設(shè)計(jì)同時(shí)兼容網(wǎng)格布局和自由布局的搭建方案. 當(dāng)用戶在搭建時(shí), 可以輕松選擇自己適合的搭建模式:

同時(shí)為了滿足自由布局下組件的層級(jí)管理, 我又設(shè)計(jì)了圖層管理面板和圖層操作, 來(lái)快速的管理頁(yè)面元素, 當(dāng)然圖層管理面板 對(duì)網(wǎng)格布局 也同樣有一定積極作用, 比如快捷的操作組件.
可擴(kuò)展的插件系統(tǒng)
在前面提到了可視化搭建平臺(tái)的統(tǒng)一搭建協(xié)議和搭建模式, 在這兩個(gè)核心要素完成之后, 我們就很容易的去設(shè)計(jì)我們的插件系統(tǒng).

從插件系統(tǒng)的本質(zhì)來(lái)看, 核心價(jià)值是對(duì)頁(yè)面操作的整個(gè)周期里為頁(yè)面賦能, 而頁(yè)面的本質(zhì)是數(shù)據(jù)(也就是DSL集).

所以只要有標(biāo)準(zhǔn)的數(shù)據(jù)規(guī)范, 我們自定義的插件就可以很輕松的來(lái)對(duì)頁(yè)面進(jìn)行賦能, 類似于各種技術(shù)里面的中間件. 下面是一個(gè)例子:
{
"pageConfig": {
"allowOverlap": "freedom",
"isLogin": false,
"bgColor": "rgba(16,20,29,1)",
"bgSize": "100%",
"title": "H5-Dooring官網(wǎng)"
},
"tpl": [
{
"id": "276059",
"item": {
"category": "base",
"config": {
"cpName": "XButton",
"id": "",
"bgColor": "rgba(22,40,212,1)",
"width": 190,
"marginTop": 0,
"round": 16,
"text": "按鈕",
"fontSize": 15,
"color": "rgba(255,255,255,1)",
"animation": "none",
"animationTurn": 1,
"delay": 0,
"interaction": {
"type": "link",
"title": "",
"params": "",
"content": "",
"height": 300,
"width": 300,
"okText": "",
"cancelText": "",
"onOk": "",
"btnColor": "rgba(20,54,226,100)"
}
},
"h": 23,
"type": "XButton"
},
"point": {
"w": 24,
"h": 23,
"x": 0,
"y": 0,
"i": "276059",
"moved": false,
"static": false,
"isBounded": true
},
"status": "inToCanvas"
},
{
"id": "260487",
"item": {
"category": "base",
"config": {
"cpName": "LongText",
"id": "",
"text": "我是長(zhǎng)文本有一段故事,dooring可視化編輯器無(wú)限可能,趕快來(lái)體驗(yàn)吧,騷年們,奧利給~",
"color": "rgba(60,60,60,1)",
"fontSize": 14,
"indent": 0,
"lineHeight": 1.8,
"textAlign": "left",
"bgColor": "rgba(255,255,255,0)",
"padding": 0,
"radius": 0
},
"h": 36,
"type": "LongText"
},
"point": {
"w": 24,
"h": 36,
"x": 0,
"y": 23,
"i": "260487",
"moved": false,
"static": false,
"isBounded": true
},
"status": "inToCanvas"
}
]
}
上面是H5-Dooring生成的一個(gè)頁(yè)面DSL結(jié)構(gòu), 如果我們要對(duì)頁(yè)面元素進(jìn)行統(tǒng)計(jì)分析, 或者實(shí)現(xiàn)出碼, 國(guó)際化, PSD解析轉(zhuǎn)化等功能, 只需要對(duì)數(shù)據(jù)結(jié)構(gòu)進(jìn)行分析和處理即可.

所以說(shuō)在H5-Dooring平臺(tái)實(shí)現(xiàn)自定義的插件還是非常容易的, 也是低代碼或者無(wú)代碼需要重點(diǎn)規(guī)劃的一個(gè)環(huán)節(jié).
可擴(kuò)展的組件編輯器
H5-Dooring平臺(tái)的組件編輯器主要是對(duì)組件屬性進(jìn)行編輯,比如:
基本樣式 交互設(shè)置 動(dòng)畫(huà)設(shè)置
當(dāng)然還有全局的數(shù)據(jù)源配置. 如下:

同時(shí)由于我們的組件數(shù)據(jù)協(xié)議高度統(tǒng)一, 所以如果想擴(kuò)展屬性配置, 也非常容易, 我們只需要按照數(shù)據(jù)協(xié)議添加屬性即可:

同理, 「v6.dooring」 也采用相似的架構(gòu), 所以我們可以輕松擴(kuò)展組件的屬性:

有關(guān)可視化大屏搭建平臺(tái)的技術(shù)實(shí)踐可以參考我的另一篇文章 從零設(shè)計(jì)可視化大屏搭建引擎
多端搭建支持

由于Dooring的技術(shù)棧采用React, 并實(shí)現(xiàn)了標(biāo)準(zhǔn)的數(shù)據(jù)協(xié)議層, 所以我們可以利用類似 Taro 等跨平臺(tái)框架實(shí)現(xiàn)多端搭建, 對(duì)于我們常用的媒介如移動(dòng)端, Pad和PC端, 目前編輯器也提供了快捷的切換模式:

所以我們可以輕松的實(shí)現(xiàn)不同端的搭建, 實(shí)現(xiàn)原理本質(zhì)上是通過(guò)切換畫(huà)布大小, 并同比例更新元素的計(jì)量衡.
圖層管理, 讓設(shè)計(jì)更高效
圖層管理模塊也是在Dooring支持了自由布局之后才上線的功能. 因?yàn)槲覀冺?yè)面中組件的數(shù)據(jù)結(jié)構(gòu)中包含統(tǒng)一的物理信息:
層級(jí) 可見(jiàn)性 類別 大小顏色等外觀 事件 / 交互 / 動(dòng)畫(huà)

所以我們只需要分析頁(yè)面的組件集合, 就可以輕松的渲染出頁(yè)面中的元素圖層信息:

有了圖層的概念我們其實(shí)可以做很多有用的事情, 比如:
多選組件 編輯組件 刪除組件 鎖定組件
后面 Dooring 也會(huì)基于圖層能力迭代更多提高用戶搭建銷效率的功能.
低代碼組件 & 模版生態(tài)
在Dooring 的迭代中花了大部分精力在優(yōu)化用戶搭建體驗(yàn)和協(xié)議標(biāo)準(zhǔn)化上, 對(duì)于組件物料的豐富上, 我也做了一些設(shè)計(jì), 最近也發(fā)布了一套低代碼組件庫(kù)的原型:

我們可以輕松的像寫(xiě) React 組件一樣來(lái)實(shí)現(xiàn)低代碼組件, 并支持線上實(shí)時(shí)編輯, 一個(gè)基本的例子如下:
import styles from './index.less';
import React, { memo, useState } from 'react';
import { MenuOutlined } from '@ant-design/icons';
import { IHeaderConfig } from './schema';
const Header = memo((props: IHeaderConfig) => {
const {
cpName,
bgColor,
logo,
logoText,
fontSize,
color,
showMenuBtn,
menuColor,
height,
} = props;
const [showMenu, setShowMenu] = useState(false);
const toggleMenu = () => {
setShowMenu(!showMenu);
};
return (
<header
className={styles.header}
style={{ backgroundColor: bgColor, height: height + 'px' }}
>
<div className={styles.logo}>
<img src={logo && logo[0] && logo[0].url} alt={logoText} />
</div>
<div className={styles.title} style={{ fontSize, color }}>
{logoText}
</div>
{showMenuBtn && (
<>
<div
className={styles.menuIconWrap}
onClick={toggleMenu}
style={{ color: menuColor, borderColor: menuColor }}
>
<MenuOutlined />
</div>
</>
)}
</header>
);
});
export default Header;通過(guò)這種標(biāo)準(zhǔn)化的方式, 我們可以給 Dooring 平臺(tái)提供更為豐富的組件物料.
除了基礎(chǔ)物料組件之外, 為了從更大粒度提高用戶搭建的效率, 我提供了模版功能, 我們可以把重復(fù)的區(qū)塊和可復(fù)用的頁(yè)面保存為模版:

我們可以在編輯器頁(yè)面輕松將頁(yè)面保存為模版, 并自動(dòng)生成海報(bào)封面:

基于網(wǎng)頁(yè)生成封面的方式也很簡(jiǎn)單, 我這里采用的是 dom-to-image 這個(gè)庫(kù), 當(dāng)然搭建也可以使用html2canvas.
表單設(shè)計(jì)器 & 數(shù)據(jù)收集分析能力
表單編輯器的實(shí)現(xiàn)思路我之前也寫(xiě)過(guò)一些分享, 這里和大家再介紹一下核心的一些思路.
動(dòng)態(tài)表單開(kāi)發(fā)的一般思路
「1. 靜態(tài)化配置列表」
靜態(tài)化配置列表是最傳統(tǒng)的表單配置方式之一,基本思路就是利用母表來(lái)生成配置項(xiàng),進(jìn)而實(shí)現(xiàn)表單配置。類似于以下方式:
早期的網(wǎng)站配置就是類似于這種呢方案實(shí)現(xiàn)的,比如說(shuō)我們要定制網(wǎng)站的主色,網(wǎng)站某些組件是否可見(jiàn),是一種比較簡(jiǎn)單的方式。但是缺點(diǎn)是每增加一個(gè)配置屬性,都要開(kāi)發(fā)人員重新編寫(xiě)一個(gè)字段配置代碼,這種方式在表單開(kāi)發(fā)中非常不靈活,而且對(duì)代碼層有強(qiáng)依賴性,所以只適合做小型配置系統(tǒng)。比如個(gè)人網(wǎng)站,簡(jiǎn)單的自定義表單。
「2. 基于json schema的動(dòng)態(tài)表單配置」
基于「json schema」的動(dòng)態(tài)表單配置有兩種實(shí)現(xiàn)方案, 一種就是支持在線修改json文件從而實(shí)現(xiàn)定制化,另一種就是完全無(wú)代碼操作,但是前提都需要提供一套通用的表單模版。類似于如下案例:
此種方案可以實(shí)現(xiàn)基本的表單自治。也是本文主要實(shí)現(xiàn)的方案。至于在線編寫(xiě)json文件的方案。筆者之前也也過(guò)成熟的方案,具體可以參考:基于jsoneditor二次封裝一個(gè)可實(shí)時(shí)預(yù)覽的json編輯器組件(react版)
「3. 支持在線coding的混合式表單設(shè)計(jì)」支持「在線編程」的混合式表單設(shè)計(jì)方案是終極方案,也是目前流行的無(wú)代碼化平臺(tái)的思想之一。一方面它提供了基于「json schema」的動(dòng)態(tài)表單配置, 對(duì)于一些強(qiáng)定制的,需要在線設(shè)計(jì)組件方案的模式,采用在線編程,實(shí)時(shí)打包成動(dòng)態(tài)組件的方式,最后根據(jù)平臺(tái)的組件約定來(lái)實(shí)現(xiàn)組件庫(kù)的方式。如下圖所示:
在線代碼編輯可以使用「react-codemirror2」或者 「react-monaco-editor」插件來(lái)實(shí)現(xiàn)。至于在線打包,我們用「nodejs」完全可以實(shí)現(xiàn),筆者在做「Dooring」項(xiàng)目的在線下載代碼時(shí)就用到了該方案,感興趣的可以了解一下。
可視化領(lǐng)域中的表單引擎
可視化領(lǐng)域一方面強(qiáng)調(diào)的是圖形(可視化)的設(shè)計(jì),一方面是動(dòng)態(tài)表單。比如說(shuō)我們想傻瓜式的改變一張圖的數(shù)據(jù),屬性,交互等,我們需要通過(guò)表單這一橋梁來(lái)實(shí)現(xiàn):
所以我們需要設(shè)計(jì)一款適合公司產(chǎn)品的“表單引擎”,來(lái)動(dòng)態(tài)根據(jù)圖形組件的類型渲染不同表單配置。這塊思想也是表單設(shè)計(jì)器要解決的問(wèn)題之一。在下面的文章中我們會(huì)詳細(xì)介紹實(shí)現(xiàn)過(guò)程。
從零實(shí)現(xiàn)一款動(dòng)態(tài)表單設(shè)計(jì)器
在實(shí)現(xiàn)表單設(shè)計(jì)器之前,我們先來(lái)整理一下思路和需求。在筆者的最初草圖中,它長(zhǎng)這樣:
從草圖中我們可以提取到如下任務(wù)信息:
定義一套表單組件庫(kù) 確定表單全局屬性配置 實(shí)現(xiàn)表單操作curd(增刪查改)
我們這里總結(jié)了幾個(gè)常用的表單組件如下:
單選框 復(fù)選框 單行文本 多行文本 下拉框 文件上傳 日期框 數(shù)值輸入框
以上這些基本滿足我們的日常開(kāi)發(fā)需求,其次我們還可以開(kāi)發(fā)數(shù)據(jù)源表單組件,列表組件,比如dooring實(shí)現(xiàn)的那樣:
類似的還有顏色面板這些,我們可以更具業(yè)務(wù)需求自行定制。
在完成表單組件庫(kù)之后,我們就需要根據(jù)配置項(xiàng)動(dòng)態(tài)渲染了。也有兩種實(shí)現(xiàn)思路,一種就是類似于多條件判斷,如下:
{
item.type === 'Number' &&
<Form.Item label={item.name} name={item.key}>
<InputNumber min={1} max={item.range && item.range[1]} step={item.step} />
</Form.Item>
}
{
item.type === 'Text' &&
<Form.Item label={item.name} name={item.key}>
<Input />
</Form.Item>
}
{
item.type === 'TextArea' &&
<Form.Item label={item.name} name={item.key}>
<TextArea rows={4} />
</Form.Item>
}
這樣做雖然可行,也有很多成熟系統(tǒng)采用該方案,但是一旦表單變多,比如一個(gè)頁(yè)面有幾十個(gè)甚至上百個(gè)表單項(xiàng),那么我們將渲染「m」 *** n**次(m為表單組件類型數(shù),n為配置項(xiàng)個(gè)數(shù))。另一種方式筆者看來(lái)是比較優(yōu)雅的,可以將復(fù)雜度降低到O(n),也就是筆者常用的對(duì)象法。思路大至如下:「將表單組件的類型作為對(duì)象的屬性,屬性值為對(duì)應(yīng)的表單組件,這樣遍歷的時(shí)候只需要對(duì)應(yīng)上對(duì)象的具體類型即可。」代碼如下:
// 維護(hù)表單控件, 提高form渲染性能
const BaseForm = {
"Text": (props) => {
const { label, placeholder, onChange } = props
return <Cell title={label}>
<Input type="text" placeholder={placeholder} onChange={onChange} />
</Cell>
},
"Number": (props) => {
const { label, placeholder, onChange } = props
return <Cell title={label}>
<Input type="number" placeholder={placeholder} onChange={onChange} />
</Cell>
}
}
// 動(dòng)態(tài)渲染表單
{
formData.map((item, i) => {
let FormItem = BaseForm[item.type]
return <div className={styles.formItem} key={i}>
<FormItem {...item} />
</div>
})
}
是不是很優(yōu)雅呢?后期我們只需要在「BaseForm」里維護(hù)表單組件即可,而且還可以基于「BaseForm」對(duì)表單進(jìn)行包裝,實(shí)現(xiàn)動(dòng)態(tài)刪除,編輯等功能。如下:

包裝后的代碼如下:
<div>
<div className={styles.disClick}><FormItem {...item} /></div>
<div className={styles.operationWrap}>
<span onClick={handleEditItem}><EditOutlined /></span>
<span onClick={handleDelItem}><MinusCircleOutlined /></span>
</div>
</div>
接下來(lái)我們看看表單的全局屬性,通過(guò)實(shí)際分析我們可以知道表單有如下外觀:
表單標(biāo)題 表單背景圖片 表單背景顏色 提交按鈕樣式
所以他們因該成為表單設(shè)計(jì)的通用屬性,如下圖所示:

以上的表單通過(guò)「H5-Dooring」設(shè)計(jì)而來(lái)。當(dāng)然我們可以利用它設(shè)計(jì)更加自定的表單頁(yè)面。
最后一個(gè)比較使用的需求就是api定制,一般公司可能需要將用戶的錄入數(shù)據(jù)收集到自己的平臺(tái),那么這個(gè)時(shí)候我們提供一個(gè)api表單提交接口積極很有必要了,上面筆者也展示過(guò),實(shí)現(xiàn)很簡(jiǎn)單,就是配置里多一個(gè)api的文本框即可。
利用H5-Dooring開(kāi)發(fā)一款表單設(shè)計(jì)平臺(tái)
在H5編輯器「Dooring」的實(shí)現(xiàn)中,我們可以做抽象,每一個(gè)頁(yè)面組件可以看成特定的表單組件,如下圖:
我們可以利用「dooring」的能力對(duì)表單平臺(tái)進(jìn)行拖拽,樣式設(shè)計(jì),數(shù)據(jù)錄入等等操作,感興趣的朋友可以基于「Dooring」設(shè)計(jì)思路改造成自己的表單設(shè)計(jì)平臺(tái)。
對(duì)于數(shù)據(jù)收集能力, 可以參考我的另一篇文章:
前端如何一鍵生成多維度數(shù)據(jù)可視化分析報(bào)表
協(xié)同支持
之前 H5-Dooring 是采用 socket 來(lái)實(shí)現(xiàn)雙向通信的, 不同的用戶如何想?yún)f(xié)作搭建, 可以通過(guò) 共享的json文件 或者 socket 來(lái)實(shí)現(xiàn). 不過(guò)最新市面上也出了非常不錯(cuò)的協(xié)作方案, 大家也可以參考一下, 這塊的功能設(shè)計(jì)目前我們正在確定方案.
出碼能力
目前 Dooring 支持2種出碼方式:
生成編譯代碼 生成源碼

以上就是我們需要做的在線實(shí)時(shí)打包下載代碼的工作流,由于nodejs是單線程的,為了不阻塞進(jìn)程我們可以采用父子進(jìn)程通信的方式和異步模型來(lái)處理復(fù)雜耗時(shí)任務(wù),為了通知用戶任務(wù)的完成狀況, 我們可以用socket做雙向通信。在當(dāng)前的場(chǎng)景下就是代碼編譯壓縮完成之后,通知給瀏覽器,以便瀏覽器顯示下載狀態(tài)彈窗。一共有三種狀態(tài):「進(jìn)行中」,「已完成」,「失敗」。對(duì)應(yīng)如下圖所示界面:
至于為什么沒(méi)有出現(xiàn)下載失敗的狀態(tài),不要問(wèn)我,問(wèn)就是沒(méi)有失敗過(guò)(完了,找虐了)。
以上就是「H5-Dooring」實(shí)時(shí)編譯下載的工作流設(shè)計(jì),至于線上更多的實(shí)際需求,我們也可以參考以上設(shè)計(jì)去實(shí)現(xiàn),接下來(lái)筆者來(lái)具體介紹實(shí)現(xiàn)過(guò)程。
2. 「nodejs」如何使用父子進(jìn)程
我們要想實(shí)現(xiàn)一個(gè)自動(dòng)化工作流, 要考慮的一個(gè)關(guān)鍵問(wèn)題就是任務(wù)的執(zhí)行時(shí)機(jī)以及以何種方式執(zhí)行. 因?yàn)橛脩粝螺d代碼之前需要等H5頁(yè)面打包編譯壓縮完成之后才能下載, 而這個(gè)過(guò)程需要一定的時(shí)間(8-30s), 所以我們可以認(rèn)定它為一個(gè)耗時(shí)任務(wù).
當(dāng)我們使用「nodejs」作為后臺(tái)服務(wù)器時(shí), 由于「nodejs」本身是單線程的,所以當(dāng)用戶請(qǐng)求傳入「nodejs」時(shí), 「nodejs」不得不等待這個(gè)"耗時(shí)任務(wù)"完成才能進(jìn)行其他請(qǐng)求的處理, 這樣將會(huì)導(dǎo)致頁(yè)面其他請(qǐng)求需要等待該任務(wù)執(zhí)行結(jié)束才能繼續(xù)進(jìn)行, 所以為了更好的用戶體驗(yàn)和流暢的響應(yīng),我們不得不考慮多進(jìn)程處理. 好在nodejs設(shè)計(jì)支持子進(jìn)程, 我們可以把耗時(shí)任務(wù)放入子進(jìn)程中來(lái)處理,當(dāng)子進(jìn)程處理完成之后再通知主進(jìn)程. 整個(gè)流程如下圖所示:
「nodejs」有3種創(chuàng)建子進(jìn)程的方式,這里筆者簡(jiǎn)單介紹一下「fork」的方式。使用方式如下:
// child.js
function computedTotal(arr, cb) {
// 耗時(shí)計(jì)算任務(wù)
}
// 與主進(jìn)程通信
// 監(jiān)聽(tīng)主進(jìn)程信號(hào)
process.on('message', (msg) => {
computedTotal(bigDataArr, (flag) => {
// 向主進(jìn)程發(fā)送完成信號(hào)
process.send(flag);
})
});
// main.js
const { fork } = require('child_process');
app.use(async (ctx, next) => {
if(ctx.url === '/fetch') {
const data = ctx.request.body;
// 通知子進(jìn)程開(kāi)始執(zhí)行任務(wù),并傳入數(shù)據(jù)
const res = await createPromisefork('./child.js', data)
}
// 創(chuàng)建異步線程
function createPromisefork(childUrl, data) {
// 加載子進(jìn)程
const res = fork(childUrl)
// 通知子進(jìn)程開(kāi)始work
data && res.send(data)
return new Promise(reslove => {
res.on('message', f => {
reslove(f)
})
})
}
await next()
})
在H5-Dooring線上打包的工作流中,我們會(huì)用到「child_process」的「exec」方法,來(lái)解析并執(zhí)行命令行指令。至于父子進(jìn)程的更多應(yīng)用,大家可以自行探索。
3. 使用「child_process」的「exec」實(shí)現(xiàn)解析并執(zhí)行命令行指令
在上面介紹的「dooring」工作流中,我們知道為了實(shí)現(xiàn)實(shí)時(shí)打包,我們需要一個(gè)「H5 Template」項(xiàng)目,作為打包的母版,當(dāng)用戶點(diǎn)擊下載時(shí),會(huì)將頁(yè)面的「json schema」數(shù)據(jù)傳給「node服務(wù)器」, 「node服務(wù)器」再將「json schema」進(jìn)行「數(shù)據(jù)清洗」最后生成「template.json」文件并移動(dòng)到「H5 Template」母版中,此時(shí)母版拿到數(shù)據(jù)源并進(jìn)行打包編譯,最后生成可執(zhí)行文件。
以上的過(guò)程很關(guān)鍵, 這里筆者畫(huà)個(gè)大致的流程圖:
為了實(shí)現(xiàn)以上過(guò)程,我們需要兩個(gè)關(guān)鍵環(huán)節(jié):
將用戶配置的數(shù)據(jù)進(jìn)行處理并生成json文件,然后移動(dòng)到「H5 Template」母版中 在母版中自動(dòng)執(zhí)行打包編譯腳本
第一個(gè)環(huán)節(jié)很好實(shí)現(xiàn),我們只需要用「nodejs」的「fs」模塊生成文件到指定目錄即可,這里筆者重點(diǎn)介紹第二個(gè)環(huán)節(jié)的實(shí)現(xiàn)。
當(dāng)我們將json數(shù)據(jù)生成到「H5 Template」中之后,就可以進(jìn)行打包了,但是這個(gè)過(guò)程需要自動(dòng)化的去處理,不能像我們之前啟動(dòng)項(xiàng)目一樣,手動(dòng)執(zhí)行「npm start」或者「yarn start」。我們需要程序自動(dòng)幫我們執(zhí)行這個(gè)命令行指令,筆者在查「nodejs API」突然發(fā)現(xiàn)了「child_process」的「exec」方法,可以用來(lái)解析指令,這個(gè)剛好能實(shí)現(xiàn)我們的需求,所以我們開(kāi)始實(shí)現(xiàn)它。代碼如下:
import { exec } from 'child_process'
const outWorkDir = resolve(__dirname, '../h5_landing')
const fid = uuid(8, 16)
const cmdStr = `cd ${outWorkDir} && yarn build ${fid}`
// ...exec相關(guān)代碼
const filePath = resolve(__dirname, '../h5_landing/src/assets/config.json')
const res = WF(filePath, data)
exec(cmdStr, function(err,stdout,stderr){
if(err) {
// 錯(cuò)誤處理
} else {
// 成功處理
}
})
以上代碼我們不難理解,我們只需要定義好打包的指令字符串(方式和命令行操作幾乎一致),然后傳入給「exec」的第一個(gè)參數(shù),他就會(huì)幫我們解析字符串并執(zhí)行對(duì)應(yīng)的命令行指令。在執(zhí)行完成之后,我們可以根據(jù)回調(diào)函數(shù)(第二個(gè)參數(shù))里的參數(shù)值來(lái)判斷執(zhí)行結(jié)果。整個(gè)過(guò)程是異步的,所以我們不用擔(dān)心阻塞問(wèn)題,為了實(shí)時(shí)反饋進(jìn)度,我們可以用「socket」來(lái)將進(jìn)度信息推送到瀏覽器端。
4. 「socket.io」實(shí)現(xiàn)消息實(shí)時(shí)推送
在上面介紹的 「exec實(shí)現(xiàn)解析并執(zhí)行命令行指令」 中還有一些細(xì)節(jié)可以優(yōu)化,比如代碼執(zhí)行進(jìn)程的反饋,執(zhí)行狀態(tài)的反饋。因?yàn)槲覀冇玫氖钱惒骄幊蹋哉?qǐng)求不會(huì)一直等待,如果不采取任何優(yōu)化措施,用戶是不可能知道何時(shí)代碼打包編譯完成, 也不知道代碼是否編譯失敗,所以這個(gè)時(shí)候會(huì)采取幾種常用的放案:
客戶端請(qǐng)求長(zhǎng)輪詢 postmessage消息推送 websocket雙向通信
很明顯使用「websocket雙向通信」會(huì)更適合本項(xiàng)目。這里我們直接使用社區(qū)比較火的「socket.io」.由于官網(wǎng)上有很多使用介紹,這里筆者就不一一說(shuō)明了。我們直接看業(yè)務(wù)里的代碼使用:
// node端
exec(cmdStr, function(err,stdout,stderr){
if(err) {
console.log('api error:'+stderr);
io.emit('htmlFail', { result: 'error', message: stderr })
} else {
io.emit('htmlSuccess', { result: dest, message: stderr })
}
})
// 瀏覽器端
const socket = io(serverUrl);
// ...省略其他業(yè)務(wù)代碼
useEffect(() => {
socket.on('connect', function(){
console.log('connect')
});
socket.on('htmlFail', function(data){
// ...
});
socket.on('disconnect', function(e){
console.log('disconnect', e)
});
}, [])
這樣我們就能實(shí)現(xiàn)服務(wù)器任務(wù)流的狀態(tài)實(shí)時(shí)反饋給瀏覽器端了。
5. 使用「jszip」實(shí)現(xiàn)服務(wù)端壓縮文件并支持前端下載「zip」包
實(shí)現(xiàn)前端下載功能其實(shí)也很簡(jiǎn)單,因?yàn)橛脩襞渲玫腍5項(xiàng)目包含了各種資源,比如「css,js,html,image」,所以為了提高下載性能和便捷性我們需要把整個(gè)網(wǎng)站打包,生成一個(gè)「zip」文件供用戶下載。原理就是使用「jszip」將目錄壓縮,然后返回壓縮后的路徑給到前端,前端采用a標(biāo)簽進(jìn)行下載。至于如何實(shí)現(xiàn)目錄遍歷壓縮和遍歷讀取目錄, 這里筆者就不說(shuō)了,感興趣的可以參考筆者其他的nodejs 的文章。
場(chǎng)景化應(yīng)用落地
跌跌撞撞的迭代了2年多, 目前已經(jīng)基本可以使用Dooring搭建大部分的場(chǎng)景應(yīng)用了, 比如:
企業(yè)官網(wǎng) H5營(yíng)銷頁(yè)面 web簡(jiǎn)歷 問(wèn)卷調(diào)查 信息流頁(yè)面 活動(dòng)聚合頁(yè)面
等等, 后期會(huì)擴(kuò)展更多的場(chǎng)景, 持續(xù)迭代, 滿足更多用戶的深度定制需求.
后期規(guī)劃
目前Dooring 已經(jīng)完成了幾個(gè)關(guān)鍵性的能力:
多模式搭建能力 出碼能力 數(shù)據(jù)源(動(dòng)態(tài)數(shù)據(jù)源和靜態(tài)數(shù)據(jù)源) 表單設(shè)計(jì)能力 組件 / 模版 應(yīng)用流 國(guó)際化 多端搭建(PC, H5, iPad)
后期會(huì)從搭建效率和資源生態(tài) 這兩個(gè)維度繼續(xù)迭代, 比如:
PSD導(dǎo)入 移動(dòng)進(jìn)度控制 營(yíng)銷組件豐富 智能模版推薦 參數(shù)化自動(dòng)生成頁(yè)面 以應(yīng)用為單位的應(yīng)用搭建平臺(tái) 埋點(diǎn)和監(jiān)控系統(tǒng)搭建 組件, 數(shù)據(jù)互通
如果大家感興趣, 也歡迎隨時(shí)和我交流討論, 探索真正的技術(shù).
?? 謝謝支持
以上便是本次分享的全部?jī)?nèi)容,希望對(duì)你有所幫助^_^
喜歡的話別忘了 分享、點(diǎn)贊、收藏 三連哦~。
歡迎關(guān)注公眾號(hào) 趣談前端 收貨大廠一手好文章~

從零搭建全棧可視化大屏制作平臺(tái)V6.Dooring
點(diǎn)個(gè)在看你最好看
