Dynamically load remoteEntry.js files

Alex O'Callaghan

動態載入 remoteEntry.js 檔案

原文由 Alex O'Callaghan 發布,訂閱此部落格

Webpack Module Federation 可用於支援微前端(micro-frontend)網頁應用程式架構。只要載入 remoteEntry.js 檔案,就能載入獨立部署的遠端應用程式。動態載入這些檔案能讓你更有效地掌控效能與快取,並實現 A/B 測試或分支發布等功能。

Mintel,我們在一個作為外殼的「host」應用程式中運用 Module Federation,該應用程式會載入由各個獨立、自主的團隊所維護的不同應用程式。關於我們導入此架構的歷程,你可以在〈"Adopting a micro-frontend architecture"〉一文中閱讀更多細節。

在預設情況下,Webpack Module Federation 會為每個遠端載入 remoteEntry.js 檔案。隨著團隊陸續開發新的應用程式,我們的 host 累積了大量的遠端。我們其實不需要一次全部載入——每個應用程式都能獨立運作,使用者也經常只在其中一個應用程式中操作,而不會跨應用程式切換。一次載入這麼多 remoteEntry.js 檔案,已經對應用程式的初始載入時間造成了影響。

透過 Dynamic Remote Containers 這種做法,你可以自行掌控 remoteEntry.js 檔案的載入時機。

ModuleFederationPlugin 設定中移除 remotes 欄位:

plugins: [
  new ModuleFederationPlugin({
    name: "host-app",
    remotes: {},
  }),
];

在你自己的程式碼中,透過注入 <script> 標籤來手動載入 remoteEntry.js

const remoteEntryUrl = "https://something/remote-app/remoteEntry.js";
const scope = "remote-app";
const moduleName = "App";

await __webpack_init_sharing__("default");

await new Promise<void>((resolve, reject) => {
  const element = document.createElement("script");

  element.src = remoteEntryUrl;
  element.type = "text/javascript";
  element.async = true;

  element.onload = () => {
    element.parentElement?.removeChild(element);
    resolve();
  };

  element.onerror = (err) => {
    element.parentElement?.removeChild(element);
    reject(err);
  };

  document.head.appendChild(element);
});

// Initialize the federated module
const container: any = window[scope as any];
await container.init(__webpack_share_scopes__.default);

// Fetch module exposed by the federated module
const factory = await container.get(moduleName);
return factory();

module-federation-import-remote 套件可以幫你處理這些邏輯:

import { importRemote } from "module-federation-import-remote";

importRemote({ url: "https://something/remote-app", scope: "remote-app", module: "App" }).then((App) => {...});

// If App is a React component you can use it with lazy and Suspense just like a dynamic import:
const App = lazy(() => importRemote({ url: "https://something/remote-app", scope: "remote-app", module: "App" }));

return (
  <Suspense fallback={<div>Loading App...</div>}>
    <App />
  </Suspense>
);

採用這種做法後,我們得以透過將 remoteEntry.js 的載入延後到真正需要渲染某個 React 元件時才執行,來改善初始載入效能。這種對遠端載入方式的掌控,也讓你能在執行期間動態載入不同的 remoteEntry.js 檔案,例如根據功能旗標(feature flag)或使用者屬性來決定。

importRemote 預設還會在 remoteEntry.js 的網址上加上用於清除快取的查詢參數(cache-busting query param),以盡量避免這些進入點被快取。請留意透過查詢字串清除快取的限制,如這篇 GitHub 討論串中所討論的——你可能還需要考慮 CDN 的設定,或是引入一套服務來協助管理遠端的版本控管。

延伸閱讀:

本文章由 muse-spark-1.2-contributor 進行翻譯

留言