阿里巴巴集團(tuán)前端委員會主席 @圓心 對前端未來期許有四點(diǎn):搭建服務(wù)呢堰, Serverless抄瑟,智能化,IDE枉疼。仔細(xì)想想皮假,一個(gè)「可視化搭建系統(tǒng)」的想象空間,正能完美命中這些方面骂维。前端的邊界在哪里惹资,對于業(yè)務(wù)的價(jià)值又在哪里,我們不妨靜下來航闺,一起從「可視化搭建系統(tǒng)」的角度來思考褪测。
—— 有人說前端「可視化搭建系統(tǒng)」說到底只是重復(fù)造輪子產(chǎn)生的玩具猴誊;有人說前端「可視化搭建系統(tǒng)」本質(zhì)是組件枚舉,毫無意義侮措。片面的認(rèn)知必有其產(chǎn)生道理懈叹,但我們不妨從更高的角度出發(fā),并真切落地實(shí)踐分扎,也許你會發(fā)現(xiàn):作為 FEer项阴,我們能做的事情也許更多。
頁面搭建技術(shù)流派概覽和彩蛋放送
據(jù)我觀察“幾乎每一個(gè)前端團(tuán)隊(duì)笆包,都會有一個(gè)頁面搭建系統(tǒng)”环揽。頁面搭建技術(shù)是一個(gè)老生常談的話題,可這個(gè)話題伴隨著前端技術(shù)的發(fā)展庵佣,歷久彌新歉胶。究其原因,包括但不限于:
- 運(yùn)營活動頁面對于產(chǎn)品業(yè)務(wù)至關(guān)重要巴粪,是吸引流量通今、提高留存的關(guān)鍵手段
- 高頻且重復(fù)度較高的活動頁面開發(fā),對于前端意味著大量的時(shí)間和人力成本消耗
在此背景下肛根,快速頁面搭建技術(shù)就顯得尤為重要辫塌。
由于每個(gè)產(chǎn)品業(yè)務(wù)的特點(diǎn)、運(yùn)營需求和設(shè)計(jì)規(guī)范不盡相同派哲,因此頁面搭建平臺就出現(xiàn)了“百花齊放臼氨,百家爭鳴”的局面。我們在“閉門造車”的同時(shí)芭届,博覽眾家之長储矩,對比歸納,持續(xù)優(yōu)化褂乍。為此持隧,我們分析了社區(qū)上幾乎所有開源產(chǎn)品和方案,包括但不限于:
- 百度 H5
- iH5
- 轉(zhuǎn)轉(zhuǎn)魔方平臺
- 百度外賣頁面配置平臺:Blocks
- 攜程樂高系統(tǒng)
- 人人貸活動運(yùn)營平臺
- 新版微信編輯器
- 魯班H5
- 阿里云鳳蝶
- MAKA
- 碼良平臺
- grapes
- 可視化布局 bootcss
- 民間方案:pipeline-editor
- 一個(gè)國外的民間方案:vvvebjs
相關(guān)技術(shù)分析文章:
- 頁面可視化搭建工具前生今世
- 頁面可視化搭建工具技術(shù)要點(diǎn)
- QQ會員活動運(yùn)營平臺演變和技術(shù)實(shí)踐——高效活動運(yùn)營
- 積木系統(tǒng)逃片,將運(yùn)營系統(tǒng)做到極致
- 活動運(yùn)營自動化平臺實(shí)踐
- 可視化搭建前端工程 - 阿里飛冰了解一下
- 飛冰對于活動引擎的可借鑒之處
- 前端工程實(shí)踐之可視化搭建系統(tǒng)
- 如何設(shè)計(jì)高擴(kuò)展的在線網(wǎng)頁制作平臺
- 魯班H5作者:@小小魯班
- 厭倦了寫活動頁屡拨?快來擼一個(gè)頁面生成器吧!
其特點(diǎn)和技術(shù)方向可以各有特點(diǎn)褥实,但總體可以歸納為以下圖示:
按照目標(biāo)受眾呀狼,可區(qū)分:
我們也從海量優(yōu)秀方案中總結(jié)出解決這一類運(yùn)營需求的通用手段:將復(fù)雜頁面的搭建抽象成結(jié)構(gòu)化數(shù)據(jù),由結(jié)構(gòu)數(shù)據(jù)驅(qū)動組件/模版的拼裝性锭。簡單的這樣一句話很好理解赠潦,按照這樣的想法也能構(gòu)建出一個(gè)可用的平臺,但能否更進(jìn)一步草冈,想在技術(shù)和業(yè)務(wù)上突破瓶頸她奥,還需要打通更多環(huán)節(jié):
- 結(jié)構(gòu)化數(shù)據(jù)如何設(shè)計(jì)才能兼顧優(yōu)雅和高性能瓮增,且天然支持活動編輯時(shí)的“時(shí)光旅行 Redo/Undo”功能
- 如何平衡頁面的自由發(fā)揮度和規(guī)范統(tǒng)一度
- 如何突破原始模版引擎,借力框架(React哩俭、Vue 等)組件化思想绷跑,并做到 framework free
- 如何優(yōu)雅實(shí)現(xiàn)專題模版功能,一鍵導(dǎo)入功能以及插拔式編輯
- 如何貼合自身業(yè)務(wù)特點(diǎn)凡资,平衡實(shí)用性砸捏、適用性和可擴(kuò)展性
- 如何不斷持續(xù)迭代,以適應(yīng)新的需求發(fā)展
- 如何借助社區(qū)的力量隙赁,做大做強(qiáng)
- 如何最大化發(fā)揮可配置垦藏,如何最大化方便接入方擴(kuò)展
- 如何避免組件枚舉堆積的混亂
業(yè)界已有方案中,有的較好地解決了這些關(guān)鍵點(diǎn)中一個(gè)或多個(gè)問題伞访,有的更像是一個(gè)練手的玩具掂骏。請讀者繼續(xù)閱讀,接下來我將介紹「結(jié)合編輯器技術(shù)的頁面搭建平臺」思路厚掷,整體如下圖:
當(dāng)編輯器技術(shù)遇見頁面搭建需求
讓我們先回到一個(gè)寬泛而有趣的問題上:“前端開發(fā)的難點(diǎn)到底在什么地方?”弟灼。
在這個(gè)問題下,舊有 @于江水 提到兩個(gè)點(diǎn):
- 業(yè)務(wù)邏輯很復(fù)雜而且多變
- 垂直領(lǐng)域解決方案并不簡單
這里對其答案進(jìn)行簡單搬運(yùn)和擴(kuò)展冒黑,原答案可參考:于江水的回答田绑。
順著這個(gè)思路我們來分析,前面提到的運(yùn)營活動頁面——單純開發(fā)這些頁面難度其實(shí)不高抡爹。但是對于前端團(tuán)隊(duì)來說掩驱,如果高頻多變的運(yùn)營需求在短時(shí)間內(nèi)集中爆發(fā),那么就成了一個(gè)系統(tǒng)性的問題了豁延。比如極端情況:對于淘寶雙十一昙篙、京東大促,簡單地堆人堆時(shí)間也只是杯水車薪诱咏。于是誕生了頁面搭建平臺。
這樣一個(gè)平臺涉及到的技術(shù)點(diǎn)是網(wǎng)狀的:比如涉及到開發(fā)工具鏈缴挖、數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)袋狞、渲染器和交互設(shè)計(jì)、數(shù)據(jù)源導(dǎo)入映屋、頁面編譯構(gòu)建苟鸯、頁面生成、代碼發(fā)布棚点、活動發(fā)布早处、版本管理、在線運(yùn)營管理瘫析、權(quán)限管理砌梆、可視化“所見即所得”實(shí)現(xiàn)默责、后端存儲、CDN 同步咸包、數(shù)據(jù)打點(diǎn)和統(tǒng)計(jì)桃序、數(shù)據(jù)分析等。后續(xù)結(jié)合平臺化能力烂瘫,也會涉及到組件市場的設(shè)計(jì)媒熊,甚至 serverless,no/low code 技術(shù)坟比。
而作為垂直領(lǐng)域一個(gè)不可忽視的方向——編輯器開發(fā)芦鳍,技術(shù)難度只會更高:除了編輯器本身的各種功能實(shí)現(xiàn)外,還需要兼顧兼容性葛账,更要適應(yīng)業(yè)務(wù)需求柠衅。同時(shí),編輯器就是生產(chǎn)工具注竿,任何一個(gè)中后臺系統(tǒng)似乎都必不可少茄茁,需求市場上,不管是石墨文檔巩割、釘釘文檔裙顽、頭條飛書等都有著廣泛而強(qiáng)烈的需求。該領(lǐng)域值得深耕而優(yōu)秀開發(fā)專家卻鳳毛麟角宣谈。
為了解決「可視化搭建系統(tǒng)」愈犹,我們嘗試把一個(gè)上述「復(fù)雜的業(yè)務(wù)平臺」和「垂直領(lǐng)域的富文本開發(fā)」這兩大難題結(jié)合起來,打造一個(gè)功能強(qiáng)大的編輯器闻丑,同時(shí)完成頁面搭建平臺的工作——這聽上去雖然是“難上加難”漩怎,但似乎兩大方向的融合是一種美妙的思路和創(chuàng)新。
具體來說嗦嗡,編輯器除了支持傳統(tǒng)富文本功能以外勋锤,需要加入對業(yè)務(wù)功能區(qū)塊的支持,這時(shí)候在數(shù)據(jù)結(jié)構(gòu)上侥祭,選用 JSON base 的存儲方式:傳統(tǒng)富文本區(qū)塊以 JSON 字段存儲富文本內(nèi)容叁执,其它復(fù)合型自定義業(yè)務(wù)區(qū)塊存儲為 JSON 對象結(jié)構(gòu)。在此基礎(chǔ)上矮冬,我們實(shí)現(xiàn)對該 JSON 對象結(jié)構(gòu)的解析谈宛,實(shí)現(xiàn)編輯器內(nèi)“所見即所得”。
這里單獨(dú)說一下富文本之外的“復(fù)合型自定義業(yè)務(wù)區(qū)塊”胎署。我們知道最終搭建出來的頁面將會充滿各種 Sku 商品吆录、自定義組件、用戶卡片等區(qū)塊琼牧,最終這些內(nèi)容的輸出需要被 C 端渲染器所理解恢筝、所解析哀卫。
我們來結(jié)合下圖,進(jìn)一步說明:
區(qū)塊 1 是傳統(tǒng)富文本內(nèi)容滋恬,區(qū)塊 2 是一個(gè)復(fù)合型自定義業(yè)務(wù)區(qū)塊——Sku 卡片聊训,區(qū)塊 3 是另一個(gè)復(fù)合型自定義業(yè)務(wù)區(qū)塊——用戶卡片。這樣一來編輯器不再是一個(gè)單一的富文本編輯器恢氯,而是最終輸出內(nèi)容為復(fù)雜 JSON 類型的多功能編輯器带斑。
不同業(yè)務(wù)場景、特點(diǎn)勋拟,需要完全不同的前端解決方案勋磕,在開發(fā)這些垂直解決方案的時(shí)候,業(yè)務(wù)分析敢靡、技術(shù)選型挂滓、架構(gòu)設(shè)計(jì)、開發(fā)落地是非常難的啸胧。接下來赶站,就讓我們一步步探索,一步步實(shí)現(xiàn)一個(gè)基于并兼顧編輯器技術(shù)的多功能的頁面搭建平臺纺念。
靈活強(qiáng)大的 Markdown 編輯器和頁面搭建創(chuàng)新嘗試
我相信現(xiàn)如今沒有程序員不知道 Markdown贝椿,它對程序員或者所有互聯(lián)網(wǎng)從業(yè)人員來說都非常友好。簡單說陷谱,Markdown 是一種輕量級標(biāo)記語言烙博,它允許我們使用易讀易寫的純文本格式編寫文檔。現(xiàn)如今許多網(wǎng)站都廣泛使用 Markdown 來撰寫幫助文檔或是用它來在社區(qū)上發(fā)表消息烟逊。比如:GitHub渣窜、Wikipedia、簡書宪躯、reddit 等乔宿。
除了易于編寫,Markdown 的可擴(kuò)展性和可轉(zhuǎn)換性也是它收到追捧的重要原因访雪。也正因?yàn)槿绱擞璨覀兂跗诘倪\(yùn)營活動頁面搭建就是基于 Markdown 編輯器實(shí)施的。具體流程如圖:
當(dāng)然這只是一個(gè)非常粗略簡易版的流程示意圖冬阳,接下來我將分:
- Markdown 擴(kuò)展和自定義解析器
- 完善使用體驗(yàn),打造頁面生成能力
兩個(gè)方面進(jìn)行詳細(xì)解釋党饮。
Markdown 擴(kuò)展和自定義解析器
Markdown 原本使用場景是面向文檔和寫作肝陪,它支持的標(biāo)記和語法并不能滿足所有場景需求。因此社區(qū)上存在不少 Markdown 解析器刑顺,其目的是對 Markdown 源內(nèi)容進(jìn)行解析和擴(kuò)展氯窍。在眾多解析器當(dāng)中,最出名的就是 marked.js 了。這里簡單對 marked.js 這個(gè)庫原理進(jìn)行分析凉驻,將會有助于理解后續(xù)我們的實(shí)現(xiàn)方案系羞。
說起解析,其實(shí)就是經(jīng)典的“編譯原理”套路政供。套用在 marked.js 上播聪,如下圖:
工作機(jī)制很簡單,marked.js 接受輸入源文本字符串后布隔,創(chuàng)建詞法解析器實(shí)例:
const lexer = new marked.Lexer()
詞法解析器實(shí)例 lexer 的使命是將輸入源進(jìn)行分詞离陶,解析出 tokens:
const tokens = lexer.lex(content)
如何理解分詞生成的 tokens 呢?其實(shí) tokens 就是 AST 對象(或直接把它理解成 json 數(shù)據(jù)衅檀,它是樹形結(jié)構(gòu)招刨,表達(dá)出 Markdown 中段落,塊引用哀军,列表沉眶,標(biāo)題,規(guī)則和代碼塊等信息)杉适。
接下來谎倔,marked.js 實(shí)例化一個(gè)解析器:
const parser = new marked.Parser()
該解析器 parser 接收 tokens,根據(jù) tokens 生成 html 富文本:
const html = parser.parse(tokens)
當(dāng)然淘衙,這只是很粗略的流程传藏,但細(xì)心的讀者可以窺出端倪:如果想擴(kuò)展 Markdown 語法:我們可以修改 lexer 生成 tokens 的函數(shù),目的是加入我們的自定義 Markdown 語法解析成新類型 token 的能力彤守;同時(shí)修改 parser 解析函數(shù)毯侦,根據(jù)新 token 類型,生成我們預(yù)期結(jié)果具垫。這里我不在深入贅述這個(gè)過程侈离,事實(shí)上,我們采用的方案也沒有 fork 去修改 marked.js 代碼筝蚕,而是自己基于 marked.js卦碾,封裝了更上層的解析器。
完善使用體驗(yàn) 打造頁面生成能力
由上可知起宽,我們的頁面搭建需求主要集中在插入各種組件卡片洲胖,插入帶鏈接 banner 圖片等復(fù)合型自定義業(yè)務(wù)區(qū)塊。這每一個(gè)需求都應(yīng)該對應(yīng)一個(gè) Markdown 的新語法規(guī)則坯沪。
比如绿映,輸入:
<SkuCell>live@12345@rondStyle</SkuCell>
則表示頁面中插入一個(gè) id 為 12345 的 Sku 卡片。
如果讓運(yùn)營同學(xué)手動輸入上述語法內(nèi)容無疑是痛苦且不可接受的。因此我們設(shè)計(jì)了 Markdown 編輯器的按鈕:「添加 Sku Cell」叉弦,點(diǎn)擊按鈕之后丐一,會彈出表單對話框,由運(yùn)營輸入 Sku 類型和 id 淹冰,即可自動在 Markdown 編輯器中光標(biāo)所在位置插入一行內(nèi)容:
<SkuCell>live@12345@rondStyle</SkuCell>
這樣的設(shè)計(jì)方便運(yùn)營使用和記憶库车。因此對于使用者來說,只需要了解基本的 Markdown 語法樱拴,而不需要再去記牢和手動輸入新型語法柠衍。
為了滿足“所見即所得”需求,我們需要在運(yùn)營鍵入內(nèi)容時(shí)疹鳄,同時(shí)進(jìn)行對輸入源的解析拧略。解析的過程需要逐行進(jìn)行:
- 如果解析當(dāng)前行內(nèi)容符合 Markdown 原始語法,則用 marked.js 進(jìn)行解析瘪弓,得到解析出來的富文本結(jié)果垫蛆,推入結(jié)果數(shù)據(jù)棧(這里的數(shù)據(jù)棧是一個(gè) result 數(shù)組)
- 如果解析當(dāng)前行內(nèi)容符合新擴(kuò)展的 Markdown 語法,則使用自己的解析器函數(shù)(暫且命名為 feParse)對該行進(jìn)行解析(解析器函數(shù)實(shí)現(xiàn)是一個(gè)簡易的編譯分詞過程)
- feParse 函數(shù)接收擴(kuò)展新語法內(nèi)容腺怯,對于不同表意方式使用不同的 helper 處理袱饭,比如處理
<SkuCell>live@12345@rondStyle</SkuCell>
將會被 skuCellHelper 函數(shù)處理 - skuCellHelper 函數(shù)解析內(nèi)容,分析得到分詞結(jié)果(標(biāo)記為 formData):
type: 'live',
sku_id: 12345,
style: 'rondStyle'
- 根據(jù)上面分詞結(jié)果呛占,請求后端接口虑乖,獲取該 Sku 對應(yīng)的數(shù)據(jù),比如該 id 為 12345 的 live 數(shù)據(jù)(標(biāo)記為 liveData):
author: 'live 作者名',
id: 12345,
created_date: '2019 10-12 20:34',
description: 'live 介紹',
duration: '20mins',
// ...
- 根據(jù)以上兩種數(shù)據(jù):formData 和 liveData晾虑,利用 React 服務(wù)端渲染能力疹味,獲得該 Sku 組件對應(yīng)的富文本 skuRichText:
const skuRichText = ReactDOMServer.renderToString(<SkuCell data={... formData, ... liveData} />)
- 將 skuRichText 推入結(jié)果數(shù)據(jù)棧 result
最終我們逐行解析的結(jié)果產(chǎn)出為:
result = [
'第一行富文本內(nèi)容',
'第二行 Sku 卡片對應(yīng)的富文本內(nèi)容'帜篇,
// ...
]
合并 result 內(nèi)容糙捺,渲染出富文本,顯示在頁面右側(cè)笙隙,實(shí)現(xiàn)所見即所得效果洪灯。
總結(jié)一下實(shí)現(xiàn)“所見即所得效果”的要點(diǎn)為:
- 自定義 Markdown 語法解析器
- 利用 React 服務(wù)端渲染能力得到特殊組件的富文本內(nèi)容
需要指出的是,在實(shí)際實(shí)施當(dāng)中:運(yùn)營在編輯器中竟痰,保存并提交給后端的數(shù)據(jù)區(qū)別于上述 result签钩,它也是一個(gè)數(shù)組:submitData,用來表示運(yùn)營輸入的內(nèi)容坏快。對于原始 Markdown 語法铅檩,我們直接使用其對應(yīng)的富文本內(nèi)容;對于新的擴(kuò)充語法莽鸿,我們并沒有使用其對應(yīng)的富文本內(nèi)容柠并,而是使用了上述 formData 的數(shù)據(jù)結(jié)構(gòu),最終提交類似內(nèi)容:
submitData = [
{
type: 'richText',
content: '<p>XXXX</p>'
},
{
type: 'sku',
content: {
type: 'live',
sku_id: 12345,
style: 'rondStyle'
}
}臼予,
// ...
]
這樣的考慮是為了 C 端用戶在請求頁面時(shí),能夠獲得最新的實(shí)時(shí) Sku 數(shù)據(jù)啃沪。如何理解實(shí)時(shí) Sku 數(shù)據(jù)呢粘拾?在運(yùn)營編輯頁面時(shí),假設(shè)插入一條 Sku 的標(biāo)題信息為“標(biāo)題一”创千。再一天后缰雇,該 Sku 的標(biāo)題信息變成了“標(biāo)題二”。如果我們保存并使用了運(yùn)營編輯時(shí)使用的富文本信息追驴,那么 C 端頁面一定是“標(biāo)題一”械哟,而不是最新的“標(biāo)題二”。因此我們只提交該 Sku 的 id殿雪。當(dāng)有 C 端用戶請求頁面時(shí)暇咆,由后端通過 RPC/Http 調(diào)用,獲取最新的數(shù)據(jù)丙曙,并由組件在服務(wù)端渲染出內(nèi)容爸业,最終返回給前端。
整個(gè)流程如下:
到此為止亏镰,我們實(shí)現(xiàn)了一款基于 Markdown扯旷,利用 Markdown 語法靈活性,擴(kuò)展而成的編輯器索抓。這個(gè)編輯器中內(nèi)置了諸如「插入 Sku 卡片」钧忽、「插入 Banner 圖」等一系列的業(yè)務(wù)功能。
基于這套思想逼肯,我們完成了幫助運(yùn)營快速搭建活動頁面的復(fù)合型編輯器和頁面生成器耸黑,它的優(yōu)點(diǎn)非常明顯:
- 輸入即所見,所見即所得
- 支持靈活擴(kuò)展汉矿,可以基于解析器支持所有類型的語法和任意組件
- 運(yùn)營只需要熟悉基本的 Markdown 語法即可崎坊,擴(kuò)展語法由點(diǎn)按按鈕完成
最終效果圖:
技術(shù)方案都是在不斷演化推進(jìn)當(dāng)中發(fā)展并完善的。在該平臺運(yùn)行半年多之后洲拇,我們大膽進(jìn)行了創(chuàng)新優(yōu)化奈揍,并最終用更高效的方案實(shí)現(xiàn)了全面替換。感興趣的讀者請繼續(xù)閱讀赋续。
不止是富文本編輯器
上面我們提到了已有復(fù)合型編輯器即頁面生成器的優(yōu)點(diǎn)男翰,經(jīng)過半年多的線上服務(wù)后,我們再去深入分析一下它的缺點(diǎn):
- 編輯器內(nèi) Markdown 語法內(nèi)容纽乱,對于運(yùn)營仍然較為晦澀難懂
- 運(yùn)營還是需要一定的學(xué)習(xí)和使用成本
- 依賴實(shí)時(shí)解析和渲染的“所見即所得”
- 對于每一種新的組件蛾绎,都要創(chuàng)建一種新的 Markdown 語法
這些缺點(diǎn)很好理解,這里著重講一下“所見即所得”。上面我們提到“所見即所得”租冠,實(shí)際依賴了實(shí)時(shí)解析內(nèi)容源為全量富文本鹏倘,并實(shí)時(shí)渲染富文本的能力。雖然滿足了需求顽爹,但是這樣的做法性能成本較高纤泵,即便加上常用的“防抖和截流”手段,對于瀏覽器的壓力仍然不小镜粤。能不能像“積木系統(tǒng)”捏题、“拖拽搭建頁面系統(tǒng)”一樣,直接在“畫布”上修改肉渴,做到更加真實(shí)的“所見即所得”呢公荧?
“拖拽系統(tǒng)”優(yōu)缺點(diǎn)鮮明。
首先同规,以大量 H5 生成工具為代表的拖拽系統(tǒng)雖然看上去功能強(qiáng)大循狰,但是本質(zhì)上卻是依靠組件的堆積和無窮盡的配置擴(kuò)展,最終產(chǎn)出的數(shù)據(jù)形態(tài)和功能野蠻生長下去捻浦,比較容易出現(xiàn)“失控”的局面晤揣,而逐漸被邊緣化。
這里的失控既指運(yùn)營側(cè)朱灿、產(chǎn)品設(shè)計(jì)側(cè)沒有統(tǒng)一約束昧识,也包含了代碼膨脹后的維護(hù)角度的失控。另一方面盗扒,從最終結(jié)果上看跪楞,拖拽系統(tǒng)將頁面的拼接轉(zhuǎn)嫁到運(yùn)營身上,這些“搬磚”的工作量對于運(yùn)營其實(shí)也并不算小侣灶,同時(shí)它缺少“規(guī)范化”的強(qiáng)制約束甸祭,不利于視覺設(shè)計(jì)的統(tǒng)一,運(yùn)營同學(xué)“自我發(fā)揮”反倒不一定完全是好事褥影。退一步來說池户,社區(qū)上已經(jīng)存在不少可用的拖拽系統(tǒng),重復(fù)造輪子也毫無意義凡怎。
結(jié)合我們的需求特點(diǎn):頁面區(qū)塊和設(shè)計(jì)樣式固定校焦、組件形態(tài)固定、頁面排版固定统倒、重文字和圖片內(nèi)容寨典、頁面交互并不復(fù)雜,我們認(rèn)為房匆,多功能富文本編輯器將會是一個(gè)值得深入試水的方向耸成。
傳統(tǒng)的富文本編輯器就是一個(gè)強(qiáng)大的“超級文字加工廠”报亩,類似我們常用的 word,運(yùn)營可以在其上“肆意揮灑”井氢。如何在富文本編輯器上弦追,加入設(shè)計(jì)規(guī)范,并實(shí)現(xiàn)業(yè)務(wù)組件添加呢毙沾?
首先骗卜,富文本編輯器是前端一個(gè)非常值得深入研究的重要方向,社區(qū)上各類開源富文本編輯器也不在少數(shù)左胞,但是從時(shí)間和開發(fā)成本的角度來看,我們既不想重新實(shí)現(xiàn)一個(gè)融入了自己業(yè)務(wù)的增強(qiáng)型富文本編輯器举户;又不想做各種魔改已有方案烤宙。
無法找到一個(gè)合適的解決方案,還是讓我們先從需求角度分析:
- 新型多功能富文本編輯器俭嘁,需要支持歷史上的 Markdown 語法數(shù)據(jù)躺枕,否則會出現(xiàn)歷史數(shù)據(jù)不兼容的線上問題
- 新型多功能富文本編輯器,不僅為頁面生成器服務(wù)供填,也要能夠支持多類型橫向業(yè)務(wù)以及純富文本編輯器業(yè)務(wù)
- 新型多功能富文本編輯器拐云,要支持所有富文本的特性,包括復(fù)制粘貼內(nèi)容等
- 新型多功能富文本編輯器近她,要支持插入自定義組件和區(qū)塊叉瘩,比如 Sku 卡片等
- 新型多功能富文本編輯器,應(yīng)該插件化粘捎,可插拔
- 新型多功能富文本編輯器薇缅,要做到完全的所見即所得
- 新型多功能富文本編輯器,要支持模版形式快速搭建頁面
- 新型多功能富文本編輯器攒磨,要接入格式自動規(guī)范機(jī)制泳桦,自動實(shí)現(xiàn)標(biāo)點(diǎn)擠壓、統(tǒng)一排版等功能
綜上需求和設(shè)計(jì)方案娩缰,我們選用了 Draft.js 作為這套多功能編輯器的底層框架灸撰,一句話足以總結(jié)做出該選擇的原因:Draft.js 實(shí)際上并不是一個(gè)富文本編輯器,它其實(shí)是一個(gè)用于構(gòu)建富文本內(nèi)容和富文本編輯器的基礎(chǔ)設(shè)施拼坎。做個(gè)比喻:如果把富文本內(nèi)容比作一幅畫浮毯,Draft.js 只提供了畫紙和畫筆,至于怎么畫演痒,開發(fā)者享有很大的自由 ——(出自文章:Draft.js 在知乎的實(shí)踐)亲轨。
這正符合我們的需要:我們不要一個(gè)完整的解決方案,而需要一個(gè)舞臺鸟顺。至于如何解析內(nèi)容惦蚊,如何渲染內(nèi)容器虾,如何生成數(shù)據(jù),應(yīng)該全部由開發(fā)者把控蹦锋。事實(shí)證明兆沙,這樣的創(chuàng)新設(shè)計(jì)對于頁面搭建生成器以及傳統(tǒng)編輯業(yè)務(wù)場景非常貼合,我們最終實(shí)現(xiàn)了目前服務(wù)于后臺系統(tǒng)的強(qiáng)大多功能編輯器 —— Versatile Editor莉掂。
Versatile 譯為“多才多藝的葛圃;有多種技能的;多面手的憎妙;多用途的库正,多功能的”。目前 Versatile Editor 已經(jīng)全面接管了所有后臺系統(tǒng)編輯需求厘唾。它的技術(shù)設(shè)計(jì)和體系也非常清晰褥符。下面我們主要從
- 數(shù)據(jù)結(jié)構(gòu)設(shè)計(jì)
- 插件體系設(shè)計(jì)
- 多數(shù)據(jù)源支持
- 使用體驗(yàn)設(shè)計(jì)
- 頁面模版支持
- 其他細(xì)節(jié)
六個(gè)方面進(jìn)行分析。
別具匠心的數(shù)據(jù)結(jié)構(gòu)
數(shù)據(jù)結(jié)構(gòu)的設(shè)計(jì)思想是:使用結(jié)果數(shù)據(jù)棧(數(shù)組)存儲每一個(gè) Draft.js 編輯器塊級內(nèi)容抚垃,數(shù)據(jù)每一項(xiàng)都順序?qū)?yīng)每一個(gè)塊元素喷楣。這些塊元素分為兩大類:純富文本內(nèi)容和純自定義組件內(nèi)容。對于純富文本內(nèi)容鹤树,我們重新實(shí)現(xiàn)了將 Draft.js 的不可變數(shù)據(jù)結(jié)構(gòu)解析轉(zhuǎn)換為富文本的工具函數(shù) draftToHtml铣焊;對于純自定義組件,我們只提取出組件最小還原數(shù)據(jù)(比如 Sku Cell 組件的 sku id 等信息)罕伯。
運(yùn)營在編輯器側(cè)提交流程如下圖:
具體說明一下圖中的核心 contentState曲伊。contentState 是 ContentState 類型的對象,它規(guī)定了如何存儲具體的富文本內(nèi)容捣炬,包括文字熊昌、塊級元素、行內(nèi)樣式湿酸、元數(shù)據(jù)等婿屹。
這里需要注意的一點(diǎn)是:在輸出數(shù)據(jù)上,我們至少提交兩種數(shù)據(jù)給后端存儲:
- rawContent
- renderTreeData
其中 rawContent 是根據(jù)不可變數(shù)據(jù) contentState 進(jìn)行序列化后的結(jié)果推溃,rawContent 可以通過數(shù)據(jù)表示出當(dāng)前編輯器內(nèi)所有內(nèi)容昂利。我們提交 rawContent 的目的是用于編輯還原。當(dāng)運(yùn)營再次打開編輯器時(shí)铁坎,編輯器可以根據(jù) rawContent 迅速渲染出上一次提交的所有內(nèi)容蜂奸,以供編輯。
而 renderTreeData 是經(jīng)過計(jì)算并處理后提交的數(shù)據(jù)硬萍,它的目的是存儲到數(shù)據(jù)庫中扩所,用于后端返回給 C 端頁面,C 端頁面最終根據(jù) renderTreeData 由渲染器渲染出完整的活動運(yùn)營頁面朴乖。由上圖可知祖屏,renderTreeData 的生成助赞,我們開發(fā)了 RenderTreeGenerator 的實(shí)例上 generate 方法:
new RenderTreeGenerator(
contentState,
getToHtmlOptions(contentState, this.props.editorConfig),
this.customBlockModules
).generate()
如圖:
RenderTreeGenerator 接受 Draft.js 的不可變數(shù)據(jù)類型 contentState 作為第一個(gè)參數(shù),自定義配置項(xiàng)作為第二個(gè)參數(shù)袁勺,React 組件集合 this.customBlockModules 作為第三個(gè)參數(shù)雹食。this.customBlockModules 是一個(gè)數(shù)組,包含了所有自定義區(qū)塊 React 組件名期丰,在自定義區(qū)塊類型命中該數(shù)組時(shí)群叶,需要啟動自定義區(qū)塊,并生成結(jié)構(gòu)化數(shù)據(jù)钝荡。
generate 方法簡單偽代碼說明如下:
generate() {
this.output = []
this.blocks = this.contentState.getBlocksAsArray()
this.totalBlocks = this.blocks.length
this.currentBlock = 0
this.indentLevel = 0
this.wrapperTag = null
this.richTextArray = []
this.finalOutput = []
const processRichText = () => {
this.output.push({
type: 'RICHTEXT',
data: this.processRichText()
})
}
while (this.currentBlock < this.totalBlocks) {
const block = this.blocks[this.currentBlock]
let blockType = block.getType()
let type = blockType
// 對于 atomic 類型街立,如果當(dāng)前類型在 this.customBlockModules 當(dāng)中,則 export 出渲染數(shù)據(jù)以及當(dāng)前 type
if (block.getEntityAt(0)) {
const entity = this.contentState.getEntity(block.getEntityAt(0))
type = entity.getType()
if (this.customBlockModules.has(type)) {
const entityData = entity.getData()
this.output.push({
type,
data: entityData
})
this.currentBlock += 1
} else {
// 不在 this.customBlockModules 當(dāng)中埠通,仍按照富文本導(dǎo)出
processRichText()
}
} else {
processRichText()
}
}
// 其他美化或清理工作几晤,比如連續(xù)富文本區(qū)塊的合并
return this.finalOutput
}
這里不同于前期 Markdown 編輯器的關(guān)鍵點(diǎn)主要有兩處:
- 我們監(jiān)聽編輯器區(qū)塊的 onBlur 事件,在此事件觸發(fā)時(shí)植阴,開始生成結(jié)果數(shù)據(jù)
- “所見即所得”——不再需要在手動實(shí)時(shí)解析渲染實(shí)現(xiàn)。因?yàn)?Draft.js 是一個(gè)基于 React 的編輯器圾浅,我們可以直接在編輯器中渲染出一個(gè) React 組件
如下圖:
以上兩個(gè)特征也正是基于 Draft.js 的多功能編輯器優(yōu)于 Markdown 編輯器的關(guān)鍵點(diǎn)掠手。
可插拔、可移植的插件化和組件化設(shè)計(jì)
多功能編輯器的多功能不是說說而已狸捕,為了支持海量功能需求喷鸽,且考慮到方便第三方功能擴(kuò)展,我們設(shè)計(jì)了良好的編輯器插件體系灸拍。目前項(xiàng)目中使用了 11 個(gè)插件做祝,它們涵蓋了:插入代碼、插入公式鸡岗、插入鏈接混槐、插入引用、插入視頻轩性、復(fù)制粘貼還原內(nèi)容声登、插入圖片、插入重點(diǎn)樣式揣苏、插入注解等悯嗓。項(xiàng)目還沉淀出來海量業(yè)務(wù)組件,包括:頁面喵點(diǎn)組件卸察、Banner 圖組件脯厨、Sku 卡片組件、各類按鈕組件坑质、滾動列表組件合武、圖片畫廊組件等临梗。所有的組件和插件原則上都是可以面向社區(qū)、面向第三方使用的眯杏,同時(shí)后續(xù)計(jì)劃只需要一個(gè) NPM 包即可接入一個(gè)新的功能或新的自定義組件類型夜焦。**這也為后續(xù)的組件市場設(shè)計(jì)、no/low code 設(shè)計(jì)打下了基礎(chǔ)岂贩。
在編輯器初始化時(shí)茫经,我們注冊并實(shí)例化各種插件以及自定義組件萎津。因?yàn)槲覀兌喙δ芫庉嬈鞯睦砟罹桶私Y(jié)構(gòu)化和數(shù)據(jù)化卸伞,所有的這些插件和組件都可以依賴 decorator 進(jìn)行解析锉屈,這也就意味著:從另外一處編輯器實(shí)例中復(fù)制任何內(nèi)容(包括自定義組件)到當(dāng)前編輯器,都可以直接還原數(shù)據(jù)颈渊,無縫完美支持組件的復(fù)制粘貼功能遂黍。
多數(shù)據(jù)源支持
任何一項(xiàng)技術(shù)創(chuàng)新和更迭,都要考慮歷史包袱和歷史債務(wù)的解決雾家。多功能編輯器也不例外,前面提到芯咧,歷史編輯內(nèi)容是使用 Markdown 格式的。以運(yùn)營頁面生成器場景為例竹揍,歷史活動頁面 A 對應(yīng)的后端存儲數(shù)據(jù)是 Markdown 字符串敬飒。我們在使用新的多功能編輯器替換舊的 Markdown 編輯器后芬位,如果運(yùn)營同學(xué)想再次編輯活動頁面 A无拗,新的多功能編輯器上自然就要兼容歷史內(nèi)容晶衷。
為此我們的方案是:在編輯器中接收到數(shù)據(jù)源后,如果嗅探為歷史 Markdown 格式晌纫,那么先利用 marked.js 將此 Markdown 格式內(nèi)容轉(zhuǎn)換為富文本內(nèi)容,再根據(jù)富文本內(nèi)容轉(zhuǎn)換為 Draft.js 支持的不可變數(shù)據(jù)結(jié)構(gòu)锹漱。
總結(jié)一下,對于編輯器初始化時(shí)的數(shù)據(jù)源(rawContent)處理流程如下圖:
對于編輯器獲取的數(shù)據(jù) rawContent哥牍,我們使用 isDraftJson 工具函數(shù)判斷該 rawContent 是否可以被多功能編輯器以 Draft.js 支持的數(shù)據(jù)解析:如果可以喝检,則證明 rawContent 為由新的多功能編輯器提交的數(shù)據(jù)撼泛,可以直接使用并恢復(fù)出編輯器內(nèi)容挠说。如果 isDraftJson(rawContent) 判別為 false愿题,那么就表示無法被 Draft.js 解析,需要兼容歷史 Markdown 語法潘酗,由 marked.js 解析出富文本后再交給 Draft.js 處理,由富文本生成 Draft.js 的不可變數(shù)據(jù)仔夺;如果解析都失敗,則直接將 rawContent 視為 textarea 內(nèi)容缸兔,直接填入到編輯器當(dāng)中。
圖中并未畫出如果 rawContent 為空(或不存在)時(shí)的處理方式惰蜜。實(shí)際上,如果 rawContent 為空蝎抽,我們使用 ContentState.createFromText('') 方法生成一個(gè)初始化為空內(nèi)容的不可變數(shù)據(jù)路克。
實(shí)際過程由于歷史包袱原因樟结,對于多數(shù)據(jù)源的支持實(shí)現(xiàn)更為復(fù)雜精算,這過于特殊,我們不再展開灰羽。
持續(xù)打磨使用體驗(yàn)
編輯器一個(gè)非常重要的話題就是體驗(yàn)。相信很多人都經(jīng)歷過編輯器的體驗(yàn)之殤:“輸入卡頓廉嚼、詭異的光標(biāo)位置”等,但這里我認(rèn)為沒有必要分析傳統(tǒng)編輯器的體驗(yàn)優(yōu)化話題怠噪,更有意義的是從我們特有的多功能編輯器特點(diǎn)入手,聊一聊用戶體驗(yàn)傍念。
舉一個(gè)例子:按照 Draft.js 的設(shè)計(jì)葛闷,每一個(gè)區(qū)塊之間上下都會有個(gè)空行。如圖:
這樣會導(dǎo)致提交編輯器內(nèi)容時(shí)双藕,生成的自定義區(qū)塊數(shù)據(jù)前后會包含了兩個(gè)空區(qū)塊數(shù)據(jù),最終導(dǎo)致渲染出的頁面也會包含兩個(gè)空白行忧陪,直接影響頁面設(shè)計(jì)效果。社區(qū)上關(guān)于這個(gè)設(shè)計(jì)的 issue 討論不少赤嚼,比如 Empty line on adding atomic block。
事實(shí)上更卒,這是為了靈活地在自定義區(qū)塊前后添加或刪除內(nèi)容。設(shè)想蹂空,如果我們連續(xù)添加了三個(gè)自定義區(qū)塊——Sku 卡片 A,Sku 卡片 B上枕,Sku 卡片 C。如果 A辨萍,B,C 之間沒有空行锈玉,那么我們?nèi)绾卧诳ㄆ?A 和卡片 B 之間插入一個(gè)新的卡片 D 呢?如果 ABC 卡片彼此之間保持一個(gè)空行拉背,那么使用者可以用光標(biāo)定位到 AB 之間的空行,再插入卡片 D椅棺。這就是自定義區(qū)塊前后自動存在空行的意義。
有的開發(fā)者可能會想:我們可以保持這個(gè)空行的存在两疚,在最終生成的數(shù)據(jù)時(shí),自動將空行刪除不就可以了嗎诱渤?事實(shí)上,拿到 Draft.js 編輯器的數(shù)據(jù)時(shí),我們無法判斷是用戶自主回車創(chuàng)建的預(yù)期中的空行鞋吉,還是自定義區(qū)塊自帶的前后空行,因此無法直接在結(jié)果數(shù)據(jù)上粗暴地移除空行谓着。
為了達(dá)到更好的使用體驗(yàn):我們開發(fā)的 FocusPlugin 插件,優(yōu)雅地解決了問題:依然是每一個(gè)自定義區(qū)塊前后不保留空行治筒,但是利用 FocusPlugin 插件,使得每一個(gè)自定義區(qū)塊都可以被點(diǎn)擊選中舷蒲,或者用鍵盤上下鍵遍歷選中耸袜,選中之后可以直接摁下回車鍵,添加空行牲平,甚至可以摁下 delete 鍵堤框,刪除該區(qū)塊纵柿。如圖:當(dāng)自定義區(qū)塊被選中時(shí):
最終這套基于 FocusPlugin 插件的方案使得交互更加順暢自然,達(dá)到了更好的效果昂儒。基于此渊跋,我們可以非常順利地完成自定義區(qū)塊的更改:比如當(dāng)前選中區(qū)塊為一個(gè) id 是 1234 的 Sku 卡片,如果運(yùn)營需要替換為 id 是 5678 的 Sku 卡片拾酝,只需要選擇當(dāng)前區(qū)塊,選中之后在右側(cè)出現(xiàn)的編輯區(qū)中更改 id 內(nèi)容微宝,確定后即完成替換,如圖所示:
基于 FocusPlugin 插件蟋软,以修改當(dāng)前 Sku 卡片 id 為例嗽桩,id 進(jìn)行修改后,發(fā)送獲取新的 id 的數(shù)據(jù)碌冶,并在數(shù)據(jù)成功獲取后調(diào)用 modifyAtomicBlock(entityKey, data)
方法,觸發(fā) replaceEntityData(editorState, entityKey, data)
方法進(jìn)行編輯器不可變數(shù)據(jù)的更新,并由 handleEditorStateChange
方法一并更新狀態(tài)拒逮,最終反應(yīng)在編輯器視圖中。
這一編輯發(fā)生過程總結(jié)圖為:
使用體驗(yàn)確實(shí)不是一蹴而就的的事情臀规,這是一個(gè)需要持續(xù)迭代優(yōu)化的過程滩援。經(jīng)過不斷地打磨塔嬉,Versatile Editor 最終趨于穩(wěn)定。目前 Versatile Editor 已經(jīng)支持了數(shù)百量級的頁面搭建谨究,以知乎投放的頁面為例,包括但不限于:
- 高考直擊
- 抗擊疫情——致敬奔赴一線的逆行者
- 愛要「鹽」選
-
朗朗活動頁面——每個(gè)愛音樂的孩子必聽的大師課
等高流量內(nèi)容畔塔。
頁面模版支持
Daft.js 編輯器內(nèi)容是完全基于數(shù)據(jù)狀態(tài)的纪吮,它使用了不可變數(shù)據(jù)庫進(jìn)行數(shù)據(jù)的更新操作俩檬,秉承純函數(shù)式更新,因而天然對于“時(shí)光旅行(Undo/Redo)”的特性能夠良好支持碾盟。另一方面,一切皆數(shù)據(jù)也讓我們實(shí)現(xiàn)“頁面模版”功能非常簡單而巧妙冰肴。
我們可以將所有模版拆分為幾個(gè)大的自定義區(qū)塊,并創(chuàng)建這個(gè)活動模版所對應(yīng)的數(shù)據(jù):比如對于模版 A:頭部為一個(gè)頭圖 Banner联逻,我們可以編輯器中創(chuàng)建一個(gè)由占位圖表示的 Banner 圖片检痰;第二區(qū)塊為電子書榜單 Top10包归,即可在編輯器中創(chuàng)建一個(gè) Ranking 組件铅歼,并由任意占位 10 個(gè)電子書數(shù)據(jù)填充公壤,以此類推。提交數(shù)據(jù)之后椎椰,即可獲得描述這個(gè)頁面模版的數(shù)據(jù)厦幅。
當(dāng)運(yùn)營在創(chuàng)建頁面慨飘,并選擇使用「排行榜模版 A」時(shí),我們就用已經(jīng)提前預(yù)制的數(shù)據(jù)作為 rawContent 進(jìn)行編輯器初始化休弃。得到模版后吞歼,運(yùn)營即可添加修改玫芦,快速完成模版頁面創(chuàng)建。
整體流程如下:
其他細(xì)節(jié)
到此為止桥帆,我們介紹了社區(qū)方案和我們自己持續(xù)迭代的方案。其中還有一些小的細(xì)節(jié)在這里簡要帶過老虫,主要包括:預(yù)覽、排版忽刽、安全性夺欲、配置系統(tǒng)幾個(gè)方面說明。
“所見即所得”使得運(yùn)營編輯活動效率大幅提高些阅,但是在編輯器提交發(fā)布和推廣之前,還是需要一個(gè)完整的可預(yù)覽頁面地址供進(jìn)一步回歸市埋。由于這些推廣頁面都是面向移動端,因此我們在這個(gè)多功能編輯器兼頁面生成器的產(chǎn)品設(shè)計(jì)上抒倚,預(yù)留有頁面發(fā)布地址和二維碼生成功能坷澡,進(jìn)一步優(yōu)化運(yùn)營使用體驗(yàn)。如圖:
另一方面频敛,我們對于頁面文字的編審有著嚴(yán)格的要求,比如:不能使用中文引號,需要使用「」岂嗓;英文和數(shù)字與其他漢字之間需要預(yù)留一個(gè)空格;甚至標(biāo)點(diǎn)的位置也有嚴(yán)格規(guī)范,需要實(shí)現(xiàn)傳統(tǒng)類似“標(biāo)點(diǎn)懸掛侈咕、標(biāo)點(diǎn)擠眼”等一系列排版需求器紧。因此,該多功能編輯器兼頁面生成器配置了可插拔的自動排版能力铲汪,主要完成自動排版規(guī)范的審校和修正,如圖:
一個(gè)頁面往往無法只由編輯器生成狰住,可能還包括配置內(nèi)容齿梁。這些配置需求我們用進(jìn)入編輯器之前的表單來承載,表單填寫完畢勺择,生成基礎(chǔ)配置數(shù)據(jù)后,再進(jìn)入編輯器進(jìn)行創(chuàng)作省核。表單是頁面中數(shù)據(jù)交互的基本形式,對于非開發(fā)人員使用也沒有使用門檻邓深,但是切記不可將表單設(shè)計(jì)的過于復(fù)雜笔刹。同時(shí)要注意芥备,編輯系統(tǒng)和配置系統(tǒng)需要解偶的原則舌菜。
前面提到編輯器就是生產(chǎn)工具,編輯器的效能就意味著生成效率袱瓮。一旦編輯器出現(xiàn)線上問題爱咬,那么就會直接影響正常的生產(chǎn)活動。因此精拟,為了保障編輯器的安全性和強(qiáng)健性虱歪,我們加入了測試環(huán)節(jié)栅表。主要包括:單元測試,UI 測試怪瓶。單元測試主要驗(yàn)證關(guān)鍵函數(shù)和方法的正確性,比如上面提到的 autoFormat 方法洗贰,各種插件的輸入和輸出正確性校驗(yàn),數(shù)據(jù)修改的工具方法校驗(yàn)等宣增;UI 測試主要依靠 Enzyme矛缨,來保證關(guān)鍵交互的正常運(yùn)行。
最后箕昭,其他涉及點(diǎn)比如:一鍵換膚淤齐、字?jǐn)?shù)統(tǒng)計(jì)等由于篇幅原因镇饺,這里都不在詳述。
富文本編輯器是一個(gè)深坑笔诵,Draft.js 雖然背靠 Facebook 團(tuán)隊(duì)授翻,但也一直在深坑中掙扎藤为,我們此間開發(fā)過程確實(shí)是一部血淚史夺刑,但我們團(tuán)隊(duì)也在此方向積累了豐富的經(jīng)驗(yàn),后續(xù)技術(shù)細(xì)節(jié)也會一一進(jìn)行分享遍愿,請持續(xù)關(guān)注訂閱。
總結(jié)
我一直在思考桅咆,什么樣的文章能夠給讀者帶來真正的思考和啟迪坞笙。一方面入木三分講解語言特性和設(shè)計(jì)刽脖,深入技術(shù)細(xì)節(jié),庖丁解牛般的分析是我們所需要的,這類文章需要靠代碼說話却邓;另一方面,總結(jié)梳理技術(shù)趨勢简十,從更高的角度敘述方案的落地和演進(jìn)撬腾,更是對大局觀和格局的培養(yǎng),這對于團(tuán)隊(duì)的技術(shù)規(guī)劃和舵向同樣至關(guān)重要民傻。
這篇文章粗淺總結(jié)了業(yè)界在「可視化頁面搭建」技術(shù)探索的方方面面,并整理了各種相關(guān)技術(shù)博客和分析文章漓踢。我們還介紹了編輯器技術(shù)和編輯器技術(shù)所能給「可視化頁面搭建」帶來的破局和創(chuàng)新。在此基礎(chǔ)上奴迅,我們更是從一個(gè)自研的公司級「可視化頁面搭建系統(tǒng)」入手挺据,從探索階段到成熟階段的演進(jìn)歷史進(jìn)行了介紹。
事實(shí)上扁耐,「可視化頁面搭建系統(tǒng)」的話題還遠(yuǎn)為結(jié)束:我們正在此方向上探索更多可能,「微組件/微前端」占哟,「頁面歸因能力」、「no/low code 技術(shù)」榨乎、「自定義組件埋點(diǎn)以及 A/B 流量能力」瘫筐、「運(yùn)行時(shí)的組件構(gòu)建和渲染方案」,甚至「Serveless」策肝、「云端 IDE」等隐绵。后續(xù)我們將會繼續(xù)產(chǎn)出相關(guān)文章拙毫,請讀者持續(xù)關(guān)注:技術(shù)博客,我們也在廣泛求賢缀蹄。
回到文章開篇所提到的那個(gè)問題上:“前端開發(fā)的難點(diǎn)到底在什么地方?”,我想已有答案的開發(fā)者將持續(xù)優(yōu)化答案蛀醉,仍然未知的開發(fā)者很快將會找到自己的答案衅码。
Happy coding!