@bleedingdev/modern-js-federation-runtime 3.9.3-ultramodern.7 → 3.9.3-ultramodern.9
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.
- package/dist/cjs/module-federation/native-browser-dev-client.js +320 -0
- package/dist/cjs/module-federation/native-browser-dev-refresh.js +226 -0
- package/dist/cjs/module-federation/native-browser-dev-runtime-plugin.js +233 -0
- package/dist/cjs/module-federation/native-dev-refresh-runtime-plugin.js +267 -0
- package/dist/cjs/module-federation/renderer-runtime-plugin.js +5 -2
- package/dist/esm/module-federation/native-browser-dev-client.mjs +276 -0
- package/dist/esm/module-federation/native-browser-dev-refresh.mjs +182 -0
- package/dist/esm/module-federation/native-browser-dev-runtime-plugin.mjs +183 -0
- package/dist/esm/module-federation/native-dev-refresh-runtime-plugin.mjs +229 -0
- package/dist/esm/module-federation/renderer-runtime-plugin.mjs +5 -2
- package/dist/esm-node/module-federation/native-browser-dev-client.mjs +277 -0
- package/dist/esm-node/module-federation/native-browser-dev-refresh.mjs +183 -0
- package/dist/esm-node/module-federation/native-browser-dev-runtime-plugin.mjs +184 -0
- package/dist/esm-node/module-federation/native-dev-refresh-runtime-plugin.mjs +230 -0
- package/dist/esm-node/module-federation/renderer-runtime-plugin.mjs +5 -2
- package/dist/types/module-federation/native-browser-dev-client.d.ts +41 -0
- package/dist/types/module-federation/native-browser-dev-refresh.d.ts +86 -0
- package/dist/types/module-federation/native-browser-dev-runtime-plugin.d.ts +40 -0
- package/dist/types/module-federation/native-dev-refresh-runtime-plugin.d.ts +90 -0
- package/package.json +60 -13
- package/src/module-federation/native-browser-dev-client.ts +424 -0
- package/src/module-federation/native-browser-dev-refresh.ts +373 -0
- package/src/module-federation/native-browser-dev-runtime-plugin.ts +276 -0
- package/src/module-federation/native-dev-refresh-runtime-plugin.ts +445 -0
- package/src/module-federation/renderer-runtime-plugin.ts +7 -2
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
|
|
3
|
+
interface ManifestRemote {
|
|
4
|
+
readonly name: string;
|
|
5
|
+
readonly alias: string;
|
|
6
|
+
readonly entry: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface RemoteInfo {
|
|
10
|
+
readonly name: string;
|
|
11
|
+
readonly entry: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface NativeHost {
|
|
15
|
+
readonly options?: unknown;
|
|
16
|
+
readonly shareScopeMap?: Record<
|
|
17
|
+
string,
|
|
18
|
+
Record<
|
|
19
|
+
string,
|
|
20
|
+
Record<string, { loaded?: boolean; loading?: unknown; lib?: unknown }>
|
|
21
|
+
>
|
|
22
|
+
>;
|
|
23
|
+
loadShare?(pkgName: string): Promise<unknown>;
|
|
24
|
+
loadRemote<T>(id: string, options?: unknown): Promise<T | null>;
|
|
25
|
+
registerRemotes(remotes: ManifestRemote[], options: { force: true }): void;
|
|
26
|
+
readonly loaderHook: {
|
|
27
|
+
readonly lifecycle: {
|
|
28
|
+
readonly fetch: {
|
|
29
|
+
emit(
|
|
30
|
+
url: string,
|
|
31
|
+
init: RequestInit,
|
|
32
|
+
remoteInfo: RemoteInfo,
|
|
33
|
+
context: {
|
|
34
|
+
initiator: 'loadRemote';
|
|
35
|
+
id: string;
|
|
36
|
+
resourceType: 'remoteEntry';
|
|
37
|
+
url: string;
|
|
38
|
+
expose?: string;
|
|
39
|
+
},
|
|
40
|
+
): Promise<Response | false | void>;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
[LOAD_WITH_CAPTURE]?: <T, Captured>(
|
|
45
|
+
id: string,
|
|
46
|
+
capture: (module: T | null, resolution?: LoadResolution) => Captured,
|
|
47
|
+
) => Promise<{ module: T | null; captured: Captured | undefined }>;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const LOAD_WITH_CAPTURE = Symbol.for(
|
|
51
|
+
'ultramodern.federation.load-with-capture',
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
interface RemoteLane {
|
|
55
|
+
readonly remote: ManifestRemote;
|
|
56
|
+
readonly active: Set<Promise<unknown>>;
|
|
57
|
+
info?: RemoteInfo;
|
|
58
|
+
revision?: string | null;
|
|
59
|
+
candidate?: string | null;
|
|
60
|
+
transition?: Promise<unknown>;
|
|
61
|
+
probe?: Promise<string | null>;
|
|
62
|
+
snapshot?: unknown;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
interface LoadLease {
|
|
66
|
+
readonly host: NativeHost;
|
|
67
|
+
readonly parent?: LoadLease;
|
|
68
|
+
lane?: RemoteLane;
|
|
69
|
+
admitted?: boolean;
|
|
70
|
+
active?: boolean;
|
|
71
|
+
snapshot?: unknown;
|
|
72
|
+
resolution?: LoadResolution;
|
|
73
|
+
release?: (success: boolean) => void;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
interface LoadResolution {
|
|
77
|
+
readonly remoteSnapshot: unknown;
|
|
78
|
+
readonly expose: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface NativeDevRefreshOptions {
|
|
82
|
+
readonly remotes: readonly ManifestRemote[];
|
|
83
|
+
readonly revisionHeader: string;
|
|
84
|
+
readonly requestTimeout: number;
|
|
85
|
+
/** Exact and prefix singleton keys owned by the selected native adapter. */
|
|
86
|
+
readonly shared?: readonly string[];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Retain the host and its shared factories while refreshing one completed
|
|
91
|
+
* development remote. Production remotes without a revision keep native MF's
|
|
92
|
+
* ordinary cache. The compiler registers this plugin only on Node dev hosts.
|
|
93
|
+
*/
|
|
94
|
+
export default function nativeDevRefreshRuntimePlugin(
|
|
95
|
+
options: NativeDevRefreshOptions,
|
|
96
|
+
) {
|
|
97
|
+
if (
|
|
98
|
+
!options.revisionHeader ||
|
|
99
|
+
!Number.isSafeInteger(options.requestTimeout) ||
|
|
100
|
+
options.requestTimeout <= 0
|
|
101
|
+
)
|
|
102
|
+
throw new TypeError(
|
|
103
|
+
'Native federation dev refresh requires a bounded revision probe.',
|
|
104
|
+
);
|
|
105
|
+
const hosts = new WeakMap<NativeHost, Map<string, RemoteLane>>();
|
|
106
|
+
const calls = new AsyncLocalStorage<LoadLease>();
|
|
107
|
+
|
|
108
|
+
const probe = (
|
|
109
|
+
host: NativeHost,
|
|
110
|
+
lane: RemoteLane,
|
|
111
|
+
expose?: string,
|
|
112
|
+
): Promise<string | null> => {
|
|
113
|
+
if (lane.probe) return lane.probe;
|
|
114
|
+
const info = lane.info;
|
|
115
|
+
if (!info)
|
|
116
|
+
throw new Error('Native federation dev remote has no admitted entry.');
|
|
117
|
+
const pending = (async () => {
|
|
118
|
+
const init: RequestInit = {
|
|
119
|
+
method: 'HEAD',
|
|
120
|
+
cache: 'no-store',
|
|
121
|
+
signal: AbortSignal.timeout(options.requestTimeout),
|
|
122
|
+
};
|
|
123
|
+
// Use the same native fetch lifecycle as entry loading, including any
|
|
124
|
+
// authored authentication hooks and the shared bounded Node transport.
|
|
125
|
+
let clearDeadline!: () => void;
|
|
126
|
+
const signal = init.signal!;
|
|
127
|
+
const deadline = new Promise<never>((_resolve, reject) => {
|
|
128
|
+
const abort = () => reject(signal.reason);
|
|
129
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
130
|
+
clearDeadline = () => signal.removeEventListener('abort', abort);
|
|
131
|
+
});
|
|
132
|
+
const request = (async () => {
|
|
133
|
+
const hooked = await host.loaderHook.lifecycle.fetch.emit(
|
|
134
|
+
info.entry,
|
|
135
|
+
init,
|
|
136
|
+
info,
|
|
137
|
+
{
|
|
138
|
+
initiator: 'loadRemote',
|
|
139
|
+
id:
|
|
140
|
+
expose && expose !== '.'
|
|
141
|
+
? `${info.name}/${expose.replace(/^\.\//u, '')}`
|
|
142
|
+
: info.name,
|
|
143
|
+
resourceType: 'remoteEntry',
|
|
144
|
+
url: info.entry,
|
|
145
|
+
expose,
|
|
146
|
+
},
|
|
147
|
+
);
|
|
148
|
+
const response =
|
|
149
|
+
hooked instanceof Response
|
|
150
|
+
? hooked
|
|
151
|
+
: await globalThis.fetch(info.entry, init);
|
|
152
|
+
if (signal.aborted) {
|
|
153
|
+
void response.body?.cancel().catch(() => {});
|
|
154
|
+
throw signal.reason;
|
|
155
|
+
}
|
|
156
|
+
return response;
|
|
157
|
+
})();
|
|
158
|
+
let response: Response;
|
|
159
|
+
try {
|
|
160
|
+
// Authored hooks must receive the native transport context, but a
|
|
161
|
+
// hook ignoring the signal must not hold this remote's gate forever.
|
|
162
|
+
response = await Promise.race([request, deadline]);
|
|
163
|
+
} finally {
|
|
164
|
+
clearDeadline();
|
|
165
|
+
}
|
|
166
|
+
void response.body?.cancel().catch(() => {});
|
|
167
|
+
if (!response.ok)
|
|
168
|
+
throw Object.assign(
|
|
169
|
+
new Error(
|
|
170
|
+
`Native federation dev revision request failed: ${response.status} ${info.entry}`,
|
|
171
|
+
),
|
|
172
|
+
{
|
|
173
|
+
status: response.status,
|
|
174
|
+
},
|
|
175
|
+
);
|
|
176
|
+
const revision = response.headers.get(options.revisionHeader);
|
|
177
|
+
if (revision !== null && !/^[a-f0-9]{64}$/u.test(revision))
|
|
178
|
+
throw new Error(
|
|
179
|
+
'Native federation dev remote returned an invalid graph revision.',
|
|
180
|
+
);
|
|
181
|
+
if (revision === null && typeof lane.revision === 'string')
|
|
182
|
+
throw new Error(
|
|
183
|
+
'Native federation dev remote lost its completed graph revision.',
|
|
184
|
+
);
|
|
185
|
+
return revision;
|
|
186
|
+
})();
|
|
187
|
+
lane.probe = pending;
|
|
188
|
+
const clear = () => {
|
|
189
|
+
if (lane.probe === pending) lane.probe = undefined;
|
|
190
|
+
};
|
|
191
|
+
void pending.then(clear, clear);
|
|
192
|
+
return pending;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
name: 'ultramodern-native-federation-dev-refresh',
|
|
197
|
+
beforeInit<T extends { origin: NativeHost }>(args: T): T {
|
|
198
|
+
const host = args.origin;
|
|
199
|
+
if (hosts.has(host)) return args;
|
|
200
|
+
const lanes = new Map<string, RemoteLane>(
|
|
201
|
+
options.remotes.map(remote => [
|
|
202
|
+
remote.name,
|
|
203
|
+
{
|
|
204
|
+
remote: { ...remote },
|
|
205
|
+
active: new Set<Promise<unknown>>(),
|
|
206
|
+
} satisfies RemoteLane,
|
|
207
|
+
]),
|
|
208
|
+
);
|
|
209
|
+
hosts.set(host, lanes);
|
|
210
|
+
const load = host.loadRemote.bind(host);
|
|
211
|
+
const loadWithCapture = async <Module, Captured>(
|
|
212
|
+
id: string,
|
|
213
|
+
loadOptions?: unknown,
|
|
214
|
+
capture?: (
|
|
215
|
+
module: Module | null,
|
|
216
|
+
resolution?: LoadResolution,
|
|
217
|
+
) => Captured,
|
|
218
|
+
): Promise<{ module: Module | null; captured: Captured | undefined }> => {
|
|
219
|
+
const parent = calls.getStore();
|
|
220
|
+
const lease: LoadLease = {
|
|
221
|
+
host,
|
|
222
|
+
...(parent?.host === host ? { parent } : {}),
|
|
223
|
+
};
|
|
224
|
+
return calls.run(lease, async () => {
|
|
225
|
+
let success = false;
|
|
226
|
+
try {
|
|
227
|
+
const module = await load<Module>(id, loadOptions);
|
|
228
|
+
// Capture this container's admitted assets before releasing its
|
|
229
|
+
// lease, even when native hooks rewrite the requested module ID.
|
|
230
|
+
const captured = capture?.(module, lease.resolution);
|
|
231
|
+
success = module !== null && lease.admitted === true;
|
|
232
|
+
return { module, captured };
|
|
233
|
+
} finally {
|
|
234
|
+
lease.active = false;
|
|
235
|
+
lease.release?.(success);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
};
|
|
239
|
+
host.loadRemote = async <Module>(id: string, loadOptions?: unknown) =>
|
|
240
|
+
(await loadWithCapture<Module, never>(id, loadOptions)).module;
|
|
241
|
+
host[LOAD_WITH_CAPTURE] = <Module, Captured>(
|
|
242
|
+
id: string,
|
|
243
|
+
capture: (
|
|
244
|
+
module: Module | null,
|
|
245
|
+
resolution?: LoadResolution,
|
|
246
|
+
) => Captured,
|
|
247
|
+
) => loadWithCapture(id, undefined, capture);
|
|
248
|
+
return args;
|
|
249
|
+
},
|
|
250
|
+
async beforeRequest<T extends { origin: NativeHost; id: string }>(
|
|
251
|
+
args: T,
|
|
252
|
+
): Promise<T> {
|
|
253
|
+
const lease = calls.getStore();
|
|
254
|
+
if (!lease || lease.host !== args.origin) return args;
|
|
255
|
+
// Authored beforeRequest hooks have already rewritten the ID. Discover
|
|
256
|
+
// its actual remote here without executing those hooks a second time.
|
|
257
|
+
const lane = [...(hosts.get(args.origin)?.values() ?? [])].find(
|
|
258
|
+
({ remote }) =>
|
|
259
|
+
[remote.alias, remote.name].some(
|
|
260
|
+
name => args.id === name || args.id.startsWith(`${name}/`),
|
|
261
|
+
),
|
|
262
|
+
);
|
|
263
|
+
if (!lane || lease.lane) return args;
|
|
264
|
+
lease.lane = lane;
|
|
265
|
+
const prefix = [lane.remote.alias, lane.remote.name].find(
|
|
266
|
+
name => args.id === name || args.id.startsWith(`${name}/`),
|
|
267
|
+
)!;
|
|
268
|
+
const expose =
|
|
269
|
+
args.id === prefix ? '.' : `./${args.id.slice(prefix.length + 1)}`;
|
|
270
|
+
for (let ancestor = lease.parent; ancestor; ancestor = ancestor.parent) {
|
|
271
|
+
if (ancestor.lane === lane && ancestor.active) {
|
|
272
|
+
// An authored native hook may await another expose on this host.
|
|
273
|
+
// Its ancestor already fences this remote's generation; borrowing
|
|
274
|
+
// that lease avoids waiting for or draining the current call itself.
|
|
275
|
+
// Track the child as well: a hook may start it without awaiting it,
|
|
276
|
+
// and a later refresh must still wait for that older native load.
|
|
277
|
+
let release!: () => void;
|
|
278
|
+
const finished = new Promise<void>(resolve => {
|
|
279
|
+
release = resolve;
|
|
280
|
+
});
|
|
281
|
+
lane.active.add(finished);
|
|
282
|
+
lease.active = true;
|
|
283
|
+
lease.snapshot = ancestor.snapshot;
|
|
284
|
+
lease.release = () => {
|
|
285
|
+
lane.active.delete(finished);
|
|
286
|
+
release();
|
|
287
|
+
};
|
|
288
|
+
return args;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
for (;;) {
|
|
292
|
+
if (lane.transition) {
|
|
293
|
+
await lane.transition;
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const revision =
|
|
297
|
+
lane.revision === undefined
|
|
298
|
+
? undefined
|
|
299
|
+
: lane.revision === null
|
|
300
|
+
? null
|
|
301
|
+
: await probe(args.origin, lane, expose);
|
|
302
|
+
if (lane.transition) {
|
|
303
|
+
await lane.transition;
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
let finish!: () => void;
|
|
307
|
+
const finished = new Promise<void>(resolve => {
|
|
308
|
+
finish = resolve;
|
|
309
|
+
});
|
|
310
|
+
if (lane.revision === undefined || revision !== lane.revision) {
|
|
311
|
+
lane.transition = finished;
|
|
312
|
+
lease.release = success => {
|
|
313
|
+
if (success) lane.revision = lane.candidate ?? null;
|
|
314
|
+
if (lane.transition === finished) lane.transition = undefined;
|
|
315
|
+
finish();
|
|
316
|
+
};
|
|
317
|
+
// Finish older native loads, including their asset capture, before
|
|
318
|
+
// evicting this remote. New calls wait for this admission to finish.
|
|
319
|
+
while (lane.active.size) await Promise.allSettled([...lane.active]);
|
|
320
|
+
lane.candidate = revision;
|
|
321
|
+
if (lane.revision !== undefined) {
|
|
322
|
+
// Authored share hooks may load another expose while we retain
|
|
323
|
+
// providers. Let those calls borrow the still-current generation,
|
|
324
|
+
// then drain any child they started before evicting its container.
|
|
325
|
+
lease.active = true;
|
|
326
|
+
lease.snapshot = lane.snapshot;
|
|
327
|
+
// Native force-registration removes remote-origin providers that
|
|
328
|
+
// only the remote explicitly consumed. The host can still retain
|
|
329
|
+
// their private state through another shared renderer export.
|
|
330
|
+
// Record those loaded providers as host uses through the ordinary
|
|
331
|
+
// share lifecycle before native eviction; do not reload unused
|
|
332
|
+
// providers or mutate native factory/cache records ourselves.
|
|
333
|
+
const retained = new Set<string>();
|
|
334
|
+
for (const scope of Object.values(args.origin.shareScopeMap ?? {}))
|
|
335
|
+
for (const [pkgName, versions] of Object.entries(scope))
|
|
336
|
+
if (
|
|
337
|
+
options.shared?.some(
|
|
338
|
+
key =>
|
|
339
|
+
key === pkgName ||
|
|
340
|
+
(key.endsWith('/') && pkgName.startsWith(key)),
|
|
341
|
+
) &&
|
|
342
|
+
Object.values(versions).some(
|
|
343
|
+
shared => shared.loaded || shared.loading || shared.lib,
|
|
344
|
+
)
|
|
345
|
+
)
|
|
346
|
+
retained.add(pkgName);
|
|
347
|
+
for (const pkgName of retained) {
|
|
348
|
+
if (!args.origin.loadShare)
|
|
349
|
+
throw new TypeError(
|
|
350
|
+
'Native federation dev refresh requires the host share lifecycle.',
|
|
351
|
+
);
|
|
352
|
+
await args.origin.loadShare(pkgName);
|
|
353
|
+
}
|
|
354
|
+
while (lane.active.size) await Promise.allSettled([...lane.active]);
|
|
355
|
+
args.origin.registerRemotes([{ ...lane.remote }], { force: true });
|
|
356
|
+
lease.snapshot = undefined;
|
|
357
|
+
}
|
|
358
|
+
} else {
|
|
359
|
+
lane.active.add(finished);
|
|
360
|
+
lease.release = () => {
|
|
361
|
+
lane.active.delete(finished);
|
|
362
|
+
finish();
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
lease.active = true;
|
|
366
|
+
return args;
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
loadSnapshot<
|
|
370
|
+
T extends {
|
|
371
|
+
options: unknown;
|
|
372
|
+
moduleInfo: { name: string };
|
|
373
|
+
remoteSnapshot?: unknown;
|
|
374
|
+
},
|
|
375
|
+
>(args: T): T {
|
|
376
|
+
const lease = calls.getStore();
|
|
377
|
+
if (
|
|
378
|
+
!lease ||
|
|
379
|
+
args.options !== lease.host.options ||
|
|
380
|
+
args.moduleInfo.name !== lease.lane?.remote.name
|
|
381
|
+
)
|
|
382
|
+
return args;
|
|
383
|
+
// A borrowed load uses its active ancestor's generation, even when a
|
|
384
|
+
// different host has replaced the process-global snapshot meanwhile.
|
|
385
|
+
// Native snapshot and renderer admission still validate this selection.
|
|
386
|
+
if (lease.snapshot !== undefined)
|
|
387
|
+
return { ...args, remoteSnapshot: lease.snapshot };
|
|
388
|
+
lease.snapshot = args.remoteSnapshot;
|
|
389
|
+
return args;
|
|
390
|
+
},
|
|
391
|
+
afterLoadSnapshot<
|
|
392
|
+
T extends {
|
|
393
|
+
host: NativeHost;
|
|
394
|
+
moduleInfo: { name: string };
|
|
395
|
+
remoteSnapshot: unknown;
|
|
396
|
+
},
|
|
397
|
+
>(args: T): T {
|
|
398
|
+
const lease = calls.getStore();
|
|
399
|
+
if (
|
|
400
|
+
lease?.host === args.host &&
|
|
401
|
+
args.moduleInfo.name === lease.lane?.remote.name
|
|
402
|
+
)
|
|
403
|
+
// Initial manifest loads have no snapshot at loadSnapshot. Retain the
|
|
404
|
+
// resolved snapshot before authored afterResolve hooks can nest loads.
|
|
405
|
+
lease.snapshot = args.remoteSnapshot;
|
|
406
|
+
return args;
|
|
407
|
+
},
|
|
408
|
+
async afterResolve<
|
|
409
|
+
T extends {
|
|
410
|
+
origin: NativeHost;
|
|
411
|
+
remote: { name: string };
|
|
412
|
+
remoteInfo: RemoteInfo;
|
|
413
|
+
remoteSnapshot: unknown;
|
|
414
|
+
expose: string;
|
|
415
|
+
},
|
|
416
|
+
>(args: T): Promise<T> {
|
|
417
|
+
const lane = hosts.get(args.origin)?.get(args.remote.name);
|
|
418
|
+
const lease = calls.getStore();
|
|
419
|
+
if (
|
|
420
|
+
!lane ||
|
|
421
|
+
lease?.host !== args.origin ||
|
|
422
|
+
lease.lane !== lane ||
|
|
423
|
+
!lease.active
|
|
424
|
+
)
|
|
425
|
+
return args;
|
|
426
|
+
// Preloads and direct remote-handler resolutions have no load lease.
|
|
427
|
+
// They must not change the entry or candidate a guarded refresh commits.
|
|
428
|
+
if (lane.info?.entry !== args.remoteInfo.entry)
|
|
429
|
+
lane.candidate = undefined;
|
|
430
|
+
lane.info = args.remoteInfo;
|
|
431
|
+
if (lane.candidate === undefined)
|
|
432
|
+
lane.candidate = await probe(args.origin, lane, args.expose);
|
|
433
|
+
lease.admitted = true;
|
|
434
|
+
lane.snapshot = args.remoteSnapshot;
|
|
435
|
+
// Native force-registration evicts a process-global snapshot. Retain
|
|
436
|
+
// the snapshot this particular host/load admitted, so another host's
|
|
437
|
+
// refresh cannot change its later CSS and hydration asset capture.
|
|
438
|
+
lease.resolution = {
|
|
439
|
+
remoteSnapshot: args.remoteSnapshot,
|
|
440
|
+
expose: args.expose,
|
|
441
|
+
};
|
|
442
|
+
return args;
|
|
443
|
+
},
|
|
444
|
+
};
|
|
445
|
+
}
|
|
@@ -155,6 +155,11 @@ export function createRendererFederationRuntimePlugin(
|
|
|
155
155
|
const observedEntries = new WeakMap<object, string>();
|
|
156
156
|
const acceptedEntries = new Set<string>();
|
|
157
157
|
const versions = rendererShareVersions(expected);
|
|
158
|
+
const sharedVersion = (name: string): string | undefined =>
|
|
159
|
+
versions.get(name) ??
|
|
160
|
+
(expected.profile.renderer === 'react'
|
|
161
|
+
? undefined
|
|
162
|
+
: [...versions].find(([owner]) => name.startsWith(`${owner}/`))?.[1]);
|
|
158
163
|
return {
|
|
159
164
|
name: 'ultramodern-renderer-federation-contract',
|
|
160
165
|
beforeRegisterRemote(args: { remote: unknown }) {
|
|
@@ -332,7 +337,7 @@ export function createRendererFederationRuntimePlugin(
|
|
|
332
337
|
return args;
|
|
333
338
|
},
|
|
334
339
|
resolveShare(args: ResolveShareArgs) {
|
|
335
|
-
const version =
|
|
340
|
+
const version = sharedVersion(args.pkgName);
|
|
336
341
|
if (version === undefined) return args;
|
|
337
342
|
const resolve = args.resolver;
|
|
338
343
|
return {
|
|
@@ -378,7 +383,7 @@ export function createRendererFederationRuntimePlugin(
|
|
|
378
383
|
// Native loading fills lib after resolveShare. Observe that completed
|
|
379
384
|
// factory identity here; rejection remains in the pre-factory resolver
|
|
380
385
|
// because native afterLoadShare deliberately swallows thrown errors.
|
|
381
|
-
if (
|
|
386
|
+
if (sharedVersion(args.pkgName) !== undefined && args.selectedShared) {
|
|
382
387
|
const shared =
|
|
383
388
|
record(args.selectedShared.treeShaking) &&
|
|
384
389
|
args.selectedShared.treeShaking.loaded
|