精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Module Federation你的浪漫我來懂

開發 前端
我們在實際開發中,經歷過許多次的模塊共享的場景。最常見的場景例如我們將代碼封裝后根據版本和環境的不同發布到公共平臺或者私有平臺,供不同項目進行使用,npm 工程化就是其中最日常的實踐。

[[411379]]

本文轉載自微信公眾號「微醫大前端技術」,作者孫舒怡。轉載本文請聯系微醫大前端技術公眾號。

前言

我們在實際開發中,經歷過許多次的模塊共享的場景。最常見的場景例如我們將代碼封裝后根據版本和環境的不同發布到公共平臺或者私有平臺,供不同項目進行使用,npm 工程化就是其中最日常的實踐。

【通關目標: 在頁面中插入輪播圖模塊】

NPM 方式-share lib

將輪播圖代碼打包發布到 NPM 包。主項目中通過 package.json 依賴加載到本地進行編譯打包。 【biu~通關成功,當前一星】

當投入生產時,多個項目對于被引入的輪播圖代碼都沒有進行共享,他們是各自獨立的。如果二次封裝的某個模塊進行更改并且要求全部同步……亦或者后期迭代或者定制化需求——

小孫 : “啊!!那豈不是要手動去一一修改?” 代碼爸爸慈祥一笑,看著二傻子痛苦加班。

Module Federation-share subApp

初初查看到一些資料的時候,腦海中浮現一些遠古項目:項目中通過 iframe 引入別的網頁作為一個塊進行展示,當然這也是微前端的一種實現,但是受 postMessage 通信機制、刷新后退、安全問題、SEO、Cookie、速度慢等影響,目前還有許多難以解決的點,一旦復雜度提升,后期迭代就可能需要更多的成本去維護項目或者妥協功能。

Module Federation 的出現使得多部門協作開發變得更便捷。多個單獨的構建形成一個應用程序。這些單獨的構建彼此之間不應該有依賴關系,因此可以單獨開發和部署它們,它們可以隨時被應用,再各自成為新的應用塊。官網對于這塊概念拆解成Low-level concepts和High-level concepts。

讓我們來結合配置項來更詳細了解作者的整個設計過程。

Low-level concepts - 代碼中的分子與原子

PS: 這里的引用不是語法中的引用哦

這里小孫想引用一下化學中的分子與原子的關系。這張圖要側重說明的是本地模塊和遠程模塊。

在圖中每一份 Host 對于它本身來說, 都被理解成本地模塊,它是當前應用構建的一部分,對于它本身項目來說,它是分子,是一個容器。但是對于引用它整個項目的 Host 爸爸來說,它整個遠程模塊是原子。

每一份 Romote遠程模塊是原子,它是運行時從容器加載的模塊,是異步行為。當使用遠程模塊時,這些異步操作將放置在遠程模塊和入口點之間的下一個塊加載操作中。如果沒有塊加載操作,就不可能使用遠程模塊。

A container is created through a container entry, which exposes asynchronous access to the specific modules. The exposed access is separated into two steps:

  • loading the module (asynchronous) 加載模塊(異步)
  • evaluating the module (synchronous) 執行模塊(同步)

加載模塊將在 chunk 加載期間完成。執行模塊將在與其他(本地和遠程)的模塊交錯執行期間完成。

我們來找個例子配合看一下整個設計過程:

一個例子介紹 MF 的常規用法

例子把 App3 的組件引入到 App2 的組件,然后 App1 再引入 App2 的這個二次封裝的組件。這個例子還是非常接近目前常見的開發場景。

  1. /* 業務代碼如下 */ 
  2.  
  3. // App1 webpack-config 
  4. new ModuleFederationPlugin({ 
  5.     // ...other config 
  6.     name"app1"
  7.     remotes: { 
  8.       app2: `app2@${getRemoteEntryUrl(3002)}`, 
  9.     }, 
  10. }) 
  11.  
  12. // App2 webpack-config 
  13. new ModuleFederationPlugin({ 
  14.     // ...other config 
  15.     name"app2"
  16.     filename: "remoteEntry.js"
  17.     exposes: { 
  18.       "./ButtonContainer""./src/ButtonContainer"
  19.     }, 
  20.     remotes: { 
  21.       app3: `app3@${getRemoteEntryUrl(3003)}`, 
  22.     }, 
  23. }) 
  24.  
  25. // App3 webpack-config 
  26. new ModuleFederationPlugin({ 
  27.   // ...other config 
  28.   name"app3"
  29.   filename: "remoteEntry.js"
  30.   exposes: { 
  31.     "./Button""./src/Button"
  32.   }, 
  33. }), 

加載模塊相關代碼解析

  1. // from App1 的 remoteEntry.js 中__webpack_modules__某個對象的 value 
  2. "webpack/container/entry/app3":((__unused_webpack_module, exports, __webpack_require__) => { 
  3.   var moduleMap = { 
  4.     "./Button": () => { 
  5.       return Promise.all([__webpack_require__.e("webpack_sharing_consume_default_react_react-_0085"), __webpack_require__.e("src_Button_js")]).then(() => (() => (( 
  6.         __webpack_require__( /*! ./src/Button */ 
  7.           "./src/Button.js"))))); 
  8.     } 
  9.   }; 
  10.   // get 方法作用:獲取 Scope 
  11.   var get = (module, getScope) => { 
  12.     __webpack_require__.R = getScope; 
  13.     getScope = ( 
  14.       __webpack_require__.o(moduleMap, module) ? 
  15.       moduleMap[module]() : 
  16.       Promise.resolve().then(() => { 
  17.         throw new Error('Module "' + module +'" does not exist in container.'); 
  18.       }) 
  19.     ); 
  20.     __webpack_require__.R = undefined; 
  21.     return getScope; 
  22.   }; 
  23.   // init 方法作用:初始化作用域對象 并把依賴存儲到 shareScope 中 
  24.   var init = (shareScope, initScope) => { 
  25.     if (!__webpack_require__.S) return
  26.     var oldScope = __webpack_require__.S["default"]; 
  27.     var name = "default" 
  28.     if (oldScope && oldScope !== shareScope) throw new Error( 
  29.       "Container initialization failed as it has already been initialized with a different share scope" 
  30.     ); 
  31.     __webpack_require__.S[name] = shareScope; 
  32.     return __webpack_require__.I(name, initScope); 
  33.   }; 
  34.   // This exports getters to disallow modifications 
  35.   __webpack_require__.d(exports, { 
  36.     get: () => (get), 
  37.     init: () => (init) 
  38.   }); 
  39. }) 
  40.  
  41.  
  42. // App main.js 后面有詳細代碼 這邊簡單介紹一下就是一個 JSONP 的下載 
  43. __webpack_require__.I = (name, initScope) => {}) 
  44.  
  45. /* 在消費模塊執行的操作 from consumes   */ 
  46. // 確認好 loaded 以后調用原子的 Scope 
  47. var get = (entry) => { 
  48.   entry.loaded = 1; 
  49.   return entry.get() 
  50. }; 
  51. // 消費模塊再執行原子的異步初始化行為 在這個模塊還會處理后面提到的一個疑問 公共模塊的版本問題 
  52. //__webpack_require__.S[scopeName] 取出 scopeName 對應的 scope 
  53. var init = (fn) => (function(scopeName, a, b, c) { 
  54.   var promise = __webpack_require__.I(scopeName); 
  55.   if (promise && promise.thenreturn promise.then(fn.bind(fn, scopeName, __webpack_require__.S[scopeName], a, b, c)); 
  56.   return fn(scopeName, __webpack_require__.S[scopeName], a, b, c); 
  57. }); 
  • 執行 init 初始化以后,收集的依賴存儲到 shareScope 中,并初始化作用域。
  • 中間會涉及到 版本號處理,關聯關系,公共模塊等處理,拼數據掛載到__webpack_require__上使用。
  • 調用時通過通過 JSONP 的遠程加載模塊(異步行為),相關代碼如下:
  1. // from App1 main.js 
  2. /***/ "webpack/container/reference/app2"
  3. /*!*******************************************************!*\ 
  4.   !*** external "app2@//localhost:3002/remoteEntry.js" ***! 
  5.   \*******************************************************/ 
  6. /***/ ((module, __unused_webpack_exports, __webpack_require__) => { 
  7.  
  8. "use strict"
  9. var __webpack_error__ = new Error(); 
  10. module.exports = new Promise((resolve, reject) => { 
  11.  if(typeof app2 !== "undefined"return resolve(); 
  12.  __webpack_require__.l("//localhost:3002/remoteEntry.js", (event) => { 
  13.   //...這個方法根據 JSONP 加載遠程腳本  
  14.  }, "app2"); 
  15. }).then(() => (app2)); 
  16.    
  17. // __webpack_require__.l 定義如下 
  18. // 對 IE 和 ES module 單獨處理 如果是 ES module,取 module[default]的值 
  19. // 這邊特別定義 inProgress 去監控多個 url 的回調狀態,這段設計挺有意思的 
  20. __webpack_require__.l = (url, done, key, chunkId) => { 
  21.     if(inProgress[url]) { inProgress[url].push(done); return; } 
  22.     var script, needAttach; 
  23.     if(key !== undefined) { 
  24.      var scripts = document.getElementsByTagName("script"); 
  25.      for(var i = 0; i < scripts.length; i++) { 
  26.       var s = scripts[i]; 
  27.       if(s.getAttribute("src") == url || s.getAttribute("data-webpack") == dataWebpackPrefix + key) { script = s; break; } 
  28.      } 
  29.     } 
  30.     if(!script) { 
  31.      needAttach = true
  32.      script = document.createElement('script'); 
  33.     
  34.      script.charset = 'utf-8'
  35.      script.timeout = 120; 
  36.      if (__webpack_require__.nc) { 
  37.       script.setAttribute("nonce", __webpack_require__.nc); 
  38.      } 
  39.      script.setAttribute("data-webpack", dataWebpackPrefix + key); 
  40.      script.src = url; 
  41.     } 
  42.     inProgress[url] = [done]; 
  43.     var onScriptComplete = (prev, event) => { 
  44.      // avoid mem leaks in IE. 
  45.      script.onerror = script.onload = null
  46.      clearTimeout(timeout); 
  47.      var doneFns = inProgress[url]; 
  48.      delete inProgress[url]; 
  49.      script.parentNode && script.parentNode.removeChild(script); 
  50.      doneFns && doneFns.forEach((fn) => (fn(event))); 
  51.      if(prev) return prev(event); 
  52.     } 
  53.     ; 
  54.     var timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000); 
  55.     script.onerror = onScriptComplete.bind(null, script.onerror); 
  56.     script.onload = onScriptComplete.bind(null, script.onload); 
  57.     needAttach && document.head.appendChild(script); 
  58.    }; 
  59.   })(); 

前面說了單個具象化的加載模塊和執行模塊的代碼,現在說說分子與原子之間的代碼關系,如何知曉并加載原子代碼:

  1. // 每個分子 main.js 中 例如 App1 只引入了 App2 的 
  2. var __webpack_modules__ = ({ 
  3.  "webpack/container/reference/app2":": () 
  4.   .... 
  5. }) 
  6.  
  7. // 如果是有原子代碼的 查看 remotes loading 模塊 
  8. // 執行后找到//localhost:3002/remoteEntry.js 的文件 再異步執行里面的原子代碼 
  9. __webpack_require__.l("//localhost:3002/remoteEntry.js", (event) => { 
  10.   if (typeof app2 !== "undefined"return resolve(); 
  11.   var errorType = event && (event.type === 'load' ? 'missing' : 
  12.                             event.type); 
  13.   var realSrc = event && event.target && event.target.src; 
  14.   __webpack_error__.message = 'Loading script failed.\n(' + 
  15.     errorType + ': ' + realSrc + ')'
  16.   __webpack_error__.name = 'ScriptExternalLoadError'
  17.   __webpack_error__.type = errorType; 
  18.   __webpack_error__.request = realSrc; 
  19.   reject(__webpack_error__); 
  20. }, "app2"); 
  21.  
  22. // 然后加載相關 chuck 的時候根據枚舉進行 get 調用  
  23. var chunkMapping = {"app2/ButtonContainer": ["webpack/container/remote/app2/ButtonContainer"]}; 
  24. var idToExternalAndNameMapping = { 
  25.   "webpack/container/remote/app2/ButtonContainer": ["default","./ButtonContainer","webpack/container/reference/app2"
  26. }; 
  27.  
  28. __webpack_require__.f.remotes = (chunkId, promises) => { 
  29.   if(__webpack_require__.o(chunkMapping, chunkId)) { 
  30.     chunkMapping[chunkId].forEach((id) => { 
  31.       var getScope = __webpack_require__.R; 
  32.       if(!getScope) getScope = []; 
  33.       // 獲取渲染時候的 moduleName 
  34.       var data = idToExternalAndNameMapping[id]; 
  35.       if(getScope.indexOf(data) >= 0) return
  36.       getScope.push(data); 
  37.       if(data.p) return promises.push(data.p); 
  38.       var onError = (error) => { 
  39.         // 處理錯誤然后給一個標志數據表示錯誤 太長不看 
  40.         data.p = 0; 
  41.       }; 
  42.       var handleFunction = (fn, arg1, arg2, d, nextfirst) => { 
  43.         // 異步執行方法 太長不看 
  44.       } 
  45.       var onExternal = (external, _, first) => (external ? handleFunction(__webpack_require__.I, data[0], 0, external, onInitialized, first) : onError()); 
  46.       // 核心代碼 本質是調用 get 方法 
  47.       var onInitialized = (_, external, first) => (handleFunction(external.get, data[1], getScope, 0, onFactory, first)); 
  48.       var onFactory = (factory) => { 
  49.         data.p = 1; 
  50.         __webpack_modules__[id] = (module) => { 
  51.           module.exports = factory(); 
  52.         } 
  53.       }; 
  54.       handleFunction(__webpack_require__, data[2], 0, 0, onExternal, 1); 
  55.     }); 
  56.   } 
  57.  
  58. // 調用 get 以后 下載下面這個文件再做具象化的處理 
  59. // 在打包后的代碼中 import 相關的原子模塊 異步加載 
  60. (self["webpackChunk_nested_app2"] = self["webpackChunk_nested_app2"] || []).push([["src_bootstrap_js"], { 
  61.  "./src/App.js""..."
  62.   "./src/ButtonContainer.js""..."
  63.   "./src/bootstrap.js":"..." 
  64. }]) 

High-level concepts - 雙向共享和推斷

前面說了容器的概念,再深入拓展一個過去常有的場景: 暫不考慮抽離公共邏輯的基礎上,組件 A 和組件 B 都互相需要移植一部分功能,你刷刷刷復制對應代碼過去,后期每次迭代都需要同時更新組件 A 和組件 B 中的對應內容,那如果這個緯度是兩個項目呢?

疑問一:例子中的 import("./bootstrap")作為入口是為什么

看看打包后做了什么:

  1. (self["webpackChunk_nested_app2"] = self["webpackChunk_nested_app2"] || []).push([["src_bootstrap_js"], { 
  2.  "./src/App.js""..."
  3.   "./src/ButtonContainer.js""..."
  4.   "./src/bootstrap.js":"..." 
  5. }]) 
  6.  
  7. //每個模塊大概處理幾件事 
  8. ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 
  9.     // exports module 
  10.     __webpack_require__.r(__webpack_exports__) 
  11.     // 處理不同 module 類型之間的差異 如果是 ES module 取這個 default 的值 
  12.     __webpack_require__.d(__webpack_exports__, { 
  13.       /* harmony export */ 
  14.       "default": () => (__WEBPACK_DEFAULT_EXPORT__) 
  15.       /* harmony export */ 
  16.     }); 
  17.     //...具體組件邏輯 或者 import 原子部分的代碼~觸發后續的回調鉤子去初始化 scoped 
  18.    // 最后掛載 
  19.    react_dom__WEBPACK_IMPORTED_MODULE_2___default().render( /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1___default().createElement( 
  20.    _App__WEBPACK_IMPORTED_MODULE_0__.defaultnull), document.getElementById("root"));   
  21.  }), 

It's still a valid a approach to wrap your entry point with import("./bootstrap"). When doing so, make sure to inline the entry chunk into the HTML for best performance (no double round trip).

This is now the recommended approach. The old "fix" no longer works as remotes could provide shared modules to the app, which requires an async step before using shared modules. Maybe we provide some flag for the entry option in future to do this automatically.

文章里寫到在開啟 MF 中共享模塊時,入口采用異步邊界可以有效規避掉雙重更新造成的性能加載問題。官方文檔對此還提供了這種做法的缺陷案例【以下來自官網】:

1.通過 ModuleFederationPlugin 將依賴的 eager 屬性設置為 true:

  1. new ModuleFederationPlugin({ 
  2.     // ...other config 
  3.     shared: { 
  4.         eager: true
  5.     } 
  6. });   
  7. // webpack beta.16 升級到 webpack beta.17 可能類似報錯 Uncaught Error: Module "./Button" does not exist in container. 

2.更改 exposes:Uncaught TypeError: fn is not a function

  1. new ModuleFederationPlugin({ 
  2.   exposes: { 
  3. -   'Button''./src/Button' 
  4. +   './Button':'./src/Button' 
  5.   } 
  6. }); 
  7.      
  8. // 此處錯誤可能是丟失了遠程容器,請確保在使用前添加它。 
  9. // 如果已為試圖使用遠程服務器的容器加載了容器,但仍然看到此錯誤,則需將主機容器的遠程容器文件也添加到 HTML 中。 

疑問二:包的版本選擇

在我們目前應用到的許多場景中,就對私有庫的自定義組件做過本地的二次封裝,由于代碼是單向更新的,在移植項目的過程中就存在許多難以規避的問題,Module Federation 通過設置singleton: true 開啟公共模塊可以一定程度解決這個問題。但是如果兩方項目所需的版本號不一致是按照什么依據呢?

  1. // 前提情況 App1 是 host App2 是 remote App1 中引用 App2 的組件 
  2. // App1 package.json: 
  3. "dependencies": { 
  4.   "mf-test-ssy""^1.0.0" 
  5. // App2 package.json: 
  6. "dependencies": { 
  7.   "mf-test-ssy""^2.0.0" 
  8.  
  9. // webpack-config-common 部分: 
  10. new ModuleFederationPlugin({ 
  11.   // ...other config 
  12.   shared: {  
  13.     react: { singleton: true },  
  14.     "react-dom": { singleton: true }, 
  15.     "mf-test-ssy":{ singleton: true },  
  16.   }, 
  17. }), 

這里小孫簡單寫了個 demo 嘗試模擬這個問題,以basic-host-remote 案例為基礎,自己發布了兩個不同版本的 npm 包,分別引入 v1.0.0 和 v2.0.0 查看一下結果。

可以看到 host 展示的 Npm 版本雖然低于 remote 中 Npm 的版本,但是展示的還是 remote 中較高的版本的代碼。

然后互換 App1 和 App2 的 npm 版本:

  1. // App1 package.json: 
  2. "dependencies": { 
  3.   "mf-test-ssy""^2.0.0" 
  4. // App2 package.json: 
  5. "dependencies": { 
  6.   "mf-test-ssy""^1.0.0" 

可以看到此時 App2 還是以低版本展示為主,App1 還是以本地的引用版本為主,開啟共享的差異性并不大。 共享中的模塊請求(from 官網中文站):

  • 只在使用時提供
  • 會匹配構建中所有使用的相等模塊請求
  • 將提供所有匹配模塊
  • 將從圖中這個位置的 package.json 提取 requiredVersion
  • 當你有嵌套的 node_modules 時,可以提供和使用多個不同的版本

如何解決? => 自動推斷的設置

packageName 選項允許通過設置包名來查找所需的版本。默認情況下,它會自動推斷模塊請求,當想禁用自動推斷時,請將 requiredVersion 設置為 false。

疑問三:共享模塊是什么程度的共享

借此猜測某些庫是不是也只會一次實例化,實驗繼續 UP!! Npm 中的構造函數邏輯更改如下:初始化成功的例子在 window 下掛載上數據,并且每次初始化后打印值遞增。兩份代碼都更新成 V5.0.0,我們看一下效果:

看似沒有問題對不對,小孫復檢的時候猛然驚醒,這是兩個項目,window 各自為政,這個例子這樣設計本身就是大錯特錯。但是沒關系,雖然小孫不靠譜,但是 webpack 靠譜呀。

  • main.js 是應用主文件每次都先加載這個。
  • remoteEntry.js 在 App1 中先加載,是因為 App1 中依賴于 App2 的一些依賴配置,所以 App1 中的 remoteEntry.js 加載優先級非常高,加載以后它可以知道自己需要遠程加載什么資源。
  • 可以看到 App2 加載了 mf-test-ssy, App1 并沒有加載 mf-test-ssy,但是直接加載了 App2 的 remoteEntry,說明 remoteEntry.js 是作為 remote 時被引的文件。
  • 構造函數應該實質上只是初始化了一次,我們從這個結論出發,看一下 webpack 相關的代碼配置,再逐步細化到源碼。

從加載文件到源碼

這里先貼會影響打包的業務代碼:

  1. /* 
  2. ** App1 app.js 
  3. */ 
  4. import React from "react"
  5. import Test from "mf-test-ssy" 
  6. // 這里用了 App2 的 button 組件代碼 
  7. const RemoteButton = React.lazy(() => import("app2/Button")); 
  8. const App = () => ( 
  9.   <div> 
  10.     <React.Suspense fallback="Loading Button"
  11.       <RemoteButton /> 
  12.     </React.Suspense> 
  13.   </div> 
  14. ); 
  15. export default App; 
  16. /* 
  17. ** App2 Html-Script 注意這里是編譯后動態生成的。 
  18. */ 
  19. <script defer src="remoteEntry.js"></script> 

 

把 remoteEntry 打包后的代碼,把 相關部分截取出來:

  1. /* webpack/runtime/sharing */ 
  2. //前面暫且忽略一些定義以及判空 
  3. //存放 scope 
  4. __webpack_require__.S = {}; 
  5. var initPromises = {}; 
  6. var initTokens = {}; 
  7. //初始化 scope,最后把數據拼成一個大對象 
  8. __webpack_require__.I = (name, initScope) => { 
  9.   if(!initScope) initScope = []; 
  10.   // handling circular init calls 
  11.   var initToken = initTokens[name]; 
  12.   if(!initToken) initToken = initTokens[name] = {}; 
  13.   if(initScope.indexOf(initToken) >= 0) return
  14.   initScope.push(initToken); 
  15.   // only runs once 
  16.   if(initPromises[name]) return initPromises[name]; 
  17.   // creates a new share scope if needed 
  18.   if(!__webpack_require__.o(__webpack_require__.S, name)) __webpack_require__.S[name] = {}; 
  19.   // runs all init snippets from all modules reachable 
  20.   var scope = __webpack_require__.S[name]; 
  21.   var warn = (msg) => (typeof console !== "undefined" && console.warn && console.warn(msg)); 
  22.   var uniqueName = "@basic-host-remote/app2"
  23.   //注冊共享模塊 
  24.   var register = (name, version, factory, eager) => { 
  25.     var versions = scope[name] = scope[name] || {}; 
  26.     var activeVersion = versions[version]; 
  27.     if(!activeVersion || (!activeVersion.loaded && (!eager != !activeVersion.eager ? eager : uniqueName > activeVersion.from))) versions[version] = { get: factory, from: uniqueName, eager: !!eager }; 
  28.   }; 
  29.   //初始化遠程外部模塊 
  30.   var initExternal = (id) => { 
  31.     var handleError = (err) => (warn("Initialization of sharing external failed: " + err)); 
  32.     try { 
  33.       var module = __webpack_require__(id); 
  34.       if(!module) return
  35.       var initFn = (module) => (module && module.init && module.init(__webpack_require__.S[name], initScope)) 
  36.       if(module.thenreturn promises.push(module.then(initFn, handleError)); 
  37.       var initResult = initFn(module); 
  38.       if(initResult && initResult.thenreturn promises.push(initResult.catch(handleError)); 
  39.     } catch(err) { handleError(err); } 
  40.   } 
  41.   var promises = []; 
  42.   //根據 chunkId 的名稱注冊共享模塊 
  43.   switch(name) { 
  44.     case "default": { 
  45.       register("mf-test-ssy""6.0.0", () => (__webpack_require__.e("node_modules_mf-test-ssy_index_js").then(() => (() => (__webpack_require__(/*! ./node_modules/mf-test-ssy/index.js */ "./node_modules/mf-test-ssy/index.js")))))); 
  46.       register("react-dom""16.14.0", () => (Promise.all([__webpack_require__.e("vendors-node_modules_react-dom_index_js"), __webpack_require__.e("webpack_sharing_consume_default_react_react-_76b1")]).then(() => (() => (__webpack_require__(/*! ./node_modules/react-dom/index.js */ "./node_modules/react-dom/index.js")))))); 
  47.       register("react""16.14.0", () => (Promise.all([__webpack_require__.e("vendors-node_modules_react_index_js"), __webpack_require__.e("node_modules_object-assign_index_js-node_modules_prop-types_checkPropTypes_js")]).then(() => (() => (__webpack_require__(/*! ./node_modules/react/index.js */ "./node_modules/react/index.js")))))); 
  48.     } 
  49.       break; 
  50.   } 
  51.   if(!promises.length) return initPromises[name] = 1; 
  52.   return initPromises[name] = Promise.all(promises).then(() => (initPromises[name] = 1)); 
  53. }; 
  54. })(); 

這段代碼所做的就是根據配置項將模塊生成內部對應的 modules,定義了一個 scope 去存儲所有的 module,然后注冊了共享模塊等操作。全部掛載在__webpack_require__上,這樣處理以方便后續 require 的方式引入進來。對應最最最核心的源碼:

  1. // 四大天王鎮宅  
  2. sharing: { 
  3.    // 處理分子原子關系的依賴 
  4.   get ConsumeSharedPlugin() { 
  5.    return require("./sharing/ConsumeSharedPlugin"); 
  6.   }, 
  7.     // 處理 provide 依賴 
  8.   get ProvideSharedPlugin() { 
  9.    return require("./sharing/ProvideSharedPlugin"); 
  10.   }, 
  11.     // 我是入口 讓我來調用 并且我實現了共享 
  12.   get SharePlugin() { 
  13.    return require("./sharing/SharePlugin"); 
  14.   }, 
  15.   get scope() { 
  16.    return require("./container/options").scope; 
  17.   } 
  18. }, 
  19. // from /webpack-master/lib/sharing/SharePlugin.js 
  20. class SharePlugin { 
  21.  /** 
  22.   * @param {SharePluginOptions} options options 
  23.   */ 
  24.  constructor(options) { 
  25.   /** @type {[string, SharedConfig][]} */ 
  26.     // 處理 options 格式 模塊二次封裝  
  27.   const sharedOptions = parseOptions(...太長不看); 
  28.   /** @type {Record<string, ConsumesConfig>[]} */ 
  29.     // 定義 Host 消費 remote 的信息 后面會根據這個關聯去加載前面說的原子的初始化以及 scoped 
  30.   const consumes = sharedOptions.map(([key, options]) => ({ 
  31.    [key]: { 
  32.     import: options.import, 
  33.     shareKey: options.shareKey || key
  34.     shareScope: options.shareScope, 
  35.     requiredVersion: options.requiredVersion, 
  36.     strictVersion: options.strictVersion, 
  37.     singleton: options.singleton, 
  38.     packageName: options.packageName, 
  39.     eager: options.eager 
  40.    } 
  41.   })); 
  42.   /** @type {Record<string, ProvidesConfig>[]} */ 
  43.     // 核心代碼 處理 
  44.   const provides = sharedOptions 
  45.    .filter(([, options]) => options.import !== false
  46.    .map(([key, options]) => ({ 
  47.     [options.import || key]: { 
  48.      shareKey: options.shareKey || key
  49.      shareScope: options.shareScope, 
  50.      version: options.version, 
  51.      eager: options.eager 
  52.     } 
  53.    })); 
  54.   this._shareScope = options.shareScope; 
  55.   this._consumes = consumes; 
  56.   this._provides = provides; 
  57.  } 
  58.  
  59.  /** 
  60.   * Apply the plugin 
  61.   * @param {Compiler} compiler the compiler instance 
  62.   * @returns {void} 
  63.   */ 
  64.  apply(compiler) { 
  65.     // 處理分子原子關系的依賴 
  66.   new ConsumeSharedPlugin({ 
  67.    shareScope: this._shareScope, 
  68.    consumes: this._consumes 
  69.   }).apply(compiler); 
  70.     // 處理 provider 依賴 
  71.   new ProvideSharedPlugin({ 
  72.    shareScope: this._shareScope, 
  73.    provides: this._provides 
  74.   }).apply(compiler); 
  75.  } 
  76. module.exports = SharePlugin; 

總結

每一個分子跟原子的愛恨糾葛終有一個文件去劃分好主次,雖然異步加載分離打包,但是愛永不失聯。 每一個公共分享的時刻,runtime 在各自心中,就像共同孕育同一個孩子,生了一次不會生第兩次。 但是—— 共享模塊中 remote 版本大,按照較大的算,如果 remote 版本小,按照我本地說了算。

其他:MF 生態

ExternalTemplateRemotesPlugin

有需求在構建中使用上下文處理處理動態 Url 的,且需要解決緩存失效問題的,可以看一下這個插件。

from https://github.com/module-federation/module-federation-examples/issues/566

  • Dynamic URL, have the ability to define the URL at runtime instead of hard code at build time.
  • Cache invalidation.
  1. // from webpack.config 
  2. plugins: [ 
  3.     new ModuleFederationPlugin({ 
  4.         //...config 
  5.         remotes: { 
  6.           'my-remote-1''my-remote-1@[window.remote-1-domain]/remoteEntry.js?[getRandomString()]'
  7.         }, 
  8.     }), 
  9.     new ExternalTemplateRemotesPlugin(), //no parameter, 

參考資料

https://webpack.js.org/concepts/module-federation/#building-blocks

https://github.com/sokra/slides/blob/master/content/ModuleFederationWebpack5.md

https://www.youtube.com/watch?v=x22F4hSdZJM

https://github.com/module-federation/module-federation-examples

https://segmentfault.com/a/1190000039031505

http://img.iamparadox.club/img/mf2.jpg

 

https://developer.aliyun.com/article/755252

 

責任編輯:武曉燕 來源: 微醫大前端技術
相關推薦

2022-04-13 08:04:40

項目應用程序代碼

2011-03-31 14:21:42

保存數據

2022-11-02 18:47:46

場景模塊化跨棧

2025-08-18 01:15:00

2012-07-24 09:16:19

郵箱技巧

2009-08-04 11:48:41

中國移動云計算技術

2023-03-01 18:40:54

應用程序代碼

2019-07-04 15:57:16

內存頻率DDR4

2022-02-15 20:08:41

JDKJavaWindows

2022-04-29 08:00:36

web3區塊鏈比特幣

2011-06-01 14:24:22

設計移動Web

2024-11-08 08:34:59

RocketMQ5.Remoting通信

2020-10-19 08:20:44

技術管理轉型

2019-07-29 10:39:39

前端性能優化緩存

2023-12-11 08:21:02

數據的可靠性一致性Kafka

2025-08-21 01:15:00

CSS背景網頁

2019-05-13 14:17:06

抓包Web安全漏洞

2016-01-07 11:18:50

用戶畫像

2016-11-10 13:09:33

2017-11-07 12:43:13

PythonC++語言
點贊
收藏

51CTO技術棧公眾號

国产盗摄视频在线观看| 国产成人一区二区在线| 天堂www中文在线资源| 欧美少妇网站| 亚洲视频资源在线| 精品久久久久久综合日本| 蜜臀尤物一区二区三区直播| 亚洲国产精品成人| 亚洲男人天堂古典| 伊人五月天婷婷| 忘忧草在线影院两性视频| 国产精品久久久久久久久久久免费看 | 国产在线不卡一区二区三区| 亚洲国产日韩在线一区模特| 日韩中文一区二区三区| 成人免费视频国产| 蜜臀av性久久久久蜜臀aⅴ四虎 | aaa免费在线观看| 青青操视频在线| 极品美女销魂一区二区三区| 5566日本婷婷色中文字幕97| 看免费黄色录像| 九九在线高清精品视频| 精品国产一区二区亚洲人成毛片| 国产在线青青草| 秋霞在线午夜| 亚洲欧洲av另类| 欧美一区二区三区四区五区六区| 精品国产av 无码一区二区三区| 免费久久99精品国产自在现线| 久久91超碰青草是什么| 老司机精品免费视频| 亚洲第一福利社区| 精品国产凹凸成av人导航| 日韩av片免费观看| 国产原创一区| 欧美中文字幕亚洲一区二区va在线| 91九色丨porny丨国产jk| 成视频免费观看在线看| 国产精品国模大尺度视频| 日产精品一线二线三线芒果 | 影音先锋日韩有码| 欧美一区二区三区成人精品| 国产精品巨作av| 欧美videossexotv100| 北条麻妃亚洲一区| 视频精品二区| 欧美sm美女调教| 日本精品一二三| 中文一区二区三区四区| 日韩精品一区二区三区中文精品| 一区二区久久精品| 亚洲精品66| 在线电影国产精品| 亚洲综合123| 日本综合精品一区| 日韩午夜在线影院| 中国特级黄色片| 伊人久久大香线蕉av超碰| 日韩一区二区在线看| 激情小说欧美色图| 久久久久97| 亚洲精品自拍第一页| 亚洲一区二区观看| 精品一区二区三| 最近免费中文字幕视频2019| av在线免费播放网址| 久久精品影视| 欧美高清视频一区二区| 五月天综合在线| 午夜在线视频一区二区区别| 国产91色在线免费| 亚洲系列第一页| 国产激情一区二区三区| 国产福利不卡| 国产香蕉在线| 亚洲色图一区二区三区| 青青青青在线视频| 亚洲风情在线资源| 欧美日韩国产高清一区二区三区| 天堂av手机在线| 伊人www22综合色| 亚洲欧美日韩在线高清直播| 精品国产乱码久久久久久天美 | 韩国精品久久久999| 久久久国产高清| 免费观看在线综合色| 91免费版黄色| 黄色毛片在线看| 国产精品成人一区二区艾草| 国产高清www| 户外露出一区二区三区| 91精品午夜视频| 久久久久麻豆v国产精华液好用吗| 免费观看久久av| 久久综合伊人77777尤物| 日操夜操天天操| 麻豆成人91精品二区三区| 91久色国产| 国产区视频在线| 亚洲国产美女搞黄色| 成人在线免费播放视频| 国内不卡的一区二区三区中文字幕 | 一级片在线观看视频| 成人精品免费网站| 亚洲国产精品综合| 九九色在线视频| 欧美色图一区二区三区| 完美搭档在线观看| 亚欧美无遮挡hd高清在线视频 | 成人涩涩网站| 精品国产一区二区三区在线观看 | 欧美精品一区二区三| 亚洲精品成人av久久| 亚洲美女啪啪| 亚洲最大av在线| 在线看黄色av| 欧美日韩亚洲高清| 自拍偷拍激情视频| 99久久综合| 国产精品第二页| 天天摸天天碰天天爽天天弄| 亚洲欧美另类小说视频| 欧美三级午夜理伦三级富婆| 精品一区毛片| 97成人超碰免| 少妇高潮一区二区三区99小说 | 尤物yw午夜国产精品视频明星| 日本在线小视频| 风流少妇一区二区| 老司机午夜网站| 国产成人免费| 一个色综合导航| 久久中文字幕免费| 99久久777色| 蜜臀av无码一区二区三区| 日韩一区二区三区精品| 久久成人精品一区二区三区| 伊人网中文字幕| 国产精品无遮挡| 在线观看av日韩| 竹菊久久久久久久| 欧洲成人午夜免费大片| 天堂在线中文资源| 欧美色道久久88综合亚洲精品| 性色av蜜臀av浪潮av老女人| 在线精品一区| 久久riav二区三区| 成人性生交大片免费观看网站| 日韩成人高清在线| 丰满人妻老熟妇伦人精品| 久久久久国产免费免费| 国产成人精品无码播放| 精品视频网站| 成人天堂噜噜噜| 国产素人视频在线观看| 日韩欧美久久久| 日本一区二区三区免费视频| 99久久国产综合精品色伊| 欧洲av无码放荡人妇网站| 久久最新网址| 国产在线精品播放| 大片免费在线看视频| 欧美不卡一区二区三区四区| www成人在线| 国产日产亚洲精品系列| 视色视频在线观看| 亚洲精品97| 福利视频一区二区三区| 三级中文字幕在线观看| 国产性色av一区二区| 亚洲怡红院av| 亚洲一区视频在线观看视频| 自拍视频一区二区| 日韩av一区二区三区四区| 伊人久久av导航| 亚洲三级av| 日本欧美精品在线| 麻豆视频在线观看免费| 欧美sm美女调教| 久久久精品毛片| 亚洲精品国产视频| 中文字幕av网址| 精品一区二区免费视频| 日韩一级性生活片| 欧美在线观看视频一区| 99r国产精品视频| 亚洲第一影院| 欧美福利在线观看| 二区三区在线| 精品日韩99亚洲| 亚洲精品国产精品乱码视色| 亚洲美女偷拍久久| 国产真实乱人偷精品人妻| 国产一区二区三区四| 丰满爆乳一区二区三区| 欧美日韩在线观看视频小说| 国产精品免费一区二区三区四区| 写真福利精品福利在线观看| 欧美另类69精品久久久久9999| 你懂得网站在线| 日韩欧美视频一区| 国产99久久久久久免费看| 亚洲一区在线看| 午夜国产福利视频| 久久这里只有精品6| 一级黄色在线播放| 视频一区在线播放| 夜夜添无码一区二区三区| 欧美大片aaaa| 日韩av一区二区三区美女毛片| 一区二区三区免费在线看| 国产精品一区久久久| av2020不卡| 美女撒尿一区二区三区| 午夜毛片在线| 亚洲一区二区久久久| 天堂中文网在线| 日韩小视频在线观看专区| 亚洲一区精品在线观看| 色94色欧美sute亚洲线路一ni | 18网站在线观看| 中文字幕亚洲一区| 国产香蕉在线| 亚洲欧美日韩中文视频| 同心难改在线观看| 日韩成人av网址| 性xxxx视频| 亚洲国产成人精品电影| 性一交一乱一伧老太| 欧美一区二区三区视频免费播放 | 国产成人在线视频网址| 色网站在线视频| 久久精品av麻豆的观看方式| www.日本xxxx| 久久精品盗摄| 国产主播在线看| 亚洲影音先锋| 成人羞羞国产免费网站| 久久夜色精品| 99视频在线免费| 日韩国产精品大片| 欧美性猛交xxx乱久交| 日韩中文字幕不卡| 日本免费观看网站| 日本不卡123| 浓精h攵女乱爱av| 久久精品国产第一区二区三区 | 成人精品在线视频观看| 动漫美女无遮挡免费| 成人免费av资源| 成人免费无码大片a毛片| 99久久综合色| 熟女俱乐部一区二区视频在线| 久久久亚洲午夜电影| 免费观看a级片| 国产精品成人免费在线| 老熟妻内射精品一区| 亚洲欧美日韩国产手机在线| 少妇久久久久久被弄高潮| 亚洲国产综合人成综合网站| 日本少妇性生活| 粉嫩老牛aⅴ一区二区三区| 天堂网免费视频| 欧美日韩精品是欧美日韩精品| 亚洲无码精品在线播放| 日韩欧美一级二级三级| 亚洲日本香蕉视频| 尤物tv国产一区| 亚洲综合影视| 欧美壮男野外gaytube| 国产精品久久久久久久久免费高清| 国产一区红桃视频| 99久久免费精品国产72精品九九| 久久久久久国产精品mv| 日韩一区电影| 97超碰人人澡| 日本伊人色综合网| 99精品视频免费版的特色功能| 成人的网站免费观看| 人妻一区二区视频| 亚洲免费观看视频| 国产午夜麻豆影院在线观看| 7777精品伊人久久久大香线蕉完整版| 亚洲第一色网站| 国产亚洲欧洲在线| 色屁屁www国产馆在线观看| 欧美中文字幕第一页| 97久久中文字幕| 美脚丝袜一区二区三区在线观看| 91精品国产乱码久久久久久| 亚洲中文字幕无码专区| 国产主播一区二区三区| www.自拍偷拍| 有码一区二区三区| 国产男人搡女人免费视频| 精品国产乱码久久久久久闺蜜| 99re热久久这里只有精品34| 久久免费视频在线观看| 欧美在线一级| 农村寡妇一区二区三区| 午夜日韩激情| 一本色道久久亚洲综合精品蜜桃| 成人免费不卡视频| 成年人av电影| 欧美日韩在线播| 日韩欧美在线观看一区二区| 欧美猛交免费看| 欧美天堂在线| 欧洲精品久久| 亚洲色诱最新| 国产乱淫av片| 亚洲美女淫视频| 一区二区三区免费在线| 亚洲天堂男人天堂| 国内精彩免费自拍视频在线观看网址| 91视频九色网站| 日韩欧美精品| 欧美性猛交久久久乱大交小说 | 欧美18—19sex性hd| 国产精品加勒比| 欧美日韩国产亚洲一区| 在线免费看v片| 中文字幕亚洲在| 中文字幕一级片| 国产亚洲在线播放| 精品国产第一福利网站| 久久久久久一区| 亚洲美女91| 麻豆精品国产传媒av| 亚洲自拍欧美精品| 亚洲春色一区二区三区| 欧美精品日韩www.p站| 国色天香久久精品国产一区| 色中文字幕在线观看| 精品制服美女丁香| 999精品在线视频| 91超碰这里只有精品国产| 3d成人动漫在线| 成人激情视频小说免费下载| 欧美好骚综合网| theporn国产精品| 亚洲天堂免费在线观看视频| 国产一区二区三区黄片| 久久久av网站| 伊色综合久久之综合久久| 人人妻人人澡人人爽欧美一区| 国产suv精品一区二区三区| 久久久精品视频在线| 精品久久人人做人人爱| 爱情岛亚洲播放路线| 国产综合18久久久久久| 国产亚洲毛片| 91视频在线网站| 欧美色图一区二区三区| а√天堂官网中文在线| 成人自拍视频网站| 国产欧美日韩一区二区三区在线| 成人网站免费观看| 欧美在线你懂的| 黄页视频在线播放| 懂色一区二区三区av片| 国产亚洲成人一区| 国产毛片欧美毛片久久久| 欧美男人的天堂一二区| 日本孕妇大胆孕交无码| 久久综合精品一区| 免费看欧美美女黄的网站| 26uuu成人网| 亚洲精品美女免费| 高清av一区| 路边理发店露脸熟妇泻火| 99久免费精品视频在线观看 | 在线免费三级电影网站| 日韩欧美手机在线| 国产乱码精品1区2区3区| 日韩av电影网址| 在线丨暗呦小u女国产精品| 高清久久一区| 亚洲欧洲日产国码无码久久99| 欧美国产精品一区| 丰满岳乱妇国产精品一区| 国产91网红主播在线观看| 中文字幕一区二区三区在线视频 | 国产精品视频午夜| 女生裸体视频一区二区三区| 黄色性生活一级片| 欧美精品久久一区二区三区| av3级在线| 丰满女人性猛交| 99久久免费视频.com| 91久久久久久久久久久久| 午夜精品久久久久久久久久久久久 | 免费观看成人在线| 国产一区二三区| www.国产毛片| 欧美精品久久久久久久久| 奇米影视亚洲| 亚洲一区二区乱码| 日韩午夜三级在线|