@bleedingdev/modern-js-federation-runtime 3.9.0-ultramodern.9 → 3.9.3-ultramodern.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/dist/cjs/module-federation/consume-surface.js +6 -16
  2. package/dist/cjs/module-federation/distributed-ssr.js +6 -1
  3. package/dist/cjs/module-federation/index.js +13 -239
  4. package/dist/cjs/module-federation/manifest-recovery-runtime-plugin.js +1 -1
  5. package/dist/cjs/module-federation/renderer-contract.js +159 -0
  6. package/dist/cjs/module-federation/renderer-runtime-plugin.js +265 -0
  7. package/dist/cjs/module-federation/telemetry.js +214 -0
  8. package/dist/esm/module-federation/consume-surface.mjs +3 -13
  9. package/dist/esm/module-federation/distributed-ssr.mjs +6 -1
  10. package/dist/esm/module-federation/index.mjs +1 -111
  11. package/dist/esm/module-federation/manifest-recovery-runtime-plugin.mjs +1 -1
  12. package/dist/esm/module-federation/renderer-contract.mjs +103 -0
  13. package/dist/esm/module-federation/renderer-runtime-plugin.mjs +222 -0
  14. package/dist/esm/module-federation/telemetry.mjs +142 -0
  15. package/dist/esm-node/module-federation/consume-surface.mjs +3 -13
  16. package/dist/esm-node/module-federation/distributed-ssr.mjs +6 -1
  17. package/dist/esm-node/module-federation/index.mjs +1 -111
  18. package/dist/esm-node/module-federation/manifest-recovery-runtime-plugin.mjs +1 -1
  19. package/dist/esm-node/module-federation/renderer-contract.mjs +104 -0
  20. package/dist/esm-node/module-federation/renderer-runtime-plugin.mjs +223 -0
  21. package/dist/esm-node/module-federation/telemetry.mjs +143 -0
  22. package/dist/types/module-federation/consume-surface.d.ts +5 -5
  23. package/dist/types/module-federation/distributed-ssr.d.ts +18 -1
  24. package/dist/types/module-federation/index.d.ts +1 -67
  25. package/dist/types/module-federation/renderer-contract.d.ts +20 -0
  26. package/dist/types/module-federation/renderer-runtime-plugin.d.ts +98 -0
  27. package/dist/types/module-federation/telemetry.d.ts +71 -0
  28. package/package.json +54 -18
  29. package/src/module-federation/consume-surface.ts +18 -33
  30. package/src/module-federation/distributed-ssr.tsx +27 -2
  31. package/src/module-federation/index.ts +1 -267
  32. package/src/module-federation/manifest-recovery-runtime-plugin.ts +5 -1
  33. package/src/module-federation/renderer-contract.ts +176 -0
  34. package/src/module-federation/renderer-runtime-plugin.ts +396 -0
  35. package/src/module-federation/telemetry.ts +297 -0
@@ -0,0 +1,98 @@
1
+ import { type RendererFederationCompatibility } from './renderer-contract.js';
2
+ type Snapshot = Record<string, unknown>;
3
+ type SnapshotArgs = {
4
+ remoteSnapshot: Snapshot;
5
+ from: 'global' | 'manifest';
6
+ manifestJson?: unknown;
7
+ };
8
+ type Shared = Record<string, unknown> & {
9
+ version?: string;
10
+ };
11
+ type ShareResolution = {
12
+ shared: Shared;
13
+ useTreesShaking?: boolean;
14
+ } | undefined;
15
+ type ResolveShareArgs = {
16
+ pkgName: string;
17
+ shareScopeMap: Record<string, Record<string, Record<string, Shared>>>;
18
+ resolver: () => ShareResolution;
19
+ };
20
+ /**
21
+ * Exact versions every shared copy of the consuming renderer tuple must carry.
22
+ * React keeps its established package set; native renderers derive theirs from
23
+ * the tuple's runtime, hydration, bootstrap and router owners.
24
+ */
25
+ export declare function rendererShareVersions(expected: RendererFederationCompatibility): ReadonlyMap<string, string>;
26
+ /** Gate the native snapshot and share lifecycles before executing component factories. */
27
+ export declare function createRendererFederationRuntimePlugin(options: RendererFederationCompatibility): {
28
+ name: string;
29
+ beforeRegisterRemote(args: {
30
+ remote: unknown;
31
+ }): {
32
+ remote: unknown;
33
+ };
34
+ afterMatchRemote(args: {
35
+ remote?: unknown;
36
+ error?: unknown;
37
+ }): Promise<{
38
+ remote?: unknown;
39
+ error?: unknown;
40
+ }>;
41
+ loadRemoteSnapshot(args: SnapshotArgs): Promise<SnapshotArgs>;
42
+ afterLoadSnapshot(args: {
43
+ remoteSnapshot: Snapshot;
44
+ moduleInfo?: {
45
+ name: string;
46
+ };
47
+ }): Promise<{
48
+ remoteSnapshot: Snapshot;
49
+ moduleInfo?: {
50
+ name: string;
51
+ };
52
+ }>;
53
+ afterResolve(args: {
54
+ remote: {
55
+ name: string;
56
+ };
57
+ remoteInfo: Record<string, unknown>;
58
+ remoteSnapshot?: Snapshot;
59
+ origin: {
60
+ moduleCache: Map<string, {
61
+ remoteInfo: Record<string, unknown>;
62
+ }>;
63
+ };
64
+ }): Promise<{
65
+ remote: {
66
+ name: string;
67
+ };
68
+ remoteInfo: Record<string, unknown>;
69
+ remoteSnapshot?: Snapshot;
70
+ origin: {
71
+ moduleCache: Map<string, {
72
+ remoteInfo: Record<string, unknown>;
73
+ }>;
74
+ };
75
+ }>;
76
+ loadEntry(args: {
77
+ remoteInfo: Record<string, unknown>;
78
+ }): void;
79
+ afterLoadEntry(args: {
80
+ remoteInfo: Record<string, unknown>;
81
+ remoteEntryExports?: unknown;
82
+ cached?: boolean;
83
+ error?: unknown;
84
+ recovered?: boolean;
85
+ }): Promise<{
86
+ remoteInfo: Record<string, unknown>;
87
+ remoteEntryExports?: unknown;
88
+ cached?: boolean;
89
+ error?: unknown;
90
+ recovered?: boolean;
91
+ }>;
92
+ resolveShare(args: ResolveShareArgs): ResolveShareArgs;
93
+ afterLoadShare(args: {
94
+ pkgName: string;
95
+ selectedShared?: Shared;
96
+ }): void;
97
+ };
98
+ export default createRendererFederationRuntimePlugin;
@@ -0,0 +1,71 @@
1
+ export declare const MODULE_FEDERATION_FALLBACK_SIGNAL_EVENT = "modernjs:mf-runtime-fallback";
2
+ export declare const MODULE_FEDERATION_RECOVERY_SIGNAL_EVENT = "modernjs:mf-runtime-recovery";
3
+ export declare const DEFAULT_RUNTIME_FALLBACK_SIGNAL_ENDPOINT = "/_modern/contract-gates/runtime-fallback";
4
+ export declare const DEFAULT_RUNTIME_FALLBACK_SIGNAL_AUTH_HEADER = "x-modernjs-runtime-signal-token";
5
+ export type ModuleFederationFallbackClassification = 'timeout' | 'network' | 'contract' | 'version-skew' | 'remote-unavailable';
6
+ export type ModuleFederationFallbackStatus = 'degraded' | 'failed' | 'recovered';
7
+ export type ModuleFederationFallbackTelemetryInput = {
8
+ appName: string;
9
+ classification: ModuleFederationFallbackClassification;
10
+ entry?: string;
11
+ error?: unknown;
12
+ eventName?: string;
13
+ exportName?: string;
14
+ metadata?: Record<string, unknown>;
15
+ phase: 'discovery' | 'load' | 'mount' | 'hydrate' | 'recover';
16
+ remote: string;
17
+ runtimeDigest?: string;
18
+ status?: ModuleFederationFallbackStatus;
19
+ };
20
+ export type ModuleFederationFallbackTelemetryPayload = {
21
+ appName: string;
22
+ entry?: string;
23
+ eventName: string;
24
+ phase: ModuleFederationFallbackTelemetryInput['phase'];
25
+ reason: ModuleFederationFallbackClassification;
26
+ runtimeDigest?: string;
27
+ schemaVersion: 1;
28
+ metadata: {
29
+ classification: ModuleFederationFallbackClassification;
30
+ errorMessage?: string;
31
+ errorName?: string;
32
+ exportName?: string;
33
+ remote: string;
34
+ runtimeDigest?: string;
35
+ status: ModuleFederationFallbackStatus;
36
+ } & Record<string, unknown>;
37
+ };
38
+ export type ModuleFederationFallbackTelemetryEmitOptions = {
39
+ authHeaderName?: string;
40
+ authToken?: string;
41
+ endpoint?: string;
42
+ fetchImpl?: typeof fetch;
43
+ postSignal?: boolean;
44
+ /** Positive reporting deadline; defaults to one second. */
45
+ timeoutMs?: number;
46
+ /** Observe the exact payload and await delivery explicitly in tests. */
47
+ observe?: (payload: ModuleFederationFallbackTelemetryPayload, delivery: Promise<ModuleFederationFallbackTelemetryEmitResult>) => void;
48
+ };
49
+ export type ModuleFederationFallbackTelemetryEmitResult = {
50
+ dispatched: boolean;
51
+ posted: boolean;
52
+ postStatus?: number;
53
+ };
54
+ export declare class ModuleFederationRemoteLoadTimeoutError extends Error {
55
+ constructor(remote: string, timeoutMs: number);
56
+ }
57
+ export declare class ModuleFederationRemoteLoadError extends Error {
58
+ readonly remote: string;
59
+ readonly attempts: number;
60
+ readonly causeError: Error;
61
+ constructor(remote: string, attempts: number, causeError: Error);
62
+ }
63
+ export declare class ModuleFederationRemoteComponentContractError extends Error {
64
+ constructor(remote: string, exportName: string);
65
+ }
66
+ export declare function classifyModuleFederationFallback(error: unknown): ModuleFederationFallbackClassification;
67
+ export declare function createModuleFederationFallbackTelemetry(input: ModuleFederationFallbackTelemetryInput): ModuleFederationFallbackTelemetryPayload;
68
+ export declare function toModuleFederationFallbackAttributes(payload: ModuleFederationFallbackTelemetryPayload): Record<string, string>;
69
+ export declare function emitModuleFederationFallbackTelemetry(input: ModuleFederationFallbackTelemetryInput, options?: ModuleFederationFallbackTelemetryEmitOptions): Promise<ModuleFederationFallbackTelemetryEmitResult>;
70
+ /** Dispatch once locally; remote delivery is bounded and best effort. */
71
+ export declare function emitModuleFederationFallbackPayload(payload: ModuleFederationFallbackTelemetryPayload, options?: ModuleFederationFallbackTelemetryEmitOptions): Promise<ModuleFederationFallbackTelemetryEmitResult>;
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@bleedingdev/modern-js-federation-runtime",
3
- "version": "3.9.0-ultramodern.9",
3
+ "version": "3.9.3-ultramodern.2",
4
4
  "description": "Federated surface consumption, distributed SSR, and bounded manifest recovery.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "git+https://github.com/BleedingDev/ultramodern.js.git",
8
+ "url": "git+https://github.com/ultramodernjs/ultramodern.js.git",
9
9
  "directory": "packages/runtime/federation-runtime"
10
10
  },
11
11
  "engines": {
12
- "node": ">=26.7.0"
12
+ "node": ">=26.10.0"
13
13
  },
14
14
  "types": "./dist/types/module-federation/index.d.ts",
15
15
  "main": "./dist/cjs/module-federation/index.js",
@@ -23,6 +23,22 @@
23
23
  },
24
24
  "default": "./dist/esm/module-federation/index.mjs"
25
25
  },
26
+ "./renderer-contract": {
27
+ "types": "./dist/types/module-federation/renderer-contract.d.ts",
28
+ "node": {
29
+ "import": "./dist/esm-node/module-federation/renderer-contract.mjs",
30
+ "require": "./dist/cjs/module-federation/renderer-contract.js"
31
+ },
32
+ "default": "./dist/esm/module-federation/renderer-contract.mjs"
33
+ },
34
+ "./renderer-runtime-plugin": {
35
+ "types": "./dist/types/module-federation/renderer-runtime-plugin.d.ts",
36
+ "node": {
37
+ "import": "./dist/esm-node/module-federation/renderer-runtime-plugin.mjs",
38
+ "require": "./dist/cjs/module-federation/renderer-runtime-plugin.js"
39
+ },
40
+ "default": "./dist/esm/module-federation/renderer-runtime-plugin.mjs"
41
+ },
26
42
  "./distributed-ssr": {
27
43
  "types": "./dist/types/module-federation/distributed-ssr.d.ts",
28
44
  "node": {
@@ -46,6 +62,14 @@
46
62
  "./dist/types/module-federation/index.d.ts",
47
63
  "./src/module-federation/index.ts"
48
64
  ],
65
+ "renderer-contract": [
66
+ "./dist/types/module-federation/renderer-contract.d.ts",
67
+ "./src/module-federation/renderer-contract.ts"
68
+ ],
69
+ "renderer-runtime-plugin": [
70
+ "./dist/types/module-federation/renderer-runtime-plugin.d.ts",
71
+ "./src/module-federation/renderer-runtime-plugin.ts"
72
+ ],
49
73
  "distributed-ssr": [
50
74
  "./dist/types/module-federation/distributed-ssr.d.ts",
51
75
  "./src/module-federation/distributed-ssr.tsx"
@@ -62,26 +86,35 @@
62
86
  ],
63
87
  "sideEffects": false,
64
88
  "dependencies": {
65
- "@modern-js/surface-resolution": "npm:@bleedingdev/modern-js-surface-resolution@3.9.0-ultramodern.9"
89
+ "@modern-js/backend-federation-contracts": "npm:@bleedingdev/modern-js-backend-federation-contracts@3.9.3-ultramodern.2",
90
+ "@modern-js/surface-resolution": "npm:@bleedingdev/modern-js-surface-resolution@3.9.3-ultramodern.2",
91
+ "@module-federation/sdk": "2.9.2"
66
92
  },
67
93
  "peerDependencies": {
68
- "@modern-js/runtime": "3.9.0-ultramodern.9",
94
+ "@modern-js/runtime": "3.9.3-ultramodern.2",
69
95
  "react": "^19.2.8"
70
96
  },
97
+ "peerDependenciesMeta": {
98
+ "@modern-js/runtime": {
99
+ "optional": true
100
+ },
101
+ "react": {
102
+ "optional": true
103
+ }
104
+ },
71
105
  "devDependencies": {
72
- "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.0-ultramodern.9",
73
- "@modern-js/tsconfig": "npm:@bleedingdev/modern-js-tsconfig@3.9.0-ultramodern.9",
74
- "@rsbuild/core": "2.2.3",
75
- "@rslib/core": "1.0.0",
106
+ "@modern-js/runtime": "npm:@bleedingdev/modern-js-runtime@3.9.3-ultramodern.2",
107
+ "@modern-js/tsconfig": "npm:@bleedingdev/modern-js-tsconfig@3.9.3-ultramodern.2",
108
+ "@rsbuild/core": "2.2.11",
109
+ "@rslib/core": "1.0.3",
76
110
  "@scripts/rstest-config": "2.66.0",
77
- "@types/node": "^26.4.1",
78
- "@types/react": "^19.2.18",
79
- "@types/react-dom": "^19.2.7",
80
- "@typescript/native-preview": "7.0.0-dev.20260707.2",
111
+ "@types/node": "^26.6.3",
112
+ "@types/react": "^19.3.0",
113
+ "@types/react-dom": "^19.3.0",
81
114
  "esbuild": "^0.28.2",
82
- "react": "^19.2.8",
83
- "react-dom": "^19.2.8",
84
- "typescript": "^7.0.2"
115
+ "react": "^19.3.0",
116
+ "react-dom": "^19.3.0",
117
+ "typescript": "7.0.2"
85
118
  },
86
119
  "publishConfig": {
87
120
  "access": "public"
@@ -91,8 +124,11 @@
91
124
  "build": "rslib build",
92
125
  "test": "rstest run"
93
126
  },
94
- "homepage": "https://github.com/BleedingDev/ultramodern.js#readme",
127
+ "homepage": "https://github.com/ultramodernjs/ultramodern.js#readme",
95
128
  "bugs": {
96
- "url": "https://github.com/BleedingDev/ultramodern.js/issues"
129
+ "url": "https://github.com/ultramodernjs/ultramodern.js/issues"
130
+ },
131
+ "ultramodern": {
132
+ "sourceName": "@modern-js/federation-runtime"
97
133
  }
98
134
  }
@@ -23,14 +23,7 @@
23
23
  * consumption during a rollout window should be driven by expand/contract, not
24
24
  * by silent degrade.
25
25
  */
26
- import {
27
- classifyModuleFederationFallback,
28
- createModuleFederationFallbackTelemetry,
29
- emitModuleFederationFallbackTelemetry,
30
- type ModuleFederationFallbackClassification,
31
- type ModuleFederationFallbackTelemetryEmitOptions,
32
- type ModuleFederationFallbackTelemetryPayload,
33
- } from './index';
26
+
34
27
  import {
35
28
  createDiscoveryError,
36
29
  type DiscoveryError,
@@ -44,6 +37,15 @@ import {
44
37
  type SurfaceResolutionProvider,
45
38
  validateSurfaceRef,
46
39
  } from './surface-resolution-types';
40
+ import {
41
+ classifyModuleFederationFallback,
42
+ createModuleFederationFallbackTelemetry,
43
+ emitModuleFederationFallbackPayload,
44
+ emitModuleFederationFallbackTelemetry,
45
+ type ModuleFederationFallbackClassification,
46
+ type ModuleFederationFallbackTelemetryEmitOptions,
47
+ type ModuleFederationFallbackTelemetryPayload,
48
+ } from './telemetry';
47
49
 
48
50
  /** Where in the consumption lifecycle a failure was raised. */
49
51
  export type SurfaceConsumptionPhase = 'discovery' | 'load' | 'mount';
@@ -100,7 +102,7 @@ export type SurfaceConsumerOptions<T> = {
100
102
  * omitted.
101
103
  *
102
104
  * - `'noncritical'`: the degraded handler's result is returned and the
103
- * failure is swallowed — the promise never rejects.
105
+ * failure is swallowed — a failed degraded handler resolves undefined.
104
106
  * - `'critical'`: the degraded handler still runs (telemetry + fallback-UI
105
107
  * obligations hold), but the returned promise then REJECTS with the typed
106
108
  * failure so callers / rollout machinery observe it. Prefer expand/contract
@@ -154,7 +156,7 @@ async function reportFailure<T>(
154
156
  error?: unknown;
155
157
  resolved?: ResolvedDeliveryUnit;
156
158
  },
157
- ): Promise<T> {
159
+ ): Promise<T | undefined> {
158
160
  const remote = toRemoteString(ref);
159
161
  const telemetry = createModuleFederationFallbackTelemetry({
160
162
  appName: options.appName,
@@ -173,24 +175,7 @@ async function reportFailure<T>(
173
175
  status: 'degraded',
174
176
  });
175
177
 
176
- // Fire-and-forget style, but awaited so tests can assert emission. Emission
177
- // failures must never mask the degraded path.
178
- try {
179
- await emitModuleFederationFallbackTelemetry(
180
- {
181
- appName: options.appName,
182
- classification: base.classification,
183
- error: base.error ?? base.discoveryError,
184
- metadata: telemetry.metadata,
185
- phase: base.phase,
186
- remote,
187
- status: 'degraded',
188
- },
189
- options.telemetry ?? {},
190
- );
191
- } catch {
192
- // Telemetry sink failure is itself a degraded condition; swallow it.
193
- }
178
+ void emitModuleFederationFallbackPayload(telemetry, options.telemetry);
194
179
 
195
180
  const failure: SurfaceConsumptionFailure = {
196
181
  ref,
@@ -234,7 +219,7 @@ async function reportFailure<T>(
234
219
  // telemetry, then for noncritical resolve undefined; for critical reject
235
220
  // with the ORIGINAL typed error (never the handler's).
236
221
  try {
237
- await emitModuleFederationFallbackTelemetry(
222
+ void emitModuleFederationFallbackTelemetry(
238
223
  {
239
224
  appName: options.appName,
240
225
  classification: base.classification,
@@ -252,7 +237,7 @@ async function reportFailure<T>(
252
237
  if (critical) {
253
238
  throw reason;
254
239
  }
255
- return undefined as T;
240
+ return undefined;
256
241
  }
257
242
 
258
243
  if (critical) {
@@ -266,14 +251,14 @@ async function reportFailure<T>(
266
251
  * load failure the required degraded handler runs (telemetry + fallback UI).
267
252
  *
268
253
  * For a `noncritical` consumption (see {@link SurfaceConsumerOptions.classification})
269
- * this resolves to the degraded value and never rejects, so a failing
254
+ * this resolves to the degraded value (or undefined if the handler fails), so a failing
270
255
  * consumption is isolated from any sibling. For a `critical` consumption (the
271
256
  * default) it rejects with the typed failure AFTER the degraded handler has
272
257
  * run, so callers / rollout machinery observe the failure.
273
258
  */
274
259
  export async function consumeSurface<T>(
275
260
  options: SurfaceConsumerOptions<T>,
276
- ): Promise<T> {
261
+ ): Promise<T | undefined> {
277
262
  let ref: ParsedSurfaceRef;
278
263
  if (typeof options.ref === 'string') {
279
264
  const parsed = parseSurfaceRef(options.ref);
@@ -360,7 +345,7 @@ export function createSurfaceConsumer(
360
345
  return function consume<T>(
361
346
  options: Omit<SurfaceConsumerOptions<T>, keyof typeof base> &
362
347
  Partial<Pick<SurfaceConsumerOptions<T>, keyof typeof base>>,
363
- ): Promise<T> {
348
+ ): Promise<T | undefined> {
364
349
  return consumeSurface<T>({
365
350
  ...base,
366
351
  ...options,
@@ -51,8 +51,28 @@ export type DistributedSsrBoundaryProps<
51
51
  remote: string;
52
52
  };
53
53
 
54
+ /**
55
+ * The Module Federation `createLazyComponent` options the distributed boundary
56
+ * owns. `injectLink` is always false: the boundary owns remote CSS (the
57
+ * fragment CSS inventory in workerd, the MF manifest in the browser), so the
58
+ * bridge must not render a second `<link>` in SSR HTML.
59
+ */
60
+ export type DistributedSsrLazyComponentOptions = {
61
+ export: 'default';
62
+ fallback: ReactNode;
63
+ injectLink: false;
64
+ loading: null;
65
+ };
66
+
54
67
  export type CreateDistributedSsrComponentOptions<Props extends object> = {
55
- createComponent: () => ComponentType<Props>;
68
+ /**
69
+ * Creates the native remote on first render. Spread `options` into
70
+ * Module Federation's `createLazyComponent` and add the instance and
71
+ * loader.
72
+ */
73
+ createComponent: (
74
+ options: DistributedSsrLazyComponentOptions,
75
+ ) => ComponentType<Props>;
56
76
  expose: string;
57
77
  fallback: ReactNode;
58
78
  remote: string;
@@ -221,7 +241,12 @@ export function createDistributedSsrComponent<Props extends object>({
221
241
  let RemoteComponent: ComponentType<Props> | undefined;
222
242
 
223
243
  function DeferredRemoteComponent(props: Props) {
224
- RemoteComponent ??= createComponent();
244
+ RemoteComponent ??= createComponent({
245
+ export: 'default',
246
+ fallback,
247
+ injectLink: false,
248
+ loading: null,
249
+ });
225
250
  return <RemoteComponent {...props} />;
226
251
  }
227
252