1. 引言
貓耳前端在開發活動的過程中,經歷過傳統的 pro code 階段,即活動頁面完全由前端開發編碼實現,直到 2020 年接入公司內部的低代碼活動平臺,滿足了大部分日常活動的需求,運營可自主配置活動并上線,釋放了相當一部分的開發人力。不過,此時的方案仍然無法很好地服務大型直播活動場景,比如年度 S 級的直播活動,這類活動賽程多且持續時間長(可長達兩周),頁面和組件都包含多種狀態,運營難以配置出大型直播活動的所有需求,故此階段的大型活動仍然完全由開發編碼實現,需要占用較多人力,但此類活動從數量上來看連 1% 都不到。鑒于我們已在日常活動中積累維護了相當多功能的低代碼組件,如何復用已有的低代碼組件來更快實現更多活動玩法就成了一個值得研究的問題。此外,大型活動從籌備到結束,時間長達數月,而密集開發階段可能只占其中的一個月,密集開發結束后的長尾需求又該如何優化提效?接下來本文將介紹貓耳前端在活動場景的低代碼探索經驗,以及最終穩定的開發模式。
2. 場景分析與問題識別
2.1 不同場景的開發方案
把時間先撥回 2022 年,當時我們已有一定的平臺使用經驗,從技術角度將活動劃分為兩大類型,根據業務需求采取不同的方案:
2.1.1 配置類頁面(Low Code + No Code)
此類活動經產運及技術負責人評估后,若無需開發參與則由產運同事自助配置并上線。若需要開發參與,則視情況修改組件或開發 JS 或 CSS 補丁代碼來實現,沉淀下來的代碼也可用于后續的類似活動。
2.1.2 開發類頁面(Pro Code)
在一些玩法較為復雜、活動狀態多的場景,配置頁面的難度可能會呈指數級上升,當時平臺也并不具備完善的預覽能力,所以此類活動幾乎完全由開發負責實現(部分頁面可能會跳轉到配置類頁面)。雖然工作量較大,但簡單來說就是根據設計稿、產品需求文檔、接口文檔這幾個關鍵輸入來實現活動頁面,在流程上并沒有太多可說的。
2.2 問題與嘗試
回到引言的問題,在討論更具體的解決方案之前,我們先看看在經過幾次大型活動的 pro code 開發后發現了哪些問題,在前期又做了哪些嘗試:
2.2.1 活動規則變更效率低
由于活動頁為了保證用戶體驗會集成較多的玩法規則,由于運營無法在活動的密集開發階段一次性給到準確無誤或完全不變的規則說明,在活動上線前,前端需要持續配合更新頁面內的活動規則,有時候是換圖,有時候是調整文字描述,改動的發布流程較長且影響開發測試效率。
前期嘗試:比較泛的解決方法是將前端寫死的切圖改為從靜態資源服務中獲取,我們也確實在個別活動中嘗試約定過資源路徑,產運將資源上傳到特定路徑后,開發使用約定好的路徑來加載資源,但此方案并沒有很好地利用現有的低代碼平臺(如:配置間距、管理資源版本),也存在額外的上手成本和維護成本。
2.2.2 部分常見的需求處理效率低
相似需求總是需要走開發上線的流程,導致開發人力總是緊張,且效率難提高,如:
-
每次活動都會設計新的榜單樣式,這類換皮工作對開發來說耗時且枯燥,影響其他需求的開發進度
-
直播活動結束后,開發需要按業務流程固化榜單數據,每次都需要將服務端導出的數據替換到代碼中,然后再走一遍上線流程
該問題在前期并沒有想到很好的處理方法,直到后續復用低代碼組件才得以解決。
2.2.3 重復開發組件
前端在大型活動中存在重復開發組件的情況(如:直播榜單組件)。榜單在直播活動中是標配組件,但大型活動頁面狀態較為復雜,無法直接通過低代碼平臺配置,導致這類場景我們一直沒能復用運營配置的組件,于是又額外開發維護了一套 pro code 場景的榜單組件。
前期嘗試:將配置了單個 low code 組件的頁面通過 iframe 的形式載入頁面框架內,也確實滿足了一些活動需求,但同時也發現了其局限性:
-
組件若涉及彈窗和遮罩,由于彈窗和遮罩都是基于 iframe 頁面插入的,難以直接基于宿主頁面居中擺放彈窗,遮罩的處理也比較復雜
-
頁面之間可能存在跨域的情況,個別需求得花心思解決
-
iframe 頁面完整加載較慢,會重復請求宿主頁面加載過的資源,部分請求也并非必要
以上提到的幾個痛點,不僅影響我們每次活動的交付效率,也使得前端團隊整體的資源利用率較低,前端團隊總是處理類似需求得不到成長,也無法推進其它業務需求。
3. 解決方案與實施
秉持著不寫新代碼就沒有新 bugDRY 的開發原則,作者在經過幾次 pro code 活動后開始思考:是否有方案可以解決以上問題,讓我們可以有精力去做其它更加價值的工作?
3.1 遠程組件
2022 年 10 月,首個接入B站遠程組件能力(內部名:片段加載器)的貓耳直播活動上線,本次活動在 pro code 頁面框架的基礎上加載了運營配置的低代碼榜單組件、彈窗、活動規則等內容,解決了第一部分提及的所有問題。
3.1.1 原理
顧名思義,遠程組件依賴于服務端的組件數據,其包含三大數據:組件 JS 資源、組件 CSS 資源、組件 JSON 參數(運營配置)。組件數據的拉取、組件的渲染等工作,則由客戶端的加載器提供支持。
前端研發人員在 pro code 頁面框架內集成加載器后,只需傳入約定好的頁面片段 ID,加載器即可完成頁面片段的拉取工作,并復用 low code 頁面的渲染器完成渲染工作。
3.1.2 項目工作流
上述編碼細節轉變的背后,更深層次的轉變其實是「開發類頁面工作流程」的轉變,以及項目人員的工作職責變化。
從新的流程中不難觀察到,前端能夠在設計稿尚未交付的情況下就提前介入,設計一些適合低代碼化的功能模塊,然后轉交給運營配置使用。前端不僅能夠在更專注業務邏輯的情況下提前交付,運營也在復雜活動中獲得了調整和發布的能力(僅限于部分功能模塊),一些運營側的調整也不必再走前端的發布流程,減少各端溝通返工的情況,項目的整體效率更高。
回到第一部分的具體問題來說,技術側從以往活動中識別出后期需求、維護需求、效率問題后,可以借助低代碼平臺將流程功能化,從而實現更順滑的交付效果。
3.1.3 接入前后的代碼對比
舊的代碼實現
為了更好地理解效率提升的效果,讓我們先看一個典型的舊代碼實現示例。這段代碼展示了如何編寫一個直播榜單組件,一個需要定制的 pro code 榜單通常需要 100+ 行的 CSS 代碼,這也意味著前端要等設計資源完全 ready 后才能進開發(給設計也帶來了壓力),而在活動中需要定制的榜單往往不止一個,且每次活動都需要開發。在活動 DDL 明確的情況下,很容易給前端造成任務堆積,人力不足的問題。
新的代碼實現
現在,通過采用遠程加載低代碼組件的方法,我們大大簡化了這個過程。下面是一個使用遠程加載低代碼組件的示例代碼。前端只需一行代碼即可加載運營配置的 low code 榜單組件,無需編寫繁瑣的 CSS 代碼,設計同事也可以調低榜單模塊的優先級,優先完成其它高優的設計任務。
以下是直播榜單組件在后臺系統中的界面截圖,展示了運營人員如何通過界面配置而不是編寫代碼來完成同樣的任務。
3.2 接入方案
以下是貓耳前端在接入公司內部低代碼平臺過程中積累的接入方案。
3.2.1 Pro Code 頁面能力對齊 Low Code 頁面
為確保 low code 組件能夠在 pro code 頁面框架內正常運行,我們將一些頁面的基礎能力封裝到了單獨的包內,使得不同環境都能夠獲得一致的效果。
基于 RxJS 實現組件間的數據共享與事件通信
在 low code 頁面中,我們基于 RxJS 的 BehaviorSubject 實現了頁面級別的數據共享方案,各組件可通過訂閱 window 上掛載的 BehaviorSubject 數據流,及時獲取全局數據(如:活動狀態)。
而在 pro code 頁面框架內,在綜合考慮開發成本、性能、隔離性(不考慮沙盒環境)這幾點后,所以我們直接在頁面框架內復用了 low code 頁面的初始化流程,確保各組件能夠直接接入頁面。
內置 Low Code 環境組件,優化加載速度
在 low code 頁面中,我們用于初始化頁面環境的組件本身也是個 low code 組件,按流程來說,在 pro code 頁面內使用時也需要等待加載器的拉取渲染時間,而其余組件則要等初始化完成后才能正常渲染,所以存在一定的阻塞情況。
優化前的默認實現
以下是優化前的頁面初始化時序圖。
優化后的實現
優化后的時序圖已經在遠程組件的 3.1.1 原理小節貼過了,可以返回查看。
簡而言之,我們將 low code 頁面的環境組件直接集成到了 pro code 頁面框架內,跟隨頁面一起構建發布,并且屏蔽了加載器對于環境組件的拉取和渲染邏輯,使得頁面能夠更快完成初始化。
Pro Code 頁面維護所有 Low Code 組件配置數據,支持「合并同類組件的請求」
對于傳統的 pro code 頁面來說,如果出現需要請求同一接口的同類組件(如:多個音頻、多個主播的信息),我們可能只需要在請求時帶上 ID 數組即可,但對于 low code 頁面來說,為了保證最靈活的制作能力,我們不一定會封裝固定布局的組件,更傾向于封裝原子組件或者較為基礎的業務組件,所以運營拖入頁面的可能是一個個獨立的組件(同類組件,但配置了多個),每個組件都單獨配置了 ID,如果不做處理的話可能會并發出非常多的請求。
對于這種場景,我們以前采取的方案是:在該 low code 組件的 JS 資源被插入執行時(此時渲染器還沒有渲染組件),讀取頁面內配置的所有同類組件,合并配置后統一發出請求,然后借助 rxjs 緩存接口響應數據。等到組件被渲染器實際渲染出來后,再訂閱該 rxjs 數據源,實現合并批量請求的目的。
如果現在需要在 pro code 頁面框架內實現該場景,對比 low code 頁面我們缺少了直接可用的完整組件配置,需要等所有片段都加載完才能拿到完整組件數據。不過這也不難解決,基于已有的 rxjs 數據共享流程,我們只需要對這類組件稍加改造,將直接獲取所有組件的配置數據改為訂閱所有組件的配置數據。當頁面框架逐個獲取到組件信息后,逐個塞入 rxjs subject 的組件數據列表,然后組件側結合 rxjs debounce 操作符(避免頻繁請求接口),按一樣的流程請求接口獲取數據即可。
以下是簡化后的請求遠程組件并渲染的核心流程。
fetchSegment(pageId)
?.then(async (res: any) => {
? ?if (!res || !containerRef.current) {
? ? ?console.error('segment load failure')
? ? ?return
? ?}
? ?const { info, render } = res
? ?if (!loadedPageIds.includes(pageId)) {
? ? ?loadedPageIds.push(pageId)
? ? ?const newComponentsMap = makeComponentsMap(info.configure?.pageData)
? ? ?window.MissEvanEvents.next((prev: any) => {
? ? ? ?const mergedComponentsMap = Object.entries(newComponentsMap).reduce((acc, [key, value]) => {
? ? ? ? ?acc[key] = (acc[key] ?? []).concat(value)
? ? ? ? ?return acc
? ? ? ?}, prev.componentsMap ?? {})
? ? ? ?return {
? ? ? ? ?...prev,
? ? ? ? ?componentsMap: mergedComponentsMap,
? ? ? ?}
? ? ?})
? ?}
? ?render({ container: containerRef.current, needRenderEnvComponent: false })
?})
?.catch((e: any) => {
? ?console.error('segment load failure', e)
?})
3.2.2 規范代碼塊開發模式
上文在配置類頁面已經提到過代碼塊,代碼塊由 JS 和 CSS 文件構成,在特定時機插入頁面執行,主要執行一些 dom 操作或添加樣式。在 pro code 頁面框架內集成代碼塊時我們也做了一些優化工作。
由于代碼塊的效果依賴 JS 的插入執行,在按需渲染的場景會存在重復插入執行的情況,若忘記清理副作用也更容易出現問題(如:事件泄漏、定時器無法終止)。為優化該問題,我們在 JS 中臨時加入了一些代碼來檢查邏輯是否執行過,以及定時任務是否需要執行等操作,避免出現不必要的問題,并在后續基于 document.currentScript 設計了一套代碼塊的生命周期函數,用于規范常用代碼塊的參數、執行、清理。
// 代碼塊被插入時的定制邏輯
function mount(dependency) {}// 代碼塊被銷毀時的清理邏輯
function unmount(dependency) {}ContextManager.initLifecycle({ mount, unmount })
???????
我們在 24 年 3 月份將該方案提交給活動中臺并進行了比較密切的幾次討論,平臺綜合各個業務的需求在 24 年 9 月份上線了更加通用的平臺級別的代碼塊開發工具,后續也仍在持續迭代。
4. 成果與效益
4.1 釋放前端開發人力
鑒于活動玩法并非一成不變,不同開發者的能力也有區別,單純的代碼量或是工時人天數據并不能準確體現出低代碼帶來的提升,頂多用于內部預估未來活動的工作量,故以下統計的是各個具有代表性的活動低代碼程度,以及組件和代碼塊的復用情況。由于部分需求比較零散,實際成果可能會比下表更多一些:
從表中可以發現前端自主開發了非常多的代碼塊,尤其是某些乍一看隨便寫寫也挺快而且沒啥復用價值的功能模塊,我們也選擇將其低代碼化交由運營配置。這樣做的好處是:不僅大部分內容可以實現脫離設計稿提前開發,還可以擺脫許多后期需求和維護需求,對前端開發來說是一個非常有利的提效模式。
從崗位職責的角度來看,也可以認為代碼塊開發模式(高頻)是在產品正式介入組件設計前(較低頻),前端提前將功能模塊進行了一部分的抽象設計,便于后續將該功能開發成低代碼組件。在代碼塊開發模式下,運營側的體驗可能弱于組件模式(平臺層面已在優化這個問題,如:降低配置難度,提高開發效率),但是仍能夠保持一定的交付效率,以及用戶側一致的體驗。
4.2 版本管理顆粒度進一步細化
pro code 頁面接入 low code 組件后,也帶來了一個副產品——頁面被打散,版本管理的顆粒度更細了。我們也親身經歷過線上活動出現緊急問題,產運直接調整發布,快速縮小影響范圍然后開發再排查修復的情況。遠程組件讓我們在應急處理線上問題時又多了一種手段。
5. 結語
以上是貓耳前端對于活動低代碼場景的探索和經驗總結,歡迎在評論區繼續討論,一起挖掘業務提效的方法。在此感謝貓耳前端組以及 EVA 平臺組一起參與建設的同事們,特別感謝 EVA 平臺組的璇兒、琥珀草、谷風長道、小白白川、V、Fryderyk 等同事對我們提供的大力支持。對 bilibili 活動中臺系統設計感興趣的小伙伴可以繼續移步查看這篇新活動平臺建設歷程與架構演進。
? ?-End-
? ? ?作者丨Helson、Rui