日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
探索Webpack5中的ModuleFederation

模塊共享的方案對比場景:目前擁有項(xiàng)目 A 和項(xiàng)目 B,我們發(fā)現(xiàn)它們存在一定的共性,比如公共 UI 組件、utils 等。那我們?nèi)绾瓮磉@些公共信息呢?

創(chuàng)新互聯(lián)公司是專業(yè)的雁江網(wǎng)站建設(shè)公司,雁江接單;提供網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站,網(wǎng)頁設(shè)計(jì),網(wǎng)站設(shè)計(jì),建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進(jìn)行雁江網(wǎng)站開發(fā)網(wǎng)頁制作和功能擴(kuò)展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團(tuán)隊(duì),希望更多企業(yè)前來合作!

簡單粗暴-CV大法

直接將項(xiàng)目 A 的組件,copy 到項(xiàng)目 B 中,這樣的方式有時(shí)候是比較快的,但也存在維護(hù)性極低的問題,后續(xù)兩個(gè)項(xiàng)目都各自維護(hù)一套。

抽象成 npm

我們可以將一些公共的模塊抽象成 npm,每個(gè)項(xiàng)目都去安裝該 npm 包,從而達(dá)到共享的目的

但是 npm 包的方式存在以下問題:

  • 編譯與構(gòu)建:一些公共的工具庫,框架以及 UI 庫存在重復(fù)構(gòu)建,造成性能低下。
  • 版本更新:需要各個(gè)項(xiàng)目都去升級。“發(fā)布 -> 通知 -> 更新” 的方式是比較低效率的。

CDN + webpack externals

跟 npm 類似,只不過將其上傳到 CDN,通過結(jié)合 webpack externals 進(jìn)行加載,除了上面提到的問題,還有 externals 沒有按需加載。

git submodule

子模塊允許你將一個(gè) Git 倉庫作為另一個(gè) Git 倉庫的子目錄。它能讓你將另一個(gè)倉庫克隆到自己的項(xiàng)目中,同時(shí)還保持提交的獨(dú)立。

其還是會存在重復(fù)構(gòu)建的問題,而且還會一定的上手成本。

相關(guān)的命令:

  • git submodule add <子模塊repository>: 添加子模塊。
  • git submodule update --recursive --remote :拉取所有子模塊的更新。

Module Federation 是什么?

官方文檔解釋其動機(jī)如下:

多個(gè)獨(dú)立的構(gòu)建可以組成一個(gè)應(yīng)用程序,這些獨(dú)立的構(gòu)建之間不應(yīng)該存在依賴關(guān)系,因此可以單獨(dú)開發(fā)和部署它們。這通常被稱作微前端,但不僅僅限于此。

Module federation 使 JavaScript 應(yīng)用得以從另一個(gè) JavaScript 應(yīng)用中動態(tài)地加載代碼,這就解決了我們上面提到的模塊共享的問題。

它不僅僅是微前端,而且場景粒度可以更加細(xì),一般微前端更多的是應(yīng)用級別,但它更偏向模塊級別的共享。

Module Federation 配置

在實(shí)戰(zhàn)之前,我們了解一下 Module Federation 的配置項(xiàng)

首先是兩個(gè)基礎(chǔ)角色的約定:

  • Host。消費(fèi)模塊的一方。
  • Remote。提供模塊的一方。

每個(gè)應(yīng)用都既可以作為 host,也可以作為 remote。

Module Federation 配置項(xiàng)如下:

  • name: 必須且唯一。
  • filename: 若沒有提供 filename,那么構(gòu)建生成的文件名與容器名稱同名。
  • remotes: 可選,作為引用方最關(guān)鍵的配置項(xiàng),用于聲明需要引用的遠(yuǎn)程資源包的名稱與模塊名稱,作為 Host 時(shí),去消費(fèi)哪些 Remote。
  • exposes: 可選,表示作為 Remote 時(shí),export 哪些屬性被消費(fèi)。
  • library: 可選,定義了 remote 應(yīng)用如何將輸出內(nèi)容暴露給 host 應(yīng)用。配置項(xiàng)的值是一個(gè)對象,如 { type: 'xxx', name: 'xxx'}。
  • shared,可選,指示 remote 應(yīng)用的輸出內(nèi)容和 host 應(yīng)用可以共用哪些依賴。shared 要想生效,則 host 應(yīng)用和 remote 應(yīng)用的 shared 配置的依賴要一致。

Singleton: 是否開啟單例模式。默認(rèn)值為 false,開啟后remote 應(yīng)用組件和 host 應(yīng)用共享的依賴只加載一次,而且是兩者中版本比較高的。

requiredVersion:指定共享依賴的版本,默認(rèn)值為當(dāng)前應(yīng)用的依賴版本。

eager:共享依賴在打包過程中是否被分離為 async chunk。設(shè)置為 true, 共享依賴會打包到 main、remoteEntry,不會被分離,因此當(dāng)設(shè)置為true時(shí)共享依賴是沒有意義的。

實(shí)戰(zhàn)演示

這里我們用 Github 中 Module Federation Examples[1]進(jìn)行演示。這里包含了基礎(chǔ)的用法、高級用法以及和一些框架的結(jié)合實(shí)踐。

注:該倉庫使用 lerna 維護(hù)。所以你需要安裝 lerna。

npm install lerna -g

通過 lerna bootstrap 安裝依賴。

簡單示例

來看 basic-host-remote 目錄下有兩個(gè)獨(dú)立的 project,分別為 app1 和 app2。其中 app2 中實(shí)現(xiàn)了一個(gè) Button 組件,現(xiàn)在 app1 要用這個(gè) Button 組件。

import React from 'react';

const Button = () => ;

export default Button;

app2 暴露組件

此時(shí),app2 的角色就是 Remote,核心 webpack 配置:

const { ModuleFederationPlugin } = require('webpack').container;
// ...

plugins: [
new ModuleFederationPlugin({
name: 'app2',
library: { type: 'var', name: 'app2' },
filename: 'remoteEntry.js', // 生成的文件名
exposes: {
'./Button': './src/Button', // Export Button 組件
},
// 共享 react 和 react-dom
shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
}),
],
// ...

app1 消費(fèi)組件

此時(shí),app1 的角色是 Host,webpack 核心配置:

const { ModuleFederationPlugin } = require('webpack').container;
// ...

//http://localhost:3002/remoteEntry.js
plugins: [
new ModuleFederationPlugin({
name: 'app1',
remotes: {
// http://localhost:3002/remoteEntry.js
// 上面配置生成的模塊文件
app2: `app2@${getRemoteEntryUrl(3002)}`,
},
// 共享模塊
shared: { react: { singleton: true }, 'react-dom': { singleton: true } },
}),
],
// ...

模塊使用:

const RemoteButton = React.lazy(() => import('app2/Button'));

const App = () => (

Basic Host-Remote


App 1






);

export default App;

效果

而且可以看到 react 和 react-dom 也是加載了一次:

高級示例-動態(tài)加載遠(yuǎn)程模塊

假如初始化的時(shí)候,不加載遠(yuǎn)程的模塊,在一定的交互之后再去加載遠(yuǎn)程模塊,該怎么實(shí)現(xiàn)呢?

本示例在 advanced-api/dynamic-remotes 中可以找到。

示例中有三個(gè) project,app1/app2/app3。app1 是 Host,消費(fèi) app2 和 app3 提供的組件,而且點(diǎn)擊相應(yīng)按鈕的時(shí)候才去加載對應(yīng)的遠(yuǎn)程模塊。另外 app2 和 app3 都用到了 moment.js。

app2 和 app3 暴露模塊

兩個(gè) project 的配置是相似的,都是暴露了 Widget 組件,而且都同享了 react 和 react-dom,以及 moment.js。這里可以留意的是,假如不聲明 requiredVersion,就會使用它能找到的當(dāng)前大版本中最高的 version。

const deps = require('./package.json').dependencies;
// ...
new ModuleFederationPlugin({
name: 'app3',
library: { type: 'var', name: 'app3' },
filename: 'remoteEntry.js',
exposes: {
'./Widget': './src/Widget',
},
// adds react as shared module
// version is inferred from package.json
// there is no version check for the required version
// so it will always use the higher version found
shared: {
react: {
requiredVersion: deps.react,
import: 'react', // the "react" package will be used a provided and fallback module
shareKey: 'react', // under this name the shared module will be placed in the share scope
shareScope: 'default', // share scope with this name will be used
singleton: true, // only a single version of the shared module is allowed
},
'react-dom': {
requiredVersion: deps['react-dom'],
singleton: true, // only a single version of the shared module is allowed
},
// adds moment as shared module
// version is inferred from package.json
// it will use the highest moment version that is>=2.24and 小于 3
moment: deps.moment,
},
})

app1 消費(fèi)模塊

app1 作為 Host,這里都是常規(guī)配置,不再贅述。

主要來看它負(fù)責(zé)動態(tài)加載的代碼,在點(diǎn)擊相應(yīng)的按鈕的時(shí)候,會觸發(fā) useFederatedComponent 方法,入?yún)⒅?remoteUrl 為遠(yuǎn)程地址,scope 為對應(yīng)應(yīng)用名稱,module 為指定的模塊。其中 useDynamicScript 負(fù)責(zé)加載的遠(yuǎn)程 JavaScript 腳本,加載完成之后,通過 loadComponent 方法動態(tài)加載組件。

export const useFederatedComponent = (remoteUrl, scope, module) => {
const key = `${remoteUrl}-${scope}-${module}`;
const [Component, setComponent] = React.useState(null);

const { ready, errorLoading } = useDynamicScript(remoteUrl);
React.useEffect(() => {
if (Component) setComponent(null);
// Only recalculate when key changes
}, [key]);

React.useEffect(() => {
if (ready && !Component) {
const Comp = React.lazy(loadComponent(scope, module));
componentCache.set(key, Comp);
setComponent(Comp);
}
// key includes all dependencies (scope/module)
}, [Component, ready, key]);

return { errorLoading, Component };
};

再來重點(diǎn)看下 loadComponent,其中 __webpack_init_sharing__ ,進(jìn)行了初始化共享作用域,用提供的已知此構(gòu)建和所有遠(yuǎn)程的模塊填充它。然后獲得遠(yuǎn)程容器 container,支持 get 和 init 方法。init 是一個(gè)兼容 async 的方法,調(diào)用時(shí),只含有一個(gè)參數(shù):共享作用域?qū)ο?shared scope object)——__webpack_share_scopes__.default。最后調(diào)用容器的 get 方法,獲取到對應(yīng)的模塊。

function loadComponent(scope, module) {
return async () => {
// Initializes the share scope. This fills it with known provided modules from this build and all remotes
// 初始化共享作用域(shared scope)用提供的已知此構(gòu)建和所有遠(yuǎn)程的模塊填充它
await __webpack_init_sharing__('default');
const container = window[scope]; // or get the container somewhere else
// 初始化容器 它可能提供共享模塊
await container.init(__webpack_share_scopes__.default);
const factory = await window[scope].get(module);
const Module = factory();
return Module;
};
}

效果演示

  • 點(diǎn)擊不同的按鈕,加載不同的組件。
  • moment.js 在首次加載后不用再重新加載。

你可以通過動態(tài)加載的方式,提供一個(gè)共享模塊的不同版本,從而實(shí)現(xiàn) A/B 測試。

Module Federation 的問題

談了這么多 Module Federation 的優(yōu)點(diǎn),我們來看看它有哪些缺點(diǎn)

  • 對環(huán)境要求略高,需要使用 webpack5,舊項(xiàng)目改造成本大。
  • 拆分粒度需要權(quán)衡,雖然能做到依賴共享,但是被共享的 lib 不能做 tree-shaking,也就是說如果共享了一個(gè) lodash,那么整個(gè) lodash 庫都會被打包到 shared-chunk 中。
  • Webpack 為了支持加載 remote 模塊對 runtime 做了大量改造,在運(yùn)行時(shí)要做的事情也因此陡然增加,可能會對我們頁面的運(yùn)行時(shí)性能造成負(fù)面影響。
  • 運(yùn)行時(shí)共享也是一把雙刃劍,如何去做版本控制以及控制共享模塊的影響是需要去考慮的問題。

對于問題1,未來應(yīng)該會慢慢變好。問題2 感覺還好,場景應(yīng)該不會特別多,而且相比于共享模塊,不重復(fù)編譯的優(yōu)點(diǎn)來講,相對可以接受。問題3,感受不大。

問題4,算是比較頭疼的一件事,比如幾個(gè)項(xiàng)目,都需要版本 react/react-dom/antd 的版本一致,假如版本更新的話,怎么辦?

我們可以使用 Module Federation 的能力,將一些核心的依賴?yán)?react、react-dom、antd,使用一個(gè) remote 服務(wù)維護(hù),然后每個(gè)項(xiàng)目分別引用這個(gè)服務(wù)導(dǎo)出的 library。我們只需要維護(hù)這個(gè) remote 服務(wù)上依賴的版本,就能保證每個(gè)項(xiàng)目核心依賴的版本是一致的,而且升級的時(shí)候,也不用每個(gè)項(xiàng)目自己升級,大大提升了效率。

總結(jié)

使用 Module Federation,我們可以在一個(gè)應(yīng)用中動態(tài)加載并執(zhí)行另一個(gè)應(yīng)用的代碼,且與技術(shù)棧無關(guān),并且能夠共享模塊,從而減小編譯時(shí)間以及降低包體積。

但在使用 Module Federation 的時(shí)候也需要權(quán)衡模塊拆分的粒度以及做好版本的控制。

參考

  • 深入探索Webpack5之Module Federation的“奇淫技巧”[2]
  • 官網(wǎng) Module Federation[3]
  • 淺析 Webpack Module Federation 在 React.js 中的實(shí)踐[4]

參考資料

[1]Module Federation Examples: https://github.com/module-federation/module-federation-examples

[2]深入探索Webpack5之Module Federation的“奇淫技巧”: https://juejin.cn/post/6938975818659921957

[3]官網(wǎng) Module Federation: https://webpack.docschina.org/concepts/module-federation/

[4]淺析 Webpack Module Federation 在 React.js 中的實(shí)踐: https://juejin.cn/post/7012990703714172964


新聞名稱:探索Webpack5中的ModuleFederation
鏈接URL:http://www.dlmjj.cn/article/dhijcee.html