Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
95cc87e
feat(managers): plan the runtime composition from federation particip…
ScriptedAlchemy Sep 25, 2026
bf3f50d
feat(managers): resolve the runtime family, select the mode, render a…
ScriptedAlchemy Sep 25, 2026
25d911e
refactor(managers): name the composition helpers after what they resolve
ScriptedAlchemy Sep 25, 2026
bf6f74b
Merge remote-tracking branch 'origin/rfc5128/03-node-esbuild-runtime'…
ScriptedAlchemy Sep 25, 2026
74ef4dd
Merge remote-tracking branch 'origin/rfc5128/03-node-esbuild-runtime'…
ScriptedAlchemy Sep 25, 2026
d2ebade
feat(managers): map ModuleFederationPlugin options to a composition p…
ScriptedAlchemy Sep 25, 2026
1d75f2f
Merge remote-tracking branch 'origin/rfc5128/03-node-esbuild-runtime'…
ScriptedAlchemy Sep 25, 2026
4e0a393
fix(managers): check byLayer externals when selecting the runtime mode
ScriptedAlchemy Sep 25, 2026
edc79c4
fix(managers): give function externals contextInfo and a failing getR…
ScriptedAlchemy Sep 25, 2026
4a4b4af
fix(managers): wait for the callback of externals that return undefined
ScriptedAlchemy Sep 25, 2026
ed9df3e
fix(managers): match graph externals by their import name
ScriptedAlchemy Sep 25, 2026
0568c38
test(managers): type-check the __tests__ directory
ScriptedAlchemy Sep 25, 2026
cf7e8b0
Merge remote-tracking branch 'origin/rfc5128/03-node-esbuild-runtime'…
ScriptedAlchemy Sep 25, 2026
63874f3
Merge remote-tracking branch 'origin/rfc5128/03-node-esbuild-runtime'…
ScriptedAlchemy Sep 25, 2026
92392fc
Merge remote-tracking branch 'origin/rfc5128/03-node-esbuild-runtime'…
ScriptedAlchemy Sep 25, 2026
0576af6
Merge remote-tracking branch 'origin/rfc5128/03-node-esbuild-runtime'…
ScriptedAlchemy Sep 25, 2026
fb8c199
Merge remote-tracking branch 'origin/rfc5128/03-node-esbuild-runtime'…
ScriptedAlchemy Sep 25, 2026
a3a65b4
Merge remote-tracking branch 'origin/rfc5128/03-node-esbuild-runtime'…
ScriptedAlchemy Sep 25, 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/managers-runtime-composition.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@module-federation/managers': minor
---

Add pure helpers that plan a composed federation runtime: `planComposition`, `resolveRuntimeFamily`, `selectMode`, `resolveImports`, `renderComposition`, and `checkFederationGraph`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove nonexistent helpers from the release note

When this changeset is consumed during a release, the generated changelog will advertise five APIs that users cannot import: a repository-wide search finds no implementations of resolveRuntimeFamily, selectMode, resolveImports, renderComposition, or checkFederationGraph, and packages/managers/src/index.ts exports only planComposition and ADAPTERS from this change. Either include and export those helpers or limit the release note to the API actually added.

Useful? React with 👍 / 👎.

68 changes: 68 additions & 0 deletions packages/managers/__tests__/planComposition.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { planComposition } from '../src/composition/plan';

describe('planComposition', () => {
it('keeps every handler on when no participant carries options', () => {
expect(
planComposition([{ kind: 'needs', needs: ['remotes'] }], 'universal'),
).toEqual({
shared: true,
remote: true,
snapshot: true,
adapters: ['remotes', 'share-scope'],
platform: 'universal',
});
});

it('turns a handler off only when every options participant disables it', () => {
const plan = planComposition(
[
{
kind: 'options',
disable: { shared: true, snapshot: true },
needs: [],
},
{ kind: 'options', disable: { shared: true }, needs: [] },
{ kind: 'needs', needs: [] },
],
'web',
);
expect(plan.shared).toBe(false);
expect(plan.remote).toBe(true);
expect(plan.snapshot).toBe(true);
});

it('ignores needs participants when voting', () => {
const plan = planComposition(
[
{ kind: 'options', disable: { remote: true }, needs: ['container'] },
{ kind: 'needs', needs: ['remotes'] },
],
'node',
);
expect(plan.remote).toBe(false);
expect(plan.adapters).toEqual(['remotes', 'container', 'share-scope']);
});

it('drops snapshot when remote is off', () => {
const plan = planComposition(
[{ kind: 'options', disable: { remote: true }, needs: [] }],
'web',
);
expect(plan.snapshot).toBe(false);
});

it('closes remotes, consumes and container over share-scope in table order', () => {
expect(
planComposition(
[
{
kind: 'needs',
needs: ['container', 'consumes', 'remotes', 'consumes'],
},
],
'web',
).adapters,
).toEqual(['remotes', 'consumes', 'container', 'share-scope']);
expect(planComposition([], 'web').adapters).toEqual([]);
});
});
59 changes: 59 additions & 0 deletions packages/managers/src/composition/plan.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
export const ADAPTERS = [
'remotes',
'consumes',
'container',
'share-scope',
] as const;
export type AdapterName = (typeof ADAPTERS)[number];

const ADAPTER_REQUIRES: Record<AdapterName, readonly AdapterName[]> = {
remotes: ['share-scope'],
consumes: ['share-scope'],
container: ['share-scope'],
'share-scope': [],
};

export type CompositionPlatform = 'web' | 'node' | 'universal';

type Handler = 'shared' | 'remote' | 'snapshot';

export type Participant =
| {
kind: 'options';
disable: { [K in Handler]?: true };
needs: readonly AdapterName[];
}
| { kind: 'needs'; needs: readonly AdapterName[] };

export interface CompositionPlan {
shared: boolean;
remote: boolean;
snapshot: boolean;
adapters: AdapterName[];
platform: CompositionPlatform;
}

export function planComposition(
participants: readonly Participant[],
platform: CompositionPlatform,
): CompositionPlan {
const voters = participants.filter((p) => p.kind === 'options');
const off = (handler: Handler) =>
voters.length > 0 && voters.every((p) => p.disable[handler]);

const needed = new Set<AdapterName>();
const add = (name: AdapterName) => {
if (needed.has(name)) return;
needed.add(name);
ADAPTER_REQUIRES[name].forEach(add);
};
participants.forEach((p) => p.needs.forEach(add));

return {
shared: !off('shared'),
remote: !off('remote'),
snapshot: !off('remote') && !off('snapshot'),
adapters: ADAPTERS.filter((name) => needed.has(name)),
platform,
};
}
8 changes: 8 additions & 0 deletions packages/managers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,11 @@ export { UNKNOWN_MODULE_NAME } from './constant';

export * as utils from './utils';
export * as types from './types';

export { planComposition, ADAPTERS } from './composition/plan';
export type {
AdapterName,
CompositionPlan,
CompositionPlatform,
Participant,
} from './composition/plan';
Loading