@remix-run/ui 0.7.0 → 0.9.0

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 (91) hide show
  1. package/README.md +90 -14
  2. package/dist/animation/demos/drag-release.js +5 -7
  3. package/dist/animation/demos/drag-release.js.map +1 -1
  4. package/dist/index.d.ts +2 -2
  5. package/dist/index.js +1 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/runtime/component.d.ts +9 -4
  8. package/dist/runtime/component.js +38 -9
  9. package/dist/runtime/component.js.map +1 -1
  10. package/dist/runtime/core/mix.d.ts +21 -0
  11. package/dist/runtime/core/mix.js +128 -0
  12. package/dist/runtime/core/mix.js.map +1 -0
  13. package/dist/runtime/diff-dom.js +15 -16
  14. package/dist/runtime/diff-dom.js.map +1 -1
  15. package/dist/runtime/document-reload.d.ts +2 -0
  16. package/dist/runtime/document-reload.js +14 -0
  17. package/dist/runtime/document-reload.js.map +1 -0
  18. package/dist/runtime/dom.d.ts +21 -11
  19. package/dist/runtime/event-types.d.ts +14 -0
  20. package/dist/runtime/event-types.js +2 -0
  21. package/dist/runtime/event-types.js.map +1 -0
  22. package/dist/runtime/frame-resolution.js +8 -0
  23. package/dist/runtime/frame-resolution.js.map +1 -1
  24. package/dist/runtime/frame.d.ts +24 -6
  25. package/dist/runtime/frame.js +187 -60
  26. package/dist/runtime/frame.js.map +1 -1
  27. package/dist/runtime/import-map-manager.d.ts +8 -0
  28. package/dist/runtime/import-map-manager.js +295 -0
  29. package/dist/runtime/import-map-manager.js.map +1 -0
  30. package/dist/runtime/mixins/link-mixin.js +4 -4
  31. package/dist/runtime/mixins/link-mixin.js.map +1 -1
  32. package/dist/runtime/mixins/mixin.d.ts +9 -4
  33. package/dist/runtime/mixins/mixin.js +18 -132
  34. package/dist/runtime/mixins/mixin.js.map +1 -1
  35. package/dist/runtime/mixins/on-mixin.d.ts +1 -1
  36. package/dist/runtime/module-preloader.d.ts +2 -1
  37. package/dist/runtime/module-preloader.js +6 -5
  38. package/dist/runtime/module-preloader.js.map +1 -1
  39. package/dist/runtime/navigation.js +211 -34
  40. package/dist/runtime/navigation.js.map +1 -1
  41. package/dist/runtime/reconcile.js +18 -3
  42. package/dist/runtime/reconcile.js.map +1 -1
  43. package/dist/runtime/run.d.ts +3 -0
  44. package/dist/runtime/run.js +8 -4
  45. package/dist/runtime/run.js.map +1 -1
  46. package/dist/runtime/scheduler.js +37 -11
  47. package/dist/runtime/scheduler.js.map +1 -1
  48. package/dist/runtime/spa-response.d.ts +30 -0
  49. package/dist/runtime/spa-response.js +47 -0
  50. package/dist/runtime/spa-response.js.map +1 -0
  51. package/dist/runtime/to-vnode.js +1 -1
  52. package/dist/runtime/to-vnode.js.map +1 -1
  53. package/dist/runtime/typed-event-target.d.ts +0 -4
  54. package/dist/runtime/typed-event-target.js.map +1 -1
  55. package/dist/server/stream.d.ts +25 -2
  56. package/dist/server/stream.js +382 -171
  57. package/dist/server/stream.js.map +1 -1
  58. package/dist/style/stylesheet.js +2 -2
  59. package/dist/style/stylesheet.js.map +1 -1
  60. package/package.json +1 -1
  61. package/src/animation/demos/drag-release.ts +5 -7
  62. package/src/index.ts +2 -2
  63. package/src/runtime/component.ts +52 -14
  64. package/src/runtime/core/mix.ts +157 -0
  65. package/src/runtime/demos/readme.demo.tsx +7 -17
  66. package/src/runtime/diff-dom.ts +13 -15
  67. package/src/runtime/document-reload.ts +14 -0
  68. package/src/runtime/dom.ts +21 -11
  69. package/src/runtime/event-types.ts +27 -0
  70. package/src/runtime/frame-resolution.ts +9 -0
  71. package/src/runtime/frame.ts +216 -65
  72. package/src/runtime/import-map-manager.ts +369 -0
  73. package/src/runtime/mixins/link-mixin.ts +4 -4
  74. package/src/runtime/mixins/mixin.ts +44 -149
  75. package/src/runtime/mixins/on-mixin.ts +1 -1
  76. package/src/runtime/module-preloader.ts +9 -6
  77. package/src/runtime/navigation.ts +257 -42
  78. package/src/runtime/reconcile.ts +20 -4
  79. package/src/runtime/run.ts +13 -4
  80. package/src/runtime/scheduler.ts +53 -13
  81. package/src/runtime/spa-response.ts +56 -0
  82. package/src/runtime/to-vnode.ts +1 -1
  83. package/src/runtime/typed-event-target.ts +1 -6
  84. package/src/server/README.md +25 -5
  85. package/src/server/stream.ts +506 -202
  86. package/src/style/stylesheet.ts +3 -3
  87. package/src/test/utils.ts +1 -6
  88. package/dist/runtime/event-listeners.d.ts +0 -50
  89. package/dist/runtime/event-listeners.js +0 -31
  90. package/dist/runtime/event-listeners.js.map +0 -1
  91. package/src/runtime/event-listeners.ts +0 -171
@@ -9,6 +9,8 @@ import { findFlushMarker } from './stream-protocol.js';
9
9
  import { getDocumentModulePreloader } from './module-preloader.js';
10
10
  import { unwrapFrameResolution } from './frame-resolution.js';
11
11
  import { disposeClientEntryBoundary, getClientEntryBoundaryOwner, setClientEntryBoundaryOwner, } from './client-entry-boundary.js';
12
+ import { getDocumentImportMapManager } from './import-map-manager.js';
13
+ import { reloadDocument } from './document-reload.js';
12
14
  const bufferedFrameTemplates = new Map();
13
15
  const frameTemplateListeners = new Map();
14
16
  const DOCTYPE_PATTERN = /<!doctype(?:\s[^>]*)?>/gi;
@@ -76,6 +78,7 @@ export function createFrame(root, init) {
76
78
  let reloadKind;
77
79
  let styleManager = init.styleManager ?? createStyleManager();
78
80
  let modulePreloader = getDocumentModulePreloader(container.doc);
81
+ let importMapManager = getDocumentImportMapManager(container.doc);
79
82
  let currentMarker = init.marker;
80
83
  let displayedContentStatus = init.marker?.status ?? 'resolved';
81
84
  let pendingTemplateMarkerId;
@@ -85,15 +88,35 @@ export function createFrame(root, init) {
85
88
  let inheritedReloadAbortUnsubscribe;
86
89
  let disposed = false;
87
90
  let lifecycleController = new AbortController();
91
+ async function consumeClientEntryResources(source, documentHref) {
92
+ let importMapStatus = importMapManager.consumeImportMaps(source);
93
+ if (importMapStatus !== 'ready') {
94
+ lifecycleController.abort();
95
+ if (importMapStatus === 'conflict')
96
+ reloadDocument(container.doc, documentHref);
97
+ return false;
98
+ }
99
+ await modulePreloader.consumePreloadLinks(source, init.processClientEntryPreloads);
100
+ return true;
101
+ }
102
+ let initialClientEntryResources;
103
+ function shouldPreserveManagedHeadNode(node) {
104
+ return (importMapManager.shouldPreserveHeadNode(node) ||
105
+ (modulePreloader.hasActivePreloads() && modulePreloader.isActivePreload(node)));
106
+ }
88
107
  if (isDocumentNode(container.root)) {
89
108
  modulePreloader.adoptInitialPreloadLinks(container.root);
90
109
  }
91
110
  else {
92
- modulePreloader.consumePreloadLinks(container.root);
111
+ initialClientEntryResources = consumeClientEntryResources(container.root);
93
112
  }
94
113
  // Merge any rmx-data found in the current document once at startup.
95
114
  mergeRmxDataFromDocument(init.data, container.doc);
96
- let runtime = createFrameRuntime({ ...init, styleManager, reloadForNavigation: reload });
115
+ let runtime = createFrameRuntime({
116
+ ...init,
117
+ styleManager,
118
+ reloadForNavigation: startReloadTransition,
119
+ });
97
120
  let frame = createFrameHandle({
98
121
  src: init.src,
99
122
  $runtime: runtime,
@@ -121,6 +144,7 @@ export function createFrame(root, init) {
121
144
  moduleLoads: init.moduleLoads,
122
145
  frameInstances: init.frameInstances,
123
146
  namedFrames: init.namedFrames,
147
+ processClientEntryPreloads: init.processClientEntryPreloads,
124
148
  lifecycleSignal: lifecycleController.signal,
125
149
  regionTailRef: container.regionTailRef,
126
150
  regionParent: container.regionParent,
@@ -168,7 +192,24 @@ export function createFrame(root, init) {
168
192
  }
169
193
  if (isRenderAborted(options.signal))
170
194
  return;
171
- contentRoot.render(content);
195
+ let previousServerFrameReload = runtime.serverFrameReload;
196
+ if (options.signal) {
197
+ runtime.serverFrameReload = {
198
+ signal: options.signal,
199
+ reconciliationTracker: options.reconciliationTracker,
200
+ blockingFrameTracker: options.blockingFrameTracker,
201
+ };
202
+ }
203
+ try {
204
+ contentRoot.render(content);
205
+ await new Promise((resolve) => context.scheduler.enqueueCommitPhase([resolve]));
206
+ options.onCommit?.();
207
+ }
208
+ finally {
209
+ runtime.serverFrameReload = previousServerFrameReload;
210
+ }
211
+ if (isRenderAborted(options.signal))
212
+ return;
172
213
  displayedContentStatus = options.contentStatus ?? 'resolved';
173
214
  return;
174
215
  }
@@ -193,10 +234,18 @@ export function createFrame(root, init) {
193
234
  options.flushKind === 'document';
194
235
  if (isFullDocumentReload && htmlContent !== undefined) {
195
236
  let parsed = new DOMParser().parseFromString(htmlContent, 'text/html');
196
- modulePreloader.consumePreloadLinks(parsed);
237
+ if (!(await consumeClientEntryResources(parsed, options.documentHref)))
238
+ return;
239
+ if (isRenderAborted(options.signal))
240
+ return;
197
241
  let responseData = options.data;
198
242
  mergeRmxDataFromDocument(responseData, parsed);
199
- let responseContext = { ...context, data: responseData };
243
+ let responseContext = {
244
+ ...context,
245
+ data: responseData,
246
+ reconciliationTracker: options.reconciliationTracker,
247
+ blockingFrameTracker: options.blockingFrameTracker,
248
+ };
200
249
  context.styleManager.adoptServerStyles(collectFrameServerStyleTags(createElementContainer(parsed)));
201
250
  syncElementAttributes(container.doc.documentElement, parsed.documentElement);
202
251
  diffNodes([container.doc.head], [parsed.head], {
@@ -204,9 +253,7 @@ export function createFrame(root, init) {
204
253
  regionParent: container.doc.documentElement,
205
254
  regionTailRef: null,
206
255
  signal: options.signal,
207
- isActiveModulePreload: modulePreloader.hasActivePreloads()
208
- ? modulePreloader.isActivePreload
209
- : undefined,
256
+ shouldPreserveHeadNode: shouldPreserveManagedHeadNode,
210
257
  });
211
258
  diffNodes([container.doc.body], [parsed.body], {
212
259
  ...responseContext,
@@ -217,20 +264,30 @@ export function createFrame(root, init) {
217
264
  let bodyContainer = createElementContainer(container.doc.body);
218
265
  if (isRenderAborted(options.signal))
219
266
  return;
220
- scheduleHydrationInContainer(bodyContainer, responseContext, options.initialHydrationTracker, options.signal);
221
- await createSubFrames(bodyContainer.childNodes, responseContext, options);
267
+ scheduleHydrationInContainer(bodyContainer, responseContext, options.reconciliationTracker, options.signal);
268
+ let subFramesReady = createSubFrames(bodyContainer.childNodes, responseContext, options);
269
+ options.onCommit?.();
270
+ await subFramesReady;
222
271
  if (isRenderAborted(options.signal))
223
272
  return;
224
273
  displayedContentStatus = options.contentStatus ?? 'resolved';
225
274
  return;
226
275
  }
227
276
  let fragment = htmlContent !== undefined ? createFragmentFromString(container.doc, htmlContent) : content;
228
- modulePreloader.consumePreloadLinks(fragment);
277
+ if (!(await consumeClientEntryResources(fragment, options.documentHref)))
278
+ return;
279
+ if (isRenderAborted(options.signal))
280
+ return;
229
281
  context.styleManager.adoptServerStyles(collectFrameServerStyleTags(createElementContainer(fragment)));
230
282
  removeEmptyHeads(fragment);
231
283
  let responseData = options.data;
232
284
  mergeRmxDataFromFragment(responseData, fragment);
233
- let responseContext = { ...context, data: responseData };
285
+ let responseContext = {
286
+ ...context,
287
+ data: responseData,
288
+ reconciliationTracker: options.reconciliationTracker,
289
+ blockingFrameTracker: options.blockingFrameTracker,
290
+ };
234
291
  let nextContainer = createContainer(fragment);
235
292
  if (isRenderAborted(options.signal))
236
293
  return;
@@ -240,10 +297,10 @@ export function createFrame(root, init) {
240
297
  regionParent: container.regionParent,
241
298
  signal: options.signal,
242
299
  });
243
- if (isRenderAborted(options.signal))
244
- return;
245
- scheduleHydrationInContainer(container, responseContext, options.initialHydrationTracker, options.signal);
246
- await createSubFrames(container.childNodes, responseContext, options);
300
+ scheduleHydrationInContainer(container, responseContext, options.reconciliationTracker, options.signal);
301
+ let subFramesReady = createSubFrames(container.childNodes, responseContext, options);
302
+ options.onCommit?.();
303
+ await subFramesReady;
247
304
  if (isRenderAborted(options.signal))
248
305
  return;
249
306
  displayedContentStatus = options.contentStatus ?? 'resolved';
@@ -286,19 +343,23 @@ export function createFrame(root, init) {
286
343
  }
287
344
  }
288
345
  async function hydrateInitial() {
289
- let initialHydrationTracker = createInitialHydrationTracker();
346
+ let reconciliationTracker = createReconciliationTracker();
347
+ if ((await initialClientEntryResources) === false)
348
+ return;
349
+ if (disposed || context.lifecycleSignal.aborted)
350
+ return;
290
351
  context.styleManager.adoptServerStyles(collectFrameServerStyleTags(container));
291
352
  let subFramesReady = createSubFrames(container.childNodes, context);
292
- scheduleHydrationInContainer(container, context, initialHydrationTracker);
353
+ scheduleHydrationInContainer(container, context, reconciliationTracker);
293
354
  try {
294
355
  await subFramesReady;
295
356
  if (disposed || context.lifecycleSignal.aborted)
296
357
  return;
297
358
  if (currentMarker?.status === 'pending') {
298
- await watchPendingFrameTemplate(currentMarker, initialHydrationTracker);
359
+ await watchPendingFrameTemplate(currentMarker, reconciliationTracker);
299
360
  }
300
- initialHydrationTracker.finalize();
301
- await initialHydrationTracker.ready();
361
+ reconciliationTracker.finalize();
362
+ await reconciliationTracker.ready();
302
363
  }
303
364
  finally {
304
365
  clearRmxData(context.data);
@@ -357,7 +418,7 @@ export function createFrame(root, init) {
357
418
  startInheritedReload(options?.signal);
358
419
  }
359
420
  if (marker.status === 'pending') {
360
- await watchPendingFrameTemplate(marker, options?.initialHydrationTracker, options?.signal, isInheritedReload
421
+ await watchPendingFrameTemplate(marker, options?.reconciliationTracker, options?.signal, isInheritedReload
361
422
  ? () => {
362
423
  completeInheritedReload();
363
424
  }
@@ -389,8 +450,23 @@ export function createFrame(root, init) {
389
450
  }
390
451
  }
391
452
  async function reload(options) {
453
+ let transition = startReloadTransition(options);
454
+ void transition.committed.catch(() => { });
455
+ return await transition.finished;
456
+ }
457
+ function startReloadTransition(options) {
392
458
  let controller = startReload(options?.signal);
393
- return await resolveAndRenderReload(controller, options);
459
+ let committed = Promise.withResolvers();
460
+ let commitStarted = false;
461
+ let finished = resolveAndRenderReload(controller, options, (ready) => {
462
+ if (commitStarted)
463
+ return;
464
+ commitStarted = true;
465
+ void ready.then(committed.resolve, committed.reject);
466
+ });
467
+ // Settle committed when a reload is aborted or fails before rendering any content.
468
+ void finished.then(() => committed.resolve(), committed.reject);
469
+ return { signal: controller.signal, committed: committed.promise, finished };
394
470
  }
395
471
  function startReload(signal) {
396
472
  let controller = replaceReloadController(signal);
@@ -438,7 +514,7 @@ export function createFrame(root, init) {
438
514
  }
439
515
  return controller;
440
516
  }
441
- async function resolveAndRenderReload(controller, options) {
517
+ async function resolveAndRenderReload(controller, options, resolveCommit) {
442
518
  try {
443
519
  let resolution = await init.resolveFrame(frame.src, {
444
520
  ...options,
@@ -452,7 +528,24 @@ export function createFrame(root, init) {
452
528
  if (reloadController !== controller || controller.signal.aborted) {
453
529
  return { signal: controller.signal };
454
530
  }
455
- await render(content, { signal: controller.signal });
531
+ let reconciliationTracker = createReconciliationTracker();
532
+ let blockingFrameTracker = createReconciliationTracker();
533
+ let commitStarted = false;
534
+ await render(content, {
535
+ documentHref: isDocumentNode(container.root) ? (redirectedTo ?? frame.src) : undefined,
536
+ signal: controller.signal,
537
+ reconciliationTracker,
538
+ blockingFrameTracker,
539
+ onCommit() {
540
+ if (commitStarted)
541
+ return;
542
+ commitStarted = true;
543
+ blockingFrameTracker.finalize();
544
+ resolveCommit?.(blockingFrameTracker.ready());
545
+ },
546
+ });
547
+ reconciliationTracker.finalize();
548
+ await reconciliationTracker.ready();
456
549
  return {
457
550
  signal: controller.signal,
458
551
  redirectedTo: reloadController === controller && !controller.signal.aborted ? redirectedTo : undefined,
@@ -534,7 +627,7 @@ export function createFrame(root, init) {
534
627
  pendingTemplateObserver = undefined;
535
628
  pendingTemplateMarkerId = undefined;
536
629
  }
537
- async function watchPendingFrameTemplate(marker, initialHydrationTracker, signal, onResolved) {
630
+ async function watchPendingFrameTemplate(marker, reconciliationTracker, signal, onResolved) {
538
631
  if (disposed || context.lifecycleSignal.aborted || signal?.aborted)
539
632
  return;
540
633
  if (pendingTemplateMarkerId === marker.id)
@@ -544,7 +637,7 @@ export function createFrame(root, init) {
544
637
  let early = consumeFrameTemplate(marker.id) ?? getEarlyFrameContent(marker.id);
545
638
  if (early) {
546
639
  clearPendingFrameTemplateWatch();
547
- await render(early, { initialHydrationTracker, signal, contentStatus: 'resolved' });
640
+ await render(early, { reconciliationTracker, signal, contentStatus: 'resolved' });
548
641
  if (!disposed && !context.lifecycleSignal.aborted && !signal?.aborted)
549
642
  onResolved?.();
550
643
  return;
@@ -574,7 +667,7 @@ export function createFrame(root, init) {
574
667
  let buffered = consumeFrameTemplate(marker.id);
575
668
  if (buffered) {
576
669
  clearPendingFrameTemplateWatch();
577
- await render(buffered, { initialHydrationTracker, signal, contentStatus: 'resolved' });
670
+ await render(buffered, { reconciliationTracker, signal, contentStatus: 'resolved' });
578
671
  if (!disposed && !context.lifecycleSignal.aborted && !signal?.aborted)
579
672
  onResolved?.();
580
673
  }
@@ -594,38 +687,58 @@ export function createFrameRuntime(init) {
594
687
  moduleLoads: init.moduleLoads,
595
688
  frameInstances: init.frameInstances,
596
689
  namedFrames: init.namedFrames,
690
+ processClientEntryPreloads: init.processClientEntryPreloads,
597
691
  serverFrameReload: undefined,
598
692
  reloadForNavigation: init.reloadForNavigation,
599
693
  };
600
694
  }
601
- function createInitialHydrationTracker() {
695
+ function createReconciliationTracker() {
602
696
  let pending = 0;
603
697
  let finalized = false;
698
+ let failed = false;
699
+ let failure;
604
700
  let resolveReady;
605
- let readyPromise = new Promise((resolve) => {
701
+ let rejectReady;
702
+ let readyPromise = new Promise((resolve, reject) => {
606
703
  resolveReady = resolve;
704
+ rejectReady = reject;
607
705
  });
608
- function maybeResolve() {
609
- if (finalized && pending === 0) {
706
+ function maybeSettle() {
707
+ if (!finalized || pending !== 0)
708
+ return;
709
+ if (failed)
710
+ rejectReady?.(failure);
711
+ else
610
712
  resolveReady?.();
611
- resolveReady = undefined;
612
- }
713
+ resolveReady = undefined;
714
+ rejectReady = undefined;
715
+ }
716
+ function track() {
717
+ pending++;
718
+ let completed = false;
719
+ return () => {
720
+ if (completed)
721
+ return;
722
+ completed = true;
723
+ pending--;
724
+ maybeSettle();
725
+ };
613
726
  }
614
727
  return {
615
- track() {
616
- pending++;
617
- let completed = false;
618
- return () => {
619
- if (completed)
620
- return;
621
- completed = true;
622
- pending--;
623
- maybeResolve();
624
- };
728
+ track,
729
+ waitFor(task) {
730
+ let complete = track();
731
+ void task.then(complete, (error) => {
732
+ if (!failed) {
733
+ failed = true;
734
+ failure = error;
735
+ }
736
+ complete();
737
+ });
625
738
  },
626
739
  finalize() {
627
740
  finalized = true;
628
- maybeResolve();
741
+ maybeSettle();
629
742
  },
630
743
  ready() {
631
744
  return readyPromise;
@@ -678,7 +791,7 @@ function collectOwnedServerStyleTags(nodes, styles) {
678
791
  i = findMarkerRangeEndIndex(nodes, end, i);
679
792
  continue;
680
793
  }
681
- if (node instanceof HTMLStyleElement && node.matches('style[data-rmx]')) {
794
+ if (node instanceof HTMLStyleElement && node.matches('style[data-rmx-style]')) {
682
795
  styles.push(node);
683
796
  continue;
684
797
  }
@@ -717,7 +830,7 @@ function copyOwnRmxEntries(target, source) {
717
830
  target[key] = source[key];
718
831
  }
719
832
  }
720
- function scheduleHydrationInContainer(container, context, initialHydrationTracker, signal) {
833
+ function scheduleHydrationInContainer(container, context, reconciliationTracker, signal) {
721
834
  let hydrationMarkers = findHydrationMarkers(container);
722
835
  if (hydrationMarkers.length === 0)
723
836
  return;
@@ -728,13 +841,13 @@ function scheduleHydrationInContainer(container, context, initialHydrationTracke
728
841
  let entry = hydrationData[marker.id];
729
842
  if (!entry)
730
843
  continue;
731
- scheduleHydrationMarker(marker, entry, context, initialHydrationTracker, signal);
844
+ scheduleHydrationMarker(marker, entry, context, reconciliationTracker, signal);
732
845
  }
733
846
  }
734
- function scheduleHydrationMarker(marker, entry, context, initialHydrationTracker, signal) {
847
+ function scheduleHydrationMarker(marker, entry, context, reconciliationTracker, signal) {
735
848
  if (signal?.aborted || context.lifecycleSignal.aborted)
736
849
  return;
737
- let done = initialHydrationTracker?.track();
850
+ let done = reconciliationTracker?.track();
738
851
  let key = `${entry.moduleUrl}#${entry.exportName}`;
739
852
  let identity = {
740
853
  moduleUrl: entry.moduleUrl,
@@ -854,25 +967,35 @@ function hydrateRegion(vElement, start, end, identity, context, signal) {
854
967
  if (signal?.aborted)
855
968
  return;
856
969
  context.pendingClientEntries.delete(start);
857
- // The same marker can be discovered by overlapping hydration passes
858
- // (for example, document root + nested frame root). Reuse the existing
859
- // virtual root instead of redefining the marker property.
860
- let owner = getClientEntryBoundaryOwner(start);
861
- if (owner) {
970
+ // During a server frame reload, expose the reload signal and reconciliation
971
+ // tracker to client frames created while this entry renders so blocking
972
+ // frames keep the reload pending until their content arrives.
973
+ let renderEntry = (root) => {
862
974
  if (!signal) {
863
- owner.root.render(vElement);
975
+ root.render(vElement);
864
976
  return;
865
977
  }
866
978
  let frameRuntime = context.frame.$runtime;
867
- invariant(isFrameRuntime(frameRuntime), 'Expected frame runtime while rendering a preserved client entry');
979
+ invariant(isFrameRuntime(frameRuntime), 'Expected frame runtime while rendering a client entry during a reload');
868
980
  let previousServerFrameReload = frameRuntime.serverFrameReload;
869
- frameRuntime.serverFrameReload = { signal };
981
+ frameRuntime.serverFrameReload = {
982
+ signal,
983
+ reconciliationTracker: context.reconciliationTracker,
984
+ blockingFrameTracker: context.blockingFrameTracker,
985
+ };
870
986
  try {
871
- owner.root.render(vElement);
987
+ root.render(vElement);
872
988
  }
873
989
  finally {
874
990
  frameRuntime.serverFrameReload = previousServerFrameReload;
875
991
  }
992
+ };
993
+ // The same marker can be discovered by overlapping hydration passes
994
+ // (for example, document root + nested frame root). Reuse the existing
995
+ // virtual root instead of redefining the marker property.
996
+ let owner = getClientEntryBoundaryOwner(start);
997
+ if (owner) {
998
+ renderEntry(owner.root);
876
999
  return;
877
1000
  }
878
1001
  let root = createRangeRoot([start, end], {
@@ -886,7 +1009,7 @@ function hydrateRegion(vElement, start, end, identity, context, signal) {
886
1009
  context.errorTarget.dispatchEvent(createComponentErrorEvent(getComponentError(event)));
887
1010
  });
888
1011
  setClientEntryBoundaryOwner(start, identity, root);
889
- root.render(vElement);
1012
+ renderEntry(root);
890
1013
  }
891
1014
  async function createSubFrames(nodes, context, options) {
892
1015
  let tasks = [];
@@ -926,8 +1049,12 @@ async function createSubFrames(nodes, context, options) {
926
1049
  moduleLoads: context.moduleLoads,
927
1050
  frameInstances: context.frameInstances,
928
1051
  namedFrames: context.namedFrames,
1052
+ processClientEntryPreloads: context.processClientEntryPreloads,
929
1053
  });
930
1054
  context.frameInstances.set(node, subFrame);
1055
+ if (frameMarker.status === 'resolved') {
1056
+ tasks.push(subFrame.ready());
1057
+ }
931
1058
  }
932
1059
  }
933
1060
  i = findMarkerRangeEndIndex(nodes, end, i);