作者 | 光弘
來源 | 阿裡技術公衆号
前言
無論是在前端刀耕火種的 jQuery/YUI 時代,還是到現在基于資料驅動 UI 的 React/Vue 時代,物料/元件一直是前端永恒的話題。基于大量重複邏輯的封裝可以很顯而易見地提升前端 UI 的建構效率,簡單而直接,是以無論技術棧如何變化,物料工作都是排在各個前端團隊的首要位置解決。
在 2021 年的現在來看,基于 React/Rax 體系下的基礎元件體系已經基本完善,既有螞蟻良好設計語言的 AntDesign[1],也有集團基于 DPL 快速定制的 Fusion[2](阿裡中背景 UI 解決方案,已開源),在基礎元件的層面功能日趨完善,各個業務團隊之間在這個層面的低級重複建設也越來越少,這是非常好的結果。但在業務元件體系的建構上,目前還呈現着百花齊放的局面,由于技術棧的不斷擴充(可視化、小程式等),業務元件的開發上還存在着很多諸如工程體系混亂,開發鍊路不通的問題。
來到企業智能的 5 年多的時間裡,經曆了團隊物料體系從最初的 Arale/kuma,到基于 react-component 的 UXCore/SaltUI,再到現在全面與 Fusion 融合。基礎層面的變動也帶來了業務元件領域的工具鍊的不斷變化。寫這篇文章,一方面記錄一下自己在這方面做的一些工作和中間的思考,另一方面也希望能在社群裡獲得大家的一些寶貴建議,以得到一些新的啟發。
一 一個業務元件要開發幾遍?
1 困境
某天,同僚 A 找到我,說 TA 的業務裡需要建構一個業務元件包,涵蓋了 PC 端,小程式和對應的可視化元件,當時我正在做一些關于前端業務能力建構的相關工作,是以想來問下我的建議。這個問題看似是很簡單,但實際分析下來卻發現有很多問題。
首先,PC 的業務元件當時是使用 飛冰[3](iceworks,阿裡 GUI 建構工具) + deep 腳手架模闆(deep 出自企業智能使用者體驗團隊)的方式來開發的,好處是可以和 Fusion 深度打通,釋出和同步物料到 Fusion 對應的 deep 站點都比較友善。小程式/移動端的元件,當時基于 Rax 的動态化小程式元件方案 Fusion Mobile 和 Deep Mobile 剛剛起步,業務元件的開發還沒有自己的标準,唯一一套可用的是之前政務釘釘的前端團隊做的 gdt-utils 來承載。
而可視化元件,則是由樂高(阿裡企業級可視化搭建平台)團隊提供的 vdev 工具鍊,而當時還有一個問題是,由于 PC 端基于 React 架構,而小程式/移動端則基于 Rax 架構,導緻可視化開發時也無法通過初始化一個包來完成 PC 和小程式端的開發。
總結起來,開發一個涵蓋三種模式的業務元件,需要适應和學習三種工具鍊,初始化四個包,釋出四個 npm package,而後續使用中,使用者需要記住四個包名以及他們對應的使用場景,和在至少兩個地方看他們的使用方法。同時在維護的階段,PC 和 移動端相似的模型、請求、資料處理邏輯也無法得到複用,如果想要複用怎麼辦?不好意思,那就需要再發一個工具包在幾個包之間做流轉。這些都在無形中增加了開發一個業務元件的成本,同學們的精力就在這些架構、包和聯調的過程中消耗掉了。
2 融合開發
雖然從結果來看,這樣子可以完成開發,但從上手門檻和開發效率上來說确實不如人意,一是需要寫大量的文檔來把這件事情說清楚,二是僅僅為了開發一個業務元件,需要這麼大的成本真的是一個好的方案嗎?那可不可以不要那麼多東西,就一個工具鍊,一個包解決所有問題,這樣不好嗎?好是好,但真要這麼做之前,有很多問題需要解決。
最大的問題在于,如何将 Rax 和 React 兩種架構有機結合在一個工具鍊中。這看似是一個不怎麼困難的問題,貌似是隻要把以前的 React 和 Rax 的 webpack 配置拿過來各跑各的就行了,但融合之後有些東西是合成一份的,比如 demo 檔案,對于一個希望融合開發的同學來說,自然是不希望,寫幾份幾乎相同的 demo,僅僅是因為不同的運作架構帶來的引用不同。相似的還有可視化元件當中的 prototypeView 檔案,這個檔案用于在樂高設計器中渲染元件,這裡需要一點樂高元件開發的知識,prototypeView 檔案的大緻代碼組成是這樣的:
這樣的設計是在可視化元件都是 React 架構的前提下設計出來的,在以往的實踐中是沒有問題的。
但融合開發後,就像上面代碼中的注釋一樣,如何驅動一個 React 元件和一個 Rax 元件混合打包,并且可以正常渲染,需要有解決的辦法。其次,從工程的角度,選取哪個工具鍊來實作這個融合開發的能力?顯然目前的每條工具鍊都不具備直接實作的能力,那是選取其中某一個進行增強,還是另起爐竈結合三者的能力?各有利弊,需要權衡。還有一個必須考慮的點,就是如何盡量減少對開發者原有心智的改變,以及盡量保持對 Fusion 物料能力的比對(如同步到 Fusion 站點),這樣才可以讓開發者以盡量少的代價遷移到新的開發模式上來。确認了這些問題之後,我們開始着手通過融合開發來解決這個開發困境。
3 着手解決
确定目錄架構
首先我們需要确定融合開發包(後簡稱“融合包”)的目錄結構:
從源碼結構上,我們基本沿用了可視化元件的檔案結構,這樣一方面滿足了可視化元件特殊的要求(如設定器的配置,設計态的渲染等),另一方面通過制定 package.json 的 main 字段,也可以實作直接引用包名調用元件的需求。
而技術棧上則全面使用 ts 和 scss,和 Fusion 元件對齊,同時友善使用 scss 變量和 css 變量。
在建構産物上,我們則結合了幾個工具鍊産物的特點,首先建構 babel 轉譯過的 es5 檔案和對應的聲明檔案,用于一般項目中。其次建構保留的 import/export 的 esmodule,用于對包體積有要求的項目做 treeShaking。而後,将對應的 md 建構成可直接浏覽器預覽的 demo 放在 build 檔案夾中與 Fusion 元件對齊,再将 lib 檔案夾下的檔案複制到 build 目錄中,友善樂高建構和識别(樂高建構的時候會直接去找 build 目錄下的 view.js 和 view.mobile.js 等檔案)。
工具鍊的選擇
在工具鍊的選擇上,我們最終選擇了樂高的開發套件 vdev 作為底座進行擴充,這樣做的考慮是可視化元件的整套開發體系較重,且未插件化,抽取遷移的成本較高。其次,相比其他兩套工具鍊,vdev 在大團隊的基礎架構部門維護,溝通協作相對簡單。剩餘的部分我們并沒有完全重造,而是盡可能地複用起現有的 build-scripts 體系下的插件。我們首先對 vdev 進行了改造,支援了 build-scripts 相關體系的調用,剩餘的開發則基于 build-scripts 下的 plugin 開始開發,最終的大緻鍊路如下:
pc.json 大緻如下:
在開發的時候我們定下了一個原則,基于通用規則下的 PC、Mobile 和小程式預覽,我們盡量通過官方的 build-plugin-component 來實作,不支援的功能或 bug 通過共建補齊或修複,而針對融合包的特殊規則,如融合需要的特殊配置處理,建構後的檔案轉移等邏輯,通過 build-scripts 體系下後序插件(我們這裡命名為 build-plugin-vdev-component)可以對前序插件定義的配置做進一步處理的特性來增加。這樣既減小了後續的維護成本,又可以通過 build-plugin-component 幫助到更多的業務。
确定好基礎的架構之後,我進一步定義了融合包相關的 npm scripts:
- start:用于啟動可視化部分的調試,包含設計器和預覽
- startPC:用于啟動 PC 部分的調試
- startMobile:用于啟動移動端 Web 部分的調試
- startMiniapp:用于啟動移動端小程式部分的調試
- build:用于生成上文提到的建構産物
其中除 start 外,其他都依靠 build-scirpts 插件體系來完成。
調試主要解決的問題
大緻架構都确定後,就要開始具體的開發流程。start 相關的流程需要修改的不多,主要是把元件名 alias 到對應的入口(PC 到 view.tsx, Mobile 到 view.mobile.tsx),剩下的主要是補全 build-plugin-component 中缺失的功能,比如 Rax 部分裡對 scss 的支援,inlineStyle 配置的支援。有個值得稍微提一下的點是關于對 demo 檔案中 jsx 的處理。我們都知道 jsx 是 React 提出的 js 的加強型文法,用于将模闆可以完全使用 js 文法來描述,實際在浏覽器是不能直接識别的,一般的處理方式是通過 babel 轉換成對應的轉換文法,如把
轉換成:
對于的 jsx 标簽如
會被轉換成 React.createElement("div")。這在單一架構下是沒有問題的,但由于融合包中 demo 是在 PC 和 移動端複用的,那這裡是轉換成 React.createElement 還是 Rax.createElement 呢?顯然都不太合适,他隻有在具體啟動調試 PC 還是小程式時才能決定,而且 Rax 官方其實更推薦的寫法是不要直接引用全量的 Rax 變量,而是從 Rax 變量中按需加載對應的 api,如 createElement,寫法上天然有差別。是以我們在 demo 檔案的寫法上取了一個交集,大緻如下:
demo 檔案的寫法上是基于 PC 的視角的,引用的依賴是 react 的 Component 和 createElement,以及基礎元件庫 @ali/deep。在啟動小程式調試時,react alias 到 rax,@ali/deep alias 到 @ali/deep-mobile。而 jsx 相關的,如
會被轉換成 createElement("div"),這樣從寫法上保證了編譯後的可行性。
建構主要解決的問題
在 build 的過程中,由于要同時建構出 PC 和 Mobile 的 demo,而 build-plugin-component 隻能啟動 Rax 或者 React 其中一邊的建構,顯然這無法滿足我們的需求,如果調用兩次腳本,中間又存在着大量的重複操作,如檔案夾的新增和删除等等。我們選擇的是使用 build-plugin-component 的 rax 部分,并在後序插件 build-plugin-vdev-component 裡另起一個 React 的 webpack task,同時完成 PC 和 Mobile 的 demo 建構。
在建構 es5 的檔案時,也遇到了 jsx 的編譯問題,由于源碼中包含了一部分 react 檔案也包含了一部分 rax 檔案,在 start 時還可以按照 entry 的依賴鍊來設定 babel 配置,但是 build 時對源檔案隻過 babel 沒有 webpack,是以要使用其他的辦法來處理。我們采取的方式是先分析每個檔案的 AST,從檔案的包引用上分析出該檔案是一個 React 還是 Rax 元件,然後采用不同的 babel 配置來進行編譯。這裡為什麼沒有采用和上文提到的 demo 裡的同樣的方式進行處理呢?原因是 demo 的方式對 createElement 的引入是強依賴,寫法上比較受限,在源碼部分我們希望能夠讓使用者擁有更靈活的使用方式。
這樣基本上在 ProCode 下的場景都可以正常使用了,但樂高環境下還不行,主要還是處在 prototypeView.tsx 這個檔案的處理上,上面我們提到過,這個檔案會同時引入 React 和 Rax 元件,雖然在設計态,隻會有其中一個元件被真正渲染出來,但有些 API 是在元件類的聲明時就會用到,例如 Rax.forwardRef。這就導緻不做任何處理的情況下,在設計态會因為缺少 API 而報錯無法使用。而樂高的元件儲存建構,和一般的 Procode 項目,是每個元件單獨建構的,而元件在建構時并不知道自己是要在樂高 PC(React 環境)還是樂高小程式(Rax 環境)裡使用。是以我們在元件本地建構時會額外生成一個 prototypeView.rax.js,這個檔案在内容和建構後的 prototypeView.js 并沒有本質差別,但是提供給樂高打包建構一個額外的入口,樂高建構時探測到是一個融合包時對這兩個檔案入口配置不同的 webpack alias,在 prototypeView.js 裡會将 rax 相關的 api alias 到 remaxjs 社群的 rax-compact 包上,用于樂高 PC 版設計态的展示(React 環境)。在 prototypeView.rax.js 中将 react 相關的 api alias 到 rax/lib/compact 上,用于樂高小程式設計态的展示(Rax 環境)。
4 最終效果
最終我們将業務元件的開發和使用過程,統一到了一個包(融合 vc 包),一個工具鍊(vdev:初始化、調試、釋出),對 @ali/deep 和 @ali/deep-mobile 這兩個基礎元件包實作按需加載編譯(也支援 Fusion/Antd)。使用的時候,無論 PC 端、小程式還是可視化的部分,都可以引用同一個包,沒必要去記住不同的包名和他們對應的平台。正常情況下,我們可以通過直接引用包名來引入 PC 部分,通過 es/view.mobile(或者 lib/view.mobile) 引入移動端/小程式部分。另外,這裡我們在 package.json 裡将對應的入口也做了注冊,這樣可以通過 webpack resolve 配置也可以實作通過包名就直接引用移動端部分的效果。
二 重複的過程是否可以不做?
融合開發的第一個版本,解決了開發者在項目工程維護、釋出和使用者使用包時的耗費心智的問題,同時對于在 PC 和 Mobile 複用邏輯上有較大幫助。但開發者在開發過程中仍有很多重複勞動的過程。比如雖然已經有了完整的 ts 支援和接口聲明,但是使用者還是需要自己配置元件的 setter,配置 setter 元件的過程主要是指定 setter 對應的屬性,預設值和類型。比如元件釋出之後,還需要通過一套入駐機制,才能在物料中心給自己和他人檢視 Demo。比如小程式調試,每次本地啟動建構後,還要打開小程式的 IDE。這些都是每個業務元件開發中不得不做,但基本重複的工作,凡是重複的事情都是有規律可言,這些重複的事情能否不做?
2 自動生成 prototype.tsx
前面也提到融合開發包裡面 PC 和 Mobile 的開發入口檔案已經全面 ts 化和模闆化了,入口檔案大緻如下(這裡展示的是 PC 入口 view.tsx,Mobile 與此類似):
通過 AST 分析,我們可以定位到所有的 interface 聲明以及 defaultProps 聲明。typescript lint 中要求一個檔案中隻有一個 Class 聲明,再加上 Component 的 ts 泛型,這些前提條件讓我們可以定位到 props 的接口,進而得到每個 prop 的 name 和類型。考慮到業務元件的最重要目的是能夠快速在樂高裡使用起來,是以我們沒有必要對每種類型和預設值做特别精細化的映射,我們可以為基礎類型找到對應的 setter,對一些複雜的類型指定為一個 JSONSetter,對渲染函數鈎子指定為 ActionSetter,對回調事件指定為 events。prototype.tsx 的模闆檔案也是一個比較标準的寫法,是以我們可以比較友善地通過 AST 分析出已經定義過的屬性,這些屬性不需要再增加一遍,以及插入新屬性代碼的位置,通過直接生成這部分設定器代碼,我們的元件基本上可以在不需要修改的情況下在設計器裡正常使用起來(指可以配置各個屬性,流暢地使用元件的各個功能,而非僅是展示)。
3 釋出自動同步物料中心
元件釋出隻是第一步,一個業務元件隻有能夠被發現,文檔可以被閱讀,才是元件服務的開始。在企業智能,我們目前統一使用物料中心進行元件的展示。過去的業務元件隻能通過物料中心背景配置的方式才能上架到物料中心,這個過程需要大量的手動填寫的東西,耗時耗力。這部分我們也做了優化,在元件釋出到 tnpm 後,vdev 來收集上架必要的資訊,如元件 owner,元件分屬業務領域,元件适配的端等等,物料中心提供 API 調用,直接将這些資訊同步至物料中心,實作一鍵釋出+上架物料中心的效果。當然,這個同步并非強制,對于一些測試版,可以選擇不進行同步。
4 小程式 WebIDE 調試
原有小程式調試必須要在本地建構出一個符合小程式結構的項目目錄,然後再使用小程式 IDE 打開對應目錄才能開始調試,這個過程跨越多個工具鍊,學習成本和操作成本都比較高。恰好支付寶小程式團隊推出了 @ali/mini 工具鍊,可以基于項目目錄下一鍵啟動 Lyra 浏覽器模拟器進行調試。我們融合開發包也對 @ali/mini 做了內建,@ali/mini 需要的配置檔案自動幫助元件開發者生成,通過一個指令就可以直接啟動 WebIDE 進行調試了。
三 如何簡單地開發/使用一個帶服務的元件?
上面兩部分,我們主要圍繞的是一個純 UI 的業務元件開發,但我們也知道很多業務子產品都是需要配合後端服務一起使用才能完整表達一個場景,最簡單的例子就是一個搜人元件,不搭配服務就是一個清單樣式有點特殊的選擇元件。
對于這類元件,以前有兩種政策,一是搭配一個跨域的 jsonp 接口,但随着目前安全越來越收緊的情況下,這類接口越來越少了。另一個就是搭配一個業務上的接口,但這類接口群組件聯調非常麻煩,因為接口隻有在對于的業務域名下才能使用,因為這個限制條件,就分出了兩種不同的思路,一個是要在業務域上做個頁面出來或者幹脆直接在業務項目裡去調,更有甚者,前端直接把後端服務起起來本地調,想想都感覺很麻煩,新接手的同學估計光搭個環境就要花很長的時間。
另一個政策就是前端使用 mock 的接口或者資料進行調試,這類環境上相對簡單一點,但帶來很大的聯調成本,首先 mock 接口有時很難模拟線上真實資料,其次誰去一直維護這個接口一直和線上保持一緻,帶來了額外的工作量。開發不友善,使用也不友善,由于接口是業務自己的,導緻本地或者樂高裡啟動調試的時候,接口無法調通,隻能花力氣部署到業務頁面上才能使用起來看效果。或者是選擇元件裡不預設攜帶服務,真正使用的時候再去配對應的接口,就出現了拿着元件去找接口的情況,接口本身有很多種形式,能 run 起來的方式也不一樣,能真正用起來看效果,也要花一段時間。
2 服務調用
上面的分析中可以看出,這類元件的主要難點在于服務的調用上,在沒有跨域請求接口的情況下,本地開發舉步維艱。是以核心問題在于提供一類服務,可以友善地在本地或者樂高環境調用,同時不用擔心安全問題(資料洩露和服務提供方壓力等),這就需要一個 API 網關來,這個網關可以解決服務調用的鑒權,以及對服務提供方進行保護。有了網關的情況下,下一步要解決的本地和樂高如何調用的問題,樂高調用相對來說比較簡單,樂高本身是有後端服務的,隻需要提供一個過程,在使用者接入某個元件和服務的時候,到網關去自動申請日常服務的調用,由于日常服務大多已做了資料脫敏,且與真實資料隔離不能直接用于生産,是以日常服務的訂閱申請和審批都是相對比較簡單的,這個過程可以在開通元件的背後直接完成。本地調用則相對會比較麻煩,一般本地調試都是 node 起一個本地 server(如 webpack dev server 等),背後很難直接完成接口的調用。這裡有兩種解決思路,一個是服務端提供一個日常的跨域調用的轉發接口,用于轉發網關服務,如 /api/gateway?id=epaas.api.key,另一個則是服務端提供一個模闆頁面,前端 server 提供 js 和 css,并注入到對應的頁面中,這樣直接請求對應的接口即可。
3 實踐
在 EI 的實踐中,恰好有這樣一個業務網關 ePaaS 來承載網關的職責,在樂高的使用側,我們設計了一個業務能力子產品來做對應的元件和服務開通,ePaaS 是通過應用間的服務訂閱來實作跨業務調用的,是以我們在開通業務能力前會先讓使用者填寫自己的 ePaaS,其實樂高預覽時這些是不需要的,讓使用者填寫是為了幫助使用者一鍵完成自己 ePaaS 這些接口的訂閱,而不需要使用者再自己去 ePaaS 上一個個手動訂閱了。
ePaaS 的功能介
當然 ePaaS 網關不是最終唯一的選擇,我們也在積極拓展其他網關的接入。
四 未來還有什麼?
- 建構效率提效:上面做的事情隻是做到能,但還遠稱不上好,尤其是經常啟動的調試指令,還有巨大的優化空間,每個人每次節省 10s,加起來也是好多時間。
- 深入業務:目前在企業智能的一些業務域下已經鋪開,但還沒有完全覆寫,這套東西是從業務裡來的,是以也應該回到業務裡去,業務的邊界場景會帶領我們逐漸進入深水區。
- 業務能力:能力是個比較虛的詞,在我這裡的了解,他是一個以一個業務功能為核心的,不定數量個服務和UI(頁面、區塊、元件)的集合,帶服務的業務元件是我們在這個領域的第一步探索,接下來我們會把他繼續做深入,包括業務能力的樂高入駐,業務能力的本地生産等等。
- LowCode:目前樂高已經具備了低代碼拖拽生成業務元件的能力,但隻能在低代碼可視化的設計器裡使用,而理想狀态下應該是可以在各種狀态下流通,但是否是業務上的痛點,這個還在收集中。
- 模型驅動?:這個點打了一個問号,是因為目前還隻是一個想法的階段。目前頁面在企業智能已經實作了 ProCode、LowCode 和模型驅動三駕馬車的生産方式。業務元件方面已經有了 ProCode 和 LowCode 的模式,那是否可以通過綁定模型的方式直接驅動 UI,通過配置生成業務元件,複用在業務裡呢?這在一定程度上也可以解決現在能适用于模型驅動的标準頁面少的問題。
五 結語
上面是我們在業務元件開發方面做的一些微小工作,核心的方向還是減少學習的成本,減少重複工作,以及将複雜的步驟變簡單,通過這些方式來做到前端業務元件開發的提效。
最後,歡迎加入企業智能-使用者體驗平台部前端團隊,我們是阿裡企業服務的先行者和創新基地,企業智能在音視訊會議、遠端辦公系統、大型 ERP 系統、企業營運活動、大型組織管理都有着豐富的實踐和業務場景。團隊技術涉及可視化搭建、跨端小程式、微前端、桌面端開發、模型驅動渲染、資料可視化、體驗度量等等前端前沿方向,對這些業務場景和技術方向感興趣的小夥伴歡迎來聯系我。
相關連結
[1]
https://ant.design/index-cn [2] https://fusion.design/ [3] https://ice.work/