Module Federation in Webpack5(上)

從提供依賴共享的第三方或者其餘的Webpack構建中import()模塊!運行時引入!歡迎來到Module Federation前端

Module Federation的起源項目爲webpack-external-import,現已經併入 Webpack5, v2.2.4爲最後獨立發佈的版本。react

原始文章信息

修改部分

  1. 原文爲PPT,章節爲本人劃分。
  2. 部分圖片轉換爲文字翻譯插入文章中。
  3. 我的添加的內容標記爲"注"。

動機

Module Federation的動機是爲了避免同開發小組間共同開發一個或者多個應用。webpack

應用將被劃分爲更小的應用塊,一個應用塊,能夠是好比"Header"或者"Sidebar"的前端組件,也能夠是邏輯組件好比"Data Fetching Logic"或者其餘業務邏輯。git

每一個應用塊由不一樣的組開發。github

應用應用塊共享其餘其餘應用塊或者庫。web

這也就是Micro Frontends(MFES)的開發模式。json

現有的方案

先看看現有的Webpack方案:架構

原生ESM模塊

  • 無需構建連接的模塊
  • 外部模塊採用原生加載

挑戰:異步

  • 沒有exports優化
  • 性能
    • 須要preloading
    • 高請求次數
    • 按需加載須要屢次往返
  • 只能支持ESM,不支持CommonJS, CSS和其餘資源文件

單個構建

  • 應用和應用塊一塊兒構建
  • 外部模塊在構建的時候肯定而且被引用

挑戰:ide

  • 每次更新都須要全量構建
    • 部署花費的時間很長
  • 多應用不在獨立
    • 或者應用間在運行時再也不共享公共模塊

DllPlugin

  • 應用塊被編譯成Dll
  • 應用構建時引用Dll
  • 外部模塊在運行時從Dll中被引用

挑戰:

  • 每次應用塊改變的時候,應用都須要從新構建
    • 額外的部署花費時間
    • 處理編譯時依賴的額外架構
  • 額外的Dll須要生成
    • 爲了共享依賴
    • 爲了手動優化使用過的模塊
  • 全部引用的Dll都須要額外的script標籤
  • Dll沒有按需加載

externals

  • 應用塊被構建成庫(一個暴露模塊一個入口)
  • 應用打包時聲明應用塊externals
  • 外部模塊在運行時被引用,好比從全局變量

挑戰:

  • 額外的庫須要建立
  • 爲了共享依賴
  • 全部引用的都externals須要額外的script標籤
  • externals沒有按需加載

總結

  • 原生ESM模塊在大規模應用中,web性能很差
  • 單個構建在大規模應用中,構建性能很差
  • Dll在多應用中伸縮性很差
  • Dllexternals都須要爲共享依賴作不少額外的人工處理

咱們須要一個可伸縮的解決方案,提供如下要求之間的取捨:

  • 良好的構建性能
  • 良好的Web性能
  • 解決依賴共享

因此咱們發明了Module Federation

Module Federation

使用Module Federation時,每一個應用塊都是一個獨立的構建,這些構建都將編譯爲容器

容器能夠被其餘應用或者其餘容器應用。

一個被引用的容器被稱爲remote, 引用者被稱爲hostremote暴露模塊給host, host則可使用這些暴露的模塊,這些模塊被成爲remote模塊

使用獨立構建,咱們能夠爲整個系統得到一個良好的構建性能。

概覽

![](https://user-gold-cdn.xitu.io/2020/6/19/172cb4ff1a4e243d?w=3059&h=1567&f=png&s=1406764)

看圖說話,這就是Module Federation概覽, 展現了2個概念: 暴露模塊和共享模塊。

容器經過異步的方式暴露模塊。你將在使用容器中的模塊前,請求容器加載(下載)你想要的模塊。異步暴露模塊將容許構建結果將不一樣的暴露模塊和他們的依賴一塊兒,放在不一樣的文件中。從而使得只有須要使用的模塊會被加載,可是容器依舊將不一樣的模塊一塊兒打包。並且也將使用webpack的chunk機制(vendor分割或者建立一個文件包含不一樣暴露模塊之間的公共依賴等).這將幫助咱們有效下降請求數量和下載大小,從而得到良好的Web性能。

容器的消費者(也就是應用者)須要能處理異步加載暴露模塊(注: 同步import代碼語義保持不變,可是運行時應轉換爲異步加載), Webpack在這裏作了特殊的處理,咱們後續會解釋。

圖上還展現了共享模塊的概念。 每個部分,好比容器, 應用,均可以在共享scope中,添加共享模塊(攜帶版本信息),同時也能夠從 共享scope中, 加載共享模塊(須要執行版本檢查)。 共享scope將會經過給每一個消費者提供版本要求內的最大可用版本的方式,對共享模塊進行冗餘剔除。

共享模塊依舊異步暴露和異步加載。因此提供共享模塊沒有額外的下載消耗,只有須要的共享的模塊將會被下載。

一個例子

上面是一個獨立構建的例子。

HomePage(Team A開發)使用了組件Dropdown(Team 開發)。HomePage按需引用了LoginModal(Team A開發),LoginModal使用了Button組件 (Team 開發)。

全局的全部模塊幾乎都依賴了react。

讓咱們放出Module Federation跑跑。

Team B


從Team B的角度,它只關心這些東西。

Team B但願構建一個容器,而且給予這些模塊標記。

Button和Dropdown是"Exposed",他們將能夠被其餘小組的人引用。react將是"Shared", 它將能夠被其餘小組的人共享。

如今輪到Webpack上場了

webpack將會爲這個容器生成一個容器入口. 這個模塊將包含全部暴露模塊和共享模塊的引用和加載方式。

每一個暴露模塊都和他們的依賴一塊兒,被放入獨立的文件。

每一個共享模塊也會被放入一個獨立的文件。

當從容器中加載Button時,只會加載button chunk和react chunk. Dropdown也是同理。

當加載Dropdown,可是其餘部分已經提供了另外一個版本的react(多是更高版本),則將會加載dropdown chunk以及其餘部分提供的react chunk(經過其餘部分請求,若是這個react chunk還沒有加載)。

Team A

如今來看看Team A如何使用Team B的這個容器

來自Team B的模塊並非直接被打包進來(注: 引入Team B提供的容器入口的時候),而是隻打包了remote module的標記。在運行時將會引用容器而且會從容器加載模塊。

webpack在這裏作了特殊處理,就是異步請求。一個常規的import是同步的,並不會等待模塊下載。webpack在這裏作了黑魔法,將全部的異步remote module的加載處理成了異步語義(就像import()). 此時,就能夠在加載本chunk的其餘普通模塊的同時,經過容器並行加載remote module

好比上圖中,當HomePage請求打開LoginModal的時候,LoginModal和Button的代碼將並行加載。

共享模塊也是相似。

ModuleFederationPlugin

咱們經過設置ModuleFederationPlugin來使用Module Federation,配置屬性少不了。

對於建立容器,重點是exposes屬性,這個屬性指明瞭這個容器的消費者將能夠獲取什麼模塊。能夠給暴露的模塊取名,同時要說明對應的內部模塊的入口。任何Webpack能處理的模塊它都支持,JS,TS,CSS, WebAssembly等等。

對於消費(引用)容器來講,要設置remotes屬性, 這個屬性是一個對象,包含了全部當前構建可用的容器。key值是當前代碼可訪問的容器暴露模塊的scope(注:就是前綴)。任何以key值開頭的模塊請求都在運行時請求remote module。值是容器的位置。默認狀況下,容器的入口來自script externals(注意: 引用的容器的entry文件經過script標籤的方式手動引入頁面)。固然也能夠指定URL、script文件,或者全局對象均可以。這個腳本(注意: 引用的容器的entry文件)在運行時會被加載,並且能夠從全局對象中訪問。

若是想共享模塊,就須要設置shared屬性。最簡單的例子就是列舉一堆共享的模塊名稱,他們將會以當前安裝的版本提供,而且被消費(注:被其餘人引用)時候會按照引用者package.json中的版本要求來加載。

每一個選項都有一些高級選項,一個值得注意的選項是共享模塊裏的singleton: true。它能確保運行時模塊的單例。這能知足有些庫好比react等不該初始化屢次的要求。這種狀況下,版本要求不符將會在運行時產生一個警告信息。

還有更多高級選項好比覆寫版本或者版本要求,關閉版本推斷,文件名,容許使用不一樣來源的庫或者外部依賴(NodeJS中),或者更嚴格版本警告(直接給錯誤而不是警告)。

相關文章
相關標籤/搜索