多個獨立的建置應該形成單一的應用程式。這些獨立的建置就像容器,可以在建置之間公開和使用程式碼,建立單一的統一應用程式。
這通常稱為微前端,但並不限於此。
我們區分本地和遠端模組。本地模組是常規模組,是目前建置的一部分。遠端模組不是目前建置的一部分,但會在執行階段從遠端容器載入。
載入遠端模組被視為非同步作業。使用遠端模組時,這些非同步作業會放置在遠端模組和進入點之間的下一個區塊載入作業中。沒有區塊載入作業就無法使用遠端模組。
區塊載入作業通常是 import()
呼叫,但 require.ensure
或 require([...])
等較舊的建構也受支援。
容器是透過容器進入點建立的,容器進入點公開對特定模組的非同步存取。公開的存取分為兩個步驟
步驟 1 會在區塊載入期間完成。步驟 2 會在模組評估期間與其他 (本地和遠端) 模組交錯進行。這樣,評估順序就不會受到將模組從本地轉換為遠端或反之亦然而影響。
可以巢狀容器。容器可以使用其他容器的模組。容器之間的循環相依性也是可能的。
每個建置都充當容器,並將其他建置當成容器使用。這樣,每個建置都能透過從其容器載入,來存取任何其他公開的模組。
共用模組是既可覆寫,又可作為覆寫提供給巢狀容器的模組。它們通常在每個建置中指向同一個模組,例如同一個函式庫。
packageName
選項允許設定套件名稱以尋找 requiredVersion
。預設會自動推斷模組要求,將 requiredVersion
設定為 false
時,會停用自動推斷。
此外掛程式會建立一個額外的容器項目,其中包含指定的公開模組。
此外掛程式會將特定參考新增至容器作為外部程式,並允許從這些容器匯入遠端模組。它也會呼叫這些容器的 override
API,以提供覆寫給它們。本機覆寫(透過 __webpack_override__
或 override
API,當建置也是容器時)和指定的覆寫會提供給所有參考的容器。
ModuleFederationPlugin
結合了 ContainerPlugin
和 ContainerReferencePlugin
。
config.context
。requiredVersion
。requiredVersion
。/
的模組要求將符合所有具有此字首的模組要求。單頁式應用程式的每個頁面都從容器組建中公開,並在獨立的組建中公開。應用程式外殼也是獨立的組建,將所有頁面視為遠端模組。這樣每個頁面都可以獨立部署。當路由更新或新增新的路由時,會部署應用程式外殼。應用程式外殼會將常用的函式庫定義為共用模組,以避免在頁面組建中重複使用這些函式庫。
許多應用程式共用一個元件程式庫,該程式庫可以建置為一個容器,其中會公開每個元件。每個應用程式都會從元件程式庫容器中使用元件。可以個別部署元件程式庫的變更,而不需要重新部署所有應用程式。應用程式會自動使用元件程式庫的最新版本。
容器介面支援 `get` 和 `init` 方法。`init` 是 `async` 相容的方法,會呼叫一個引數:共用範圍物件。此物件用作遠端容器中的共用範圍,並會填入主機提供的模組。可以在執行階段動態地將遠端容器連線到主機容器。
init.js
(async () => {
// Initializes the shared scope. Fills it with known provided modules from this build and all remotes
await __webpack_init_sharing__('default');
const container = window.someContainer; // or get the container somewhere else
// Initialize the container, it may provide shared modules
await container.init(__webpack_share_scopes__.default);
const module = await container.get('./module');
})();
容器會嘗試提供共用模組,但如果已使用共用模組,系統會顯示警告,且會忽略提供的共用模組。容器仍可能將其用作備用選項。
如此一來,您可以動態載入提供不同版本共用模組的 A/B 測試。
範例
init.js
function loadComponent(scope, module) {
return async () => {
// Initializes the shared scope. Fills it with known provided modules from this build and all remotes
await __webpack_init_sharing__('default');
const container = window[scope]; // or get the container somewhere else
// Initialize the container, it may provide shared modules
await container.init(__webpack_share_scopes__.default);
const factory = await window[scope].get(module);
const Module = factory();
return Module;
};
}
loadComponent('abtests', 'test123');
一般而言,遠端會使用 URL 組態,如下例所示
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
app1: 'app1@https://127.0.0.1:3001/remoteEntry.js',
},
}),
],
};
但您也可以將 Promise 傳遞給此遠端,它將在執行期間解析。您應該使用符合上述所述的 get/init
介面的任何模組來解析此 Promise。例如,如果您想透過查詢參數傳入應使用的聯合模組版本,您可以執行類似下列的動作
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 = 'https://127.0.0.1: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);
})
`,
},
// ...
}),
],
};
請注意,在使用此 API 時,您必須解析包含 get/init API 的物件。
透過公開遠端模組的方法,主機可以設定遠端模組的 publicPath。
當您在主機網域的子路徑上掛載獨立部署的子應用程式時,此方法特別有用。
場景
您有一個主機應用程式,其網址為 https://my-host.com/app/*
,而子應用程式則位於 https://foo-app.com
。子應用程式也掛載在主機網域上,因此預期 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 (遠端)
module.exports = {
entry: {
remote: './public-path',
},
plugins: [
new ModuleFederationPlugin({
name: 'remote', // this name needs to match with the entry name
exposes: ['./public-path'],
// ...
}),
],
};
public-path.js (遠端)
export function set(value) {
__webpack_public_path__ = value;
}
src/index.js (主機)
const publicPath = await import('remote/public-path');
publicPath.set('/your-public-path');
//bootstrap app e.g. import('./bootstrap.js')
可以從 document.currentScript.src
中的腳本標籤推論出 publicPath,並在執行階段使用 __webpack_public_path__
模組變數設定它。
範例
webpack.config.js (遠端)
module.exports = {
entry: {
remote: './setup-public-path',
},
plugins: [
new ModuleFederationPlugin({
name: 'remote', // this name needs to match with the entry name
// ...
}),
],
};
setup-public-path.js(遠端)
// derive the publicPath with your own logic and set it with the __webpack_public_path__ API
__webpack_public_path__ = document.currentScript.src + '/../';
Uncaught Error: 共享模組無法急切使用
應用程式急切執行一個作為全方位主機運作的應用程式。有以下選項可供選擇
您可以在 Module Federation 的進階 API 中將相依性設定為急切,這不會將模組放入非同步區塊中,而是同步提供它們。這讓我們可以在初始區塊中使用這些共享模組。但請小心,因為所有提供的和後備模組都將永遠下載。建議僅在應用程式的某一點提供它,例如 shell。
我們強烈建議使用非同步邊界。它會分割較大區塊的初始化程式碼,以避免任何額外的往返行程,並在一般情況下提升效能。
例如,您的入口看起來像這樣
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
讓我們建立 bootstrap.js
檔案,並將入口的內容移入其中,然後將該 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'));
這個方法有效,但可能會有限制或缺點。
透過 ModuleFederationPlugin
設定相依性的 eager: true
webpack.config.js
// ...
new ModuleFederationPlugin({
shared: {
...deps,
react: {
eager: true,
},
},
});
Uncaught Error: 模組「./Button」在容器中不存在。
它可能不會顯示為 "./Button"
,但錯誤訊息看起來會很類似。如果你從 webpack beta.16 升級到 webpack beta.17,通常會看到這個問題。
在 ModuleFederationPlugin 中,將 exposes 從下列變更:
new ModuleFederationPlugin({
exposes: {
- 'Button': './src/Button'
+ './Button':'./src/Button'
}
});
Uncaught TypeError: fn 不是函式
你可能缺少遠端容器,請務必將其加入。如果你已為你嘗試使用的遠端載入容器,但仍然看到這個錯誤,請將主機容器的遠端容器檔案也加入 HTML。
如果你要從不同遠端載入多個模組,建議為你的遠端組建設定 output.uniqueName
選項,以避免多個 webpack 執行時間之間的衝突。