W3Cschool
恭喜您成為首批注冊用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
多個(gè)獨(dú)立的構(gòu)建可以組成一個(gè)應(yīng)用程序,這些獨(dú)立的構(gòu)建之間不應(yīng)該存在依賴關(guān)系,因此可以單獨(dú)開發(fā)和部署它們。
這通常被稱作微前端,但并不僅限于此。
我們區(qū)分本地模塊和遠(yuǎn)程模塊。本地模塊即為普通模塊,是當(dāng)前構(gòu)建的一部分。遠(yuǎn)程模塊不屬于當(dāng)前構(gòu)建,并在運(yùn)行時(shí)從所謂的容器加載。
加載遠(yuǎn)程模塊被認(rèn)為是異步操作。當(dāng)使用遠(yuǎn)程模塊時(shí),這些異步操作將被放置在遠(yuǎn)程模塊和入口之間的下一個(gè) chunk 的加載操作中。如果沒有 chunk 加載操作,就不能使用遠(yuǎn)程模塊。
chunk 的加載操作通常是通過調(diào)用 ?import()
? 實(shí)現(xiàn)的,但也支持像 ?require.ensure
? 或 ?require([...])
? 之類的舊語法。
容器是由容器入口創(chuàng)建的,該入口暴露了對特定模塊的異步訪問。暴露的訪問分為兩個(gè)步驟:
步驟 1 將在 chunk 加載期間完成。步驟 2 將在與其他(本地和遠(yuǎn)程)的模塊交錯(cuò)執(zhí)行期間完成。這樣一來,執(zhí)行順序不受模塊從本地轉(zhuǎn)換為遠(yuǎn)程或從遠(yuǎn)程轉(zhuǎn)為本地的影響。
容器可以嵌套使用,容器可以使用來自其他容器的模塊。容器之間也可以循環(huán)依賴。
每個(gè)構(gòu)建都充當(dāng)一個(gè)容器,也可將其他構(gòu)建作為容器。通過這種方式,每個(gè)構(gòu)建都能夠通過從對應(yīng)容器中加載模塊來訪問其他容器暴露出來的模塊。
共享模塊是指既可重寫的又可作為向嵌套容器提供重寫的模塊。它們通常指向每個(gè)構(gòu)建中的相同模塊,例如相同的庫。
packageName 選項(xiàng)允許通過設(shè)置包名來查找所需的版本。默認(rèn)情況下,它會自動(dòng)推斷模塊請求,當(dāng)想禁用自動(dòng)推斷時(shí),請將 requiredVersion 設(shè)置為 false 。
該插件使用指定的公開模塊來創(chuàng)建一個(gè)額外的容器入口。
該插件將特定的引用添加到作為外部資源(externals)的容器中,并允許從這些容器中導(dǎo)入遠(yuǎn)程模塊。它還會調(diào)用這些容器的 ?override
? API 來為它們提供重載。本地的重載(當(dāng)構(gòu)建也是一個(gè)容器時(shí),通過 ?__webpack_override__
? 或 ?override
? API)和指定的重載被提供給所有引用的容器。
?ModuleFederationPlugin
? 組合了 ?ContainerPlugin
? 和 ?ContainerReferencePlugin
?。
config.context
?requiredVersion
?requiredVersion
?單頁應(yīng)用的每個(gè)頁面都是在單獨(dú)的構(gòu)建中從容器暴露出來的。主體應(yīng)用程序(application shell)也是獨(dú)立構(gòu)建,會將所有頁面作為遠(yuǎn)程模塊來引用。通過這種方式,可以單獨(dú)部署每個(gè)頁面。在更新路由或添加新路由時(shí)部署主體應(yīng)用程序。主體應(yīng)用程序?qū)⒊S脦於x為共享模塊,以避免在頁面構(gòu)建中出現(xiàn)重復(fù)。
許多應(yīng)用程序共享一個(gè)通用的組件庫,可以將其構(gòu)建成暴露所有組件的容器。每個(gè)應(yīng)用程序使用來自組件庫容器的組件??梢詥为?dú)部署對組件庫的更改,而不需要重新部署所有應(yīng)用程序。應(yīng)用程序自動(dòng)使用組件庫的最新版本。
該容器接口支持 ?get
? 和 ?init
? 方法。 ?init
? 是一個(gè)兼容 ?async
? 的方法,調(diào)用時(shí),只含有一個(gè)參數(shù):共享作用域?qū)ο?shared scope object)。此對象在遠(yuǎn)程容器中用作共享作用域,并由 host 提供的模塊填充。 可以利用它在運(yùn)行時(shí)動(dòng)態(tài)地將遠(yuǎn)程容器連接到 host 容器。
init.js
(async () => {
// 初始化共享作用域(shared scope)用提供的已知此構(gòu)建和所有遠(yuǎn)程的模塊填充它
await __webpack_init_sharing__('default');
const container = window.someContainer; // 或從其他地方獲取容器
// 初始化容器 它可能提供共享模塊
await container.init(__webpack_share_scopes__.default);
const module = await container.get('./module');
})();
容器嘗試提供共享模塊,但是如果共享模塊已經(jīng)被使用,則會發(fā)出警告,并忽略所提供的共享模塊。容器仍能將其作為降級模塊。
你可以通過動(dòng)態(tài)加載的方式,提供一個(gè)共享模塊的不同版本,從而實(shí)現(xiàn) A/B 測試。
例子:
init.js
function loadComponent(scope, module) {
return async () => {
// 初始化共享作用域(shared scope)用提供的已知此構(gòu)建和所有遠(yuǎn)程的模塊填充它
await __webpack_init_sharing__('default');
const container = window[scope]; // 或從其他地方獲取容器
// 初始化容器 它可能提供共享模塊
await container.init(__webpack_share_scopes__.default);
const factory = await window[scope].get(module);
const Module = factory();
return Module;
};
}
loadComponent('abtests', 'test123');
一般來說,remote 是使用 URL 配置的,示例如下:
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
app1: 'app1@http://localhost:3001/remoteEntry.js',
},
}),
],
};
但是你也可以向 remote 傳遞一個(gè) promise,其會在運(yùn)行時(shí)被調(diào)用。你應(yīng)該用任何符合上面描述的 get/init 接口的模塊來調(diào)用這個(gè) promise。例如,如果你想傳遞你應(yīng)該使用哪個(gè)版本的聯(lián)邦模塊,你可以通過一個(gè)查詢參數(shù)做以下事情:
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
app1: `promise new Promise(resolve => {
const urlParams = new URLSearchParams(window.location.search)
const version = urlParams.get('app1VersionParam')
// This part depends on how you plan on hosting and versioning your federated modules
const remoteUrlWithVersion = 'http://localhost:3001/' + version + '/remoteEntry.js'
const script = document.createElement('script')
script.src = remoteUrlWithVersion
script.onload = () => {
// the injected script has loaded and is available on window
// we can now resolve this Promise
const proxy = {
get: (request) => window.app1.get(request),
init: (arg) => {
try {
return window.app1.init(arg)
} catch(e) {
console.log('remote container already initialized')
}
}
}
resolve(proxy)
}
// inject this script with the src set to the versioned remoteEntry.js
document.head.appendChild(script);
})
`,
},
// ...
}),
],
};
請注意當(dāng)使用該 API 時(shí),你 必須 resolve 一個(gè)包含 get/init API 的對象。
可以允許 host 在運(yùn)行時(shí)通過公開遠(yuǎn)程模塊的方法來設(shè)置遠(yuǎn)程模塊的 publicPath。
當(dāng)你在 host 域的子路徑上掛載獨(dú)立部署的子應(yīng)用程序時(shí),這種方法特別有用。
場景:
你在 ?https://my-host.com/app/*
?上有一個(gè) host 應(yīng)用,并且在 ?https://foo-app.com
? 上有一個(gè)子應(yīng)用。子應(yīng)用程序也掛載在 host 域上, 因此, ?https://foo-app.com
? 可以通過 ?https://my-host.com/app/foo-app
? 訪問,并且 ?https://my-host.com/app/foo-app/*
? 可以通過代理重定向到 ?https://foo-app.com/*
?。
示例:
webpack.config.js (remote)
module.exports = {
entry: {
remote: './public-path',
},
plugins: [
new ModuleFederationPlugin({
name: 'remote', // 該名稱必須與入口名稱相匹配
exposes: ['./public-path'],
// ...
}),
],
};
public-path.js (remote)
export function set(value) {
__webpack_public_path__ = value;
}
src/index.js (host)
const publicPath = await import('remote/public-path');
publicPath.set('/your-public-path');
//bootstrap app e.g. import('./bootstrap.js')
在運(yùn)行時(shí),可以從 document.currentScript.src
的腳本標(biāo)簽中推斷出 publicPath
,并使用 __webpack_public_path__
模塊變量進(jìn)行設(shè)置。
示例:
webpack.config.js (remote)
module.exports = {
entry: {
remote: './setup-public-path',
},
plugins: [
new ModuleFederationPlugin({
name: 'remote', // 該名稱必須與入口名稱相匹配
// ...
}),
],
};
setup-public-path.js (remote)
// 使用你自己的邏輯派生 publicPath,并使用 __webpack_public_path__ API 設(shè)置它
__webpack_public_path__ = document.currentScript.src + '/../';
應(yīng)用程序正急切地執(zhí)行一個(gè)作為全局主機(jī)運(yùn)行的應(yīng)用程序。有如下選項(xiàng)可供選擇:
你可以在模塊聯(lián)邦的高級 API 中將依賴設(shè)置為即時(shí)依賴,此 API 不會將模塊放在異步 chunk 中,而是同步地提供它們。這使得我們在初始塊中可以直接使用這些共享模塊。但是要注意,由于所有提供的和降級模塊是要異步下載的,因此,建議只在應(yīng)用程序的某個(gè)地方提供它,例如 shell。
我們強(qiáng)烈建議使用異步邊界(asynchronous boundary)。它將把初始化代碼分割成更大的塊,以避免任何額外的開銷,以提高總體性能。
例如,你的入口看起來是這樣的:
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
讓我們創(chuàng)建 bootstrap.js 文件,并將入口文件的內(nèi)容放到里面,然后將 bootstrap 引入到入口文件中:
index.js
+ import('./bootstrap');
- import React from 'react';
- import ReactDOM from 'react-dom';
- import App from './App';
- ReactDOM.render(<App />, document.getElementById('root'));
bootstrap.js
+ import React from 'react';
+ import ReactDOM from 'react-dom';
+ import App from './App';
+ ReactDOM.render(<App />, document.getElementById('root'));
這種方法有效,但存在局限性或缺點(diǎn)。
通過 ?ModuleFederationPlugin
? 將依賴的 ?eager
? 屬性設(shè)置為 ?true
?
webpack.config.js
// ...
new ModuleFederationPlugin({
shared: {
...deps,
react: {
eager: true,
},
},
});
錯(cuò)誤提示中可能不會顯示 ?"./Button"
?,但是信息看起來差不多。這個(gè)問題通常會出現(xiàn)在將 webpack beta.16 升級到 webpack beta.17 中。
在 ModuleFederationPlugin 里,更改 exposes:
new ModuleFederationPlugin({
exposes: {
- 'Button': './src/Button'
+ './Button':'./src/Button'
}
});
此處錯(cuò)誤可能是丟失了遠(yuǎn)程容器,請確保在使用前添加它。 如果已為試圖使用遠(yuǎn)程服務(wù)器的容器加載了容器,但仍然看到此錯(cuò)誤,則需將主機(jī)容器的遠(yuǎn)程容器文件也添加到 HTML 中。
如果你想從不同的 remote 中加載多個(gè)模塊,建議為你的遠(yuǎn)程構(gòu)建設(shè)置 ?output.uniqueName
? 以避免多個(gè) webpack 運(yùn)行時(shí)之間的沖突。
Copyright©2021 w3cschool編程獅|閩ICP備15016281號-3|閩公網(wǎng)安備35020302033924號
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號
聯(lián)系方式:
更多建議: