前端 Module Federation 架構介紹

前端 Module Federation 架構介紹

2024-01-19

前言

隨著前端應用越來越龐大,單一 repo 難以維護、多個團隊同時開發同一個專案也容易互相干擾。微前端(Micro Frontend)的概念因此逐漸受到關注,而 Module Federation 是其中一種實作方式,最早由 Webpack 5 在 2020 年正式引入。

簡單來說,Module Federation 讓不同的應用程式可以在執行期間(runtime)動態地載入彼此的模組,不需要事先把所有程式碼打包在一起。


什麼是 Module Federation

傳統的前端打包方式,是把所有的程式碼在建置時(build time)合併成一個或幾個 bundle 檔案,部署後整包上線。這在小型專案沒什麼問題,但當專案規模變大、團隊人數增加,問題就開始出現:每次有任何一個小改動,整個應用都要重新建置和部署。

Module Federation 的做法不同。它讓每個獨立應用(稱為 remote)可以對外暴露(expose)自己的模組,而另一個應用(稱為 host)則可以在執行時動態去抓這些模組來用,就像引入自己本地的程式碼一樣。

這種方式讓多個團隊可以各自維護、各自部署,彼此之間只透過定義好的介面來溝通。


基本運作方式

以 Webpack 5 為例,設定上大概長這樣:

Remote(提供模組的應用)

// webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'remoteApp',
      filename: 'remoteEntry.js',
      exposes: {
        './Button': './src/components/Button',
      },
      shared: ['react', 'react-dom'],
    }),
  ],
}

Host(使用模組的應用)

// webpack.config.js
const { ModuleFederationPlugin } = require('webpack').container

module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name: 'hostApp',
      remotes: {
        remoteApp: 'remoteApp@http://localhost:3001/remoteEntry.js',
      },
      shared: ['react', 'react-dom'],
    }),
  ],
}

Host 在程式碼裡就可以直接這樣用:

import Button from 'remoteApp/Button'

Webpack 會在執行時去 http://localhost:3001/remoteEntry.js 抓取 Button 元件,不是在建置時打包進去的。


優點

獨立部署

每個子應用可以各自建置、各自部署,改 A 不影響 B。這對多團隊協作的大型專案來說很重要,可以縮短部署週期,減少相互等待的成本。

共用依賴,避免重複載入

透過 shared 設定,多個應用可以共用同一份 React 或其他套件,不會每個應用各載一份,減少使用者瀏覽器的載入量。

彈性的架構組合

子應用可以是用不同技術棧(例如一個用 React、一個用 Vue)實作的,只要 host 正確加載即可。這讓舊系統的漸進式遷移成為可能,不需要一次全部重寫。

模組層級的共用

Module Federation 不只能共用整個頁面,也可以共用單一元件、工具函數、甚至設計系統,比起傳統的 npm 套件方式,更新更即時,不需要發版。


缺點

版本管理複雜

各個子應用各自維護,依賴版本很容易出現不一致的問題。例如 host 用 React 18,remote 用 React 17,若 shared 設定沒做好,可能會導致問題難以排查。

執行時錯誤難以預測

相較於傳統打包在建置時就能發現問題,Module Federation 的模組是在執行時動態載入的,若 remote 掛掉或模組介面改了,host 不會在建置時得到任何警告,只會在使用者瀏覽時才出錯。

本地開發體驗較差

要在本地測試整個系統,需要同時把多個應用跑起來,並確保 URL 和設定都正確。這比單一 monorepo 的開發體驗要麻煩許多,特別是在有很多子應用的情況下。

效能難以精準掌控

動態載入模組帶來彈性的同時,也增加了網路請求的不確定性。如果 remote 的 remoteEntry.js 回應慢,整個載入流程就會卡住,需要額外處理 loading 狀態和錯誤邊界(Error Boundary)。

測試難度提升

由於子應用之間的整合是在執行時才發生,整合測試的覆蓋難度比傳統架構高。通常需要另外搭建一套 E2E 測試環境來驗證各個應用組合後的行為。


小結

Module Federation 解決了大型前端專案在多團隊協作、獨立部署上的痛點,讓各個子應用可以真正做到邏輯分離、獨立維護。

不過它不是萬靈丹。引入它之後,版本管理、錯誤處理、本地開發體驗都需要額外的投入。如果團隊規模不大,或專案複雜度還不到需要拆分的程度,直接用 monorepo 加上清楚的模組邊界可能是更務實的選擇。

適不適合用 Module Federation,取決於你的團隊規模和專案需求,而不是技術本身是否夠新或夠潮。