Runtime Hooks

Module Federation 运行时在加载远程模块、初始化容器、注册 / 解析 shared 等关键阶段都暴露了 hooks。可用于诊断埋点、错误兜底、改写共享依赖、自定义资源加载等场景。

Hook 返回值

对于 SyncHookAsyncHook,返回 undefined 表示插件只观察事件,不会清空前一个插件已经返回的结果。后续插件如果返回新的非 undefined 值,仍然可以替换这个结果。

对于 SyncWaterfallHookAsyncWaterfallHook,需要改写参数时返回完整的新 args 对象;返回 undefined 会保留当前 args,并继续传给下一个插件。

beforeInit

SyncWaterfallHook

在 MF 实例初始化之前更新对应 init 配置

function beforeInit(args: BeforeInitOptions): BeforeInitOptions;

type BeforeInitOptions = {
  userOptions: UserOptions;
  options: ModuleFederationRuntimeOptions;
  origin: ModuleFederation;
  shareInfo: ShareInfos;
};

interface ModuleFederationRuntimeOptions {
  id?: string;
  name: string;
  version?: string;
  remotes: Array<Remote>;
  shared: ShareInfos;
  plugins: Array<ModuleFederationRuntimePlugin>;
  inBrowser: boolean;
}

init

SyncHook

在 MF 实例初始化后调用

function init(args: InitOptions): void;

type InitOptions = {
  options: ModuleFederationRuntimeOptions;
  origin: ModuleFederation;
};

beforeRequest

AsyncWaterfallHook

在解析 remote 路径前调用,对于在查找之前更新某些内容很有用。

async function beforeRequest(
  args: BeforeRequestOptions,
): Promise<BeforeRequestOptions>;

type BeforeRequestOptions = {
  id: string;
  options: ModuleFederationRuntimeOptions;
  origin: ModuleFederation;
};

afterMatchRemote

AsyncHook

在 runtime 把一次 loadRemote 请求匹配到具体 remote 之后触发。如果匹配失败,也会带着 error 触发。适合用于诊断、链路追踪,以及判断问题是否发生在 manifest 或 remoteEntry 加载之前。

async function afterMatchRemote(args: AfterMatchRemoteOptions): Promise<void>;

type AfterMatchRemoteOptions = {
  id: string;
  options: ModuleFederationRuntimeOptions;
  remote?: Remote;
  expose?: string;
  remoteInfo?: RemoteInfo;
  error?: unknown;
  origin: ModuleFederation;
};

afterResolve

AsyncWaterfallHook

在解析 remote 路径后调用,允许修改解析后的内容。

async function afterResolve(
  args: AfterResolveOptions,
): Promise<AfterResolveOptions>;

type AfterResolveOptions = {
  id: string;
  pkgNameOrAlias: string;
  expose: string;
  remote: Remote;
  options: ModuleFederationRuntimeOptions;
  origin: ModuleFederation;
  remoteInfo: RemoteInfo;
  remoteSnapshot?: ModuleInfo;
};

onLoad

AsyncHook

加载 remote 后触发,允许访问和修改已加载文件的导出 expose。

async function onLoad(args: OnLoadOptions): Promise<void>;

type OnLoadOptions = {
  id: string;
  expose: string;
  pkgNameOrAlias: string;
  remote: Remote;
  options: ModuleOptions;
  origin: ModuleFederation;
  exposeModule: any;
  exposeModuleFactory: any;
  moduleInstance: Module;
};

type ModuleOptions = {
  remoteInfo: RemoteInfo;
  host: ModuleFederation;
};

interface RemoteInfo {
  name: string;
  version?: string;
  buildVersion?: string;
  entry: string;
  type: RemoteEntryType;
  entryGlobalName: string;
  shareScope: string;
}

afterLoadRemote

AsyncHook

在一次 loadRemote 请求结束后触发。加载成功、加载失败、以及通过 errorLoadRemote 兜底恢复的场景都会触发。适合记录 remote 加载的最终状态。

成功加载时,onLoad 会先于 afterLoadRemote 触发。如果加载失败后由 errorLoadRemote 返回兜底结果,afterLoadRemote 会带上原始 errorrecovered: true

async function afterLoadRemote(args: AfterLoadRemoteOptions): Promise<void>;

type AfterLoadRemoteOptions = {
  id: string;
  expose?: string;
  remote?: RemoteInfo;
  options?: {
    loadFactory?: boolean;
    from?: 'build' | 'runtime';
  };
  error?: unknown;
  recovered?: boolean;
  origin: ModuleFederation;
};

beforeInitContainer

AsyncWaterfallHook

在 host(消费者)调用 remoteEntry.init(...) 之前触发,可用于动态改写本次初始化使用的 shareScope / initScope,以及传给 remote 的 remoteEntryInitOptions

async function beforeInitContainer(
  args: BeforeInitContainerOptions,
): Promise<BeforeInitContainerOptions>;

type BeforeInitContainerOptions = {
  shareScope: ShareScopeMap[string];
  initScope: InitScope;
  remoteEntryInitOptions: RemoteEntryInitOptions;
  remoteInfo: RemoteInfo;
  origin: ModuleFederation;
};

initContainer

AsyncWaterfallHook

remoteEntry.init(...) 调用成功后触发。可用于在容器初始化完成后执行观测、埋点或补充处理。

async function initContainer(
  args: InitContainerOptions,
): Promise<InitContainerOptions>;

type InitContainerOptions = {
  shareScope: ShareScopeMap[string];
  initScope: InitScope;
  remoteEntryInitOptions: RemoteEntryInitOptions;
  remoteInfo: RemoteInfo;
  remoteEntryExports: RemoteEntryExports;
  origin: ModuleFederation;
  id?: string;
  remoteSnapshot?: ModuleInfo;
};

handlePreloadModule

SyncHook

处理 remotes 的预加载逻辑。

function handlePreloadModule(args: HandlePreloadModuleOptions): void;

type HandlePreloadModuleOptions = {
  id: string;
  name: string;
  remote: Remote;
  remoteSnapshot: ModuleInfo;
  preloadConfig: PreloadRemoteArgs;
  origin: ModuleFederation;
};

errorLoadRemote

AsyncHook

如果加载 remotes 失败,则调用,从而启用自定义错误处理。可返回自定义的兜底逻辑。

async function errorLoadRemote(
  args: ErrorLoadRemoteOptions,
): Promise<void | unknown>;

type ErrorLoadRemoteOptions = {
  id: string;
  error: unknown;
  options?: any;
  from: 'build' | 'runtime';
  lifecycle: 'beforeRequest' | 'beforeLoadShare' | 'afterResolve' | 'onLoad';
  remote?: RemoteInfo;
  expose?: string;
  origin: ModuleFederation;
};

lifecycle 表示错误发生阶段:

  • beforeRequest: 处理 remote 请求参数阶段出错
  • afterResolve: 解析/拉取 manifest 阶段出错(常见于网络异常)
  • onLoad: 加载 exposes 模块阶段出错
  • beforeLoadShare: shared 初始化过程中加载 remoteEntry 出错

如果这个 hook 返回兜底模块,runtime 会使用这个值继续执行。如果诊断或日志插件返回 undefined,不会清空其他插件已经返回的兜底结果。

import { createInstance } from '@module-federation/enhanced/runtime';

import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime';

const fallbackPlugin: () => ModuleFederationRuntimePlugin = function () {
  return {
    name: 'fallback-plugin',
    errorLoadRemote(args) {
      const fallback = 'fallback';
      return fallback;
    },
  };
};

const mf = createInstance({
  name: 'mf_host',
  remotes: [
    {
      name: 'remote',
      alias: 'app1',
      entry: 'http://localhost:2001/mf-manifest.json',
    },
  ],
  plugins: [fallbackPlugin()],
});

mf.loadRemote('app1/un-existed-module').then((mod) => {
  expect(mod).toEqual('fallback');
});

beforeLoadShare

AsyncWaterfallHook

在加载 shared 之前调用,可用于修改对应的 shared 配置

async function beforeLoadShare(
  args: BeforeLoadShareOptions,
): Promise<BeforeLoadShareOptions>;

type BeforeLoadShareOptions = {
  pkgName: string;
  shareInfo?: Shared;
  shared: Options['shared'];
  origin: ModuleFederation;
};

afterLoadShare

SyncHook

loadShareloadShareSync 成功解析 shared 依赖后触发。适合观察最终选中了哪个提供方和版本。

function afterLoadShare(args: AfterLoadShareOptions): void;

type AfterLoadShareOptions = {
  pkgName: string;
  shareInfo?: Partial<Shared>;
  selectedShared?: Partial<Shared>;
  shared: Options['shared'];
  shareScopeMap: ShareScopeMap;
  lifecycle: 'loadShare' | 'loadShareSync';
  origin: ModuleFederation;
};

errorLoadShare

SyncHook

在 shared 依赖解析失败,或者无法选中可用 shared 依赖时触发。适合诊断 shared 缺失、版本不匹配、eager 配置错误等问题。

function errorLoadShare(args: ErrorLoadShareOptions): void;

type ErrorLoadShareOptions = {
  pkgName: string;
  shareInfo?: Partial<Shared>;
  shared: Options['shared'];
  shareScopeMap: ShareScopeMap;
  lifecycle: 'loadShare' | 'loadShareSync';
  origin: ModuleFederation;
  error?: unknown;
  recovered?: boolean;
};

initContainerShareScopeMap

SyncWaterfallHook

在 host(消费者)初始化 remote(生产者)共享池映射时触发,可用于对齐/重定向某个 scope 的共享池对象(例如把 scope1 直接指向 default,让两者共用同一套共享依赖池)。

function initContainerShareScopeMap(
  args: InitContainerShareScopeMapOptions,
): InitContainerShareScopeMapOptions;

type InitContainerShareScopeMapOptions = {
  shareScope: ShareScopeMap[string];
  options: Options;
  origin: ModuleFederation;
  scopeName: string;
  hostShareScopeMap?: ShareScopeMap;
};

resolveShare

SyncWaterfallHook

允许改写最终选中的共享模块结果。

resolveShare 触发时,运行时已经先选好了候选的 scope 和 version。这个阶段单独修改 args.scopeargs.version 之类的字段,并不会自动影响最终结果。要真正改掉最终使用的 shared,需要改写 args.resolver,让它返回你希望使用的那条记录。

function resolveShare(args: ResolveShareOptions): ResolveShareOptions;

type ResolveShareOptions = {
  shareScopeMap: ShareScopeMap;
  scope: string;
  pkgName: string;
  version: string;
  shareInfo: Shared;
  GlobalFederation: Federation;
  resolver: () =>
    | {
        shared: Shared;
        useTreesShaking: boolean;
      }
    | undefined;
};
import {
  createInstance,
  loadRemote,
} from '@module-federation/enhanced/runtime';

import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime';

const customSharedPlugin: () => ModuleFederationRuntimePlugin = function () {
  return {
    name: 'custom-shared-plugin',
    resolveShare(args) {
      const { pkgName, shareScopeMap } = args;

      if (pkgName !== 'react') {
        return args;
      }

      const fallbackShared = shareScopeMap.default?.react?.['17.0.0'];
      if (!fallbackShared) {
        return args;
      }

      args.resolver = function () {
        return {
          shared: fallbackShared,
          useTreesShaking: false,
        };
      };
      return args;
    },
  };
};

const mf = createInstance({
  name: 'mf_host',
  shared: {
    react: {
      version: '17.0.0',
      scope: 'default',
      lib: () => React,
      shareConfig: {
        singleton: true,
        requiredVersion: '^17.0.0',
      },
    },
  },
  plugins: [customSharedPlugin()],
});

mf.loadShare('react').then((reactFactory) => {
  expect(reactFactory()).toEqual(React);
});

beforePreloadRemote

AsyncHook

在预加载处理程序执行任何预加载逻辑之前调用

async function beforePreloadRemote(
  args: BeforePreloadRemoteOptions,
): Promise<void>;

type BeforePreloadRemoteOptions = {
  preloadOps: Array<PreloadRemoteArgs>;
  options: Options;
  origin: ModuleFederation;
};

generatePreloadAssets

AsyncHook

用于控制生成需要预加载的资源

async function generatePreloadAssets(
  args: GeneratePreloadAssetsOptions,
): Promise<PreloadAssets>;

type GeneratePreloadAssetsOptions = {
  origin: ModuleFederation;
  preloadOptions: PreloadOptions[number];
  remote: Remote;
  remoteInfo: RemoteInfo;
  remoteSnapshot: ModuleInfo;
  globalSnapshot: GlobalModuleInfo;
};

interface PreloadAssets {
  cssAssets: Array<string>;
  jsAssetsWithoutEntry: Array<string>;
  entryAssets: Array<EntryAssets>;
}

loaderHook

loaderHook 用于拦截资源加载与工厂获取流程。

beforeInitRemote

AsyncHook

在调用 remoteEntry.init(...) 初始化 remote 容器之前触发。

async function beforeInitRemote(args: BeforeInitRemoteOptions): Promise<void>;

type BeforeInitRemoteOptions = {
  id?: string;
  remoteInfo: RemoteInfo;
  remoteSnapshot?: ModuleInfo;
  origin: ModuleFederation;
};

afterInitRemote

AsyncHook

在 remote 容器初始化成功或失败后触发。如果 remote 已经初始化过,会带上 cached: true

async function afterInitRemote(args: AfterInitRemoteOptions): Promise<void>;

type AfterInitRemoteOptions = {
  id?: string;
  remoteInfo: RemoteInfo;
  remoteSnapshot?: ModuleInfo;
  remoteEntryExports?: RemoteEntryExports;
  error?: unknown;
  cached?: boolean;
  origin: ModuleFederation;
};

beforeGetExpose

AsyncHook

在调用 remoteEntry.get(expose) 之前触发。

async function beforeGetExpose(args: BeforeGetExposeOptions): Promise<void>;

type BeforeGetExposeOptions = {
  id: string;
  expose: string;
  moduleInfo: RemoteInfo;
  remoteEntryExports: RemoteEntryExports;
  origin: ModuleFederation;
};

afterGetExpose

AsyncHook

remoteEntry.get(expose) 成功或失败后触发。

async function afterGetExpose(args: AfterGetExposeOptions): Promise<void>;

type AfterGetExposeOptions = {
  id: string;
  expose: string;
  moduleInfo: RemoteInfo;
  remoteEntryExports: RemoteEntryExports;
  moduleFactory?: () => unknown | Promise<unknown>;
  error?: unknown;
  origin: ModuleFederation;
};

beforeExecuteFactory

AsyncHook

在执行 exposed module factory 之前触发。loadRemote 使用 loadFactory: false 时不会触发。

async function beforeExecuteFactory(
  args: BeforeExecuteFactoryOptions,
): Promise<void>;

type BeforeExecuteFactoryOptions = {
  id: string;
  expose: string;
  moduleInfo: RemoteInfo;
  loadFactory: boolean;
  origin: ModuleFederation;
};

afterExecuteFactory

AsyncHook

在 exposed module factory 执行成功或失败后触发。loadRemote 使用 loadFactory: false 时不会触发。

async function afterExecuteFactory(
  args: AfterExecuteFactoryOptions,
): Promise<void>;

type AfterExecuteFactoryOptions = {
  id: string;
  expose: string;
  moduleInfo: RemoteInfo;
  loadFactory: boolean;
  exposeModule?: unknown;
  error?: unknown;
  origin: ModuleFederation;
};

createScript

SyncHook

用于修改加载资源时的 script

function createScript(args: CreateScriptOptions): CreateScriptHookReturn;

type CreateScriptOptions = {
  url: string;
  attrs?: Record<string, any>;
  remoteInfo?: RemoteInfo;
  resourceContext?: ResourceLoadContext;
};

type CreateScriptHookReturn =
  | HTMLScriptElement
  | { script?: HTMLScriptElement; timeout?: number }
  | void;

type ResourceLoadContext = {
  initiator: 'loadRemote' | 'preloadRemote';
  id: string;
  resourceType: 'manifest' | 'remoteEntry' | 'js' | 'css';
  url?: string;
};

timeout 单位为毫秒,用于设置脚本加载超时时间。默认值为 20000resourceContext 用于判断这次资源加载来自 loadRemote 还是 preloadRemote,以及对应的资源类型和 id。 可以结合 timeoutresourceContext,为实际远程加载和预加载设置不同的超时时间。

import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime';

const changeScriptAttributePlugin: () => ModuleFederationRuntimePlugin =
  function () {
    return {
      name: 'change-script-attribute',
      createScript({ url }) {
        if (url === testRemoteEntry) {
          let script = document.createElement('script');
          script.src = testRemoteEntry;
          script.setAttribute('loader-hooks', 'isTrue');
          script.setAttribute('crossorigin', 'anonymous');
          return {
            script,
            timeout: 30000,
          };
        }
      },
    };
  };

fetch

AsyncHook

fetch 函数允许自定义获取清单(manifest)JSON 的请求。成功的 Response 必须返回一个有效的 JSON。

function fetch(
  manifestUrl: string,
  requestInit: RequestInit,
  remoteInfo?: RemoteInfo,
  resourceContext?: ResourceLoadContext,
): Promise<Response> | void | false;

resourceContext 可用于判断这次 manifest 请求来自 loadRemote 还是 preloadRemote

  • 示例:在获取清单(manifest)JSON 时包含凭证:
import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime';

// fetch-manifest-with-credentials-plugin.ts
export default function (): FederationRuntimePlugin {
  return {
    name: 'fetch-manifest-with-credentials-plugin',
    fetch(manifestUrl, requestInit) {
      return fetch(manifestUrl, {
        ...requestInit,
        credentials: 'include',
      });
    },
  };
}

SyncHook

用于修改预加载/样式加载时创建的 link 元素。

function createLink(args: CreateLinkOptions): HTMLLinkElement | void;

type CreateLinkOptions = {
  url: string;
  attrs?: Record<string, any>;
  remoteInfo?: RemoteInfo;
  resourceContext?: ResourceLoadContext;
};

type CreateLinkHookReturn =
  | HTMLLinkElement
  | { link?: HTMLLinkElement; timeout?: number }
  | void;

resourceContext 的结构和 createScript 一致,可用于区分预加载的 JS/CSS、实际加载的 remoteEntry,以及它们对应的资源 id。 timeout 单位为毫秒,用于设置 link 加载超时时间。默认值为 20000

示例:让实际加载 remoteEntry 时等待更久,让低优先级预加载资源更快结束。

import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime';

const resourceTimeoutPlugin = (): ModuleFederationRuntimePlugin => ({
  name: 'resource-timeout-plugin',
  createScript({ resourceContext }) {
    if (
      resourceContext?.initiator === 'loadRemote' &&
      resourceContext.resourceType === 'remoteEntry'
    ) {
      return {
        timeout: 30000,
      };
    }
  },
  createLink({ resourceContext }) {
    if (resourceContext?.initiator === 'preloadRemote') {
      return {
        timeout: 5000,
      };
    }
  },
});

loadEntryError

AsyncHook

在 remoteEntry 加载失败(通常是脚本加载异常)时触发,可用于重试或自定义兜底。

async function loadEntryError(
  args: LoadEntryErrorOptions,
): Promise<Promise<RemoteEntryExports | undefined> | undefined>;

type LoadEntryErrorOptions = {
  getRemoteEntry: typeof getRemoteEntry;
  origin: ModuleFederation;
  remoteInfo: RemoteInfo;
  remoteEntryExports?: RemoteEntryExports;
  globalLoading: Record<string, Promise<void | RemoteEntryExports> | undefined>;
  uniqueKey: string;
};

afterLoadEntry

AsyncHook

在 remoteEntry 加载成功、失败,或被 loadEntryError 恢复之后触发。

async function afterLoadEntry(args: AfterLoadEntryOptions): Promise<void>;

type AfterLoadEntryOptions = {
  origin: ModuleFederation;
  remoteInfo: RemoteInfo;
  remoteEntryExports?: RemoteEntryExports | false | void;
  error?: unknown;
  recovered?: boolean;
};

getModuleFactory

AsyncHook

在调用 remoteEntry.get(expose) 前触发,可自定义模块工厂获取逻辑。

async function getModuleFactory(
  args: GetModuleFactoryOptions,
): Promise<(() => Promise<Module>) | undefined>;

type GetModuleFactoryOptions = {
  remoteEntryExports: RemoteEntryExports;
  expose: string;
  moduleInfo: RemoteInfo;
};

loadEntry

asyncHook

loadEntry 函数允许对 remotes 进行完全自定义,从而可以扩展并创建新的 remote 类型。以下两个简单示例分别演示了如何加载 JSON 数据以及模块代理(module delegation)。

function loadEntry(args: LoadEntryOptions): RemoteEntryExports | void;

type LoadEntryOptions = {
  createScriptHook: SyncHook;
  remoteEntryExports?: RemoteEntryExports;
  remoteInfo: RemoteInfo;
};
interface RemoteInfo {
  name: string;
  version?: string;
  buildVersion?: string;
  entry: string;
  type: RemoteEntryType;
  entryGlobalName: string;
  shareScope: string;
}
export type RemoteEntryExports = {
  get: (id: string) => () => Promise<Module>;
  init: (
    shareScope: ShareScopeMap[string],
    initScope?: InitScope,
    remoteEntryInitOPtions?: RemoteEntryInitOptions,
  ) => void | Promise<void>;
};
  • 示例:加载 JSON 数据
import { init } from '@module-federation/enhanced/runtime';

import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime';

// load-json-data-plugin.ts
const changeScriptAttributePlugin: () => FederationRuntimePlugin = function () {
  return {
    name: 'load-json-data-plugin',
    loadEntry({ remoteInfo }) {
      if (remoteInfo.jsonA === 'jsonA') {
        return {
          init(shareScope, initScope, remoteEntryInitOPtions) {},
          async get(path) {
            const json = await fetch(remoteInfo.entry + '.json').then((res) =>
              res.json(),
            );
            return () => ({
              path,
              json,
            });
          },
        };
      }
    },
  };
};
// module-federation-config
{
  remotes: {
    jsonA: "jsonA@https://cdn.jsdelivr.net/npm/@module-federation/runtime/package";
  }
}
// src/bootstrap.js
import jsonA from 'jsonA';
jsonA; // {...json data}
  • 示例:模块代理(Delegate Modules)
import { init } from '@module-federation/enhanced/runtime';

import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime';

// delegate-modules-plugin.ts
const changeScriptAttributePlugin: () => FederationRuntimePlugin = function () {
  return {
    name: 'delegate-modules-plugin',
    loadEntry({ remoteInfo }) {
      if (remoteInfo.name === 'delegateModulesA') {
        return {
          init(shareScope, initScope, remoteEntryInitOPtions) {},
          async get(path) {
            path = path.replace('./', '');
            const { [path]: factory } = await import('./delegateModulesA.js');
            const result = await factory();
            return () => result;
          },
        };
      }
    },
  };
};
// ./src/delegateModulesA.js
export async function test1() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('test1 value');
    }, 3000);
  });
}
export async function test2() {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('test2 value');
    }, 3000);
  });
}
// module-federation-config
{
  remotes: {
    delegateModulesA: 'delegateModulesA@https://delegateModulesA.js';
  }
}
// src/bootstrap.js
import test1 from 'delegateModulesA/test1';
import test2 from 'delegateModulesA/test2';
test1; // "test1 value"
test2; // "test2 value"

bridgeHook

bridgeHook 用于桥接渲染/销毁阶段(如 React/Vue bridge)扩展上下文。

beforeBridgeRender

SyncHook

在桥接渲染前触发,可返回对象扩展渲染参数(例如追加 extraProps)。

function beforeBridgeRender(
  args: Record<string, any>,
): void | Record<string, any>;

afterBridgeRender

SyncHook

在桥接渲染后触发。

function afterBridgeRender(
  args: Record<string, any>,
): void | Record<string, any>;

beforeBridgeDestroy

SyncHook

在桥接销毁前触发。

function beforeBridgeDestroy(
  args: Record<string, any>,
): void | Record<string, any>;

afterBridgeDestroy

SyncHook

在桥接销毁后触发。

function afterBridgeDestroy(
  args: Record<string, any>,
): void | Record<string, any>;