
導(dǎo)讀:Federated Modules 是一個(gè)令人激動(dòng)的功能,它可能會(huì)改變未來幾年的前端打包方式,作者深入分析了 Module Federation 的原理及其應(yīng)用場(chǎng)景,希望能對(duì)大家有所啟發(fā)。 WHAT(Module Federation 是什么?)Module Federation [?fed??re??n] 使 JavaScript 應(yīng)用得以在客戶端或服務(wù)器上動(dòng)態(tài)運(yùn)行另一個(gè) bundle 的代碼。
這其中的關(guān)鍵點(diǎn)是:

一些相關(guān)的概念:
一個(gè)應(yīng)用可以是 Host,也可以是 Remote,也可以同時(shí)是 Host 和 Remote。

通過回答 Module Federation 如何運(yùn)轉(zhuǎn)?Host 如何消費(fèi) Remote?以及 Remote 如何優(yōu)先使用 Host shared 的依賴?這三個(gè)問題,我們分析一下 Module Federation 的原理。 整體是通過 ModuleFederationPlugin(https://github.com/webpack/webpack/blob/dev-1/lib/container/ModuleFederationPlugin.js)這個(gè)插件串聯(lián)起來的。
配置示例: new ModuleFederationPlugin({
name: 'app-1',
library: { type: 'var', name: 'app_1' },
filename: 'remoteEntry.js',
remotes: {
app_02: 'app_02',
app_03: 'app_03',
},
exposes: {
antd: './src/antd',
button: './src/button',
},
shared: ['react', 'react-dom'],
}),
配置屬性:
name,必須,唯一 ID,作為輸出的模塊名,使用的時(shí)通過 ${name}/${expose} 的方式使用;
library,必須,其中這里的 name 為作為 umd 的 name; remotes,可選,表示作為 Host 時(shí),去消費(fèi)哪些 Remote; exposes,可選,表示作為 Remote 時(shí),export 哪些屬性被消費(fèi); shared,可選,優(yōu)先用 Host 的依賴,如果 Host 沒有,再用自己的;

產(chǎn)物:

所以比如下面如圖示例的應(yīng)用集群:

加載方式應(yīng)該這樣: <script src='C/remoteEntry.js'></script>
<script src='B/remoteEntry.js'></script>
<script src='A/main.js'></script
C/remoteEntry.js 和 B/remoteEntry 的順序沒有要求,只要在 A/main.js 之前就好了。
可以通過代碼示例來進(jìn)行理解。
B 源碼: // src/react.js
export * from 'react';
// webpack.config.js
...
exposes: {
react: './src/react',
},
A 源碼:
// 異步加載 B 的 react 模塊
const React = await import('B/react');
B 構(gòu)建產(chǎn)物:
// windows 變量
let B;
const moduleMap = {
'react': () => {
return Promise.all([e('a'), e('b'), e('c')]),
},
};
B = {
get(moduleId) {
return moduleMap(moduleId);
}
}
A 構(gòu)建產(chǎn)物:
const modules = {
'B': () => {
return B;
}
};
// 異步獲取模塊 export 內(nèi)容
function e(moduleId) {
// 1. 取 shared 的模塊
// 2. 取 remote 的模塊
const idToExternalAndNameMapping = {
'B/react': ['B', 'react'],
};
// 從 module B 里取 react
const data = idToExternalAndNameMapping[moduleId];
__webpack_require__(data[0]).get(data[1]);
// 3. 取當(dāng)前項(xiàng)目的異步模塊
}
// 初始化
e('B/react');
這其中的原理:
再看如下兩個(gè)代碼示例。
B 構(gòu)建產(chǎn)物:
let B;
__webpack_require__.Overrides = {};
function e(moduleId) {
// 1. 取 shared 的模塊
// 當(dāng)前項(xiàng)目的 shared 模塊列表
const fallbackMapping = {};
// 先從 Overrides 里取,再從當(dāng)前項(xiàng)目里取
push_require_try(__webpack_require__.Overrides[moduleId] || fallbackMapping[moduleId]);
// 2. 取 remote 的模塊
// 3. 取當(dāng)前項(xiàng)目的異步模塊
}
B = {
override(override) {
Object.assign(__webpack_require__.Overrides, override);
}
}
A 構(gòu)建產(chǎn)物:
B.override(Object.assign({
'react': () => {
// A 的 react 內(nèi)容
},
}, __webpack_require__.Overrides));
原理分析:
這樣,B 里面在 require react 時(shí),就會(huì)用 A 的 react 模塊。
WHY(它的應(yīng)用場(chǎng)景有哪些?)Module Federation 可以用在哪里?

如上圖,這是去年畫的一張微前端的圖,其中最下面的 “公共依賴加載” 一直是沒有非常優(yōu)雅的方案。

方法一:讓每個(gè)子應(yīng)用都分開打包,主應(yīng)用不管,這樣不會(huì)有問題,但問題就是尺寸大,而且大了不是一點(diǎn)點(diǎn)。
方法二:主應(yīng)用包含 antd 和 react,子應(yīng)用如果版本一致不打包 react 和 antd,版本不一致就自己打一份,但有幾個(gè)問題:
antd 和 react 是通過 umd 的方式同步載入的,主應(yīng)用初始化會(huì)比較慢; 主應(yīng)用升級(jí)了 antd 的時(shí)候,所有子應(yīng)用可能需要一起升級(jí),這個(gè)成本就很大了。
方法三:利用 Module Federation 的 shared 能力,子應(yīng)用的依賴如果和主應(yīng)用匹配,那么,能解決方法二里的第一個(gè)問題,但第二個(gè)問題依舊解不了。
方法四:利用 Module Federation 的 remotes 能力,再提一個(gè)應(yīng)用專門提供庫被消費(fèi),看起來前面的問題都能解。

有沒有感覺技術(shù)又輪回到了 seajs + spmjs 的時(shí)代。
微前端是應(yīng)用集群的解法之一,但不是唯一方案。
現(xiàn)狀是,通過 npm 共享組件。

基于 Module Federation,除通過 npm 共享依賴,還可以有運(yùn)行時(shí)的依賴、組件、頁面甚至應(yīng)用的直接共享。

這樣一來,靈活性就非常大了,可以在應(yīng)用的各個(gè)層面做共享。A 應(yīng)用引用 B 整個(gè)應(yīng)用,也可以應(yīng)用 B 的的頁面和組件,還可以提一個(gè)庫應(yīng)用,做 npm 依賴的運(yùn)行時(shí)共享。
我們大部分場(chǎng)景不是微前端或應(yīng)用集群,Module Federation 還可以幫助我們干什么?
現(xiàn)在項(xiàng)目組織和文件依賴通常是這樣:

現(xiàn)狀是:
期望的是:
node_modules 下的提前打包好,通過 runtime 的方式引; 本地調(diào)試和編譯時(shí)只打項(xiàng)目文件; 快,根據(jù)項(xiàng)目復(fù)雜度可提升到 1s - 7s 之內(nèi);
為什么不是其他的編譯速度優(yōu)化方案?

舉一個(gè)對(duì)比的例子,比如 external,我們之前還有做過自動(dòng)的 external 方案,雖然他也可能顯著提速,但有以下問題:
以空間換時(shí)間,依賴包全量引用導(dǎo)致 npm,用在生產(chǎn)上會(huì)犧牲部分產(chǎn)品體驗(yàn),需權(quán)衡; 不是所有的依賴都有 umd 包,覆蓋率不夠; npm 可能有依賴,比如 antd 依賴 react 和 moment,那么 react 和 moment 也得 external 并且在html 里引用他們; 需要手動(dòng)修改 html 里的引用,維護(hù)上有成本提升。
更多參考:
關(guān)注「Alibaba F2E」 把握阿里巴巴前端新動(dòng)向
|