自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Module Federation你的浪漫我來懂

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

[[411379]]

本文轉(zhuǎn)載自微信公眾號「微醫(yī)大前端技術(shù)」,作者孫舒怡。轉(zhuǎn)載本文請聯(lián)系微醫(yī)大前端技術(shù)公眾號。

前言

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

【通關(guān)目標: 在頁面中插入輪播圖模塊】

NPM 方式-share lib

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

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

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

Module Federation-share subApp

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

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

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

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

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

這里小孫想引用一下化學(xué)中的分子與原子的關(guān)系。這張圖要側(cè)重說明的是本地模塊和遠程模塊。

在圖中每一份 Host 對于它本身來說, 都被理解成本地模塊,它是當前應(yīng)用構(gòu)建的一部分,對于它本身項目來說,它是分子,是一個容器。但是對于引用它整個項目的 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) 執(zhí)行模塊(同步)

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

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

一個例子介紹 MF 的常規(guī)用法

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

  1. /* 業(yè)務(wù)代碼如下 */ 
  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. }), 

加載模塊相關(guān)代碼解析

  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 方法作用:初始化作用域?qū)ο?nbsp;并把依賴存儲到 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. /* 在消費模塊執(zhí)行的操作 from consumes   */ 
  46. // 確認好 loaded 以后調(diào)用原子的 Scope 
  47. var get = (entry) => { 
  48.   entry.loaded = 1; 
  49.   return entry.get() 
  50. }; 
  51. // 消費模塊再執(zhí)行原子的異步初始化行為 在這個模塊還會處理后面提到的一個疑問 公共模塊的版本問題 
  52. //__webpack_require__.S[scopeName] 取出 scopeName 對應(yīng)的 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. }); 
  • 執(zhí)行 init 初始化以后,收集的依賴存儲到 shareScope 中,并初始化作用域。
  • 中間會涉及到 版本號處理,關(guān)聯(lián)關(guān)系,公共模塊等處理,拼數(shù)據(jù)掛載到__webpack_require__上使用。
  • 調(diào)用時通過通過 JSONP 的遠程加載模塊(異步行為),相關(guān)代碼如下:
  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.   //...這個方法根據(jù) JSONP 加載遠程腳本  
  14.  }, "app2"); 
  15. }).then(() => (app2)); 
  16.    
  17. // __webpack_require__.l 定義如下 
  18. // 對 IE 和 ES module 單獨處理 如果是 ES module,取 module[default]的值 
  19. // 這邊特別定義 inProgress 去監(jiān)控多個 url 的回調(diào)狀態(tài),這段設(shè)計挺有意思的 
  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.   })(); 

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

  1. // 每個分子 main.js 中 例如 App1 只引入了 App2 的 
  2. var __webpack_modules__ = ({ 
  3.  "webpack/container/reference/app2":": () 
  4.   .... 
  5. }) 
  6.  
  7. // 如果是有原子代碼的 查看 remotes loading 模塊 
  8. // 執(zhí)行后找到//localhost:3002/remoteEntry.js 的文件 再異步執(zhí)行里面的原子代碼 
  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. // 然后加載相關(guān) chuck 的時候根據(jù)枚舉進行 get 調(diào)用  
  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.         // 處理錯誤然后給一個標志數(shù)據(jù)表示錯誤 太長不看 
  40.         data.p = 0; 
  41.       }; 
  42.       var handleFunction = (fn, arg1, arg2, d, nextfirst) => { 
  43.         // 異步執(zhí)行方法 太長不看 
  44.       } 
  45.       var onExternal = (external, _, first) => (external ? handleFunction(__webpack_require__.I, data[0], 0, external, onInitialized, first) : onError()); 
  46.       // 核心代碼 本質(zhì)是調(diào)用 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. // 調(diào)用 get 以后 下載下面這個文件再做具象化的處理 
  59. // 在打包后的代碼中 import 相關(guān)的原子模塊 異步加載 
  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 - 雙向共享和推斷

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

疑問一:例子中的 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 原子部分的代碼~觸發(fā)后續(xù)的回調(diào)鉤子去初始化 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 中共享模塊時,入口采用異步邊界可以有效規(guī)避掉雙重更新造成的性能加載問題。官方文檔對此還提供了這種做法的缺陷案例【以下來自官網(wǎng)】:

1.通過 ModuleFederationPlugin 將依賴的 eager 屬性設(shè)置為 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. // 如果已為試圖使用遠程服務(wù)器的容器加載了容器,但仍然看到此錯誤,則需將主機容器的遠程容器文件也添加到 HTML 中。 

疑問二:包的版本選擇

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

  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 案例為基礎(chǔ),自己發(fā)布了兩個不同版本的 npm 包,分別引入 v1.0.0 和 v2.0.0 查看一下結(jié)果。

可以看到 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 官網(wǎng)中文站):

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

如何解決? => 自動推斷的設(shè)置

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

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

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

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

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

從加載文件到源碼

這里先貼會影響打包的業(yè)務(wù)代碼:

  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 注意這里是編譯后動態(tài)生成的。 
  18. */ 
  19. <script defer src="remoteEntry.js"></script> 

 

把 remoteEntry 打包后的代碼,把 相關(guān)部分截取出來:

  1. /* webpack/runtime/sharing */ 
  2. //前面暫且忽略一些定義以及判空 
  3. //存放 scope 
  4. __webpack_require__.S = {}; 
  5. var initPromises = {}; 
  6. var initTokens = {}; 
  7. //初始化 scope,最后把數(shù)據(jù)拼成一個大對象 
  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.   //根據(jù) 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. })(); 

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

  1. // 四大天王鎮(zhèn)宅  
  2. sharing: { 
  3.    // 處理分子原子關(guān)系的依賴 
  4.   get ConsumeSharedPlugin() { 
  5.    return require("./sharing/ConsumeSharedPlugin"); 
  6.   }, 
  7.     // 處理 provide 依賴 
  8.   get ProvideSharedPlugin() { 
  9.    return require("./sharing/ProvideSharedPlugin"); 
  10.   }, 
  11.     // 我是入口 讓我來調(diào)用 并且我實現(xiàn)了共享 
  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 的信息 后面會根據(jù)這個關(guān)聯(lián)去加載前面說的原子的初始化以及 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.     // 處理分子原子關(guān)系的依賴 
  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; 

總結(jié)

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

其他:MF 生態(tài)

ExternalTemplateRemotesPlugin

有需求在構(gòu)建中使用上下文處理處理動態(tài) 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

 

責任編輯:武曉燕 來源: 微醫(yī)大前端技術(shù)
相關(guān)推薦

2022-04-13 08:04:40

項目應(yīng)用程序代碼

2011-03-31 14:21:42

保存數(shù)據(jù)

2022-11-02 18:47:46

場景模塊化跨棧

2023-03-01 18:40:54

應(yīng)用程序代碼

2009-08-04 11:48:41

中國移動云計算技術(shù)

2012-07-24 09:16:19

郵箱技巧

2019-07-04 15:57:16

內(nèi)存頻率DDR4

2022-02-15 20:08:41

JDKJavaWindows

2022-04-29 08:00:36

web3區(qū)塊鏈比特幣

2011-06-01 14:24:22

設(shè)計移動Web

2024-11-08 08:34:59

RocketMQ5.Remoting通信

2020-10-19 08:20:44

技術(shù)管理轉(zhuǎn)型

2019-07-29 10:39:39

前端性能優(yōu)化緩存

2023-12-11 08:21:02

數(shù)據(jù)的可靠性一致性Kafka

2016-11-10 13:09:33

2016-01-07 11:18:50

用戶畫像

2017-11-07 12:43:13

PythonC++語言

2019-05-13 14:17:06

抓包Web安全漏洞

2022-05-08 13:05:22

職位產(chǎn)品經(jīng)理開源
點贊
收藏

51CTO技術(shù)棧公眾號