偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Module Federation你的浪漫我來(lái)懂

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

[[411379]]

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

前言

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

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

NPM 方式-share lib

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

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

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

Module Federation-share subApp

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

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

讓我們來(lái)結(jié)合配置項(xiàng)來(lái)更詳細(xì)了解作者的整個(gè)設(shè)計(jì)過(guò)程。

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

PS: 這里的引用不是語(yǔ)法中的引用哦

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

在圖中每一份 Host 對(duì)于它本身來(lái)說(shuō), 都被理解成本地模塊,它是當(dāng)前應(yīng)用構(gòu)建的一部分,對(duì)于它本身項(xiàng)目來(lái)說(shuō),它是分子,是一個(gè)容器。但是對(duì)于引用它整個(gè)項(xiàng)目的 Host 爸爸來(lái)說(shuō),它整個(gè)遠(yuǎn)程模塊是原子。

每一份 Romote遠(yuǎn)程模塊是原子,它是運(yùn)行時(shí)從容器加載的模塊,是異步行為。當(dāng)使用遠(yuǎn)程模塊時(shí),這些異步操作將放置在遠(yuǎn)程模塊和入口點(diǎn)之間的下一個(gè)塊加載操作中。如果沒(méi)有塊加載操作,就不可能使用遠(yuǎn)程模塊。

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í)行模塊將在與其他(本地和遠(yuǎn)程)的模塊交錯(cuò)執(zhí)行期間完成。

我們來(lái)找個(gè)例子配合看一下整個(gè)設(shè)計(jì)過(guò)程:

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

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

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

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

  1. // 每個(gè)分子 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 的時(shí)候根據(jù)枚舉進(jìn)行 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.       // 獲取渲染時(shí)候的 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.         // 處理錯(cuò)誤然后給一個(gè)標(biāo)志數(shù)據(jù)表示錯(cuò)誤 太長(zhǎng)不看 
  40.         data.p = 0; 
  41.       }; 
  42.       var handleFunction = (fn, arg1, arg2, d, nextfirst) => { 
  43.         // 異步執(zhí)行方法 太長(zhǎng)不看 
  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 以后 下載下面這個(gè)文件再做具象化的處理 
  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 - 雙向共享和推斷

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

疑問(wèn)一:例子中的 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. //每個(gè)模塊大概處理幾件事 
  8. ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => { 
  9.     // exports module 
  10.     __webpack_require__.r(__webpack_exports__) 
  11.     // 處理不同 module 類(lèi)型之間的差異 如果是 ES module 取這個(gè) 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.

文章里寫(xiě)到在開(kāi)啟 MF 中共享模塊時(shí),入口采用異步邊界可以有效規(guī)避掉雙重更新造成的性能加載問(wèn)題。官方文檔對(duì)此還提供了這種做法的缺陷案例【以下來(lái)自官網(wǎng)】:

1.通過(guò) ModuleFederationPlugin 將依賴(lài)的 eager 屬性設(shè)置為 true:

  1. new ModuleFederationPlugin({ 
  2.     // ...other config 
  3.     shared: { 
  4.         eager: true
  5.     } 
  6. });   
  7. // webpack beta.16 升級(jí)到 webpack beta.17 可能類(lèi)似報(bào)錯(cuò) 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. // 此處錯(cuò)誤可能是丟失了遠(yuǎn)程容器,請(qǐng)確保在使用前添加它。 
  9. // 如果已為試圖使用遠(yuǎn)程服務(wù)器的容器加載了容器,但仍然看到此錯(cuò)誤,則需將主機(jī)容器的遠(yuǎn)程容器文件也添加到 HTML 中。 

疑問(wèn)二:包的版本選擇

在我們目前應(yīng)用到的許多場(chǎng)景中,就對(duì)私有庫(kù)的自定義組件做過(guò)本地的二次封裝,由于代碼是單向更新的,在移植項(xiàng)目的過(guò)程中就存在許多難以規(guī)避的問(wèn)題,Module Federation 通過(guò)設(shè)置singleton: true 開(kāi)啟公共模塊可以一定程度解決這個(gè)問(wèn)題。但是如果兩方項(xiàng)目所需的版本號(hào)不一致是按照什么依據(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. }), 

這里小孫簡(jiǎn)單寫(xiě)了個(gè) demo 嘗試模擬這個(gè)問(wèn)題,以basic-host-remote 案例為基礎(chǔ),自己發(fā)布了兩個(gè)不同版本的 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" 

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

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

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

packageName 選項(xiàng)允許通過(guò)設(shè)置包名來(lái)查找所需的版本。默認(rèn)情況下,它會(huì)自動(dòng)推斷模塊請(qǐng)求,當(dāng)想禁用自動(dòng)推斷時(shí),請(qǐng)將 requiredVersion 設(shè)置為 false。

疑問(wèn)三:共享模塊是什么程度的共享

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

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

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

從加載文件到源碼

這里先貼會(huì)影響打包的業(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 注意這里是編譯后動(dòng)態(tài)生成的。 
  18. */ 
  19. <script defer src="remoteEntry.js"></script> 

 

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

  1. /* webpack/runtime/sharing */ 
  2. //前面暫且忽略一些定義以及判空 
  3. //存放 scope 
  4. __webpack_require__.S = {}; 
  5. var initPromises = {}; 
  6. var initTokens = {}; 
  7. //初始化 scope,最后把數(shù)據(jù)拼成一個(gè)大對(duì)象 
  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.   //注冊(cè)共享模塊 
  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.   //初始化遠(yuǎn)程外部模塊 
  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 的名稱(chēng)注冊(cè)共享模塊 
  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ù)配置項(xiàng)將模塊生成內(nèi)部對(duì)應(yīng)的 modules,定義了一個(gè) scope 去存儲(chǔ)所有的 module,然后注冊(cè)了共享模塊等操作。全部掛載在__webpack_require__上,這樣處理以方便后續(xù) require 的方式引入進(jìn)來(lái)。對(duì)應(yīng)最最最核心的源碼:

  1. // 四大天王鎮(zhèn)宅  
  2. sharing: { 
  3.    // 處理分子原子關(guān)系的依賴(lài) 
  4.   get ConsumeSharedPlugin() { 
  5.    return require("./sharing/ConsumeSharedPlugin"); 
  6.   }, 
  7.     // 處理 provide 依賴(lài) 
  8.   get ProvideSharedPlugin() { 
  9.    return require("./sharing/ProvideSharedPlugin"); 
  10.   }, 
  11.     // 我是入口 讓我來(lái)調(diào)用 并且我實(shí)現(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(...太長(zhǎng)不看); 
  28.   /** @type {Record<string, ConsumesConfig>[]} */ 
  29.     // 定義 Host 消費(fèi) remote 的信息 后面會(huì)根據(jù)這個(gè)關(guān)聯(lián)去加載前面說(shuō)的原子的初始化以及 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)系的依賴(lài) 
  66.   new ConsumeSharedPlugin({ 
  67.    shareScope: this._shareScope, 
  68.    consumes: this._consumes 
  69.   }).apply(compiler); 
  70.     // 處理 provider 依賴(lài) 
  71.   new ProvideSharedPlugin({ 
  72.    shareScope: this._shareScope, 
  73.    provides: this._provides 
  74.   }).apply(compiler); 
  75.  } 
  76. module.exports = SharePlugin; 

總結(jié)

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

其他:MF 生態(tài)

ExternalTemplateRemotesPlugin

有需求在構(gòu)建中使用上下文處理處理動(dòng)態(tài) Url 的,且需要解決緩存失效問(wèn)題的,可以看一下這個(gè)插件。

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

 

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

2022-04-13 08:04:40

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

2011-03-31 14:21:42

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

2012-07-24 09:16:19

郵箱技巧

2022-11-02 18:47:46

場(chǎng)景模塊化跨棧

2009-08-04 11:48:41

中國(guó)移動(dòng)云計(jì)算技術(shù)

2023-03-01 18:40:54

應(yīng)用程序代碼

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è)計(jì)移動(dòng)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-01-07 11:18:50

用戶(hù)畫(huà)像

2016-11-10 13:09:33

2019-05-13 14:17:06

抓包Web安全漏洞

2017-11-07 12:43:13

PythonC++語(yǔ)言

2019-10-18 09:50:47

網(wǎng)絡(luò)分層模型網(wǎng)絡(luò)協(xié)議

2021-10-26 14:35:10

架構(gòu)
點(diǎn)贊
收藏

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