微前端實踐思考與總結
本文作者系 360 奇舞團前端開發工程師
業務線大多老舊項目在開發體驗以及用戶體驗上都有很多瓶頸,加之項目需要聚合後由內轉外等需求,決定使用微前端方案對各項目進行改造。
簡單介紹下微前端
微前端是一種多個團隊通過獨立發佈功能的方式來共同構建現代化 web 應用的技術手段及方法策略。“類似 Iframe,卻沒有它的各種問題。” 微前端最核心的價值或者特性,就是技術棧無關。通常我們在構建 web 應用時,會有同一框架、同一大版本的限制。微前端的這個特性就打破了傳統 web 的限制。我們利用這個特性,做到 應用拆分聚合、增量升級。再結合實際的場景,來解決問題。
比如,應用拆分可以做到模塊化開發、降低項目複雜性、提升部署效率和不同團隊之間項目耦合性的問題。
應用聚合,能夠解決用戶體驗,產品一致性等產品層面的問題,以及優化業務流程和定製化交付。
漸進式升級,相比較於全量升級的優點是,降低風險成本,適應性和靈活性也高,可以持續的靈活的把控重構進度,不影響現有業務。
你並不一定需要微前端
在決定使用微前端之前,首先要明白的是,微前端不是銀彈,微前端是和業務耦合性比較高的技術方案。微前端在解決實際場景下問題的同時,也帶來了更高的複雜性。
通常需要微前端方案解決的項目一般是 “巨石應用” 類,這類項目經過長期迭代維護,加之近些年前端技術的快速更新迭代,其的特點就是 業務邏輯複雜、龐大且技術棧老舊、兼容性差等。
在遇到問題,考慮使用微前端方案解決的同時,也要思考項目業務邏輯和技術複雜性的問題,考慮投入產出比。
爲什麼使用微前端
前端項目由各個部門前端團隊各自開發維護,沒有任何限制,主要有以下特點:
-
各項目啓動時間跨度較長,技術棧繁雜
-
各團隊獨立維護項目,在規範、流程等方面存在差異
-
多數項目爲對內,在用戶體驗、交互等有一定缺陷
-
項目多爲中後臺、雲控制檯類,前端場景具有一致性
-
項目涉及複雜表單類場景較多,業務邏輯複雜
-
……
產品體驗
-
UI 交互體驗:內部項目本身會偏功能性,加上部分項目迭代週期過長,用戶體驗存在較多問題。
-
產品體驗:業務線的功能產品融合從效率和成本方面考慮,大多基於現有項目改造,產品整體一致性不完善。
-
內外用戶:內部用戶對內部項目會有一定的容忍度,後續對外後整個用戶體驗會不太友好。
技術維度
由於歷史背景和包袱,加上中後臺系統的生命週期較長、技術棧老舊繁雜,導致很多問題,且隨着產品的持續迭代,問題會越發嚴重。其主要特性和問題包括但不限於以下點:
-
巨石應用代碼量高
-
規範流程不統一
-
工程側提效受限
-
維護代碼量和代碼重複率高
-
開發人員交接維護成本高、分配靈活度受限
-
研發效率及交付質量持續打折
-
開發心智負擔增加
-
項目本身穩定性及可維護性持續變差
-
……
從業務本身的特性、現階段各產品項目形態、未來發展方向出發考慮,結合技術層面分析。現在遇到的問題,在未來會持續演化加重,造成的不利影響是全方面的。結合幾點來說,使用微前端可以比較好的解決上述痛點。
整體流程
這裏貼一張圖,大家可以參考:
微前端方案在業界比較成熟了,不同業務線,項目,場景可以結合使用不同的微前端框架和方案。過程都相差不大,分爲幾步:
-
基座落地
-
業界類似應用雲控制檯、中後臺場景基本都有上述通用的痛點和解決方案,技術方案比較成熟,不在一一贅述,需要注意的是結合業務場景和項目靈活應用。
-
項目接入
-
包含登錄、組織架構、權限邏輯、統一 UI 等。統一的邏輯可抽取出來,兼容主子應用
-
無侵入接入,各項目可保持其獨立的邏輯
-
項目升級
-
對需要技術體驗升級的項目,根據具體的業務邏輯拆分新項目,二八原則選擇性升級,所有新建項目使用統一技術棧
-
新開發項目如需獨立開發部署,可使用統一的 sdk 簡化通用邏輯,也可由主應用控制
-
快速開發
-
同類型業務線項目、中後臺系統、線上交互功能確定、樣式邏輯較爲統一。表單頁、列表頁、操作組件、業務組件等,可進行大量的物料開發複用
-
基於模板化配置,結合 cli 和 vscode plugin 提供大量可複用物料,提升研發效率
-
其他提升研發效率的方案,如:結合 AI
通用業務邏輯集成
集成統一登錄、業務組織體系、消息工單、權限等通用邏輯。
-
組織體系、資源組、消息工單等,已經在各系統之間打通,可複用現有 sdk。
-
各系統登錄體系相同,每個項目內各自實現登錄狀態邏輯判斷。後續由基座統一實現登錄邏輯,分發給各子應用登錄狀態和信息。保留兼容子應用特殊邏輯的處理能力,確保子應用的登錄狀態完善。
-
各系統權限控制,由其對應的管理系統單獨控制,暫保留其本身控制能力,後續逐步收斂統一。主應用提供子應用級別的權限控制。
路由導航控制
基座維護一份針對各子應用的路由表,可依據頁面功能劃分,對菜單項進行控制以及實現頁面上的產品拆分聚合。具體路由劃分由原系統域名和業務模塊進行配置對應,如:
-
aaa.xxx.xx 爲主站域名
-
原 kkk 系統,由 kkk.xxx.xx => aaa.xxx.xx/kkk
-
原 hhh 系統,由 hhh.xxx.xx => aaa.xxx.xx/db
-
原 qqq 系統,qqq.xxx.xx => aaa.xxx.xx/qqq
-
……
基座除了維護子應用級的路由,還需要管理子應用下的一級路由,確保子應用內各功能的入口。
前期階段基座只維護子應用及子應用內的一級路由,後期將各子應用的路由、權限平臺化,由基座應用根據權限控制子應用及其功能。通過配置化、請求攔截,做到頁面級、按鈕級路由權限控制統一。
主題樣式控制
將適配業務主題的樣式變量整理統一,抽離出樣式配置表,通過基座下發給子應用。
-
針對現有老項目,可逐步根據下發的配置表替換原有主題變量。
-
新項目,通過 antd 等主題配置抽離,子應用無需改動,直接複用。
通過基座維護的統一配置,可以做到全系統主題快速切換。
組件類庫共享
各項目在工程體系、技術棧、版本、分包策略等方面各不相同。如果對現有項目進行整合複用資源,面臨差異過大、調試成本等困難。
基座落地階段需要考慮到後續技術棧統一,主子應用之間資源共享的問題。基於後續規劃,基座考慮實現如下幾點確保資源最大化共享:
-
主應用統一導入公共依賴、類庫、第三方包,子應用公共依賴通過 external 的方式配置。
-
組件方面,考慮到業務的特性、通用性以及後續前端技術棧的情況,將組件劃分爲業務組件、通用組件、區塊、公用類庫等。
應用集成通信
現有各應用之間,除資源組、用戶等必要信息外,各應用、功能之間沒有過強的狀態依賴。得益於業務的這些特性,以及現有的項目實現流程,使得應用之間的通信需求比較簡單。另外,從應用設計層面來說,各應用之前不應過多耦合其它應用的業務邏輯,部分狀態的共享也可從後端獲取。各框架都提供了較爲完善的通信方案。
應用隔離
確保 js 和 css 的隔離方案,能夠兼容主子應用以及新老應用。
主子應用優化
除通過預加載的形式,可結合緩存、按需加載、依賴提取等方式提升子應用加載速度。對於一些高頻子應用,還可通過手動掛載應用,樣式隱藏的方式提升用戶體驗。
無侵入集成開發
子應用依賴基座的開發,通常需要啓動基座和子應用兩個服務,大多數情況下只需要開發對應子應用的功能。
開發方式有如下幾種:
-
主應用在線上,線上主應用通過配置加載運行本地子應用,需要測試機器運行主應用,且維護較爲麻煩
-
主子應用均在本地開發,需要拉取啓動兩個項目
-
將基座進行包裝集成,兼容子應用的開發,提供基座版本更新的能力
建議第三種方案,提供一個腳手架工具,簡化工程師開發流程。同時集成項目所需模板、技術、規範、mock 等能力。
穩定性保障
-
開發規範:項目的穩定性取決於長期開發過程中對規範的實施程度,除文檔等方式外,提供結合 lint 編碼規範,項目規範,公共組件、函數工具等規範實踐。以腳手架,插件等工具方式集成,在本地開發及 CI 階段進行強校驗,保證項目的可持續維護性。
-
監控方案:完成對子應用靜態資源、配置等維度的監控。實時獲取子應用的加載成功率,白屏時間等問題。
-
部署方案:在確保子應用能獨立部署運行的基礎上,增加快速回滾的能力
技術和其他細節思考
微前端在解決問題的同時也會帶來一些不可知的問題和更高的複雜性。選擇合適的微前端方案,能減少踩坑的次數。
現在業界內微前端方案還是比較多的,結合實際情況選擇合適的微前端方案,可以幫助我們在實踐落地的過程中事半功倍。
原生技術
如果原生技術能滿足,越簡單越好
原生技術方案實現如:iframe、nginx 代理等,成本較低、接入簡單,但都具有片面性,比如:
-
URL 不同步
-
UI 不同步
-
通信麻煩
-
進入子應用需要重新加載資源,構建上下文
框架
微前端框架基本上要滿足,技術棧無關、應用加載、路由同步、通信、隔離、預加載等能力。
下面基於三個業界比較成熟完善、具有代表性的框架 qiankun,wujie、micro-app 簡單分析一下,在選擇微前端方案時需要關注的技術點。
css 隔離 - 單實例
原理:每次子應用加載時,刪除上一個子應的 link、style 樣式,只保留當前子應用的樣式,能有效區分子應用和子應用之前的樣式衝突。
-
不支持主子應用的樣式隔離
-
不支持多實例模式
css 隔離 - Scoped CSS
原理:改寫子應用所添加的樣式爲所有樣式規則增加一個特殊的選擇器規則來限定其影響範圍,達到樣式隔離的目的,類似 vue 的 scope-css。
.app-main {
font-size: 14px;
}
div[data-qiankun-react16] .app-main {
font-size: 14px;
}
由於需要在運行時替換子應用中所有的樣式規則,性能會受一定影響
css 隔離 - Shadow DOM
原理:爲每個微應用的容器包裹上一個 shadow dom 節點,從而確保微應用的樣式不會對全局造成影響。
-
影響 React 17 之前的版本,導致在 react 中事件無響應
-
解決:在 React 17 版本之前,所有用戶事件都需要冒泡到 document 上,由 React 做統一分發與處理,如果冒泡的過程中碰到 shadowRoot 節點,就會將事件攔截在 shadowRoot 範圍內,此時 event.target 強制指向 shadowRoot,導致在 react 中事件無響應。React 17 之後事件監聽位置由 document 改爲了掛載 App 組件的 root 節點,就不存在此問題了。
-
部分 UI 框架,如 antd 彈窗樣式丟失
-
解決:主流 UI 框架比如 antd 爲了避免上層元素的樣式影響,通常會把彈框相關的 DOM 通過 document.body.appendChild 插入到頂層 body 的下邊。此時子應用中 antd 的樣式規則,由於開啓了 shadowDom ,只對其下層的元素產生影響,自然就對全局 body 下的彈框不起作用了,造成了樣式丟失的問題。可以改寫 modal 掛載節點解決。
-
基於 web Components 實現,存在兼容性問題
css 隔離 - 規範性限制
-
前綴命名空間
-
BEM 規範
-
CSS Modules
-
css-in-js
依賴於規範,隔離效果不好。可以和微前端框架隔離方案,結合使用。比如:
單實例模式下,給主應用增加前綴命名空間,用來隔離主子應用的樣式。
js 隔離 - iframe
iframe 完美支持 js 隔離。比如,wujie 利用 iframe 的特性隔離 js,且解決了 iframe 的其他問題。
js 隔離 - 快照沙箱
在子應用加載和卸載時,對全局對象(如 window 對象)進行快照保存和恢復,從而確保各子應用之間的全局狀態互不干擾
保存全局狀態:在子應用加載之前,快照沙箱會對當前全局對象的狀態進行保存。遍歷全局對象(如 window)上的所有屬性,並將它們的值存儲在一個快照對象中。對每個屬性的值進行深拷貝,確保保存的是屬性的當前值,而不是引用。
恢復全局狀態:在子應用卸載之後,快照沙箱會恢復之前保存的全局狀態。
清空當前全局對象上的所有屬性,避免舊的子應用狀態影響新的子應用。將快照對象中的屬性值恢復到全局對象上,確保全局狀態回到子應用加載前的狀態。
-
兼容性較好
-
快照沙箱的實現相對簡單,適用於不頻繁切換的子應用
-
對於包含大量全局變量的應用,快照和恢復操作的性能開銷較大
-
直接操作的是全局唯一的 window,不支持多實例
js 隔離 - 代理沙箱
把當前 window 的一些原生屬性(如 document, location 等)拷貝出來,單獨放在一個對象上,這個對象也稱爲 fakeWindow,對每個微應用分配一個 fakeWindow,實現隔離。
當微應用修改全局變量時:如果是原生屬性,則修改全局的 window。如果不是原生屬性,則修改 fakeWindow 裏的內容。
微應用獲取全局變量時:如果是原生屬性,則從 window 裏獲取。如果不是原生屬性,則優先從 fakeWindow 裏獲取。
-
使用 ES6 的 proxy,存在兼容問題。
-
涉及到大量 dom 操作時,存在一些問題。
應用通信
如果是簡單傳值,使用 props 方式就行。如果主子應用之間需要頻繁交互,各微前端框架都有比較完善的通信機制。基本上都能滿足,結合實際項目選擇合適的即可。
-
props
-
發佈訂閱模式
-
EventBus
-
……
多實例
支持多個子應用同時加載。
在實際的選擇過程中,需要考慮是否支持多實例模式。
預加載 & 應用保活
預加載和應用保活可以有效提升用戶體驗和頁面性能,如果對這方面有要求,在選擇的時候可以着重考慮。
預加載:空閒時加載資源,可以極大的提升子應用打開的首屏時間
應用保活:子應用只會進行一次渲染,數據和路由的狀態不會隨着頁面切換而丟失
子應用接入成本
根據接入子應用的數量、技術棧等方面考慮子應用接入成本。如果子應用的數量很多,需要根據每個子應用在技術側和業務側需要做的改動量進行權衡。
技術:
-
本地環境 & 線上環境 資源跨域代理配置
-
框架生命週期導出、構建配置調整
-
兼容獨立運行環境
-
其它……
業務:
-
登錄、權限等統一業務邏輯
-
Layout 佈局兼容改造
-
…………
框架穩定性 & 社區活躍度
雖然現階段微前端方案較爲成熟,但是子應用在框架、UI 庫、編譯、版本存在的差異更大,無法 100% 確定在接入之後會遇到什麼問題。所以在技術選型的時候,要重視框架的穩定性和社區活躍度。
兼容性 & 性能
微前端框架的底層實現技術差異。
兼容性:
-
瀏覽器兼容性
-
微前端框架與子應用技術棧的兼容性
-
各技術棧、框架版本之間的兼容性
性能:
- 各個框架性能差異不大,基本能滿足需求
兼容性:web componts 和 proxy 實現的隔離方案 瀏覽器支持不太好。技術棧之間的兼容性,qiankun 對 vite 的支持不太好,因沙箱是 eval 執行 性能有點問題。還有一些版本上的差異 也會存在一定的兼容性問題 (angular antd 和 zone.js)
性能方面:各個框架性能差異不大,基本能滿足需求。(需要關注的是內存泄漏問題,在實際開發中,要確保子應用切換後被卸載)。
部署
考慮已有項目的部署方式結合前端路由,靜態資源、api 等方面來決定是否對子應用的部署方式進行改造。
如果是應用聚合子應用較多,各子應用都有自己的部署方案,建議使用主應用,對子應用的路由、資源和 api 進行代理,儘量減少減少子應用改動,同時也要考慮子應用是否需要獨立運行。
如果是項目重構或者拆分,子應用不多的情況下,可以在原有的部署方式進行調整,放在同一 ip 不同目錄下部署。
主子應用職責劃分思考
簡單的說,就是什麼功能放在主應用,什麼放在子應用。
主應用:微前端肯定是需要一個主應用的,那麼是新起一個項目作爲主應用,還是在原有項目上改造主應用?
技術側:如果本地環境下子應用資源加載出現了問題,是在主應用增加 devserver 的代理,還是更改子應用的 publicpath?
業務側:建議所有的統一邏輯,登錄、權限,layout 佈局等集成在主應用內。子應用只關注業務邏輯。但在實際的業務場景下,可能因爲業務邏輯的複雜性,跨部門溝通、子應用數量問題,後端實現邏輯複雜等原因造成業務改造的成本比較大。這時候在不影響體驗的情況下,部分邏輯也是可以放在子應用內獨自實現。
快速試錯
在選擇技術方案的時候,肯定是沒有完美的方案。遇到問題的時候,首先是要把問題進行分類,首先是框架和接入項目之間的問題,比如 wujie 影響富文本編輯器,single-spa 與 angular 的衝突等,如果遇到類似的問題,可以進行快速的試錯,實踐,嘗試其他的方案。
另外就是實現層面的問題,這種問題是可以確定能解決的,優先級可以放低。比如,基於 shodw dom 的隔離方案會影響 modal 的掛載樣式,這時候只需要換一種隔離方案或者統一調整 modal 的掛載節點。
應用顆粒度
合理的子應用顆粒度劃分,可以更好的管理項目。
應用聚合:其顆粒度本身就已經確定了就是每個接入的項目。具體每個項目還用不用拆分,可以具體分析。
應用拆分:顆粒度通常建議是按業務功能、團隊維護範圍進行拆分。
項目重構:最低只需要兩個應用就行了,一個是新的技術棧,一個是老的技術棧。將功能從老的技術棧逐步遷移到新的技術棧即可。當然也可以劃分更細的顆粒度。
其他
-
通常微前端方案的推進,不會單獨進行,儘量實時同步主分支功能,不影響當前業務功能。
-
儘量保證子應用的獨立運行,對業務進行兜底。
-
代碼管理,需要根據具體情況分析使用的策略,比如單倉庫,多倉庫,權限分配等。
總結
上述便是在結合業務場景使用微前端方案的一些思考。希望能對大家有所幫助。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/vOHazTv6EMt9tgTb8P5-yg