Micro-FrontEnds Research in Jan 2022
原文寫自 Notion, 推薦直接點擊此處閱讀。
Why
系統現狀
- 巨石應用(Monolithic-Applications)
- 開發體驗(開發倉單的真實體驗) - JSX 刷新有問題?!template 未更新?!再刷新。emmm...
- 啟動慢 - 👀 首次啟動(無緩存的冷啟動)要3~6分鍾,二次啟動(有緩存的熱啟動) 1 分鍾,修改代碼后的熱更新 HMR 5~10s。
- 構建慢 - 在等待構建的時間中虛度生命?🤦♂️ 構建一次 10~20 分鍾?
- 用戶體驗 - 舉個例子,刷新一下瀏覽器查看枚舉值(頁面打開速度)要 10s。我不想再等了 🤷♂️...
- 維護體驗 - 好多代碼,好多組件,好多依賴, emmm...
- 開發體驗(開發倉單的真實體驗) - JSX 刷新有問題?!template 未更新?!再刷新。emmm...
- 技術棧 - Vue + VueRouter + Vuex + ElementUI + axios
- 依賴耦合 - 究竟是誰依賴了誰?不知道,不想查,不用管。因為項目前身已經被拆分,所以依賴關系還算好找。
- 使用
@作為項目 src/* 別名,find @\/ in ./src/modules - 使用
@name作為模塊 src/modules/name 別名
- 使用
- 跨團隊開發 - 協同/溝通/開發成本,我們團隊怎么做?
- 熟悉組件和其 API
- 編寫 README.md
- 編寫 component demo 頁面
- 開發業務組件並集成到 usage examples 到 component demo 供開發參考
How
解決方案
使用微前端技術分拆應用,以解決上述問題。理想目標是:
- 程序低入侵
- 開發體驗好
- 用戶體驗快
- 理解成本低
微前端的定義

微前端是一種應用架構,通過拆分應用、遠程加載應用(通過組件/模塊/包的運行時加載)達到團隊/工程/應用解藕的目的以實現獨立開發、獨立部署。
微前端的優點
- 專注開發體驗 - 開發爽,用戶才能爽
- 應用更輕
- 依賴解耦
- 同步更新 - 如果多個業務應用依賴同一個服務應用的功能模塊,只需要更新服務應用,其他業務應用就可以立馬更新,從而縮短了更新流程和節約了更新成本。
- 增量升級
- 公共文檔
- 技術無關
- 提升開發效率 - 開發快,下班才能早
- 啟動更快
- HMR更快
- 構建更快
- 降低協同成本 - 針對跨團隊場景
- 命名空間
- 獨立開發
- 獨立測試
- 獨立部署
- 提升用戶體驗
- 按需加載
- 增量加載
- 預加載
- 軟件工程優點
- 康威定律
軟件架構反映了組織架構。因此通過調整組織架構,反過來也能推動軟件架構的演進。
- 分而治之
- 單一職責
- 邊界清晰
- 更易維護
- 團隊管理 - 同上
- 康威定律

—— 來自 Dan 的 tweet 吐槽
微前端的缺點
- 復雜度從代碼轉向基礎設施
- 微前端架構框架 - Micro-Frontends framework
- 微前端構建工具 - Webpack/Vite/Babel plugins
- 公共文檔 - 組件/模塊/物料共享。除了看文檔和代碼,否則我不知道暴露出了哪些內容?又該怎么用? - Docs system for shared components/modules/materials etc.
- 調試工具 —— 單框架則使用框架本身,若涉及到跨框架的事件分發,數據通信,狀態隔離呢?沒有調試工具,打 log 也能將就?emmm... Devtools for micro-frontends communication.
- [監控系統] —— Sentry?略
- [部署平台] —— CI/CD Jenkins?Travis? 略
- 調試成本因為應用依賴拆分而變得更高
- 父子嵌套依賴關系
- 基座應用/主應用 依賴 微應用/子應用
- 微應用/子應用 依賴 基座應用/父應用
- 應用嵌套依賴關系
- SubApp A 依賴 SubApp B 某組件
- SubApp B 依賴 SubApp C 某模塊
- SubApp C 依賴 SubApp A 某枚舉
- 父子嵌套依賴關系
- 框架的學習和理解成本
- 可以不用理解 VS 理解更利於開發
總結
從長期收益上來說,利大於弊。
- 快,提高生產力。
- 解藕,只關注業務,不關注整體。
- 獨立,多團隊並行,你開發你的我開發我的。我不想管你寫了什么,除非你寫進了公共文檔/組件庫。如果你沒把公共模塊放進去,那你很危險啊。
微前端的分類
按項目類型:
- 主應用 —— 聚合共性,加載子應用。
- 子應用 —— 解藕特性,分治業務。可能依賴其他應用。
按技術棧:
- 支持跨技術棧 —— 微前端化。基座應用 MainApp / 微應用 MicroApp,跨技術棧框架,React/Preact/Vue/Angular/Svelte/Cycle/Ember/Backbone/jQuery etc.
- 不支持跨技術棧 —— 微應用化。主應用 App / 子應用 SubApp,來自統一技術棧,支持跨技術棧,Works for only one framework.
按構建方式:
編譯時微前端,通常將第三方庫中的組件作為包,在構建時引入依賴。這種實現引入新的微前端需要重新編譯,不夠靈活。
運行時微前端,全量加載或增量加載將微應用注入到當前應用程序中。當引入新的微前端的時候,不需要構建,可以在代碼中動態加載。
—— 摘自一文讀懂微前端架構
按開發職責:
- 客服端實現 —— 微前端,微應用,Module federation,iframe,公共包 等
- 服務端實現 —— nginx 反向代理,域名映射 等
按實現方式:
基座模式:通過搭建基座、配置中心來管理子應用。 —— 容器(包含)
約定模式: 通過約定進行互調,但會遇到處理第三方依賴等問題。—— 拼圖(拼接)
去中心模式: 脫離基座模式,每個應用之間都可以彼此分享資源。—— 膠水(粘貼)
改造方案
下面這張圖最早看到是在 18 年底還是 19 年初的樣子,出自 Umi 團隊(即乾坤 Qiankun) sorrycc 的一篇微前端的文章:

真正的微前端跨技術棧的場景,但不多。大多數公司,應該是統一技術棧的。所以,從某種程度上講,微應用比微前端更實際。
社區調研
調研了社區目前已知的微前端框架及庫的實現,分析微前端幾大核心模塊及其實現成本和優缺點。
| Framework/Features | Sandbox - 隔離沙盒 | Router - 中心化路由 | Loader - 模塊加載器 | State/Event communication - 跨應用數據通信 | Registry - 應用注冊中心 | Lifecycles - 應用生命周期 | Module - 獨立模塊 | Builder - 構建與部署 |
|---|---|---|---|---|---|---|---|---|
| single-spa - library agnostic | - | reroute.js | load.js | window.dispatchEvent, CustomEvent | apps.js | timeout.js | parcel | - |
| QianKun - library agnostic | sandbox.ts | ‣ | 基於 ‣ 和 single-spa 的 loadApp | globalState.ts, use single-spa | use single-spa | use single-spa | - | Webpack Entry - Webpack Jsonp + UMD |
| icestark - library agnostic | @ice/sandbox icestark-sandbox | checkAlive, appHistory | @ice/stark-module loader | @ice/stark-data store,event, 事件通信 | @ice/stark apps.ts | @ice/stark appLifeCycle.ts | @ice/stark-module | |
| Garfish - library agnostic | @garfish/browser-snapshot Snapshot, @garfish/browser-vm VM | router/src/context.ts | ||||||
| router/src/linkTo.ts | @garfish/loader loader Garfish.loadApp | @garfish/hooks | @garfish/core garfish.ts | @garfish/hooks | @garfish/remote-module | Webpack Entry | ||
| alibabacloud-alfa - ng/vue/react | @alicloud/console-os-browser-vm browser-vm | 子應用自身處理,通過 iframe 同步到主應用。History.js | @alicloud/console-os-loader src/requireEnsure.ts | nodejs module {eventEmitter} from events | @alicloud/console-os-kernal src/application/createApp.ts | @alicloud/console-os-kernal createAppLoader.ts | Webpack Plugin | |
| Mooa - Angular/iFrame | - | src/router.ts | ||||||
| reRouter 同 single-spa 的 reroute | helper/loader.helper.ts | src/helper/app.helper.ts customEvent, | ||||||
| src/model/constants.ts MOOA_EVENT | src/mooa.ts registerApplication, registerApplicationByLink | src/lifecycles 代碼 80% 與 single-spa 雷同 | - | - | ||||
| VueMFE v1.0 - Vue | - | core/router/index.js | helpers/loader.js | app.$emit , app.$on, Vuex Dynamic Module Registration app.$store.registerModule / app.$store.unregisterModule | src/core/app/config.js registerApp | - | core/lazy.js | vue-cli-plugin-mfe |
| EMP - 基於 module federation | - | - | Webpack5 module federation | - | - | - | - | Webpack5 module federation |
核心模塊
-
Sandbox - 隔離沙盒。JS 被放入沙盒執行,以此隔離全局副作用,實現應用獨立運行時。每個微應用之間狀態隔離,運行時狀態不共享。
- 副作用類型:
- 靜態副作用,HTML 中靜態標簽內容:Script 標簽、Style 標簽、Link 標簽。
- 動態副作用,由 JavaScript 調用 BOM/DOM 動態創建出來的:動態創建 Style、動態創建 Script、動態創建 Link、動態執行代碼、動態添加 DOM 元素、添加全局變量、添加定時器、網絡請求、localStorage 等對當前頁面產生副作用的內容。
- 實例分類:
- 單實例:同一個時刻只有一個微應用實例存在,此刻瀏覽器所有瀏覽器資源都是這個應用獨占的,方案要解決的很大程度是應用切換的時候的清理和現場恢復。比較輕量,實現起來也相對簡單。
- 多實例:資源不是應用獨占,就要解決資源共享的情況,比如路由,樣式,全局變量讀寫,DOM。可能需要考慮的情況比較多,實現較為復雜。
- 實現原理:
- snapshot:在應用運行前通過快照的模式來保存當前執行環境,在應用銷毀后恢復會應用之前的執行環境,用於實現應用間副作用的隔離和清除。同時運行多個快照沙箱實例時,在代碼執行順序非線性的場景下,並不能有效的收集和處理應用的副作用。
- vm: 核心邏輯是創建一個 fakeWindow 並使用 proxy 代理真實的 nativeWindow 的屬性和方法,並在當前 context 中標記和收集。在退出或卸載 context 時清空標記及其引用。
- Window
- 用於隔離全局環境
- document
- 收集 DOM 副作用
- 收集 Style 副作用
- 收集 Script 繼續放入沙箱執行
- 用於捕獲動態創建的 DOM 節點、Style、Script
- timeout、interval
- 處理定時器
- localStorage
- 隔離 localStorage
- listener
- 收集全局事件
- Window
- 框架對比:
-
QianKun
- ProxySandbox - 使用 proxy 攔截,並在 active/inActive 時分別從緩存的 map 中 set/delete 相關 modified/added 屬性。
- SnapshotSandbox - 基於 diff 方式實現的沙箱,用於不支持 Proxy 的低版本瀏覽器
-
Garfish - 默認情況下使用 VM 沙箱(VM 沙箱支持多實例),不使用快照沙箱。原理同上,但細節場景覆蓋得更齊全。
-
VMSandbox - 復制 window, document 等對象,使用
Object.defineProperty冰凍 native window & document,使用 proxy 攔截並收集。

import { historyModule } from './modules/history'; import { networkModule } from './modules/network'; import { documentModule } from './modules/document'; import { UiEventOverride } from './modules/uiEvent'; import { localStorageModule } from './modules/storage'; import { listenerModule } from './modules/eventListener'; import { observerModule } from './modules/mutationObserver'; import { timeoutModule, intervalModule } from './modules/timer'; import { makeElInjector } from './dynamicNode'; -
SnapshotSandbox - 快照沙箱。
snapshot.take()對之前的狀態執行快照然后通過 diff 回滾狀態。- PatchGlobalVal
- PatchStyle
- PatchEvent
- PatchHistory
- PatchInterval
- PatchWebpackJsonp
-
-
alibabacloud-alfa - 使用 iframe 的 contextWindow 作為隔離上下文,處理了一部分副作用。屬於最簡單實用的方案了,但問題是 iframe 對於 history 的 path 需要通過 postMessage 與主應用同步。如何“取巧”實現一個微前端沙箱?
import Window from './Window'; import Document from './Document'; import Location from './Location'; import History from './History';
-
- 副作用類型:
-
Router - 中心化路由,攔截符合規則的路由並加載其對應的微應用。
-
Module - 微模塊,通常是一個模塊或頁面,跟頁面路由無關,可以隨處掛載,也會出現多個微模塊同時渲染運行。比如說:Component、Service、ES Module、CSS/image/icon 等靜態資源。其實現是:
- UMD
- Webpack jsonp
- Webpack5 Module Federation
- ESM - Bundless
-
Builder - 構建與打包。
- Webpack4 - UMD/jsonp
- Webpack5 - Module Federation
- Vite?
-
Loader - 用於加載 MicroApp or MicroModule.
- 程序入口
- 進入到微應用時解析微應用入口資源,自動觸發 Loader 加載
- html-entry
- config-entry/javascript-entry
- 手動的編程式觸發 Loader.load(path) 加載遠程模塊
- 進入到微應用時解析微應用入口資源,自動觸發 Loader 加載
- 實現原理
- 通過 XHR/fetch/request or script/link/meta tag 獲取入口文件內容
html-entry當入口文件是 html 文檔時:- 解析 html 內容生成 ast
- 獲取 script/link/mata/style 等資源標簽
- 添加對應標簽並插入到主應用 html 文檔流中
config-entry當入口為 js 文件時:- [啟用沙箱]
- 加載 js 文件資源
- 執行 js 文件
- 微應用
mount()成功
- 框架對比
- import-html-entry - QianKun 解析 html 模版中
<script src="/main.js" entry />中標記了entry屬性的 script 標簽,並返回所有 JS 序列化執行后的結果 - templateParse - Garfish 通過 fetch 拿到 html 的文本內容並賦值給自定義的
html = document.createElement('html')的 innerText 后遍歷 meta, link, style, script 節點,拿到特定元素后執行loader.load加載。
- import-html-entry - QianKun 解析 html 模版中
- 程序入口
-
State/Event communication - 微前端跨應用間通信。
- CustomEvent - SingleSPA/QianKun
- Event - icestark
- EventEmitter2 - Garfish
-
Registry - 微應用/微模塊的注冊中心。
apps: App[]- 使用數組。registerApp/registerApplication- single-spa/icestark/Garfish/mooa<App /><AppRouter />- 同時支持 JSX/Template 的有 icestark
Map<string, App>- 使用 Map 儲存所有注冊的 App.createSubAppregisterApp- VueMfe
-
Lifecycles - 微應用的生命周期。
- bootstrap - single-spa 啟動
- registerAppEnter - icestark 加載前
- beforeLoad - Garfish 加載前
- afterLoad - Garfish 加載后
- beforeMount - Garfish 掛載前
- mount - single-spa 掛載
- afterMount - Garfish 掛載后
- registerAppLeave - icestark 卸載前
- beforeUnmount - Garfish 卸載前
- unmount- single-spa 卸載
- afterUnmount - Garfish 卸載后
- unload - single-spa 移除

圖片摘自字節跳動是如何落地微前端的
成本分析
| 模塊/類型\實現難度 | 跨技術棧 | 單技術棧 | 必要 |
|---|---|---|---|
| Sandbox | 高 | 高 | 否 |
| Router | 中 | 低 | 否 |
| Module | 中 | 低 | 否, Use UMD |
| Builder | 低 | 高 | 是 |
| Loader | 中 - HTML entry | 低 - JavaScript entry | 是 |
| State/Event | 低 | 無 - 框架自帶 | 否 |
| Registry | 低 | 低 | 是 |
| Lifecycles | 中 | 低 | 否 |
- Sandbox:
| 實現成本 | 優點 | 缺點 | 適用場景 | |
|---|---|---|---|---|
| VM | 高 | 支持多實例 | 實現起來比較復雜,需要考慮的細節太多 | 嵌套、多實例、跨技術棧 |
| Snapshot | 低 | 相對簡單,不需要考慮太多細節 | 不支持多實例嵌套,嵌套導致內部快照依賴關系混亂 | 無嵌套 |

圖片摘自字節跳動是如何落地微前端的
- Router
| 實現成本 | 優點 | 缺點 | 適用場景 | |
|---|---|---|---|---|
| 事件劫持 | 中 | 通用 | 攔截全局事件,派發全局事件,重寫全局事件。 | 跨技術棧 |
| 路由注入 | 低 | 簡單 | 不支持跨技術棧 | 單技術棧 |
- Module
| 實現成本 | 優點 | 缺點 | 適用場景 | |
|---|---|---|---|---|
| UMD Module | 低 | 通用 | 污染全局命名空間 | all |
| Module Federation | 高 | 支持多實例共享 | ||
| 多實例不會污染 | 編譯時配置,添加全局變量 | |||
| 需啟動多個實例 runtime | ||||
| 支持運行時,但配置復雜 | shared modules |
- Builder
| 實現成本 | 優點 | 缺點 | 適用場景 | 備注 | |
|---|---|---|---|---|---|
| UMD | 低 | 簡單,基礎設施不需要做任何修改,只需要修改 webpack 構建配置。 | |||
| 全局共享,代碼執行完成后可通過全局變量訪問模塊引用。 | 暴露全局變量 | ||||
| 污染全局命名空間 | 跨技術棧 | ||||
| 有公共依賴 | |||||
| shard modules + External | |||||
| Webpack Jsonp + UMD | 低 | 同上,額外配置 jsonp name。 | 同上 | 同上 | Why need different JSONP name? |
- Same moduleId
- Same global variable |
| Webpack5 Module Federation | 高 | 通用 | 構建系統整體遷移 Webpack 5 | 跨技術棧
有公共依賴 | TODO |
| Webpack Entry | 無 | 無成本 | 無法暴露公共模塊以共用 | 跨技術棧
無公共依賴 | |
- Loader
| 實現成本 | 優點 | 缺點 | 適用場景 | |
|---|---|---|---|---|
| HTML entry | 高 | 通用 | 額外 html parser 的解析開支 | 跨技術棧 |
| Config entry | 低 | 簡單 | Module Only | 跨技術棧/單技術棧 |
方案對比
| 技術難度 | 實施成本 | 維護成本 | 理解成本 | |
|---|---|---|---|---|
| 跨技術棧 | 高 | 中 | 高 | 高 |
| 單技術棧 | 低 | 中 | 中 | 低 |
- 微前端化,針對跨技術棧 - 路由攔截流派。
- 設計哲學:膠水,可以卸載即“撕開”。
- 框架方案:
- Single-SPA - 主應用重寫
window.addEventListener攔截監聽路由的時間,執行內部的reroute邏輯,加載子應用。 - QianKun - 基於 single-spa,增加了
html-entry,sandbox,globalSate等核心功能。 - icestark - 同上。把大部分配置通過 cache 寫進了
window['icestark']全局變量。 - Garfish - 看起來是對市場所有 MFE 框架功能和實現調研后的寫的增強版。
- feature-hub - 未做深入調查。
- Mooa - single-spa 的 angular 版本。看源碼中除了 NG,並沒有對 Vue/React 的 adapter/bridge。但不排除魔改一下也能 work,因為有做 router 的魔改與適配。
- Single-SPA - 主應用重寫
- 微應用化,針對單技術棧 - 路由注入流派。
- 設計哲學:模塊,加水進水桶,用完從桶中”倒"出來。
- 怎么把加進去的水倒出來? - 隔離標記
- 注入之后如何銷毀?- 隔離刪除
- 桶什么時候崩潰?- 裝不下了?!
- 水什么時候可能出問題。- 沙箱?!
- 技術方案:
- 路由鈎子
router.beforeHook探測 - 繼承 Vue-Router 重寫
router.push
- 路由鈎子
- 設計哲學:模塊,加水進水桶,用完從桶中”倒"出來。
理想方案
這套方案很理想。開發成本最高,做出來后值得推廣。
| Application/Mode | Dev - 開發調試 | Build - 構建部署 | Prod - 生產環境 |
|---|---|---|---|
| App | App Local + SubApp Remote | HTML entry | HTML entry |
| SubApp | App Remote + Current SubApp Local + Other SubApp Remote | JavaScript Entry + Module Federation Exposes | JavaScript Entry + Module Federation Exposes/Module Federation Remotes |
| Module | Local Module + Remotes | Module Federation Exposes | Module Federation Exposes/Module Federation Remotes |
- 開發方案最佳實踐:
- 主應用需要更新時 - 本地使用 localhost,而子應用都通過 Module Federation Remote 遠程拉取。
- 主應用線下環境 - App Local
- 所有子應用線上環境 - SubApp Remote
- 子應用需要更新時 - 本地開發公共依賴即主應用依賴用
https://mainApp/cdn/remoteEntry.js,而子應用依賴用本地 host 的靜態入口http://localhost:PORT/subAppName/remoteEntry.js。- 主應用線上環境 - App Remote
- 需要開發的子應用線下環境 - Current SubApp Local
- 不需要開發的子應用使用線上環境 - Other SubApp Remote
- 同時調試主應用和子應用
- 主應用線下環境 - App Local
- 調試的子應用也配置成線下環境 - Current SubApp Local
- 不需要開發的子應用使用線上環境 - Other SubApp Remote
- 主應用需要更新時 - 本地使用 localhost,而子應用都通過 Module Federation Remote 遠程拉取。
- 模塊共享最佳實踐:
- Webpack5 Module Federation
- 編譯時自動配置 remotes 與 exposes,生成 remoteEntry
- 啟動應用時 html 需要注入依賴的 remotes 應用與 remoteEntry 列表
- 更偏向運行時通過 getResources 接口獲取各個子應用的 entry 並在 runtime 注入
- 理想的方案是 Runtime Module Federation, 假如有的話,需要調研
- Umi 的 依賴預構建 Pre-build 與 MFSU 技術,提升開發體驗 ?!
- 主應用
- shared 公共庫,公共功能,公共組件,公共配置等。
- 分別單獨作為 exposes 被構建以供子應用啟動時設置 federate。
- 子應用
- shared 在編譯時注入主應用公共依賴
- remotes 在編譯時注入主應用與其他子應用依賴
- exposes 組件,模塊,配置等
- 多個子應用間互相依賴時自動生成運行時 自動配置 Federation
- 多個子應用間的 exposes 如何統一管理?因為開發者並不知道 moduleA 暴露出了啥啥啥?這里還是有着增量的溝通/理解成本。增量加載 VS 全量加載。
- Webpack5 Module Federation
- 設計思路:
- 基座應用/主應用
- 公共部分 - Common parts
- 公共資源 - fonts/images/styles/scripts etc.
- 公共依賴 - libraries/plugins/hooks/lifecycles
- 公共組件 - components
- 公共布局 - layouts
- 公共數據 - store/data
- 公共服務 - services/events/utils
- 公共路由 - router/routes
- 公共配置 - configurations
- App 基類 - Micro-FrontEnds
- Sandbox
- Router
- Loader
- Registry
- Lifecycles
- ModuleManager
- Builder for App prod from Html-Entry
- 公共部分 - Common parts
- 子應用/微應用
- 私有部分 - 業務代碼
- Assets
- Routes
- Views
- Servies
- Components
- Utils
- Entry - 構建入口
- SupApp 繼承自 App 基類 - Micro-FrontEnds
- SubApp 自帶了 App 的功能並支持重寫
- 開發構建 - Builder for Dev
- 注冊應用 - Registry
- 依賴加載 - Sandbox
- 路由注入 - Router
- 模塊加載 - Loader
- 模塊共享 - ModuleManager
- 應用加載 - Lifecycles:bootstrap/onLoad/afterLoad
- 應用渲染 - Lifecycles:beforeMount/afterMount
- 應用卸載 - Lifecycles:beforeUnload/afterUnload
- 生產構建 - Builder for SubApp prod from config-entry/js-entry
- SubApp 自帶了 App 的功能並支持重寫
- 私有部分 - 業務代碼
- App & SubApp 都需要整合 Webpack 的 module federation 構建功能
- 基座應用/主應用
現有方案
目前一體化平台采用的方案,Vue-MFE 1.0 + vue-cli-plugin-mfe。19年投入使用,已穩定運行 2.5年。
- 風險最低
- 遷移成本低
- 技術棧穩定
- 開發模式:

折中方案
對 Vue-MFE 的功能做增強(具體內容見 Vue-MFE 2.0 Roadmap),重寫 Loader 與 Builder 以實現 Runtime Module federation 。
社區方案
社區現有的微前端方案,建議嘗試 Garfish。
- 功能實現完善
- 代碼寫得好
- API/Document 雖簡,但擴展性強
Module Federation
Module Federation 實現了類似動態鏈接庫的能力,可以在運行時加載遠程代碼,遠程代碼本質上是一個加載在 window 上的全局變量,Module Federation 可以幫助解決依賴的問題。—— 一文讀懂微前端架構
- 首先,mf 會讓 Webpack 以
filename作為文件名生成入口文件,Just like JS entry. - 其次,文件中以 var 的形式暴露了一個名為
name的全局變量,其中包含了exposes以及shared中配置的內容 - 最后,作為
host時,先通過remote的init方法將自身shared寫入remote中,再通過get獲取remote中expose的組件,而作為remote時,判斷host中 是否有可用的共享依賴,若有,則加載host的這部分依賴,若無,則加載自身依賴。
Motivation(動機)
將多個獨立的構建可以組成一個應用程序,這些獨立的構建之間不存在依賴關系,因此可以單獨開發和部署它們。 —— 微前端,微模塊。
Low Level Concepts
- 本地模塊:即為普通模塊,是當前構建的一部分。
- 遠程模塊:不屬於當前構建,並在運行時從所謂的容器加載。
High Level Concepts
- 容器:每個構建都充當一個容器,也可將其他構建作為容器。通過這種方式,每個構建都能夠通過從對應容器中加載模塊來訪問其他容器暴露出來的模塊。
- 共享模塊:指既可重寫的又可作為向嵌套容器提供重寫的模塊。它們通常指向每個構建中的相同模塊,例如相同的庫。
Building Blocks
- ContainerPlugin - 使用指定的公開模塊來創建一個額外的容器入口。
- ContainerReferencePlugin - 將特定的引用添加到作為外部資源(externals)的容器中,並允許從這些容器中導入遠程模塊。
- ModuleFederationPlugin -
ContainerPlugin+ContainerReferencePlugin。
Concept Goals
Features
- 動態遠程容器 - 在遠程容器中用作共享作用域對象,並由 host 提供的模塊填充。 可以利用它在運行時動態地將遠程容器連接到 host 容器。
- 基於 Promise 的動態 Remote - 向 remote 傳遞一個 promise,其會在運行時被調用。你應該用任何符合上面描述的
get/init接口的模塊來調用這個 promise。當使用該 API 時,你 必須 resolve 一個包含 get/init API 的對象。{ get: () ⇒ any, init: () ⇒ any} - 動態 Public Path - 可以允許 host 在運行時通過公開遠程模塊的方法來設置遠程模塊的 publicPath。
- output.uniqueName - 在全局環境下為防止多個 webpack 運行時沖突所使用的唯一名稱。默認使用
[output.library](https://webpack.docschina.org/configuration/output/#outputlibrary)名稱或者上下文中的package.json的包名稱(package name), 如果兩者都不存在,值為''。 - output.publicPath - 在編譯時(compile time)無法知道輸出文件的
publicPath的情況下,可以留空,然后在入口文件(entry file)處使用自由變量(free variable)__webpack_public_path__,以便在運行時(runtime)進行動態設置。
Usage
對 Module Federation 的 API 不太熟悉,需要時間摸索,目前存以下疑問:
- Module Federation 在運行時如何暴露 Module ?
- 在編譯時寫 expose 聲明暴露的模塊感覺有點冗余。
- 取巧的做法是,固定暴露出的單個文件,然后在單個文件內給出所有模塊內容,同 一體化平台 中導出模塊的實現。
- SubApp 集成插件,默認暴露出
module.entry.js作為 入口。
- Module Federation 在運行時如何動態植入跨應用的 Module?
- App 則在
remotes使用 基於 Promise 的動態 Remote 通過接口接收所有 domain 的資源列表。
- App 則在
- Module Federation 如何處理嵌套的跨 domain 依賴?
- App → SubApp:接口獲取所有 SubApp > 生成基於 Promise 的 Remotes 。
- SubApp → App:接口獲取所有 SubApp > 生成基於 Promise 的 Remotes 。
- SubApp1 → SubApp2:接口獲取所有 SubApp > 生成基於 Promise 的 Remotes 。
