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