Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .changeset/ssr-resolved-provider-identity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@module-federation/sdk': patch
'@module-federation/runtime-core': patch
'@module-federation/webpack-bundler-runtime': patch
---

Preserve the manifest provider identity independently of registration and container global names during SSR cache invalidation. Keep live shared providers intact and avoid deleting business globals that happen to match a remote registration name. Resolved identity is captured before removal hooks so an earlier hook clearing moduleCache cannot erase that cleanup context.
28 changes: 24 additions & 4 deletions packages/runtime-core/__tests__/register-remotes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,10 +387,12 @@ describe('ModuleFederation', () => {
{ registered: true, loading: true },
{ registered: false, loading: true },
].flatMap((flags) =>
['@register-remotes/app1', 'app1'].map((providerName) => ({
...flags,
providerName,
})),
['@register-remotes/app1', 'app1', 'manifest-provider'].map(
(providerName) => ({
...flags,
providerName,
}),
),
),
)(
'keeps shared provider caches: %j',
Expand Down Expand Up @@ -431,6 +433,7 @@ describe('ModuleFederation', () => {
FM.moduleCache.set('@register-remotes/app1', {
remoteInfo: {
name: '@register-remotes/app1',
providerName,
alias: 'app1',
entry,
type: 'global',
Expand Down Expand Up @@ -490,6 +493,7 @@ describe('ModuleFederation', () => {
it('keeps loaded remote cleanup context when removeRemote hook clears moduleCache first', async () => {
const entry =
'http://localhost:1111/resources/register-remotes/app1/federation-remote-entry.js';
const observed = rs.fn();
const remoteEntryClear = rs.fn();
const libClear = rs.fn();
const globalClear = rs.fn();
Expand All @@ -510,6 +514,15 @@ describe('ModuleFederation', () => {
origin.moduleCache.delete('@register-remotes/app1');
},
},
{
name: 'later-remove-hook',
removeRemote({ origin, remoteInfo }) {
expect(origin.moduleCache.has('@register-remotes/app1')).toBe(
false,
);
observed(remoteInfo);
},
},
],
});

Expand Down Expand Up @@ -560,6 +573,13 @@ describe('ModuleFederation', () => {

await FM.removeRemote('app1');

expect(observed).toHaveBeenCalledWith(
expect.objectContaining({
name: '@register-remotes/app1',
entryGlobalName: 'app1',
entry,
}),
);
expect(remoteEntryClear).toHaveBeenCalledTimes(1);
expect(libClear).toHaveBeenCalledTimes(1);
expect(globalClear).toHaveBeenCalledTimes(1);
Expand Down
17 changes: 17 additions & 0 deletions packages/runtime-core/__tests__/snapshot.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { assignRemoteInfo } from '../src/plugins/snapshot';
import { assert, describe, it } from '@rstest/core';
import { ModuleFederation } from '../src';
import { getGlobalSnapshot, resetFederationGlobalInfo } from '../src/global';
Expand Down Expand Up @@ -47,3 +48,19 @@ describe('snapshot', () => {
});
});
});

it('keeps manifest provider identity separate from the host registration and container global', () => {
const remote = { name: 'dynamic' } as any;
assignRemoteInfo(remote, {
providerName: 'provider',
remoteEntry: 'entry.js',
remoteEntryType: 'global',
globalName: '__FEDERATION_custom:custom__',
publicPath: 'https://example.com/',
version: '1',
buildVersion: 'build',
} as any);
expect(remote.name).toBe('dynamic');
expect(remote.providerName).toBe('provider');
expect(remote.entryGlobalName).toBe('__FEDERATION_custom:custom__');
});
1 change: 1 addition & 0 deletions packages/runtime-core/src/plugins/snapshot/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export function assignRemoteInfo(
entryUrl = `https:${entryUrl}`;
}

remoteInfo.providerName = remoteSnapshot.providerName;
remoteInfo.type = remoteEntryInfo.type;
remoteInfo.entryGlobalName = remoteEntryInfo.globalName;
remoteInfo.entry = entryUrl;
Expand Down
16 changes: 14 additions & 2 deletions packages/runtime-core/src/remote/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ export class RemoteHandler {
{
remote: Remote;
origin: ModuleFederation;
/** Resolved identity captured before user removal hooks mutate caches. */
remoteInfo?: RemoteInfo;
},
],
void
Expand Down Expand Up @@ -768,7 +770,13 @@ export class RemoteHandler {
const { name } = remote;
const loadedModule = host.moduleCache.get(remote.name);
return Promise.resolve(
this.hooks.lifecycle.removeRemote.emit({ remote, origin: host }),
this.hooks.lifecycle.removeRemote.emit({
remote,
origin: host,
remoteInfo: loadedModule?.remoteInfo
? { ...loadedModule.remoteInfo }
: undefined,
}),
)
.then(() => {
const remoteIndex = host.options.remotes.findIndex(
Expand All @@ -789,7 +797,11 @@ export class RemoteHandler {
// name (for example dynamic -> catalog). Shared.from uses the provider
// identity, so scanning only the registration name can clear live libs.
const providerNames = new Set(
[remoteInfo.name, remoteInfo.entryGlobalName].filter(Boolean),
[
remoteInfo.providerName,
remoteInfo.name,
remoteInfo.entryGlobalName,
].filter((name): name is string => Boolean(name)),
);
let remoteInsId = remoteInfo.buildVersion
? composeKeyWithSeparator(remoteInfo.name, remoteInfo.buildVersion)
Expand Down
2 changes: 2 additions & 0 deletions packages/runtime-core/src/type/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ export interface SharedLoadContext {
}

export interface RemoteInfo {
/** Resolved manifest provider identity, separate from the registration name. */
providerName?: string;
alias?: string;
name: string;
version?: string;
Expand Down
12 changes: 12 additions & 0 deletions packages/sdk/__tests__/resources/manifestSnapshotMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -572,6 +572,7 @@ const manifest: { [key: string]: Stats } = {

const snapshot: { [key: string]: ModuleInfo } = {
devAppSnapshot: {
providerName: manifest.devAppManifest.name,
version: '',
buildVersion: 'local',
globalName: '__FEDERATION_@garfish/micro-app-sub2:local__',
Expand Down Expand Up @@ -667,6 +668,7 @@ const snapshot: { [key: string]: ModuleInfo } = {
publicPath: 'http://localhost:2004/',
},
devAppSnapshotWithVersion: {
providerName: manifest.devAppManifest.name,
version: 'http://localhost:2006/vmok-manifest.json',
buildVersion: 'local',
globalName: '__FEDERATION_@garfish/micro-app-sub2:local__',
Expand Down Expand Up @@ -762,6 +764,7 @@ const snapshot: { [key: string]: ModuleInfo } = {
publicPath: 'http://localhost:2004/',
},
devAppSnapshotWithGetPublicPath: {
providerName: manifest.devAppManifestWithGetPublicPath.name,
version: '',
buildVersion: 'local',
globalName: '__FEDERATION_@garfish/micro-app-sub3:local__',
Expand Down Expand Up @@ -867,6 +870,7 @@ const snapshot: { [key: string]: ModuleInfo } = {
getPublicPath: "return 'http://localhost:2005/'",
},
prodAppSnapshot: {
providerName: manifest.prodAppManifest.name,
version: '',
buildVersion: '1.0.0.1517',
globalName: '__FEDERATION_@garfish/micro-app-sub2:1.0.0.1517__',
Expand Down Expand Up @@ -956,6 +960,7 @@ const snapshot: { [key: string]: ModuleInfo } = {
publicPath: 'https://__CDN_PREFIX__/micro-app-sub2/1.0.0.1517/',
},
prodAppSnapshotWithGetPublicPath: {
providerName: manifest.prodAppManifestWithGetPublicPath.name,
version: '',
buildVersion: '1.0.0.1513',
globalName: '__FEDERATION_@garfish/micro-app-sub3:1.0.0.1513__',
Expand Down Expand Up @@ -1055,6 +1060,7 @@ const snapshot: { [key: string]: ModuleInfo } = {
"return 'https://xxx.com/__FEDERATION_micro-app-sub3/1.0.0.1513/'",
},
ssrProdAppSnapshotWithAllParams: {
providerName: manifest.ssrAppManifest.name,
version: '',
buildVersion: '1.0.0.1517',
globalName: '__FEDERATION_@mf/ssr-manifest-provider:1.0.0.1517__',
Expand Down Expand Up @@ -1086,6 +1092,7 @@ const snapshot: { [key: string]: ModuleInfo } = {
publicPath: 'https://__CDN_PREFIX__/ssr-manifest-provider/1.0.0.1517/',
},
prodAppSnapshotWithAllParams: {
providerName: manifest.prodAppManifest.name,
version: '',
buildVersion: '1.0.0.1517',
globalName: '__FEDERATION_@garfish/micro-app-sub2:1.0.0.1517__',
Expand Down Expand Up @@ -1178,6 +1185,7 @@ const snapshot: { [key: string]: ModuleInfo } = {
publicPath: 'https://__CDN_PREFIX__/micro-app-sub2/1.0.0.1517/',
},
devAppSnapshotWithOverrides: {
providerName: manifest.devAppManifest.name,
version: '',
buildVersion: 'local',
globalName: '__FEDERATION_@garfish/micro-app-sub2:local__',
Expand Down Expand Up @@ -1273,6 +1281,7 @@ const snapshot: { [key: string]: ModuleInfo } = {
publicPath: 'http://localhost:2004/',
},
devAppSnapshotWithRemotes: {
providerName: manifest.devAppManifest.name,
version: '',
buildVersion: 'local',
globalName: '__FEDERATION_@garfish/micro-app-sub2:local__',
Expand Down Expand Up @@ -1368,6 +1377,7 @@ const snapshot: { [key: string]: ModuleInfo } = {
publicPath: 'http://localhost:2004/',
},
devAppSnapshotWithPartRemotes: {
providerName: manifest.devAppManifest.name,
version: '',
buildVersion: 'local',
globalName: '__FEDERATION_@garfish/micro-app-sub2:local__',
Expand Down Expand Up @@ -1460,6 +1470,7 @@ const snapshot: { [key: string]: ModuleInfo } = {
publicPath: 'http://localhost:2004/',
},
devAppSnapshotWithAllParams: {
providerName: manifest.devAppManifest.name,
version: '',
buildVersion: 'local',
globalName: '__FEDERATION_@garfish/micro-app-sub2:local__',
Expand Down Expand Up @@ -1555,6 +1566,7 @@ const snapshot: { [key: string]: ModuleInfo } = {
publicPath: 'http://localhost:2004/',
},
devAppSnapshotWithRemotesAndOverrides: {
providerName: manifest.devAppManifest.name,
version: '',
buildVersion: 'local',
globalName: '__FEDERATION_@garfish/micro-app-sub2:local__',
Expand Down
1 change: 1 addition & 0 deletions packages/sdk/src/generateSnapshotFromManifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export function generateSnapshotFromManifest(
const { exposes } = manifest;

let basicRemoteSnapshot: BasicProviderModuleInfo = {
providerName: manifest.name,
version: version ? version : '',
buildVersion,
globalName,
Expand Down
2 changes: 2 additions & 0 deletions packages/sdk/src/types/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { TreeShakingStatus } from '../constant';
import { RemoteEntryType, StatsAssets } from './stats';

interface BasicModuleInfo {
/** Provider identity from the manifest; independent of host registration/global names. */
providerName?: string;
dev?: {
version?: string;
remotes?: { [nameWithType: string]: string };
Expand Down
Loading
Loading