@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.
- package/README.md +90 -14
- package/dist/animation/demos/drag-release.js +5 -7
- package/dist/animation/demos/drag-release.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/runtime/component.d.ts +9 -4
- package/dist/runtime/component.js +38 -9
- package/dist/runtime/component.js.map +1 -1
- package/dist/runtime/core/mix.d.ts +21 -0
- package/dist/runtime/core/mix.js +128 -0
- package/dist/runtime/core/mix.js.map +1 -0
- package/dist/runtime/diff-dom.js +15 -16
- package/dist/runtime/diff-dom.js.map +1 -1
- package/dist/runtime/document-reload.d.ts +2 -0
- package/dist/runtime/document-reload.js +14 -0
- package/dist/runtime/document-reload.js.map +1 -0
- package/dist/runtime/dom.d.ts +21 -11
- package/dist/runtime/event-types.d.ts +14 -0
- package/dist/runtime/event-types.js +2 -0
- package/dist/runtime/event-types.js.map +1 -0
- package/dist/runtime/frame-resolution.js +8 -0
- package/dist/runtime/frame-resolution.js.map +1 -1
- package/dist/runtime/frame.d.ts +24 -6
- package/dist/runtime/frame.js +187 -60
- package/dist/runtime/frame.js.map +1 -1
- package/dist/runtime/import-map-manager.d.ts +8 -0
- package/dist/runtime/import-map-manager.js +295 -0
- package/dist/runtime/import-map-manager.js.map +1 -0
- package/dist/runtime/mixins/link-mixin.js +4 -4
- package/dist/runtime/mixins/link-mixin.js.map +1 -1
- package/dist/runtime/mixins/mixin.d.ts +9 -4
- package/dist/runtime/mixins/mixin.js +18 -132
- package/dist/runtime/mixins/mixin.js.map +1 -1
- package/dist/runtime/mixins/on-mixin.d.ts +1 -1
- package/dist/runtime/module-preloader.d.ts +2 -1
- package/dist/runtime/module-preloader.js +6 -5
- package/dist/runtime/module-preloader.js.map +1 -1
- package/dist/runtime/navigation.js +211 -34
- package/dist/runtime/navigation.js.map +1 -1
- package/dist/runtime/reconcile.js +18 -3
- package/dist/runtime/reconcile.js.map +1 -1
- package/dist/runtime/run.d.ts +3 -0
- package/dist/runtime/run.js +8 -4
- package/dist/runtime/run.js.map +1 -1
- package/dist/runtime/scheduler.js +37 -11
- package/dist/runtime/scheduler.js.map +1 -1
- package/dist/runtime/spa-response.d.ts +30 -0
- package/dist/runtime/spa-response.js +47 -0
- package/dist/runtime/spa-response.js.map +1 -0
- package/dist/runtime/to-vnode.js +1 -1
- package/dist/runtime/to-vnode.js.map +1 -1
- package/dist/runtime/typed-event-target.d.ts +0 -4
- package/dist/runtime/typed-event-target.js.map +1 -1
- package/dist/server/stream.d.ts +25 -2
- package/dist/server/stream.js +382 -171
- package/dist/server/stream.js.map +1 -1
- package/dist/style/stylesheet.js +2 -2
- package/dist/style/stylesheet.js.map +1 -1
- package/package.json +1 -1
- package/src/animation/demos/drag-release.ts +5 -7
- package/src/index.ts +2 -2
- package/src/runtime/component.ts +52 -14
- package/src/runtime/core/mix.ts +157 -0
- package/src/runtime/demos/readme.demo.tsx +7 -17
- package/src/runtime/diff-dom.ts +13 -15
- package/src/runtime/document-reload.ts +14 -0
- package/src/runtime/dom.ts +21 -11
- package/src/runtime/event-types.ts +27 -0
- package/src/runtime/frame-resolution.ts +9 -0
- package/src/runtime/frame.ts +216 -65
- package/src/runtime/import-map-manager.ts +369 -0
- package/src/runtime/mixins/link-mixin.ts +4 -4
- package/src/runtime/mixins/mixin.ts +44 -149
- package/src/runtime/mixins/on-mixin.ts +1 -1
- package/src/runtime/module-preloader.ts +9 -6
- package/src/runtime/navigation.ts +257 -42
- package/src/runtime/reconcile.ts +20 -4
- package/src/runtime/run.ts +13 -4
- package/src/runtime/scheduler.ts +53 -13
- package/src/runtime/spa-response.ts +56 -0
- package/src/runtime/to-vnode.ts +1 -1
- package/src/runtime/typed-event-target.ts +1 -6
- package/src/server/README.md +25 -5
- package/src/server/stream.ts +506 -202
- package/src/style/stylesheet.ts +3 -3
- package/src/test/utils.ts +1 -6
- package/dist/runtime/event-listeners.d.ts +0 -50
- package/dist/runtime/event-listeners.js +0 -31
- package/dist/runtime/event-listeners.js.map +0 -1
- package/src/runtime/event-listeners.ts +0 -171
package/src/runtime/frame.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { createRangeRoot, createRoot } from './vdom.ts'
|
|
|
10
10
|
import { diffNodes } from './diff-dom.ts'
|
|
11
11
|
import { createStyleManager, type StyleManager } from '../style/index.ts'
|
|
12
12
|
import { findFlushMarker, type FlushKind } from './stream-protocol.ts'
|
|
13
|
-
import { getDocumentModulePreloader } from './module-preloader.ts'
|
|
13
|
+
import { getDocumentModulePreloader, type ProcessClientEntryPreloads } from './module-preloader.ts'
|
|
14
14
|
import { unwrapFrameResolution } from './frame-resolution.ts'
|
|
15
15
|
import {
|
|
16
16
|
disposeClientEntryBoundary,
|
|
@@ -18,6 +18,8 @@ import {
|
|
|
18
18
|
setClientEntryBoundaryOwner,
|
|
19
19
|
type ClientEntryIdentity,
|
|
20
20
|
} from './client-entry-boundary.ts'
|
|
21
|
+
import { getDocumentImportMapManager } from './import-map-manager.ts'
|
|
22
|
+
import { reloadDocument } from './document-reload.ts'
|
|
21
23
|
|
|
22
24
|
type FrameRoot = [Comment, Comment] | Element | Document | DocumentFragment
|
|
23
25
|
|
|
@@ -104,6 +106,12 @@ type FrameReloadResult = {
|
|
|
104
106
|
redirectedTo?: string
|
|
105
107
|
}
|
|
106
108
|
|
|
109
|
+
type FrameReloadTransition = {
|
|
110
|
+
signal: AbortSignal
|
|
111
|
+
committed: Promise<void>
|
|
112
|
+
finished: Promise<FrameReloadResult>
|
|
113
|
+
}
|
|
114
|
+
|
|
107
115
|
type FrameTemplateListener = (fragment: DocumentFragment) => void
|
|
108
116
|
|
|
109
117
|
const bufferedFrameTemplates = new Map<string, DocumentFragment[]>()
|
|
@@ -170,8 +178,15 @@ export type FrameRuntime = {
|
|
|
170
178
|
moduleLoads: Map<string, Promise<ElementFunction | undefined>>
|
|
171
179
|
frameInstances: WeakMap<Comment, Frame>
|
|
172
180
|
namedFrames: Map<string, FrameHandle>
|
|
173
|
-
|
|
174
|
-
|
|
181
|
+
processClientEntryPreloads?: ProcessClientEntryPreloads
|
|
182
|
+
serverFrameReload:
|
|
183
|
+
| {
|
|
184
|
+
signal: AbortSignal
|
|
185
|
+
reconciliationTracker?: ReconciliationTracker
|
|
186
|
+
blockingFrameTracker?: ReconciliationTracker
|
|
187
|
+
}
|
|
188
|
+
| undefined
|
|
189
|
+
reloadForNavigation?: (options?: FrameReloadOptions) => FrameReloadTransition
|
|
175
190
|
}
|
|
176
191
|
|
|
177
192
|
export function isFrameRuntime(value: unknown): value is FrameRuntime {
|
|
@@ -188,7 +203,7 @@ export function isFrameRuntime(value: unknown): value is FrameRuntime {
|
|
|
188
203
|
export function reloadFrameForNavigation(
|
|
189
204
|
frame: FrameHandle,
|
|
190
205
|
options?: FrameReloadOptions,
|
|
191
|
-
):
|
|
206
|
+
): FrameReloadTransition {
|
|
192
207
|
let runtime = frame.$runtime
|
|
193
208
|
invariant(isFrameRuntime(runtime), 'Expected a frame runtime')
|
|
194
209
|
let reload = runtime.reloadForNavigation
|
|
@@ -210,11 +225,14 @@ export type FrameContext = {
|
|
|
210
225
|
moduleLoads: Map<string, Promise<ElementFunction | undefined>>
|
|
211
226
|
frameInstances: WeakMap<Comment, Frame>
|
|
212
227
|
namedFrames: Map<string, FrameHandle>
|
|
228
|
+
processClientEntryPreloads?: ProcessClientEntryPreloads
|
|
213
229
|
lifecycleSignal: AbortSignal
|
|
214
230
|
regionTailRef?: ChildNode | null
|
|
215
231
|
regionParent?: ParentNode | null
|
|
216
232
|
signal?: AbortSignal
|
|
217
|
-
|
|
233
|
+
shouldPreserveHeadNode?: (node: Node) => boolean
|
|
234
|
+
reconciliationTracker?: ReconciliationTracker
|
|
235
|
+
blockingFrameTracker?: ReconciliationTracker
|
|
218
236
|
}
|
|
219
237
|
|
|
220
238
|
type FrameInit = {
|
|
@@ -233,6 +251,7 @@ type FrameInit = {
|
|
|
233
251
|
moduleLoads: Map<string, Promise<ElementFunction | undefined>>
|
|
234
252
|
frameInstances: WeakMap<Comment, Frame>
|
|
235
253
|
namedFrames: Map<string, FrameHandle>
|
|
254
|
+
processClientEntryPreloads?: ProcessClientEntryPreloads
|
|
236
255
|
}
|
|
237
256
|
|
|
238
257
|
export type Frame = {
|
|
@@ -259,8 +278,11 @@ export type Frame = {
|
|
|
259
278
|
}
|
|
260
279
|
|
|
261
280
|
type RenderOptions = {
|
|
281
|
+
documentHref?: string
|
|
262
282
|
flushKind?: FlushKind
|
|
263
|
-
|
|
283
|
+
reconciliationTracker?: ReconciliationTracker
|
|
284
|
+
blockingFrameTracker?: ReconciliationTracker
|
|
285
|
+
onCommit?: () => void
|
|
264
286
|
signal?: AbortSignal
|
|
265
287
|
contentStatus?: 'pending' | 'resolved'
|
|
266
288
|
data?: RmxData
|
|
@@ -281,6 +303,7 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
281
303
|
let reloadKind: 'direct' | 'ancestor' | undefined
|
|
282
304
|
let styleManager = init.styleManager ?? createStyleManager()
|
|
283
305
|
let modulePreloader = getDocumentModulePreloader(container.doc)
|
|
306
|
+
let importMapManager = getDocumentImportMapManager(container.doc)
|
|
284
307
|
let currentMarker = init.marker
|
|
285
308
|
let displayedContentStatus: 'pending' | 'resolved' = init.marker?.status ?? 'resolved'
|
|
286
309
|
let pendingTemplateMarkerId: string | undefined
|
|
@@ -291,16 +314,42 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
291
314
|
let disposed = false
|
|
292
315
|
let lifecycleController = new AbortController()
|
|
293
316
|
|
|
317
|
+
async function consumeClientEntryResources(
|
|
318
|
+
source: ParentNode,
|
|
319
|
+
documentHref?: string,
|
|
320
|
+
): Promise<boolean> {
|
|
321
|
+
let importMapStatus = importMapManager.consumeImportMaps(source)
|
|
322
|
+
if (importMapStatus !== 'ready') {
|
|
323
|
+
lifecycleController.abort()
|
|
324
|
+
if (importMapStatus === 'conflict') reloadDocument(container.doc, documentHref)
|
|
325
|
+
return false
|
|
326
|
+
}
|
|
327
|
+
await modulePreloader.consumePreloadLinks(source, init.processClientEntryPreloads)
|
|
328
|
+
return true
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
let initialClientEntryResources: Promise<boolean> | undefined
|
|
332
|
+
function shouldPreserveManagedHeadNode(node: Node): boolean {
|
|
333
|
+
return (
|
|
334
|
+
importMapManager.shouldPreserveHeadNode(node) ||
|
|
335
|
+
(modulePreloader.hasActivePreloads() && modulePreloader.isActivePreload(node))
|
|
336
|
+
)
|
|
337
|
+
}
|
|
338
|
+
|
|
294
339
|
if (isDocumentNode(container.root)) {
|
|
295
340
|
modulePreloader.adoptInitialPreloadLinks(container.root)
|
|
296
341
|
} else {
|
|
297
|
-
|
|
342
|
+
initialClientEntryResources = consumeClientEntryResources(container.root)
|
|
298
343
|
}
|
|
299
344
|
|
|
300
345
|
// Merge any rmx-data found in the current document once at startup.
|
|
301
346
|
mergeRmxDataFromDocument(init.data, container.doc)
|
|
302
347
|
|
|
303
|
-
let runtime = createFrameRuntime({
|
|
348
|
+
let runtime = createFrameRuntime({
|
|
349
|
+
...init,
|
|
350
|
+
styleManager,
|
|
351
|
+
reloadForNavigation: startReloadTransition,
|
|
352
|
+
})
|
|
304
353
|
|
|
305
354
|
let frame = createFrameHandle({
|
|
306
355
|
src: init.src,
|
|
@@ -331,6 +380,7 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
331
380
|
moduleLoads: init.moduleLoads,
|
|
332
381
|
frameInstances: init.frameInstances,
|
|
333
382
|
namedFrames: init.namedFrames,
|
|
383
|
+
processClientEntryPreloads: init.processClientEntryPreloads,
|
|
334
384
|
lifecycleSignal: lifecycleController.signal,
|
|
335
385
|
regionTailRef: container.regionTailRef,
|
|
336
386
|
regionParent: container.regionParent,
|
|
@@ -385,7 +435,24 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
385
435
|
}
|
|
386
436
|
|
|
387
437
|
if (isRenderAborted(options.signal)) return
|
|
388
|
-
|
|
438
|
+
let previousServerFrameReload = runtime.serverFrameReload
|
|
439
|
+
if (options.signal) {
|
|
440
|
+
runtime.serverFrameReload = {
|
|
441
|
+
signal: options.signal,
|
|
442
|
+
reconciliationTracker: options.reconciliationTracker,
|
|
443
|
+
blockingFrameTracker: options.blockingFrameTracker,
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
try {
|
|
448
|
+
contentRoot.render(content)
|
|
449
|
+
await new Promise<void>((resolve) => context.scheduler.enqueueCommitPhase([resolve]))
|
|
450
|
+
options.onCommit?.()
|
|
451
|
+
} finally {
|
|
452
|
+
runtime.serverFrameReload = previousServerFrameReload
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (isRenderAborted(options.signal)) return
|
|
389
456
|
displayedContentStatus = options.contentStatus ?? 'resolved'
|
|
390
457
|
return
|
|
391
458
|
}
|
|
@@ -416,10 +483,16 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
416
483
|
|
|
417
484
|
if (isFullDocumentReload && htmlContent !== undefined) {
|
|
418
485
|
let parsed = new DOMParser().parseFromString(htmlContent, 'text/html')
|
|
419
|
-
|
|
486
|
+
if (!(await consumeClientEntryResources(parsed, options.documentHref))) return
|
|
487
|
+
if (isRenderAborted(options.signal)) return
|
|
420
488
|
let responseData = options.data
|
|
421
489
|
mergeRmxDataFromDocument(responseData, parsed)
|
|
422
|
-
let responseContext = {
|
|
490
|
+
let responseContext = {
|
|
491
|
+
...context,
|
|
492
|
+
data: responseData,
|
|
493
|
+
reconciliationTracker: options.reconciliationTracker,
|
|
494
|
+
blockingFrameTracker: options.blockingFrameTracker,
|
|
495
|
+
}
|
|
423
496
|
context.styleManager.adoptServerStyles(
|
|
424
497
|
collectFrameServerStyleTags(createElementContainer(parsed)),
|
|
425
498
|
)
|
|
@@ -431,9 +504,7 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
431
504
|
regionParent: container.doc.documentElement,
|
|
432
505
|
regionTailRef: null,
|
|
433
506
|
signal: options.signal,
|
|
434
|
-
|
|
435
|
-
? modulePreloader.isActivePreload
|
|
436
|
-
: undefined,
|
|
507
|
+
shouldPreserveHeadNode: shouldPreserveManagedHeadNode,
|
|
437
508
|
})
|
|
438
509
|
diffNodes([container.doc.body], [parsed.body], {
|
|
439
510
|
...responseContext,
|
|
@@ -447,10 +518,12 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
447
518
|
scheduleHydrationInContainer(
|
|
448
519
|
bodyContainer,
|
|
449
520
|
responseContext,
|
|
450
|
-
options.
|
|
521
|
+
options.reconciliationTracker,
|
|
451
522
|
options.signal,
|
|
452
523
|
)
|
|
453
|
-
|
|
524
|
+
let subFramesReady = createSubFrames(bodyContainer.childNodes, responseContext, options)
|
|
525
|
+
options.onCommit?.()
|
|
526
|
+
await subFramesReady
|
|
454
527
|
if (isRenderAborted(options.signal)) return
|
|
455
528
|
displayedContentStatus = options.contentStatus ?? 'resolved'
|
|
456
529
|
return
|
|
@@ -458,14 +531,20 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
458
531
|
|
|
459
532
|
let fragment =
|
|
460
533
|
htmlContent !== undefined ? createFragmentFromString(container.doc, htmlContent) : content
|
|
461
|
-
|
|
534
|
+
if (!(await consumeClientEntryResources(fragment, options.documentHref))) return
|
|
535
|
+
if (isRenderAborted(options.signal)) return
|
|
462
536
|
context.styleManager.adoptServerStyles(
|
|
463
537
|
collectFrameServerStyleTags(createElementContainer(fragment)),
|
|
464
538
|
)
|
|
465
539
|
removeEmptyHeads(fragment)
|
|
466
540
|
let responseData = options.data
|
|
467
541
|
mergeRmxDataFromFragment(responseData, fragment)
|
|
468
|
-
let responseContext = {
|
|
542
|
+
let responseContext = {
|
|
543
|
+
...context,
|
|
544
|
+
data: responseData,
|
|
545
|
+
reconciliationTracker: options.reconciliationTracker,
|
|
546
|
+
blockingFrameTracker: options.blockingFrameTracker,
|
|
547
|
+
}
|
|
469
548
|
|
|
470
549
|
let nextContainer = createContainer(fragment)
|
|
471
550
|
|
|
@@ -478,14 +557,15 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
478
557
|
signal: options.signal,
|
|
479
558
|
})
|
|
480
559
|
|
|
481
|
-
if (isRenderAborted(options.signal)) return
|
|
482
560
|
scheduleHydrationInContainer(
|
|
483
561
|
container,
|
|
484
562
|
responseContext,
|
|
485
|
-
options.
|
|
563
|
+
options.reconciliationTracker,
|
|
486
564
|
options.signal,
|
|
487
565
|
)
|
|
488
|
-
|
|
566
|
+
let subFramesReady = createSubFrames(container.childNodes, responseContext, options)
|
|
567
|
+
options.onCommit?.()
|
|
568
|
+
await subFramesReady
|
|
489
569
|
if (isRenderAborted(options.signal)) return
|
|
490
570
|
displayedContentStatus = options.contentStatus ?? 'resolved'
|
|
491
571
|
}
|
|
@@ -532,11 +612,13 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
532
612
|
}
|
|
533
613
|
|
|
534
614
|
async function hydrateInitial(): Promise<void> {
|
|
535
|
-
let
|
|
615
|
+
let reconciliationTracker = createReconciliationTracker()
|
|
536
616
|
|
|
617
|
+
if ((await initialClientEntryResources) === false) return
|
|
618
|
+
if (disposed || context.lifecycleSignal.aborted) return
|
|
537
619
|
context.styleManager.adoptServerStyles(collectFrameServerStyleTags(container))
|
|
538
620
|
let subFramesReady = createSubFrames(container.childNodes, context)
|
|
539
|
-
scheduleHydrationInContainer(container, context,
|
|
621
|
+
scheduleHydrationInContainer(container, context, reconciliationTracker)
|
|
540
622
|
|
|
541
623
|
try {
|
|
542
624
|
await subFramesReady
|
|
@@ -544,11 +626,11 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
544
626
|
if (disposed || context.lifecycleSignal.aborted) return
|
|
545
627
|
|
|
546
628
|
if (currentMarker?.status === 'pending') {
|
|
547
|
-
await watchPendingFrameTemplate(currentMarker,
|
|
629
|
+
await watchPendingFrameTemplate(currentMarker, reconciliationTracker)
|
|
548
630
|
}
|
|
549
631
|
|
|
550
|
-
|
|
551
|
-
await
|
|
632
|
+
reconciliationTracker.finalize()
|
|
633
|
+
await reconciliationTracker.ready()
|
|
552
634
|
} finally {
|
|
553
635
|
clearRmxData(context.data)
|
|
554
636
|
}
|
|
@@ -615,7 +697,7 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
615
697
|
if (marker.status === 'pending') {
|
|
616
698
|
await watchPendingFrameTemplate(
|
|
617
699
|
marker,
|
|
618
|
-
options?.
|
|
700
|
+
options?.reconciliationTracker,
|
|
619
701
|
options?.signal,
|
|
620
702
|
isInheritedReload
|
|
621
703
|
? () => {
|
|
@@ -659,8 +741,25 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
659
741
|
}
|
|
660
742
|
|
|
661
743
|
async function reload(options?: FrameReloadOptions): Promise<FrameReloadResult> {
|
|
744
|
+
let transition = startReloadTransition(options)
|
|
745
|
+
void transition.committed.catch(() => {})
|
|
746
|
+
return await transition.finished
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
function startReloadTransition(options?: FrameReloadOptions): FrameReloadTransition {
|
|
662
750
|
let controller = startReload(options?.signal)
|
|
663
|
-
|
|
751
|
+
let committed = Promise.withResolvers<void>()
|
|
752
|
+
let commitStarted = false
|
|
753
|
+
let finished = resolveAndRenderReload(controller, options, (ready) => {
|
|
754
|
+
if (commitStarted) return
|
|
755
|
+
commitStarted = true
|
|
756
|
+
void ready.then(committed.resolve, committed.reject)
|
|
757
|
+
})
|
|
758
|
+
|
|
759
|
+
// Settle committed when a reload is aborted or fails before rendering any content.
|
|
760
|
+
void finished.then(() => committed.resolve(), committed.reject)
|
|
761
|
+
|
|
762
|
+
return { signal: controller.signal, committed: committed.promise, finished }
|
|
664
763
|
}
|
|
665
764
|
|
|
666
765
|
function startReload(signal?: AbortSignal): AbortController {
|
|
@@ -722,6 +821,7 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
722
821
|
async function resolveAndRenderReload(
|
|
723
822
|
controller: AbortController,
|
|
724
823
|
options?: FrameReloadOptions,
|
|
824
|
+
resolveCommit?: (ready: Promise<void>) => void,
|
|
725
825
|
): Promise<FrameReloadResult> {
|
|
726
826
|
try {
|
|
727
827
|
let resolution = await init.resolveFrame(frame.src, {
|
|
@@ -736,7 +836,23 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
736
836
|
if (reloadController !== controller || controller.signal.aborted) {
|
|
737
837
|
return { signal: controller.signal }
|
|
738
838
|
}
|
|
739
|
-
|
|
839
|
+
let reconciliationTracker = createReconciliationTracker()
|
|
840
|
+
let blockingFrameTracker = createReconciliationTracker()
|
|
841
|
+
let commitStarted = false
|
|
842
|
+
await render(content, {
|
|
843
|
+
documentHref: isDocumentNode(container.root) ? (redirectedTo ?? frame.src) : undefined,
|
|
844
|
+
signal: controller.signal,
|
|
845
|
+
reconciliationTracker,
|
|
846
|
+
blockingFrameTracker,
|
|
847
|
+
onCommit() {
|
|
848
|
+
if (commitStarted) return
|
|
849
|
+
commitStarted = true
|
|
850
|
+
blockingFrameTracker.finalize()
|
|
851
|
+
resolveCommit?.(blockingFrameTracker.ready())
|
|
852
|
+
},
|
|
853
|
+
})
|
|
854
|
+
reconciliationTracker.finalize()
|
|
855
|
+
await reconciliationTracker.ready()
|
|
740
856
|
return {
|
|
741
857
|
signal: controller.signal,
|
|
742
858
|
redirectedTo:
|
|
@@ -825,7 +941,7 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
825
941
|
|
|
826
942
|
async function watchPendingFrameTemplate(
|
|
827
943
|
marker: FrameMarkerData,
|
|
828
|
-
|
|
944
|
+
reconciliationTracker?: ReconciliationTracker,
|
|
829
945
|
signal?: AbortSignal,
|
|
830
946
|
onResolved?: () => void,
|
|
831
947
|
): Promise<void> {
|
|
@@ -838,7 +954,7 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
838
954
|
let early = consumeFrameTemplate(marker.id) ?? getEarlyFrameContent(marker.id)
|
|
839
955
|
if (early) {
|
|
840
956
|
clearPendingFrameTemplateWatch()
|
|
841
|
-
await render(early, {
|
|
957
|
+
await render(early, { reconciliationTracker, signal, contentStatus: 'resolved' })
|
|
842
958
|
if (!disposed && !context.lifecycleSignal.aborted && !signal?.aborted) onResolved?.()
|
|
843
959
|
return
|
|
844
960
|
}
|
|
@@ -872,7 +988,7 @@ export function createFrame(root: FrameRoot, init: FrameInit): Frame {
|
|
|
872
988
|
let buffered = consumeFrameTemplate(marker.id)
|
|
873
989
|
if (buffered) {
|
|
874
990
|
clearPendingFrameTemplateWatch()
|
|
875
|
-
await render(buffered, {
|
|
991
|
+
await render(buffered, { reconciliationTracker, signal, contentStatus: 'resolved' })
|
|
876
992
|
if (!disposed && !context.lifecycleSignal.aborted && !signal?.aborted) onResolved?.()
|
|
877
993
|
}
|
|
878
994
|
}
|
|
@@ -890,7 +1006,8 @@ export function createFrameRuntime(init: {
|
|
|
890
1006
|
moduleLoads: Map<string, Promise<ElementFunction | undefined>>
|
|
891
1007
|
frameInstances: WeakMap<Comment, Frame>
|
|
892
1008
|
namedFrames: Map<string, FrameHandle>
|
|
893
|
-
|
|
1009
|
+
processClientEntryPreloads?: ProcessClientEntryPreloads
|
|
1010
|
+
reloadForNavigation?: (options?: FrameReloadOptions) => FrameReloadTransition
|
|
894
1011
|
}): FrameRuntime {
|
|
895
1012
|
return {
|
|
896
1013
|
[FRAME_RUNTIME]: true,
|
|
@@ -905,47 +1022,66 @@ export function createFrameRuntime(init: {
|
|
|
905
1022
|
moduleLoads: init.moduleLoads,
|
|
906
1023
|
frameInstances: init.frameInstances,
|
|
907
1024
|
namedFrames: init.namedFrames,
|
|
1025
|
+
processClientEntryPreloads: init.processClientEntryPreloads,
|
|
908
1026
|
serverFrameReload: undefined,
|
|
909
1027
|
reloadForNavigation: init.reloadForNavigation,
|
|
910
1028
|
}
|
|
911
1029
|
}
|
|
912
1030
|
|
|
913
|
-
type
|
|
1031
|
+
export type ReconciliationTracker = {
|
|
914
1032
|
track: () => () => void
|
|
1033
|
+
waitFor: (task: Promise<void>) => void
|
|
915
1034
|
finalize: () => void
|
|
916
1035
|
ready: () => Promise<void>
|
|
917
1036
|
}
|
|
918
1037
|
|
|
919
|
-
function
|
|
1038
|
+
function createReconciliationTracker(): ReconciliationTracker {
|
|
920
1039
|
let pending = 0
|
|
921
1040
|
let finalized = false
|
|
1041
|
+
let failed = false
|
|
1042
|
+
let failure: unknown
|
|
922
1043
|
|
|
923
1044
|
let resolveReady: (() => void) | undefined
|
|
924
|
-
let
|
|
1045
|
+
let rejectReady: ((error: unknown) => void) | undefined
|
|
1046
|
+
let readyPromise = new Promise<void>((resolve, reject) => {
|
|
925
1047
|
resolveReady = resolve
|
|
1048
|
+
rejectReady = reject
|
|
926
1049
|
})
|
|
927
1050
|
|
|
928
|
-
function
|
|
929
|
-
if (finalized
|
|
930
|
-
|
|
931
|
-
|
|
1051
|
+
function maybeSettle() {
|
|
1052
|
+
if (!finalized || pending !== 0) return
|
|
1053
|
+
if (failed) rejectReady?.(failure)
|
|
1054
|
+
else resolveReady?.()
|
|
1055
|
+
resolveReady = undefined
|
|
1056
|
+
rejectReady = undefined
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
function track(): () => void {
|
|
1060
|
+
pending++
|
|
1061
|
+
let completed = false
|
|
1062
|
+
return () => {
|
|
1063
|
+
if (completed) return
|
|
1064
|
+
completed = true
|
|
1065
|
+
pending--
|
|
1066
|
+
maybeSettle()
|
|
932
1067
|
}
|
|
933
1068
|
}
|
|
934
1069
|
|
|
935
1070
|
return {
|
|
936
|
-
track
|
|
937
|
-
|
|
938
|
-
let
|
|
939
|
-
|
|
940
|
-
if (
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1071
|
+
track,
|
|
1072
|
+
waitFor(task) {
|
|
1073
|
+
let complete = track()
|
|
1074
|
+
void task.then(complete, (error) => {
|
|
1075
|
+
if (!failed) {
|
|
1076
|
+
failed = true
|
|
1077
|
+
failure = error
|
|
1078
|
+
}
|
|
1079
|
+
complete()
|
|
1080
|
+
})
|
|
945
1081
|
},
|
|
946
1082
|
finalize() {
|
|
947
1083
|
finalized = true
|
|
948
|
-
|
|
1084
|
+
maybeSettle()
|
|
949
1085
|
},
|
|
950
1086
|
ready() {
|
|
951
1087
|
return readyPromise
|
|
@@ -1006,7 +1142,7 @@ function collectOwnedServerStyleTags(nodes: Node[], styles: HTMLStyleElement[]):
|
|
|
1006
1142
|
continue
|
|
1007
1143
|
}
|
|
1008
1144
|
|
|
1009
|
-
if (node instanceof HTMLStyleElement && node.matches('style[data-rmx]')) {
|
|
1145
|
+
if (node instanceof HTMLStyleElement && node.matches('style[data-rmx-style]')) {
|
|
1010
1146
|
styles.push(node)
|
|
1011
1147
|
continue
|
|
1012
1148
|
}
|
|
@@ -1049,7 +1185,7 @@ function copyOwnRmxEntries<T>(target: Record<string, T>, source: Record<string,
|
|
|
1049
1185
|
function scheduleHydrationInContainer(
|
|
1050
1186
|
container: FrameContainer,
|
|
1051
1187
|
context: FrameContext,
|
|
1052
|
-
|
|
1188
|
+
reconciliationTracker?: ReconciliationTracker,
|
|
1053
1189
|
signal?: AbortSignal,
|
|
1054
1190
|
): void {
|
|
1055
1191
|
let hydrationMarkers = findHydrationMarkers(container)
|
|
@@ -1061,7 +1197,7 @@ function scheduleHydrationInContainer(
|
|
|
1061
1197
|
for (let marker of hydrationMarkers) {
|
|
1062
1198
|
let entry = hydrationData[marker.id]
|
|
1063
1199
|
if (!entry) continue
|
|
1064
|
-
scheduleHydrationMarker(marker, entry, context,
|
|
1200
|
+
scheduleHydrationMarker(marker, entry, context, reconciliationTracker, signal)
|
|
1065
1201
|
}
|
|
1066
1202
|
}
|
|
1067
1203
|
|
|
@@ -1069,12 +1205,12 @@ function scheduleHydrationMarker(
|
|
|
1069
1205
|
marker: HydrationMarker,
|
|
1070
1206
|
entry: HydrationData,
|
|
1071
1207
|
context: FrameContext,
|
|
1072
|
-
|
|
1208
|
+
reconciliationTracker?: ReconciliationTracker,
|
|
1073
1209
|
signal?: AbortSignal,
|
|
1074
1210
|
): void {
|
|
1075
1211
|
if (signal?.aborted || context.lifecycleSignal.aborted) return
|
|
1076
1212
|
|
|
1077
|
-
let done =
|
|
1213
|
+
let done = reconciliationTracker?.track()
|
|
1078
1214
|
let key = `${entry.moduleUrl}#${entry.exportName}`
|
|
1079
1215
|
let identity: ClientEntryIdentity = {
|
|
1080
1216
|
moduleUrl: entry.moduleUrl,
|
|
@@ -1216,30 +1352,41 @@ function hydrateRegion(
|
|
|
1216
1352
|
|
|
1217
1353
|
context.pendingClientEntries.delete(start)
|
|
1218
1354
|
|
|
1219
|
-
//
|
|
1220
|
-
//
|
|
1221
|
-
//
|
|
1222
|
-
let
|
|
1223
|
-
if (owner) {
|
|
1355
|
+
// During a server frame reload, expose the reload signal and reconciliation
|
|
1356
|
+
// tracker to client frames created while this entry renders so blocking
|
|
1357
|
+
// frames keep the reload pending until their content arrives.
|
|
1358
|
+
let renderEntry = (root: Pick<VirtualRoot, 'render'>) => {
|
|
1224
1359
|
if (!signal) {
|
|
1225
|
-
|
|
1360
|
+
root.render(vElement)
|
|
1226
1361
|
return
|
|
1227
1362
|
}
|
|
1228
1363
|
|
|
1229
1364
|
let frameRuntime = context.frame.$runtime
|
|
1230
1365
|
invariant(
|
|
1231
1366
|
isFrameRuntime(frameRuntime),
|
|
1232
|
-
'Expected frame runtime while rendering a
|
|
1367
|
+
'Expected frame runtime while rendering a client entry during a reload',
|
|
1233
1368
|
)
|
|
1234
1369
|
|
|
1235
1370
|
let previousServerFrameReload = frameRuntime.serverFrameReload
|
|
1236
1371
|
|
|
1237
|
-
frameRuntime.serverFrameReload = {
|
|
1372
|
+
frameRuntime.serverFrameReload = {
|
|
1373
|
+
signal,
|
|
1374
|
+
reconciliationTracker: context.reconciliationTracker,
|
|
1375
|
+
blockingFrameTracker: context.blockingFrameTracker,
|
|
1376
|
+
}
|
|
1238
1377
|
try {
|
|
1239
|
-
|
|
1378
|
+
root.render(vElement)
|
|
1240
1379
|
} finally {
|
|
1241
1380
|
frameRuntime.serverFrameReload = previousServerFrameReload
|
|
1242
1381
|
}
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// The same marker can be discovered by overlapping hydration passes
|
|
1385
|
+
// (for example, document root + nested frame root). Reuse the existing
|
|
1386
|
+
// virtual root instead of redefining the marker property.
|
|
1387
|
+
let owner = getClientEntryBoundaryOwner(start)
|
|
1388
|
+
if (owner) {
|
|
1389
|
+
renderEntry(owner.root)
|
|
1243
1390
|
return
|
|
1244
1391
|
}
|
|
1245
1392
|
|
|
@@ -1254,7 +1401,7 @@ function hydrateRegion(
|
|
|
1254
1401
|
})
|
|
1255
1402
|
|
|
1256
1403
|
setClientEntryBoundaryOwner(start, identity, root)
|
|
1257
|
-
root
|
|
1404
|
+
renderEntry(root)
|
|
1258
1405
|
}
|
|
1259
1406
|
|
|
1260
1407
|
async function createSubFrames(
|
|
@@ -1300,8 +1447,12 @@ async function createSubFrames(
|
|
|
1300
1447
|
moduleLoads: context.moduleLoads,
|
|
1301
1448
|
frameInstances: context.frameInstances,
|
|
1302
1449
|
namedFrames: context.namedFrames,
|
|
1450
|
+
processClientEntryPreloads: context.processClientEntryPreloads,
|
|
1303
1451
|
})
|
|
1304
1452
|
context.frameInstances.set(node, subFrame)
|
|
1453
|
+
if (frameMarker.status === 'resolved') {
|
|
1454
|
+
tasks.push(subFrame.ready())
|
|
1455
|
+
}
|
|
1305
1456
|
}
|
|
1306
1457
|
}
|
|
1307
1458
|
|