:共享代碼卻不共享狀態(tài)(isolate-shared-dependencies 深入解析))
示例工程【免費下載鏈接】module-federation-examplesImplementation examples of module federation , by the creators of module federation項目地址https://gitcode.com/gh_mirrors/mo/module-federation-examples點擊查看免費下載導讀本指南圍繞 module-federation-examples 倉庫中的 isolate-shared-dependencies 示例 展開講解如何在 Module Federation 場景下做到共享依賴的代碼、但隔離依賴的狀態(tài)——通過一個可組合的 runtime plugin在多個容器按父子層級加載且?guī)缀醪换ハ嘟换r既節(jié)省網(wǎng)絡(luò)流量與加載耗時又規(guī)避 Vue 2.x 這類單例模式庫因全局狀態(tài)導致的沖突。讀完本文你將掌握該 runtime plugin 的配置寫法、隔離原理resolveShare 鉤子 webpack require 緩存技巧以及三個應用演示的完整運行方式并了解如何用 Playwright 端到端驗證隔離行為是否符合預期。問題背景共享依賴意味著共享狀態(tài)Module Federation 的shared機制在多個容器間復用依賴實例時默認共享的是代碼 實例狀態(tài)。絕大多數(shù)場景下這是理想行為——各應用共用同一個 React 或 Vue 運行時避免重復下載。但共享狀態(tài)在某些庫上會帶來麻煩依賴單例模式的庫例如 Vue 2.x 會暴露一個全局實例當多個容器都在該實例上注冊全局組件時會產(chǎn)生命名沖突或狀態(tài)互相污染父子層級加載場景多個容器按父 → 子關(guān)系依次加載彼此之間幾乎沒有交互此時共享狀態(tài)不僅無用反而有害而共享代碼卻非常有價值——可以顯著減少網(wǎng)絡(luò)占用、降低加載耗時。isolate-shared-dependencies 示例正是對這一矛盾的 PoC概念驗證實現(xiàn)通過一個 runtime plugin讓每個容器可以精確指定哪些依賴需要隔離被隔離的依賴仍然共享同一份代碼產(chǎn)物chunk但每個容器拿到的是各自獨立的實例。核心特性該示例在 README.md 中明確了三點特性共享代碼但不共享狀態(tài)減少網(wǎng)絡(luò)用量與加載耗時可精確指定要隔離的依賴通過插件參數(shù)聲明隔離名單所有容器都必須啟用該插件即使某個容器不隔離任何依賴保持默認共享行為也必須注冊插件否則會拋出 warning且依賴無法被正確隔離。第三條是易被忽略的硬性約束插件的隔離機制依賴每個容器的__webpack_require__緩存快照缺了任何一個容器的配合隔離都會靜默失效。示例架構(gòu)與角色分工示例由 5 個部分組成通過 pnpm workspace 組織在 runtime-plugins/isolate-shared-dependencies 目錄下組件角色行為app1父應用第一個加載共享 vue shared-lib shared-lib-2不隔離任何依賴使用共享實例加載 app2app2子應用第二個加載隔離 shared-lib不使用共享實例加載 app3app3子應用最后加載同 app1不隔離依賴應與 app1 共享實例shared-lib共享庫實例化時生成隨機 ID 并保留用于直觀判斷每個應用跑的是哪個實例shared-lib-2共享庫被 shared-lib 傳遞使用、也被所有應用直接使用同樣生成隨機 ID 標識實例應用之間的加載鏈路是 app1 → app2 → app3形成一個三級父子嵌套。三個 Vue 應用分別在 3001、3002、3003 端口提供服務(wù)頁面上一秒刷新一次實例 ID方便肉眼觀察實例歸屬。shared-lib 與 shared-lib-2如何暴露實例兩個共享庫的實現(xiàn)非常精簡核心是模塊加載時生成一次隨機 ID之后一直復用shared-lib/index.js模塊頂層const instanceId getRandomInt(100000)生成 ID導出getLib1InstanceId()同時內(nèi)部 import 了 shared-lib-2 并透傳getLib2InstanceIdThroughLib1()用于演示傳遞依賴場景shared-lib-2/index.js同樣在模塊頂層生成 ID導出getLib2InstanceId()。關(guān)鍵點在于ID 是在模塊工廠執(zhí)行實例化時生成的。如果多個應用共享同一個模塊實例它們拿到的 ID 必然相同如果某個應用被隔離它的模塊工廠會獨立再執(zhí)行一次于是拿到一個不同的 ID。這正是演示與測試判斷是否共享實例的依據(jù)。插件核心plugin/isolatePluginFactory.ts 逐行拆解插件的完整實現(xiàn)在 plugin/isolatePluginFactory.ts依賴module-federation/runtime本倉庫鎖定 2.5.1見 plugin/package.json。它返回一個標準FederationRuntimePlugin對象只實現(xiàn)了兩個鉤子beforeInit與resolveShare與 runtime-plugins/README.md 中 Hook Coverage 表格的描述一致。工廠函數(shù)與參數(shù)解析export default function IsolateSharedDependenciesPluginFactory( options: string[] | IsolateSharedDependenciesPluginOptions {}, ): FederationRuntimePlugin { const dependencies Array.isArray(options) ? options : options.dependencies ?? []; if (!Array.isArray(dependencies)) { throw new Error( IsolateSharedDependenciesPlugin needs to receive a list of dependencies to isolate, ); } ... }工廠函數(shù)支持兩種傳參形式直接傳字符串數(shù)組或傳{ dependencies: [...] }對象若傳入的不是數(shù)組會立即拋錯防止配置失誤被靜默吞掉內(nèi)部用dependenciesToIsolateSet new Set(dependencies)保存隔離名單并維護patchedInstances緩存已隔離依賴的實例。beforeInit保存容器標識與 webpack requirebeforeInit: args { originId args.origin.name; (args.origin as ExtendedFederationHost).__webpack_require__ __webpack_require__; return args; },每個容器初始化時插件把當前容器的名稱記錄為originId并把該容器編譯期注入的__webpack_require__掛到 FederationHost 實例上。這一步是后續(xù)清空模塊緩存、重新實例化的前提——它拿到了該容器 webpack 運行時模塊緩存的訪問句柄。resolveShare隔離邏輯的核心resolveShare: args { if (!dependenciesToIsolateSet.has(args.pkgName)) { return args; } const resolvedDependency args.resolver(); if (!resolvedDependency) { return args; } args.resolver () ({ ...resolvedDependency, scope: [originId], lib: undefined, loaded: false, loading: new Promise(async resolve { const originalFactory await resolvedDependency.get(); resolvedDependency.lib originalFactory; resolvedDependency.loaded true; resolve(() { if (!patchedInstances[args.pkgName]) { const originInstance args.GlobalFederation.__INSTANCES__.find( instance instance.name resolvedDependency.from, ) as ExtendedFederationHost; ... } return patchedInstances[args.pkgName]; }); }), }); return args; },resolveShare是 Module Federation runtime 的 share 策略鉤子負責為每個依賴產(chǎn)出resolver解析器。插件的處理邏輯可分四步理解命中隔離名單才處理args.pkgName不在dependenciesToIsolateSet中時原樣返回依賴走默認共享路徑改造 resolver命中時調(diào)用原始args.resolver()拿到共享描述對象然后返回一個副本scope限定為[originId]防止與其他容器合并共享作用域、lib置空、loaded置 false并重寫loadingPromise等待原始模塊工廠await resolvedDependency.get()先取到其他容器已經(jīng)執(zhí)行過的模塊工廠保證共享的代碼 chunk 只下載一次包裝工廠執(zhí)行真正require時若該依賴尚未隔離過則執(zhí)行一段緩存隔離技巧見下節(jié)并把產(chǎn)物存入patchedInstances復用若其他容器沒有注冊本插件缺少__webpack_require__則打印 warning 并回退為返回原始工廠——這正是 README 第三條特性的代碼落點。隔離的底層技巧清空 webpack require 緩存再實例化const originCache originInstance.__webpack_require__.c; const savedOriginCache { ...originCache }; Object.keys(originCache).forEach(key { delete originCache[key]; }); patchedInstances[args.pkgName] originalFactory(); Object.keys(originCache).forEach(key { delete originCache[key]; }); Object.keys(savedOriginCache).forEach(key { originCache[key] savedOriginCache[key]; });這段代碼是插件的魔術(shù)所在思路非常直接找到第一個容器即 resolvedDependency 的提供方from的__webpack_require__.c模塊緩存表把緩存快照保存下來然后臨時清空全部緩存調(diào)用originalFactory()重新執(zhí)行模塊工廠——由于緩存已被清空模塊會重新初始化生成全新的實例 ID也就是隔離出的獨立實例緊接著再次清空緩存防止重執(zhí)行產(chǎn)生的中間產(chǎn)物殘留最后把快照完整還原確保其他模塊的緩存不受影響。整個操作發(fā)生在同一 tick 內(nèi)同步完成因此對運行時其他模塊是透明的代碼 chunk 只加載一次但被隔離依賴的實例被復制出了一份獨立的。三份 webpack 配置如何聲明隔離名單三個應用都基于module-federation/enhanced的 ModuleFederationPlugin 構(gòu)建差異僅在name、端口與runtimePlugins參數(shù)上。app1/webpack.config.js父應用不隔離new ModuleFederationPlugin({ experiments: { asyncStartup: true }, name: app1, shareStrategy: loaded-first, filename: remoteEntry.js, manifest: false, shared: [vue, shared-lib, shared-lib-2], exposes: { .: ./src/index.ts }, runtimePlugins: [[require.resolve(../plugin/isolatePluginFactory.ts), { dependencies: [] }]], })app2/webpack.config.js子應用隔離 shared-libruntimePlugins: [[require.resolve(../plugin/isolatePluginFactory.ts), { dependencies: [shared-lib] }]],app3/webpack.config.js子應用不隔離runtimePlugins: [[require.resolve(../plugin/isolatePluginFactory.ts), { dependencies: [] }]],需要留意的配置要點runtime plugin 元組形式runtimePlugins數(shù)組的每一項是[插件模塊路徑, 插件選項]元組插件選項會作為工廠函數(shù)參數(shù)傳入。這是 runtime plugin 的標準傳參方式也是 runtime-plugins/README.md 中所說的 tuple runtime plugin parameters三個應用必須都注冊插件app1、app3 雖傳{ dependencies: [] }但依然注冊了插件否則 warning 出現(xiàn)且隔離失效shareStrategy: loaded-first保證共享解析時優(yōu)先復用先加載容器的實例是隔離邏輯得以成立的基礎(chǔ)策略exposes: { .: ./src/index.ts }每個應用對外暴露自己的掛載函數(shù)由父應用通過loadRemote動態(tài)加載后調(diào)用見 app1/src/index.ts 導出的mount({ parentContainer })。頁面如何呈現(xiàn)實例 IDApp.vue 的觀察窗口三個應用的 App.vue 結(jié)構(gòu)一致模板渲染三段信息spanLib 1 instance ID: {{ sharedLib1Id }} | /span spanLib 2 instance ID through lib 1: {{ sharedLib2IdThroughLib1 }} |/span spanLib 2 instance ID: {{ sharedLib2Id }}/spandata()初始化時分別調(diào)用getLib1InstanceId()、getLib2InstanceIdThroughLib1()、getLib2InstanceId()取值并設(shè)置setInterval每 1000ms 刷新一次見 app1/src/App.vue這樣運行時實例歸屬一目了然app1 與 app3 不隔離二者顯示相同的 Lib1 / Lib2 ID共享實例app2 隔離了 shared-lib其 Lib1 ID 與 app1/app3 不同但 Lib2shared-lib-2 未被隔離仍與 app1/app3 一致每個應用內(nèi) Lib 2 instance ID through lib 1 與 Lib 2 instance ID 始終相等說明傳遞依賴與直接依賴解析到同一個 shared-lib-2 實例。父子加載通過 runtime 的initloadRemote實現(xiàn)app1 在 mounted 中init注冊 remote app2 并loadRemote(app2)后調(diào)用其mountapp1/src/App.vueapp2 同樣加載 app3app2/src/App.vueapp3 則不再加載子應用。運行演示與驗證啟動三個應用在 runtime-plugins/isolate-shared-dependencies 目錄下執(zhí)行pnpm run start該命令見 package.json 的pnpm --filter isolate-shared-dependencies_app* --parallel start會并行構(gòu)建并啟動 app1、app2、app3分別監(jiān)聽http://localhost:3001/ app1父應用http://localhost:3002/ app2子應用http://localhost:3003/ app3子應用打開 3001 端口頁面即可看到三個應用嵌套渲染并實時顯示各自的實例 ID。端到端驗證Playwright 測試倉庫自帶完整的 Playwright 測試 e2e/checkIsolatedDependencies.spec.ts測試配置見 playwright.config.ts基于 runtime-plugins/playwright.base.ts 的createRuntimePluginPlaywrightConfig生成默認 baseURL 指向 3001。運行方式pnpm run test:e2e測試對隔離是否真的生效做了精確斷言三個應用都成功加載App 1/2/3 loaded可見app2 的 Lib1 ID 與 app1 不同證明 shared-lib 被隔離出獨立實例app3 的 Lib1/Lib2 ID 與 app1 相同證明不隔離的應用仍共享實例每個應用內(nèi)部 Lib2 直接與透傳 ID 一致證明 shared-lib-2 的傳遞依賴解析正確。其中extractInstanceId通過正則/\d/從頁面文本中抽取數(shù)值并比較是驗證共享/隔離行為的核心斷言邏輯見 checkIsolatedDependencies.spec.ts。實現(xiàn)要點總結(jié)與適用邊界適用場景多個容器以父子關(guān)系加載、彼此幾乎不交互但希望復用依賴的代碼產(chǎn)物以降低網(wǎng)絡(luò)與加載成本或使用了 Vue 2.x 這類依賴全局單例的庫需要規(guī)避全局狀態(tài)沖突實現(xiàn)路徑beforeInit保存容器標識與__webpack_require__resolveShare命中隔離名單后改寫 resolver通過臨時清空并還原首個容器的模塊緩存強制模塊工廠重新執(zhí)行以得到獨立實例硬性前提所有參與容器都必須注冊本插件且依賴的提供方必須先行加載loaded-first策略否則插件會打印[IsolateSharedDependenciesPlugin] ... cant isolate ...形式的 warning 并回退為共享實例限制說明該實現(xiàn)是官方倉庫中的 PoC概念驗證示例倉庫 README 也標注了部分功能 Not implemented yet生產(chǎn)環(huán)境接入前應結(jié)合自身依賴圖評估其適用性。若想進一步探索 runtime plugin 體系可繼續(xù)閱讀 runtime-plugins 目錄其中展示了beforeRequest、errorLoadRemote、createScript、beforePreloadRemote等更多鉤子的用法以及 control-sharing、remote-router 等其他策略型插件示例。贊分享示例工程【免費下載鏈接】module-federation-examplesImplementation examples of module federation , by the creators of module federation項目地址https://gitcode.com/gh_mirrors/mo/module-federation-examples點擊查看免費下載相關(guān)推薦深入解析 WAMR 共享堆Shared Heap多 WASM 實例零拷貝數(shù)據(jù)共享實戰(zhàn)深入解析 WAMR 共享堆Shared Heap多 WASM 實例零拷貝數(shù)據(jù)共享實戰(zhàn) 導讀 共享堆Shared Heap是 WebAssembly M可觀測性日志分析云原生流處理終極指南Webpack Module Federation 跨應用代碼共享實踐終極指南Webpack Module Federation 跨應用代碼共享實踐 Webpack Module Federation 是現(xiàn)代前端工程化中實現(xiàn)跨應前端示例工程前端構(gòu)建如何3步掌握LangChain新手也能快速上手的AI代理開發(fā)完整教程如何3步掌握LangChain新手也能快速上手的AI代理開發(fā)完整教程 LangChain是當前最流行的AI代理工程平臺它讓開發(fā)者能夠輕松構(gòu)建基于大語言模型的人工智能大模型AI AgentAgent 框架RAG上一篇uni-app x 鴻蒙HarmonyOS斷點調(diào)試完全指南uts / uvue / ets 調(diào)試與聯(lián)編調(diào)試實戰(zhàn)下一篇基于 QMK 固件打造 Cluecard RGB 燈光效果測試鍵位原理、配置與 HID 調(diào)試實戰(zhàn)創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考