@bleedingdev/modern-js-renderer-octane 3.9.3-ultramodern.1 → 3.9.3-ultramodern.11

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 (100) hide show
  1. package/dist/cjs/client.js +56 -6
  2. package/dist/cjs/entry-application.js +7 -2
  3. package/dist/cjs/entry-client.js +3 -0
  4. package/dist/cjs/entry-server.js +13 -5
  5. package/dist/cjs/federation.js +108 -6
  6. package/dist/cjs/i18n/I18nProvider.js +6 -14
  7. package/dist/cjs/i18n/LocalizedLink.js +24 -33
  8. package/dist/cjs/i18n/index.js +0 -3
  9. package/dist/cjs/i18n-external-snapshot.js +645 -0
  10. package/dist/cjs/index.js +4 -4
  11. package/dist/cjs/manifest.js +2 -2
  12. package/dist/cjs/plugin/compiler.js +2 -5
  13. package/dist/cjs/plugin/create.js +27 -26
  14. package/dist/cjs/plugin/index.js +2 -2
  15. package/dist/cjs/plugin/profile.js +12 -10
  16. package/dist/cjs/router-client.js +1 -1
  17. package/dist/cjs/router-external-snapshot.js +291 -0
  18. package/dist/cjs/router-handler.js +11 -8
  19. package/dist/cjs/router-server.js +3 -11
  20. package/dist/cjs/router.js +1 -1
  21. package/dist/cjs/routes.js +20 -17
  22. package/dist/cjs/server.js +28 -7
  23. package/dist/cjs/worker-federation-context.js +63 -0
  24. package/dist/cjs/worker-federation-contexts.js +97 -0
  25. package/dist/cjs/worker-federation.js +97 -0
  26. package/dist/esm/client.mjs +56 -6
  27. package/dist/esm/entry-application.mjs +7 -2
  28. package/dist/esm/entry-client.mjs +3 -0
  29. package/dist/esm/entry-server.mjs +11 -6
  30. package/dist/esm/federation.mjs +110 -8
  31. package/dist/esm/i18n/I18nProvider.mjs +7 -15
  32. package/dist/esm/i18n/LocalizedLink.mjs +24 -30
  33. package/dist/esm/i18n/index.mjs +1 -1
  34. package/dist/esm/i18n-external-snapshot.mjs +576 -0
  35. package/dist/esm/index.mjs +4 -4
  36. package/dist/esm/manifest.mjs +2 -2
  37. package/dist/esm/router-client.mjs +1 -1
  38. package/dist/esm/router-external-snapshot.mjs +244 -0
  39. package/dist/esm/router-handler.mjs +11 -8
  40. package/dist/esm/router-server.mjs +0 -1
  41. package/dist/esm/router.mjs +2 -2
  42. package/dist/esm/routes.mjs +18 -18
  43. package/dist/esm/server.mjs +24 -3
  44. package/dist/esm/worker-federation-context.mjs +19 -0
  45. package/dist/esm/worker-federation-contexts.mjs +47 -0
  46. package/dist/esm/worker-federation.mjs +59 -0
  47. package/dist/esm-node/manifest.mjs +2 -2
  48. package/dist/esm-node/plugin/compiler.mjs +2 -5
  49. package/dist/esm-node/plugin/create.mjs +27 -26
  50. package/dist/esm-node/plugin/index.mjs +2 -2
  51. package/dist/esm-node/plugin/profile.mjs +12 -10
  52. package/dist/types/client.d.ts +5 -1
  53. package/dist/types/entry-client.d.ts +2 -0
  54. package/dist/types/entry-server.d.ts +4 -1
  55. package/dist/types/federation.d.ts +2 -0
  56. package/dist/types/i18n/LocalizedLink.d.ts +5 -32
  57. package/dist/types/i18n/context.d.ts +1 -1
  58. package/dist/types/i18n/index.d.ts +1 -1
  59. package/dist/types/i18n-external-snapshot.d.ts +43 -0
  60. package/dist/types/index.d.ts +4 -4
  61. package/dist/types/manifest.d.ts +2 -2
  62. package/dist/types/router-external-snapshot.d.ts +10 -0
  63. package/dist/types/router-server.d.ts +0 -2
  64. package/dist/types/routes.d.ts +2 -0
  65. package/dist/types/server.d.ts +6 -0
  66. package/dist/types/worker-federation-context.d.ts +34 -0
  67. package/dist/types/worker-federation-contexts.d.ts +25 -0
  68. package/dist/types/worker-federation.d.ts +20 -0
  69. package/package.json +84 -22
  70. package/src/client.ts +99 -16
  71. package/src/entry-application.ts +7 -3
  72. package/src/entry-client.ts +10 -0
  73. package/src/entry-server.ts +20 -9
  74. package/src/federation.ts +164 -15
  75. package/src/i18n/I18nProvider.ts +11 -20
  76. package/src/i18n/LocalizedLink.ts +46 -72
  77. package/src/i18n/context.ts +1 -1
  78. package/src/i18n/index.ts +1 -5
  79. package/src/i18n-external-snapshot.ts +803 -0
  80. package/src/index.ts +4 -4
  81. package/src/manifest.ts +2 -2
  82. package/src/plugin/compiler-manifest.cjs +27 -1
  83. package/src/plugin/compiler.ts +6 -14
  84. package/src/plugin/create.ts +27 -26
  85. package/src/plugin/index.ts +4 -2
  86. package/src/plugin/profile.ts +14 -10
  87. package/src/router-client.ts +1 -1
  88. package/src/router-external-snapshot.ts +363 -0
  89. package/src/router-handler.ts +11 -8
  90. package/src/router-server.ts +0 -2
  91. package/src/router.ts +2 -2
  92. package/src/routes.ts +37 -26
  93. package/src/server.ts +45 -5
  94. package/src/worker-federation-context.ts +51 -0
  95. package/src/worker-federation-contexts.ts +91 -0
  96. package/src/worker-federation.ts +120 -0
  97. package/dist/cjs/router-injection.js +0 -139
  98. package/dist/esm/router-injection.mjs +0 -101
  99. package/dist/types/router-injection.d.ts +0 -8
  100. package/src/router-injection.ts +0 -145
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bleedingdev/modern-js-renderer-octane",
3
- "version": "3.9.3-ultramodern.1",
3
+ "version": "3.9.3-ultramodern.11",
4
4
  "description": "UltraModern native Octane application adapter.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -143,29 +143,61 @@
143
143
  "default": "./dist/cjs/plugin/index.js"
144
144
  }
145
145
  },
146
- "./package.json": "./package.json"
146
+ "./package.json": "./package.json",
147
+ "./router-external-snapshot": {
148
+ "import": {
149
+ "types": "./dist/types/router-external-snapshot.d.ts",
150
+ "default": "./dist/esm/router-external-snapshot.mjs"
151
+ },
152
+ "require": {
153
+ "types": "./dist/types/router-external-snapshot.d.ts",
154
+ "default": "./dist/cjs/router-external-snapshot.js"
155
+ }
156
+ },
157
+ "./i18n-external-snapshot": {
158
+ "import": {
159
+ "types": "./dist/types/i18n-external-snapshot.d.ts",
160
+ "default": "./dist/esm/i18n-external-snapshot.mjs"
161
+ },
162
+ "require": {
163
+ "types": "./dist/types/i18n-external-snapshot.d.ts",
164
+ "default": "./dist/cjs/i18n-external-snapshot.js"
165
+ }
166
+ },
167
+ "./worker-federation-contexts": {
168
+ "import": {
169
+ "types": "./dist/types/worker-federation-contexts.d.ts",
170
+ "default": "./dist/esm/worker-federation-contexts.mjs"
171
+ },
172
+ "require": {
173
+ "types": "./dist/types/worker-federation-contexts.d.ts",
174
+ "default": "./dist/cjs/worker-federation-contexts.js"
175
+ }
176
+ }
147
177
  },
148
178
  "bin": {
149
179
  "octane-tsc": "bin/octane-tsc.mjs"
150
180
  },
151
181
  "engines": {
152
- "node": ">=26.10.0"
182
+ "node": ">=26.12.0"
153
183
  },
154
184
  "dependencies": {
155
- "@modern-js/renderer-core": "npm:@bleedingdev/modern-js-renderer-core@3.9.3-ultramodern.1",
185
+ "@modern-js/renderer-core": "npm:@bleedingdev/modern-js-renderer-core@3.9.3-ultramodern.11",
156
186
  "@volar/language-core": "2.4.28",
157
187
  "jsonc-parser": "3.3.1",
158
- "seroval": "1.6.8",
159
- "seroval-plugins": "1.6.8",
188
+ "seroval": "1.6.9",
189
+ "seroval-plugins": "1.6.9",
160
190
  "typescript": "7.0.2"
161
191
  },
162
192
  "peerDependencies": {
163
- "@modern-js/i18n-runtime-extensions": "3.9.3-ultramodern.1",
164
- "@octanejs/rspack-plugin": "0.1.55",
165
- "@octanejs/tanstack-router": "0.1.60+ultramodern.0b9f76ee3003",
166
- "@rsbuild/core": "2.2.11",
193
+ "@modern-js/i18n-runtime-extensions": "3.9.3-ultramodern.11",
194
+ "@octanejs/rspack-plugin": "0.2.4",
195
+ "@octanejs/tanstack-router": "0.2.10",
196
+ "@rsbuild/core": "2.2.13",
197
+ "@tanstack/router-core": "1.171.38",
198
+ "@tsrx/oxc": "0.20.0",
167
199
  "i18next": ">=25.7.4",
168
- "octane": "0.7.1+ultramodern.1331985ea3b0"
200
+ "octane": "0.13.8"
169
201
  },
170
202
  "peerDependenciesMeta": {
171
203
  "@modern-js/i18n-runtime-extensions": {
@@ -180,6 +212,9 @@
180
212
  "@rsbuild/core": {
181
213
  "optional": true
182
214
  },
215
+ "@tsrx/oxc": {
216
+ "optional": true
217
+ },
183
218
  "i18next": {
184
219
  "optional": true
185
220
  },
@@ -188,22 +223,52 @@
188
223
  }
189
224
  },
190
225
  "devDependencies": {
191
- "@modern-js/i18n-runtime-extensions": "npm:@bleedingdev/modern-js-i18n-runtime-extensions@3.9.3-ultramodern.1",
192
- "@modern-js/tsconfig": "npm:@bleedingdev/modern-js-tsconfig@3.9.3-ultramodern.1",
193
- "@octanejs/rspack-plugin": "0.1.55",
194
- "@octanejs/tanstack-router": "https://github.com/bleedingdev/octane/releases/download/%40octanejs%2Ftanstack-router%400.1.60%2Bultramodern.0b9f76ee3003/octanejs-tanstack-router-0.1.60%2Bultramodern.0b9f76ee3003.tgz",
195
- "@rsbuild/core": "2.2.11",
226
+ "@modern-js/i18n-runtime-extensions": "npm:@bleedingdev/modern-js-i18n-runtime-extensions@3.9.3-ultramodern.11",
227
+ "@modern-js/tsconfig": "npm:@bleedingdev/modern-js-tsconfig@3.9.3-ultramodern.11",
228
+ "@octanejs/rspack-plugin": "https://github.com/BleedingDev/octane/releases/download/ultramodern-octane-2383d2ab1e70/octanejs-rspack-plugin-0.2.4.tgz",
229
+ "@octanejs/tanstack-router": "https://github.com/BleedingDev/octane/releases/download/ultramodern-octane-2383d2ab1e70/octanejs-tanstack-router-0.2.10.tgz",
230
+ "@rsbuild/core": "2.2.13",
196
231
  "@rslib/core": "1.0.3",
197
232
  "@scripts/rstest-config": "2.66.0",
198
- "@types/node": "^26.6.3",
199
- "happy-dom": "^20.14.5",
200
- "octane": "https://github.com/bleedingdev/octane/releases/download/octane%400.7.1%2Bultramodern.1331985ea3b0/octane-0.7.1%2Bultramodern.1331985ea3b0.tgz",
233
+ "@tanstack/router-core": "https://github.com/BleedingDev/router/releases/download/ultramodern-router-core-585d3233f7f5/tanstack-router-core-1.171.38.tgz",
234
+ "@tsrx/oxc": "0.20.0",
235
+ "@types/jsdom": "27.0.0",
236
+ "@types/node": "^26.6.5",
237
+ "happy-dom": "^21.0.4",
238
+ "i18next": "26.4.6",
239
+ "jsdom": "30.0.1",
240
+ "octane": "https://github.com/BleedingDev/octane/releases/download/ultramodern-octane-2383d2ab1e70/octane-0.13.8.tgz",
201
241
  "typescript": "7.0.2"
202
242
  },
203
243
  "sideEffects": false,
204
244
  "publishConfig": {
205
245
  "access": "public"
206
246
  },
247
+ "ultramodern": {
248
+ "nativeArtifacts": {
249
+ "octane": {
250
+ "request": "https://github.com/BleedingDev/octane/releases/download/ultramodern-octane-2383d2ab1e70/octane-0.13.8.tgz",
251
+ "version": "0.13.8",
252
+ "integrity": "sha512-ryNxtzX4pwMVHNhWyEYG8VnpRUByWUDhtVDpYlQkeqipTSF7s8cP86CzBl/miNCruE48D27mnR7E8UW6V1fEnA=="
253
+ },
254
+ "@octanejs/tanstack-router": {
255
+ "request": "https://github.com/BleedingDev/octane/releases/download/ultramodern-octane-2383d2ab1e70/octanejs-tanstack-router-0.2.10.tgz",
256
+ "version": "0.2.10",
257
+ "integrity": "sha512-wdnPhVZSQqZg2DDllUyz4RZufn7I0GEnPKrb/CB21Z8JYKnunAtZn41Dma/+xhg5xvN/USnIg9Eyyf2nbJgxMQ=="
258
+ },
259
+ "@octanejs/rspack-plugin": {
260
+ "request": "https://github.com/BleedingDev/octane/releases/download/ultramodern-octane-2383d2ab1e70/octanejs-rspack-plugin-0.2.4.tgz",
261
+ "version": "0.2.4",
262
+ "integrity": "sha512-I8m/d/oj3Qh8nG5nqkWD90m8qgNwlPK9oNh5tlVvGNdSYCTLddRG7Og8wpYKFAGxe2D+0zOIhYGarC0ZKZgSzw=="
263
+ },
264
+ "@tanstack/router-core": {
265
+ "request": "https://github.com/BleedingDev/router/releases/download/ultramodern-router-core-585d3233f7f5/tanstack-router-core-1.171.38.tgz",
266
+ "version": "1.171.38",
267
+ "integrity": "sha512-d/aGvcyEVwDh2Cn9MuZM1n1dqTIqkFYi7AHY2Na/6x/a0jVSDVTNVVVY7QEsuKFXktaJF9zjVwetaRm/0MALZg=="
268
+ }
269
+ },
270
+ "sourceName": "@modern-js/renderer-octane"
271
+ },
207
272
  "scripts": {
208
273
  "build": "rslib build && tsc -p tsconfig.types.json",
209
274
  "test": "rstest"
@@ -211,8 +276,5 @@
211
276
  "homepage": "https://github.com/ultramodernjs/ultramodern.js#readme",
212
277
  "bugs": {
213
278
  "url": "https://github.com/ultramodernjs/ultramodern.js/issues"
214
- },
215
- "ultramodern": {
216
- "sourceName": "@modern-js/renderer-octane"
217
279
  }
218
280
  }
package/src/client.ts CHANGED
@@ -24,6 +24,11 @@ import {
24
24
  FederationApplication,
25
25
  type OctaneFederationScope,
26
26
  } from './federation-context';
27
+ import {
28
+ type OctaneWorkerFederationFactory,
29
+ type OctaneWorkerFederationScope,
30
+ WorkerFederationApplication,
31
+ } from './worker-federation-context';
27
32
 
28
33
  export type { OctaneDocumentBootstrap } from './bootstrap';
29
34
  export { readOctaneDocumentBootstrap } from './bootstrap';
@@ -50,9 +55,14 @@ export interface OctaneApplicationOptions {
50
55
  readonly signal?: AbortSignal;
51
56
  /** Hydration installs the native signal bridge before invoking this importer. */
52
57
  readonly load: () => Promise<OctaneApplicationModule>;
53
- readonly options?: Omit<RootOptions, 'signalOwner'>;
58
+ readonly options?: Omit<RootOptions, 'signalOwner' | 'externalSnapshots'>;
54
59
  /** This browser compilation's native Module Federation runtime. */
55
60
  readonly federation?: NativeFederationBinding;
61
+ readonly workerFederation?:
62
+ | OctaneWorkerFederationScope
63
+ | OctaneWorkerFederationFactory;
64
+ /** Trusted document bootstrap identity, also present for a CSR document. */
65
+ readonly documentId?: string;
56
66
  }
57
67
 
58
68
  export interface OctaneHydrationOptions extends OctaneApplicationOptions {
@@ -296,6 +306,8 @@ function createHandle(input: {
296
306
  signal?: AbortSignal;
297
307
  onCleanupError?: (error: unknown) => void;
298
308
  federation?: OctaneFederationScope;
309
+ workerFederation?: OctaneWorkerFederationScope;
310
+ retireWorkerFederation?: () => void;
299
311
  }): OctaneApplicationHandle {
300
312
  let releaseModule = input.releaseModule;
301
313
  let disposed = false;
@@ -310,12 +322,18 @@ function createHandle(input: {
310
322
  const nextRelease = claimModuleResources(next);
311
323
  releaseModule = nextRelease;
312
324
  try {
313
- input.federation
314
- ? input.root.render(FederationApplication, {
315
- scope: input.federation,
325
+ input.workerFederation
326
+ ? input.root.render(WorkerFederationApplication, {
327
+ scope: input.workerFederation,
328
+ federation: input.federation,
316
329
  application: next,
317
330
  })
318
- : input.root.render(next.default, next.props);
331
+ : input.federation
332
+ ? input.root.render(FederationApplication, {
333
+ scope: input.federation,
334
+ application: next,
335
+ })
336
+ : input.root.render(next.default, next.props);
319
337
  previousRelease();
320
338
  } catch (error) {
321
339
  cleanupResources([previousRelease, () => handle.dispose()]);
@@ -327,6 +345,7 @@ function createHandle(input: {
327
345
  disposed = true;
328
346
  input.signal?.removeEventListener('abort', onAbort);
329
347
  cleanupResources([
348
+ () => input.retireWorkerFederation?.(),
330
349
  () => input.root.unmount(),
331
350
  () => releaseModule(),
332
351
  () => input.bridge?.dispose(),
@@ -356,6 +375,21 @@ async function startApplication(
356
375
  hydration?: { document: Document; documentId: string },
357
376
  ): Promise<OctaneApplicationHandle> {
358
377
  const errors = observeRootErrors(input.options);
378
+ const workerController = input.workerFederation
379
+ ? new AbortController()
380
+ : undefined;
381
+ let workerFederation: OctaneWorkerFederationScope | undefined;
382
+ const retireWorkerFederation = () =>
383
+ cleanupResources([
384
+ () =>
385
+ workerController?.abort(
386
+ new DOMException(
387
+ 'The Worker federation document was disposed',
388
+ 'AbortError',
389
+ ),
390
+ ),
391
+ () => workerFederation?.transport.dispose(),
392
+ ]);
359
393
  let bridge: StreamedSignalHydration | undefined;
360
394
  let root: Root | undefined;
361
395
  let releaseModule: (() => void) | undefined;
@@ -369,6 +403,7 @@ async function startApplication(
369
403
  if (cleaned) return;
370
404
  cleaned = true;
371
405
  cleanupResources([
406
+ retireWorkerFederation,
372
407
  () => root?.unmount(),
373
408
  () => releaseModule?.(),
374
409
  () => bridge?.dispose(),
@@ -376,10 +411,30 @@ async function startApplication(
376
411
  ]);
377
412
  };
378
413
  try {
379
- // Capture the selected host before asynchronous application imports.
380
- const federation = input.federation
381
- ? createFederationScope(input.federation, false)
382
- : undefined;
414
+ if (input.workerFederation && workerController) {
415
+ const signal = AbortSignal.any([
416
+ workerController.signal,
417
+ ...(input.signal ? [input.signal] : []),
418
+ ]);
419
+ const scope =
420
+ typeof input.workerFederation === 'function'
421
+ ? input.workerFederation(signal)
422
+ : input.workerFederation;
423
+ workerFederation = {
424
+ ...scope,
425
+ signal: scope.signal ? AbortSignal.any([signal, scope.signal]) : signal,
426
+ };
427
+ }
428
+ if (workerFederation && !hydration && !input.documentId)
429
+ throw new Error(
430
+ 'Worker federation requires the trusted document identity before application loading',
431
+ );
432
+ // Capture the host before imports and retain Worker SSR's provider ranges
433
+ // even when no regular Module Federation binding is configured.
434
+ const federation =
435
+ input.federation || workerFederation
436
+ ? createFederationScope(input.federation, false)
437
+ : undefined;
383
438
  if (hydration) {
384
439
  const { document, documentId } = hydration;
385
440
  bridge = bootstrapStreamedSignalHydration({
@@ -402,15 +457,41 @@ async function startApplication(
402
457
  if (bridge) {
403
458
  root = hydrateRoot(
404
459
  input.container,
405
- federation ? FederationApplication : application.default,
406
- federation ? { scope: federation, application } : application.props,
407
- { ...errors.options, signalOwner: bridge.signalOwner },
460
+ workerFederation
461
+ ? WorkerFederationApplication
462
+ : federation
463
+ ? FederationApplication
464
+ : application.default,
465
+ workerFederation
466
+ ? { scope: workerFederation, federation, application }
467
+ : federation
468
+ ? { scope: federation, application }
469
+ : application.props,
470
+ {
471
+ ...errors.options,
472
+ signalOwner: bridge.signalOwner,
473
+ externalSnapshots: { documentId: hydration!.documentId },
474
+ },
408
475
  );
409
476
  } else {
410
- root = createRoot(input.container, errors.options);
411
- federation
412
- ? root.render(FederationApplication, { scope: federation, application })
413
- : root.render(application.default, application.props);
477
+ root = createRoot(input.container, {
478
+ ...errors.options,
479
+ ...(input.documentId
480
+ ? { externalSnapshots: { documentId: input.documentId } }
481
+ : {}),
482
+ });
483
+ workerFederation
484
+ ? root.render(WorkerFederationApplication, {
485
+ scope: workerFederation,
486
+ federation,
487
+ application,
488
+ })
489
+ : federation
490
+ ? root.render(FederationApplication, {
491
+ scope: federation,
492
+ application,
493
+ })
494
+ : root.render(application.default, application.props);
414
495
  }
415
496
  errors.assertInitialRender();
416
497
  input.signal?.throwIfAborted();
@@ -421,6 +502,8 @@ async function startApplication(
421
502
  release,
422
503
  bridge,
423
504
  federation,
505
+ workerFederation,
506
+ retireWorkerFederation,
424
507
  ...(input.signal === undefined ? {} : { signal: input.signal }),
425
508
  onCleanupError: error => reportCleanupError(input, error),
426
509
  });
@@ -10,6 +10,7 @@ import {
10
10
  import { type ComponentBody, createElement } from 'octane';
11
11
  import { I18nProvider } from './i18n/I18nProvider';
12
12
  import type { I18nInstanceLike } from './i18n/types';
13
+ import { associateOctaneFileRouteSnapshot } from './router-external-snapshot';
13
14
  import {
14
15
  createFileSystemRouteTree,
15
16
  type FileSystemRouteModule,
@@ -41,8 +42,8 @@ export const octaneRouterFactory: NativeRouterFactory<AnyRoute, AnyRouter> = {
41
42
  modules as Readonly<Record<string, FileSystemRouteModule>>,
42
43
  options,
43
44
  ),
44
- router: ({ location, ...options }) =>
45
- createRouter({
45
+ router: ({ location, ...options }) => {
46
+ const router = createRouter({
46
47
  ...options,
47
48
  ...(location
48
49
  ? {
@@ -50,7 +51,10 @@ export const octaneRouterFactory: NativeRouterFactory<AnyRoute, AnyRouter> = {
50
51
  history: createMemoryHistory({ initialEntries: [location.href] }),
51
52
  }
52
53
  : {}),
53
- }),
54
+ });
55
+ associateOctaneFileRouteSnapshot(router);
56
+ return router;
57
+ },
54
58
  };
55
59
 
56
60
  interface LocalizedRouterProps {
@@ -21,11 +21,18 @@ import {
21
21
  OctaneRouterRoot,
22
22
  prepareOctaneRouterHydration,
23
23
  } from './router-client';
24
+ import type {
25
+ OctaneWorkerFederationFactory,
26
+ OctaneWorkerFederationScope,
27
+ } from './worker-federation-context';
24
28
 
25
29
  export interface OctaneClientEntryOptions
26
30
  extends NativeClientEntryOptions<NativeI18nInstance, LocalisedUrlsOption> {
27
31
  /** The native client compilation hash, `__webpack_hash__`. */
28
32
  readonly nativeHydrationBuildId: string;
33
+ readonly workerFederation?:
34
+ | OctaneWorkerFederationScope
35
+ | OctaneWorkerFederationFactory;
29
36
  }
30
37
 
31
38
  const LocalizedRouterRoot = localizedRouterRoot(OctaneRouterRoot);
@@ -38,6 +45,7 @@ export function startNativeClient(options: OctaneClientEntryOptions): void {
38
45
  readOctaneDocumentBootstrap(document, identity, nativeHydrationBuildId),
39
46
  createRouter: (application, routerOptions) =>
40
47
  createNativeRouter(application, routerOptions, octaneRouterFactory),
48
+ disposeRouter: router => router.history.destroy(),
41
49
  async prepareRouter(router, hydrating, signal) {
42
50
  // Hydration adopts the server's matches; a fresh mount loads its own.
43
51
  if (hydrating) await prepareOctaneRouterHydration(router, { signal });
@@ -49,6 +57,8 @@ export function startNativeClient(options: OctaneClientEntryOptions): void {
49
57
  identity,
50
58
  nativeHydrationBuildId,
51
59
  federation: options.federation,
60
+ workerFederation: options.workerFederation,
61
+ documentId: bootstrap?.documentId,
52
62
  signal,
53
63
  // Hydration installs the native signal bridge before this importer runs.
54
64
  load: async (): Promise<OctaneApplicationModule> => {
@@ -23,8 +23,8 @@ import {
23
23
  import { validateOctaneModuleManifest } from './manifest';
24
24
  import {
25
25
  createOctaneRequestHandler,
26
- createOctaneRouterInjection,
27
26
  createSsrStreamResponse,
27
+ getSsrStatus,
28
28
  OctaneRouterServer,
29
29
  } from './router-server';
30
30
  import { renderOctaneApplication, renderOctaneCSRDocument } from './server';
@@ -52,10 +52,12 @@ export function withOctaneDocumentFields(
52
52
  : policy;
53
53
  }
54
54
 
55
- export type OctaneServerEntryOptions = NativeServerEntryOptions<
56
- NativeI18nInstance,
57
- LocalisedUrlsOption
58
- >;
55
+ export { createNativeWorkerFederationRuntime } from './worker-federation';
56
+
57
+ export interface OctaneServerEntryOptions
58
+ extends NativeServerEntryOptions<NativeI18nInstance, LocalisedUrlsOption> {
59
+ readonly workerFederationLocale?: string;
60
+ }
59
61
 
60
62
  const LocalizedRouterServer = localizedRouterRoot(OctaneRouterServer);
61
63
 
@@ -77,16 +79,23 @@ export function createNativeServerEntry(
77
79
  createNativeRouter(application, routerOptions, octaneRouterFactory),
78
80
  renderCSR: ({ session }, document) =>
79
81
  renderOctaneCSRDocument({ session, document }),
80
- renderComponent: ({ session }, component, document, i18n) =>
82
+ renderComponent: (
83
+ { session, workerFederation },
84
+ component,
85
+ document,
86
+ i18n,
87
+ ) =>
81
88
  renderOctaneApplication({
82
89
  session,
83
90
  App: componentView(component, i18n) as ServerRenderNode,
84
91
  document,
85
92
  federation: options.federation,
93
+ workerFederation,
94
+ workerFederationLocale: options.workerFederationLocale,
86
95
  }),
87
96
  async renderRoutes({
88
97
  request,
89
- context: { session },
98
+ context: { session, workerFederation },
90
99
  router,
91
100
  outcomes,
92
101
  document,
@@ -100,7 +109,7 @@ export function createNativeServerEntry(
100
109
  serialization: { forbiddenValues },
101
110
  })(async ({ router, responseHeaders }) => {
102
111
  const metadata = mergeDataResponseMetadata(outcomes, {
103
- status: router.state.statusCode ?? 200,
112
+ status: getSsrStatus(router),
104
113
  });
105
114
  // Route headers() take precedence over loader singleton fields; list
106
115
  // fields keep both.
@@ -127,7 +136,9 @@ export function createNativeServerEntry(
127
136
  }),
128
137
  document,
129
138
  federation: options.federation,
130
- injection: createOctaneRouterInjection(router, session),
139
+ workerFederation,
140
+ workerFederationLocale: options.workerFederationLocale,
141
+ router,
131
142
  });
132
143
  return createSsrStreamResponse(router, rendered);
133
144
  });