基於Module Federation的模組化跨棧方案探索

陶然陶然發表於2022-11-07

   一、背景

  公司發展到一定程度,隨著業務分支不斷變多,B端C端的專案也隨之增多,由於歷史原因可能產生新老技術棧(vue/react)共存的情況,這既不利於元件物料的抽離統一(一類通用元件需適配多套技術棧),也增大了開發者跨專案開發的適應成本。因此技術棧收斂是提升前端平臺體系開發效率重要的一環。

  提到技術棧遷移,我們首先想到的是微前端方案,在隔離性上來說,微前端確實很好的方案,但是對於一些複雜核心模組,往往需要較長的週期遷移,並且伴隨著該模組的不斷迭代,使得整體專案的遷移進度逐步拉長。最終核心痛點可能還是沒有完全解決。

  基於以上的背景,我們需要解決兩個問題:

  更絲滑的技術棧遷移:不僅是新頁面,舊有頁面的需求也能用react開發,做到程式碼塊級遷移。

  跨技術棧開發:MF元件化開發,需要將react元件轉化為vue元件以實現在同一介面嵌入react元件。

   二、跨技術棧開發

  vuereact-combined [1]提供了元件轉換較為通用的解決方案,利用applyReactInVue在vue指定生命週期渲染React元件,並區分了update階段與create階段以減少不必要的dom構建,同時監聽上層的$attrs、$listenters,並透過reactComponentWrapper中間層拿到的reactInstance透傳相應狀態及方法。

  React -> Vue 轉化原始碼可參考這裡[2]  

  同時vuereact-combined [3]內部還進行了vuex、router轉化使得react元件可以獲取到全域性狀態以及router例項以滿足路由跳轉以及鑑權相關需求。

  以下是其支援的轉化特性:  

  同一專案下混合開發?

  嗯,看似一切都解決了,但其中有一些無法避免的問題:首先,React與Vue的依賴以及編譯babel生態是有區別的,如果有同一依賴,需要找到兩個技術棧同時適配的版本,並且構建成本成倍提升,在本地開發與線上構建中需要單獨拿出精力最佳化,一個資料夾下同時存在.vue與.tsx,結構雜亂,不利於維護。

   三、更絲滑的技術棧遷移

  3.1 頁面級微前端對於技術棧遷移以及提效的侷限性

  提到技術棧遷移,我們首先想到的是微前端,例如QianKun、Single SPA、Micro App,他們能做到在平臺內部根據大的業務模組做專案級的分拆,並且與技術棧無關。本質上解決了專案維護成本與構建最佳化成本隨著專案不斷提升的問題。

  注意,頁面級的微前端雖然能做跨技術棧開發,但是隻能做增量改造,新的頁面我們可以使用內部統一的技術棧,但是在業務迭代中有相當多的需求是基於舊有頁面進行改造,我們還是需要基於以往的技術棧開發,除非是全量重構。那麼按照常規方式是,單獨抽時間對核心模組做遷移,其中的阻礙可想而知,業務在不斷推進,而重構對於使用者來說無感,並且還需要測試資源迴歸,不管對於業務提升以及結果產出短期來看都是沒有明顯正向作用的。所以,結果只能是舊有模組維持原狀,技術棧統一任重道遠。  

  3.2 Web components?

  Web components 是 chrome推動的原生元件API,即不依賴技術棧開發元件,實現元件的高複用率。在作為元件級共享上是一個比較好的方案,但由於是原生API,狀態管理,元件通訊需要開發者自己實現,由於技術棧遷移這個場景不管是被遷的還是遷入的都是技術棧相關,對於元件轉化會有較高的成本。

  3.3 Module Federation的程式碼塊級引入

  MF本質上是webpack提供的一種能力,可以使得開發者在一個 JavaScript 應用中動態載入並執行另一個 JavaScript 應用的程式碼,並實現應用之間的依賴共享。具體原理可參考Module Federation原理剖析[4]。

  這樣我們就能對於舊有的vue專案在減少重構成本的前提下做漸進式的遷移。  

  基於前面微前端與模組化的對比,考慮到模組邏輯的複雜度與遷移成本,我們決定使用基於Module Federation的模組化開發,這使得複雜模組的遷移更加平滑,並且能夠平衡同模組下技術遷移與業務開發的節奏,兩者儘量松耦合,做到漸進式遷移。下面將介紹實現模組化遷移方案的關鍵點。

   四、實現方案  

  4.1 元件轉換

  首先我們需要將開發者的react元件轉換為vue元件,在每一次react micro專案變動時,我們需要遍歷該專案並找到.jsx/.tsx的檔案,並宣告其對應的.vue檔案,.vue檔案裡面做了什麼呢?它會基於vuereact引入react檔案,並透傳變數與方法,這些.vue檔案使用者是沒有感知的,因此它們會存放在臨時目錄中(.mfveat)。  

  .vue檔案的程式碼模板如下:  

  4.2 生成元件expose對映

  上節提到的.vue檔案會生成expose元件地址到檔案地址對映,以被Module Federation使用。  

  4.3 Module Federation動態注入

  大家都知道Module Federation是寫在構建配置檔案裡的,exposes決定了這個微應用暴露哪些元件,但是在本地開發時我們業務程式碼以及匯出是變動的,如果每次修改expose都得透過重啟工程的方式效率是很低的。  

  如何解決動態expose的問題呢?這裡就要講到Module Federation Plugin的組成,核心是ContainerPlugin(remote端)與ContainerReferencePlugin(host),方案是在基於ContainerPlugin上層封裝一個外掛mf-veact-plugin,支援expose傳入,在微專案構建完成後按照以上步驟生成expose.json,然後動態注入mf-veact-plugin。  

  以上3步就構成了跨技術棧開發的構建全鏈路,使用者只需關心react業務程式碼,然後透過Module Federation Host在主專案中引入即可。

   五、開發體驗

  5.1 重新整理監聽與MF引入

  由於MF與主專案是獨立的,那麼使用者在改了React程式碼後如何觸發主專案的重新整理呢?在每一個子專案編譯完成後,webpack外掛會向主專案寫入更新唯一標識,主專案迴圈監聽唯一標識,變化時觸發頁面重新整理,最近加入了熱過載保證子專案與主專案通訊順滑。

  webpack-dev-server的作者在近期版本中更新了熱過載功能,無需手動監聽過載。  

  5.2 UI庫樣式降級,避免全域性汙染

  MF做到了元件級微前端,同時又帶來了一些問題,由於各個專案可能使用的不同的UI庫,而UI庫本身會有全域性樣式的改造,不可避免的會影響其他專案UI庫的樣式,而MF的元件粒度有很難做到如頁面微前端一樣的專案樣式隔離。

  這裡拿Antd舉例,Antd中的global.less會對全域性樣式做格式化,在社群中已經有很多討論,但直到今天也沒有進展。因為 Ant-Design 是一套設計語言,所以 antd 會引入一套 fork 自normalize.css[4]的瀏覽器預設樣式重置庫global.less。

  因此這裡的方案是,「收斂 base.less,並保證外部的全域性樣式無法輕易覆蓋 antd 的樣式」,從編譯角度解決樣式汙染問題。

  1)Antd-vue 樣式汙染問題  

   六、總結

  本文簡要介紹了前端領域在邁出技術棧統一這一步後經歷的痛點以及挑戰,分別從遷移粒度以及使用場景兩個方面針對微前端以及模組化這兩類開發模式進行了對比,並且從解決使用者開發痛點出發闡述架構如此設計的原因。最後列出了在模組化遷移或開發過程中需要注意的問題並給出瞭解決方案。主旨還是希望能夠以更低的成本與更好的開發體驗推動技術棧統一與遷移。

  模組化開發是前端領域離不開的話題,解決技術棧統一問題僅是其一個分支,同時模組化程式碼隔離與非版本化改動也是我們未來要解決最佳化的方向,元件、平臺模組的自動化共享一直在被提及,希望本次方案探索能夠給大家帶來一些靈感,也歡迎大家在前端平臺體系元件通用化這一方向上一起交流討論。

  參考文章:

  [1]https://github.com/devilwjp/vuereact-combined

  [2]https://github.com/devilwjp/vuereact-combined/blob/master/src/applyReactInVue.js

  [3]https://github.com/devilwjp/vuereact-combined

  [4]

  [5]《如何優雅地徹底解決 antd 全域性樣式問題》

  

  [6]《Module Federation原理剖析》

來自 “ 得物技術 ”, 原文作者:天意;原文連結:http://server.it168.com/a2022/1107/6773/000006773067.shtml,如有侵權,請聯絡管理員刪除。

相關文章