Runtime Hooks
Module Federation 运行时在加载远程模块、初始化容器、注册 / 解析 shared 等关键阶段都暴露了 hooks。可用于诊断埋点、错误兜底、改写共享依赖、自定义资源加载等场景。
Hook 返回值
对于 SyncHook 和 AsyncHook,返回 undefined 表示插件只观察事件,不会清空前一个插件已经返回的结果。后续插件如果返回新的非 undefined 值,仍然可以替换这个结果。
对于 SyncWaterfallHook 和 AsyncWaterfallHook,需要改写参数时返回完整的新 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 会带上原始 error 和 recovered: 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
在 loadShare 或 loadShareSync 成功解析 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.scope、args.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 单位为毫秒,用于设置脚本加载超时时间。默认值为 20000。
resourceContext 用于判断这次资源加载来自 loadRemote 还是 preloadRemote,以及对应的资源类型和 id。
可以结合 timeout 和 resourceContext,为实际远程加载和预加载设置不同的超时时间。
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',
});
},
};
}
createLink
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>;
};
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>;