Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
436fbfe
fix(runtime): resolve getInstance from global
cursoragent Feb 9, 2026
81b7999
fix(runtime): resolve instance for router e2e
cursoragent Feb 9, 2026
81022e4
fix(router-remote1-2001): revert dts overrides
cursoragent Feb 9, 2026
7fda502
Merge remote-tracking branch 'origin/main' into cursor/module-federat…
cursoragent Feb 9, 2026
095bc2e
Merge remote-tracking branch 'origin/main' into cursor/module-federat…
ScriptedAlchemy Feb 9, 2026
f6bc4d7
Merge remote-tracking branch 'origin/main' into cursor/module-federat…
ScriptedAlchemy Feb 12, 2026
97d9a21
chore(core): add changeset coverage for pr #4396
ScriptedAlchemy Feb 12, 2026
db17875
chore(runtime): add contextual instance resolution changeset
ScriptedAlchemy Feb 12, 2026
8b71a9e
Merge branch 'main' into cursor/module-federation-nextjs-bug-75bf
ScriptedAlchemy Feb 14, 2026
74fdcb4
Merge branch 'main' into cursor/module-federation-nextjs-bug-75bf
ScriptedAlchemy Feb 14, 2026
144d7e7
Merge branch 'main' into cursor/module-federation-nextjs-bug-75bf
ScriptedAlchemy Feb 15, 2026
dd592a1
Merge branch 'main' into cursor/module-federation-nextjs-bug-75bf
ScriptedAlchemy Feb 15, 2026
a7d3b55
Merge branch 'main' into cursor/module-federation-nextjs-bug-75bf
ScriptedAlchemy Feb 16, 2026
9d7d66f
Merge branch 'main' into cursor/module-federation-nextjs-bug-75bf
ScriptedAlchemy Feb 19, 2026
03317a8
Merge branch 'main' into cursor/module-federation-nextjs-bug-75bf
ScriptedAlchemy Feb 19, 2026
425400e
Merge remote-tracking branch 'origin/main' into cursor/module-federat…
ScriptedAlchemy Feb 24, 2026
2c58b0c
fix(dts-plugin): align workspace entrypoints and RawSource typing
ScriptedAlchemy Feb 24, 2026
0f1fef4
Merge branch 'main' into cursor/module-federation-nextjs-bug-75bf
ScriptedAlchemy Feb 25, 2026
30b18d2
fix(sdk): align package entrypoints with emitted artifacts
ScriptedAlchemy Feb 25, 2026
d61f68c
Merge branch 'main' into cursor/module-federation-nextjs-bug-75bf
ScriptedAlchemy Feb 25, 2026
7ef8a9e
Merge branch 'main' into cursor/module-federation-nextjs-bug-75bf
ScriptedAlchemy Feb 26, 2026
243f1db
Merge branch 'main' into cursor/module-federation-nextjs-bug-75bf
ScriptedAlchemy Feb 26, 2026
f362ff3
Merge branch 'main' into cursor/module-federation-nextjs-bug-75bf
ScriptedAlchemy Feb 27, 2026
84e9a40
Merge branch 'main' into cursor/module-federation-nextjs-bug-75bf
ScriptedAlchemy Feb 27, 2026
19852ca
Merge branch 'main' into cursor/module-federation-nextjs-bug-75bf
ScriptedAlchemy Feb 28, 2026
556744c
Merge branch 'main' into cursor/module-federation-nextjs-bug-75bf
ScriptedAlchemy Feb 28, 2026
d83c412
Merge branch 'main' into cursor/module-federation-nextjs-bug-75bf
ScriptedAlchemy Mar 2, 2026
8e575ee
Merge branch 'main' into cursor/module-federation-nextjs-bug-75bf
ScriptedAlchemy Mar 3, 2026
5227713
Merge branch 'main' into cursor/module-federation-nextjs-bug-75bf
ScriptedAlchemy Mar 5, 2026
776d79e
Merge remote-tracking branch 'origin/main' into cursor/module-federat…
ScriptedAlchemy Mar 9, 2026
6d34d68
Merge branch 'main' into cursor/module-federation-nextjs-bug-75bf
ScriptedAlchemy Mar 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/ten-hounds-kick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@module-federation/runtime': patch
---

Improve runtime instance resolution by selecting instances from global federation state (including remote-aware matching) and keeping `getInstance` stable after module resets.
23 changes: 21 additions & 2 deletions packages/runtime/__tests__/api.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, it, expect } from 'vitest';
import { init } from '../src';
import { describe, it, expect, vi } from 'vitest';
import { init, getInstance, createInstance } from '../src';

// eslint-disable-next-line max-lines-per-function
describe('api', () => {
Expand Down Expand Up @@ -83,6 +83,25 @@ describe('api', () => {
});
expect(FM3).not.toBe(FM4);
});
it('getInstance resolves the global instance after reset', async () => {
const FM = init({
name: '@federation/get-instance-global',
remotes: [],
});

vi.resetModules();
const { getInstance: getInstanceAfterReset } = await import('../src');
expect(getInstanceAfterReset('@federation/get-instance-global')).toBe(FM);
});

it('createInstance updates getInstance', () => {
const FM = createInstance({
name: '@federation/create-instance',
remotes: [],
});

expect(getInstance('@federation/create-instance')).toBe(FM);
});

it('alias check', () => {
// 校验 alias 是否等于 remote.name 和 remote.alias 的前缀,如果是则报错
Expand Down
114 changes: 90 additions & 24 deletions packages/runtime/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import {
ModuleFederation,
type UserOptions,
CurrentGlobal,
getGlobalFederationConstructor,
matchRemoteWithNameAndExpose,
setGlobalFederationInstance,
assert,
setGlobalFederationConstructor,
} from '@module-federation/runtime-core';
import { runtimeDescMap, RUNTIME_009 } from '@module-federation/error-codes';
import { getGlobalFederationInstance } from './utils';
import {
runtimeDescMap,
getShortErrorMsg,
RUNTIME_009,
} from '@module-federation/error-codes';
import { getBuilderId, getGlobalFederationInstance } from './utils';

export {
loadScript,
Expand All @@ -28,10 +34,65 @@ export function createInstance(options: UserOptions) {
getGlobalFederationConstructor() || ModuleFederation;
const instance = new ModuleFederationConstructor(options);
setGlobalFederationInstance(instance);
FederationInstance = instance;
return instance;
}

let FederationInstance: ModuleFederation | null = null;
function resolveFederationInstance(
name?: string,
version?: string,
remoteId?: string,
): ModuleFederation | null {
const buildId = getBuilderId();
if (buildId) {
const buildInstance = getGlobalFederationInstance(name || '', version);
if (buildInstance) {
FederationInstance = buildInstance;
return buildInstance;
}
}

if (name) {
const namedInstance = getGlobalFederationInstance(name, version);
if (namedInstance) {
FederationInstance = namedInstance;
return namedInstance;
}
}

if (remoteId) {
const instances = CurrentGlobal.__FEDERATION__?.__INSTANCES__ || [];
const matchingInstance = instances.find((instance) =>
matchRemoteWithNameAndExpose(instance.options.remotes, remoteId),
);
if (matchingInstance) {
FederationInstance = matchingInstance;
return matchingInstance;
}
}

if (
FederationInstance &&
(!remoteId ||
matchRemoteWithNameAndExpose(
FederationInstance.options.remotes,
remoteId,
))
) {
return FederationInstance;
}

if (!name && !version) {
const instances = CurrentGlobal.__FEDERATION__?.__INSTANCES__ || [];
if (instances.length === 1) {
FederationInstance = instances[0];
return FederationInstance;
}
}

return null;
}
/**
* @deprecated Use createInstance or getInstance instead
*/
Expand All @@ -54,67 +115,72 @@ export function init(options: UserOptions): ModuleFederation {
export function loadRemote<T>(
...args: Parameters<ModuleFederation['loadRemote']>
): Promise<T | null> {
assert(FederationInstance, RUNTIME_009, runtimeDescMap);
const loadRemote: typeof FederationInstance.loadRemote<T> =
FederationInstance.loadRemote;
const instance = resolveFederationInstance(undefined, undefined, args[0]);
assert(instance, getShortErrorMsg(RUNTIME_009, runtimeDescMap));
const loadRemote: typeof instance.loadRemote<T> = instance.loadRemote;
// eslint-disable-next-line prefer-spread
return loadRemote.apply(FederationInstance, args);
return loadRemote.apply(instance, args);
}

export function loadShare<T>(
...args: Parameters<ModuleFederation['loadShare']>
): Promise<false | (() => T | undefined)> {
assert(FederationInstance, RUNTIME_009, runtimeDescMap);
const instance = resolveFederationInstance();
assert(instance, getShortErrorMsg(RUNTIME_009, runtimeDescMap));
// eslint-disable-next-line prefer-spread
const loadShare: typeof FederationInstance.loadShare<T> =
FederationInstance.loadShare;
return loadShare.apply(FederationInstance, args);
const loadShare: typeof instance.loadShare<T> = instance.loadShare;
return loadShare.apply(instance, args);
}

export function loadShareSync<T>(
...args: Parameters<ModuleFederation['loadShareSync']>
): () => T | never {
assert(FederationInstance, RUNTIME_009, runtimeDescMap);
const loadShareSync: typeof FederationInstance.loadShareSync<T> =
FederationInstance.loadShareSync;
const instance = resolveFederationInstance();
assert(instance, getShortErrorMsg(RUNTIME_009, runtimeDescMap));
const loadShareSync: typeof instance.loadShareSync<T> =
instance.loadShareSync;
// eslint-disable-next-line prefer-spread
return loadShareSync.apply(FederationInstance, args);
return loadShareSync.apply(instance, args);
}

export function preloadRemote(
...args: Parameters<ModuleFederation['preloadRemote']>
): ReturnType<ModuleFederation['preloadRemote']> {
assert(FederationInstance, RUNTIME_009, runtimeDescMap);
const instance = resolveFederationInstance();
assert(instance, getShortErrorMsg(RUNTIME_009, runtimeDescMap));
// eslint-disable-next-line prefer-spread
return FederationInstance.preloadRemote.apply(FederationInstance, args);
return instance.preloadRemote.apply(instance, args);
}

export function registerRemotes(
...args: Parameters<ModuleFederation['registerRemotes']>
): ReturnType<ModuleFederation['registerRemotes']> {
assert(FederationInstance, RUNTIME_009, runtimeDescMap);
const instance = resolveFederationInstance();
assert(instance, getShortErrorMsg(RUNTIME_009, runtimeDescMap));
// eslint-disable-next-line prefer-spread
return FederationInstance.registerRemotes.apply(FederationInstance, args);
return instance.registerRemotes.apply(instance, args);
}

export function registerPlugins(
...args: Parameters<ModuleFederation['registerPlugins']>
): ReturnType<ModuleFederation['registerRemotes']> {
assert(FederationInstance, RUNTIME_009, runtimeDescMap);
const instance = resolveFederationInstance();
assert(instance, getShortErrorMsg(RUNTIME_009, runtimeDescMap));
// eslint-disable-next-line prefer-spread
return FederationInstance.registerPlugins.apply(FederationInstance, args);
return instance.registerPlugins.apply(instance, args);
}

export function getInstance() {
return FederationInstance;
export function getInstance(name?: string, version?: string) {
return resolveFederationInstance(name, version);
}

export function registerShared(
...args: Parameters<ModuleFederation['registerShared']>
): ReturnType<ModuleFederation['registerShared']> {
assert(FederationInstance, RUNTIME_009, runtimeDescMap);
const instance = resolveFederationInstance();
assert(instance, getShortErrorMsg(RUNTIME_009, runtimeDescMap));
// eslint-disable-next-line prefer-spread
return FederationInstance.registerShared.apply(FederationInstance, args);
return instance.registerShared.apply(instance, args);
}

// Inject for debug
Expand Down
Loading