Runtime Hooks
Hook return values
For SyncHook and AsyncHook, returning undefined means the plugin only observes the event. It does not clear a value returned by an earlier plugin. A later plugin can still replace that value by returning another non-undefined value.
For SyncWaterfallHook and AsyncWaterfallHook, return the full updated args object when you need to change the payload. Returning undefined keeps the current args and passes them to the next plugin.
beforeInit
SyncWaterfallHook
Updates the corresponding init configuration before the MF instance is initialized.
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
Called after the MF instance is initialized.
function init(args: InitOptions): void
type InitOptions ={
options: ModuleFederationRuntimeOptions;
origin: ModuleFederation;
}
beforeRequest
AsyncWaterfallHook
Called before resolving the remote path, useful for updating something before lookup.
async function beforeRequest(args: BeforeRequestOptions): Promise<BeforeRequestOptions>
type BeforeRequestOptions ={
id: string;
options: ModuleFederationRuntimeOptions;
origin: ModuleFederation;
}
afterMatchRemote
AsyncHook
Called after the runtime matches a loadRemote request to a configured remote. If matching fails, the hook is still called with error. It is useful for diagnostics, request tracing, and checking whether a failure happened before manifest or remoteEntry loading.
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
Called after resolving the remote path, allowing modification of the resolved content.
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
Triggered once a federated module is loaded, allowing access and modification to the exports of the loaded file.
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
Called after a loadRemote request finishes. It runs for successful loads, failed loads, and loads recovered by errorLoadRemote. Use it to record the final remote loading status.
onLoad runs before afterLoadRemote on a successful load. If a load fails and errorLoadRemote returns a fallback, afterLoadRemote receives the original error and 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
Triggered before the host calls remoteEntry.init(...). Useful for dynamically rewriting the shareScope / initScope used in this initialization and the remoteEntryInitOptions passed to the remote.
async function beforeInitContainer(args: BeforeInitContainerOptions): Promise<BeforeInitContainerOptions>
type BeforeInitContainerOptions ={
shareScope: ShareScopeMap[string];
initScope: InitScope;
remoteEntryInitOptions: RemoteEntryInitOptions;
remoteInfo: RemoteInfo;
origin: ModuleFederation;
}
initContainer
AsyncWaterfallHook
Triggered after remoteEntry.init(...) completes successfully. Useful for metrics, observability, or post-initialization customization.
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
Handles the preloading logic for remotes.
function handlePreloadModule(args: HandlePreloadModuleOptions): void
type HandlePreloadModuleOptions ={
id: string;
name: string;
remote: Remote;
remoteSnapshot: ModuleInfo;
preloadConfig: PreloadRemoteArgs;
origin: ModuleFederation;
}
errorLoadRemote
AsyncHook
Called if loading remotes fails, enabling custom error handling. Can return a custom fallback logic.
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;
}
The lifecycle parameter indicates the stage where the error occurred:
beforeRequest: Error during initial request processing
afterResolve: Error during manifest loading (most common for network failures)
onLoad: Error during module loading and execution
beforeLoadShare: Error while loading a remoteEntry during shared dependency initialization
If this hook returns a fallback module, the runtime uses that value to continue. If a diagnostics or logging plugin returns undefined, it does not clear a fallback returned by another plugin.
import { createInstance, loadRemote } from '@module-federation/enhanced/runtime'
import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime';
const fallbackPlugin: () => ModuleFederationRuntimePlugin =
function () {
return {
name: 'fallback-plugin',
errorLoadRemote(args) {
const { lifecycle, id, error } = args;
if (error) {
console.warn(`Failed to load remote ${id} at ${lifecycle}:`, error?.message || error);
}
switch (lifecycle) {
case 'afterResolve':
return {
id: id || 'fallback',
name: id || 'fallback',
metaData: { /* fallback manifest */ },
shared: [],
remotes: [],
exposes: []
};
case 'beforeRequest':
console.warn(`Request processing failed for ${id}`);
return void 0;
case 'onLoad':
return () => ({
__esModule: true,
default: () => 'Fallback Component'
});
case 'beforeLoadShare':
console.warn(`Shared dependency loading failed for ${id}`);
return () => ({
__esModule: true,
default: {}
});
default:
console.warn(`Unknown lifecycle ${lifecycle} for ${id}`);
return void 0;
}
},
};
};
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
Called before loading shared, can be used to modify the corresponding shared configuration.
async function beforeLoadShare(args: BeforeLoadShareOptions): Promise<BeforeLoadShareOptions>
type BeforeLoadShareOptions ={
pkgName: string;
shareInfo?: Shared;
shared: Options['shared'];
origin: ModuleFederation;
}
afterLoadShare
SyncHook
Called after a shared dependency is successfully resolved by loadShare or loadShareSync. Use it to observe which provider and version were selected.
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
Called when shared dependency resolution fails or cannot select a valid shared dependency. It is useful for diagnostics around missing shared dependencies, version mismatch, and eager configuration errors.
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
Triggered when the host aligns a remote’s share-scope mapping. Useful for redirecting one scope to another (for example, aliasing scope1 to default so they share the same pool).
function initContainerShareScopeMap(args: InitContainerShareScopeMapOptions): InitContainerShareScopeMapOptions
type InitContainerShareScopeMapOptions ={
shareScope: ShareScopeMap[string];
options: Options;
origin: ModuleFederation;
scopeName: string;
hostShareScopeMap?: ShareScopeMap;
}
resolveShare
SyncWaterfallHook
Allows overriding the final shared module selection result.
resolveShare runs after the runtime has already picked the candidate scope and version. At this point, changing fields like scope or version on args does not change the final result by itself. To switch to a different shared module, replace args.resolver and return the shared record you want to use.
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
Called before the preload handler executes any preload logic.
async function beforePreloadRemote(args: BeforePreloadRemoteOptions): Promise<void>
type BeforePreloadRemoteOptions ={
preloadOps: Array<PreloadRemoteArgs>;
options: Options;
origin: ModuleFederation;
}
generatePreloadAssets
AsyncHook
Used to control the generation of resources that need to be preloaded.
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 is used to intercept resource loading and module-factory resolution.
beforeInitRemote
AsyncHook
Called before initializing the remote container with remoteEntry.init(...).
async function beforeInitRemote(args: BeforeInitRemoteOptions): Promise<void>
type BeforeInitRemoteOptions ={
id?: string;
remoteInfo: RemoteInfo;
remoteSnapshot?: ModuleInfo;
origin: ModuleFederation;
}
afterInitRemote
AsyncHook
Called after remote container initialization succeeds or fails. When the remote was already initialized, cached is set to 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
Called before remoteEntry.get(expose).
async function beforeGetExpose(args: BeforeGetExposeOptions): Promise<void>
type BeforeGetExposeOptions ={
id: string;
expose: string;
moduleInfo: RemoteInfo;
remoteEntryExports: RemoteEntryExports;
origin: ModuleFederation;
}
afterGetExpose
AsyncHook
Called after remoteEntry.get(expose) succeeds or fails.
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
Called before executing the exposed module factory. It is skipped when loadRemote is called with loadFactory: false.
async function beforeExecuteFactory(args: BeforeExecuteFactoryOptions): Promise<void>
type BeforeExecuteFactoryOptions ={
id: string;
expose: string;
moduleInfo: RemoteInfo;
loadFactory: boolean;
origin: ModuleFederation;
}
afterExecuteFactory
AsyncHook
Called after the exposed module factory succeeds or fails. It is skipped when loadRemote is called with 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
Used to modify the script when loading resources.
function createScript(args: CreateScriptOptions): CreateScriptHookReturn
type CreateScriptOptions ={
url: string;
attrs?: Record<string, any>;
remoteInfo?: RemoteInfo;
}
import type { ModuleFederationRuntimePlugin } from '@module-federation/enhanced/runtime';
const changeScriptAttributePlugin: () => ModuleFederationRuntimePlugin =
function () {
return {
name: 'change-script-attribute',
createScript({ url }) {
if (url === 'http://localhost:3001/remoteEntry.js') {
let script = document.createElement('script');
script.src = url;
script.setAttribute('loader-hooks', 'isTrue');
script.setAttribute('crossorigin', 'anonymous');
return script;
}
}
};
};
fetch
The fetch function allows customizing the request that fetches the manifest JSON. A successful Response must yield a valid JSON.
AsyncHook
function fetch(manifestUrl: string, requestInit: RequestInit, remoteInfo?: RemoteInfo): Promise<Response> | void | false;
- Example for including the credentials when fetching the manifest JSON:
// fetch-manifest-with-credentials-plugin.ts
import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime';
export default function (): FederationRuntimePlugin {
return {
name: 'fetch-manifest-with-credentials-plugin',
fetch(manifestUrl, requestInit) {
return fetch(manifestUrl, {
...requestInit,
credentials: 'include'
});
},
}
};
createLink
SyncHook
Used to customize the link element created for preload/style loading.
function createLink(args: CreateLinkOptions): HTMLLinkElement | void
type CreateLinkOptions ={
url: string;
attrs?: Record<string, any>;
remoteInfo?: RemoteInfo;
}
loadEntryError
AsyncHook
Triggered when loading remoteEntry fails (typically script loading errors). Useful for retries or custom fallback strategies.
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
Called after loading a remoteEntry succeeds, fails, or is recovered by loadEntryError.
async function afterLoadEntry(args: AfterLoadEntryOptions): Promise<void>
type AfterLoadEntryOptions ={
origin: ModuleFederation;
remoteInfo: RemoteInfo;
remoteEntryExports?: RemoteEntryExports | false | void;
error?: unknown;
recovered?: boolean;
}
getModuleFactory
AsyncHook
Triggered before calling remoteEntry.get(expose), allowing custom module-factory resolution.
async function getModuleFactory(args: GetModuleFactoryOptions): Promise<(() => Promise<Module>) | undefined>
type GetModuleFactoryOptions ={
remoteEntryExports: RemoteEntryExports;
expose: string;
moduleInfo: RemoteInfo;
}
loadEntry
The loadEntry function allows for full customization of remotes, enabling you to extend and create new remote types. The following two simple examples demonstrate loading JSON data and module delegation.
asyncHook
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>;
};
- Example Loading JSON Data
// load-json-data-plugin.ts
import { init } from '@module-federation/enhanced/runtime';
import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime';
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-plugin.ts
import { init } from '@module-federation/enhanced/runtime';
import type { FederationRuntimePlugin } from '@module-federation/enhanced/runtime';
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 is defined in runtime-core/src/core.ts and is used to extend context across bridge render/destroy stages (for example, React/Vue bridge).
beforeBridgeRender
SyncHook
Triggered before bridge rendering. You can return an object to augment render arguments (for example, extraProps).
function beforeBridgeRender(args: Record<string, any>): void | Record<string, any>
afterBridgeRender
SyncHook
Triggered after bridge rendering.
function afterBridgeRender(args: Record<string, any>): void | Record<string, any>
beforeBridgeDestroy
SyncHook
Triggered before bridge teardown.
function beforeBridgeDestroy(args: Record<string, any>): void | Record<string, any>
afterBridgeDestroy
SyncHook
Triggered after bridge teardown.
function afterBridgeDestroy(args: Record<string, any>): void | Record<string, any>