26
RenderState,
27
ResumableState,
28
FormatContext,
29
- BoundaryResources,
29
+ HoistableState,
30
} from './ReactFizzConfig';
31
import type {ContextSnapshot} from './ReactFizzNewContext';
32
import type {ComponentStackNode} from './ReactFizzComponentStack';
57
writeClientRenderBoundaryInstruction,
58
writeCompletedBoundaryInstruction,
59
writeCompletedSegmentInstruction,
60
+ writeHoistablesForBoundary,
61
pushTextInstance,
62
pushStartInstance,
63
pushEndInstance,
65
pushEndCompletedSuspenseBoundary,
66
pushSegmentFinale,
67
getChildFormatContext,
67
- writeResourcesForBoundary,
68
- writePreamble,
68
writeHoistables,
69
+ writePreamble,
70
writePostamble,
71
- hoistResources,
72
- setCurrentlyRenderingBoundaryResourcesTarget,
73
- createBoundaryResources,
71
+ hoistHoistables,
72
+ createHoistableState,
73
prepareHostDispatcher,
74
supportsRequestStorage,
75
requestStorage,
210
completedSegments: Array<Segment>, // completed but not yet flushed segments.
211
byteSize: number, // used to determine whether to inline children boundaries.
212
fallbackAbortableTasks: Set<Task>, // used to cancel task on the fallback if the boundary completes or gets canceled.
214
- resources: BoundaryResources,
213
+ contentState: HoistableState,
214
+ fallbackState: HoistableState,
215
trackedContentKeyPath: null | KeyNode, // used to track the path for replay nodes
216
trackedFallbackNode: null | ReplayNode, // used to track the fallback for replay nodes
217
};
223
ping: () => void,
224
blockedBoundary: Root | SuspenseBoundary,
225
blockedSegment: Segment, // the segment we'll write to
226
+ hoistableState: null | HoistableState, // Boundary state we'll mutate while rendering. This may not equal the state of the blockedBoundary
227
abortSet: Set<Task>, // the abortable set that this task belongs to
228
keyPath: Root | KeyNode, // the path of all parent keys currently rendering
229
formatContext: FormatContext, // the format's specific context (e.g. HTML/SVG/MathML)
232
treeContext: TreeContext, // the current tree context that this task is executing in
233
componentStack: null | ComponentStackNode, // stack frame description of the currently rendering component
234
thenableState: null | ThenableState,
235
+ isFallback: boolean, // whether this task is rendering inside a fallback tree
236
};
237
238
type ReplaySet = {
250
ping: () => void,
251
blockedBoundary: Root | SuspenseBoundary,
252
blockedSegment: null, // we don't write to anything when we replay
253
+ hoistableState: null | HoistableState, // Boundary state we'll mutate while rendering. This may not equal the state of the blockedBoundary
254
abortSet: Set<Task>, // the abortable set that this task belongs to
255
keyPath: Root | KeyNode, // the path of all parent keys currently rendering
256
formatContext: FormatContext, // the format's specific context (e.g. HTML/SVG/MathML)
259
treeContext: TreeContext, // the current tree context that this task is executing in
260
componentStack: null | ComponentStackNode, // stack frame description of the currently rendering component
261
thenableState: null | ThenableState,
262
+ isFallback: boolean, // whether this task is rendering inside a fallback tree
263
};
264
265
export type Task = RenderTask | ReplayTask;
425
-1,
426
null,
427
rootSegment,
428
+ null,
429
abortSet,
430
null,
431
rootFormatContext,
433
rootContextSnapshot,
434
emptyTreeContext,
435
null,
436
+ false,
437
);
438
pingedTasks.push(rootTask);
439
return request;
538
-1,
539
null,
540
rootSegment,
541
+ null,
542
abortSet,
543
null,
544
postponedState.rootFormatContext,
546
rootContextSnapshot,
547
emptyTreeContext,
548
null,
549
+ false,
550
);
551
pingedTasks.push(rootTask);
552
return request;
564
children,
565
-1,
566
null,
567
+ null,
568
abortSet,
569
null,
570
postponedState.rootFormatContext,
572
rootContextSnapshot,
573
emptyTreeContext,
574
null,
575
+ false,
576
);
577
pingedTasks.push(rootTask);
578
return request;
611
byteSize: 0,
612
fallbackAbortableTasks,
613
errorDigest: null,
604
- resources: createBoundaryResources(),
614
+ contentState: createHoistableState(),
615
+ fallbackState: createHoistableState(),
616
trackedContentKeyPath: null,
617
trackedFallbackNode: null,
618
};
625
childIndex: number,
626
blockedBoundary: Root | SuspenseBoundary,
627
blockedSegment: Segment,
628
+ hoistableState: null | HoistableState,
629
abortSet: Set<Task>,
630
keyPath: Root | KeyNode,
631
formatContext: FormatContext,
633
context: ContextSnapshot,
634
treeContext: TreeContext,
635
componentStack: null | ComponentStackNode,
636
+ isFallback: boolean,
637
): RenderTask {
638
request.allPendingTasks++;
639
if (blockedBoundary === null) {
648
ping: () => pingTask(request, task),
649
blockedBoundary,
650
blockedSegment,
651
+ hoistableState,
652
abortSet,
653
keyPath,
654
formatContext,
657
treeContext,
658
componentStack,
659
thenableState,
660
+ isFallback,
661
};
662
abortSet.add(task);
663
return task;
670
node: ReactNodeList,
671
childIndex: number,
672
blockedBoundary: Root | SuspenseBoundary,
673
+ hoistableState: null | HoistableState,
674
abortSet: Set<Task>,
675
keyPath: Root | KeyNode,
676
formatContext: FormatContext,
678
context: ContextSnapshot,
679
treeContext: TreeContext,
680
componentStack: null | ComponentStackNode,
681
+ isFallback: boolean,
682
): ReplayTask {
683
request.allPendingTasks++;
684
if (blockedBoundary === null) {
694
ping: () => pingTask(request, task),
695
blockedBoundary,
696
blockedSegment: null,
697
+ hoistableState,
698
abortSet,
699
keyPath,
700
formatContext,
703
treeContext,
704
componentStack,
705
thenableState,
706
+ isFallback,
707
};
708
abortSet.add(task);
709
return task;
911
912
const prevKeyPath = task.keyPath;
913
const parentBoundary = task.blockedBoundary;
914
+ const parentHoistableState = task.hoistableState;
915
const parentSegment = task.blockedSegment;
916
917
// Each time we enter a suspense boundary, we split out into a new segment for
964
// context switching. We just need to temporarily switch which boundary and which segment
965
// we're writing to. If something suspends, it'll spawn new suspended task with that context.
966
task.blockedBoundary = newBoundary;
967
+ task.hoistableState = newBoundary.contentState;
968
task.blockedSegment = contentRootSegment;
948
- if (enableFloat) {
949
- setCurrentlyRenderingBoundaryResourcesTarget(
950
- request.renderState,
951
- newBoundary.resources,
952
- );
953
- }
969
task.keyPath = keyPath;
970
971
try {
1015
// We don't need to schedule any task because we know the parent has written yet.
1016
// We do need to fallthrough to create the fallback though.
1017
} finally {
1003
- if (enableFloat) {
1004
- setCurrentlyRenderingBoundaryResourcesTarget(
1005
- request.renderState,
1006
- parentBoundary ? parentBoundary.resources : null,
1007
- );
1008
- }
1018
task.blockedBoundary = parentBoundary;
1019
+ task.hoistableState = parentHoistableState;
1020
task.blockedSegment = parentSegment;
1021
task.keyPath = prevKeyPath;
1022
task.componentStack = previousComponentStack;
1053
-1,
1054
parentBoundary,
1055
boundarySegment,
1056
+ newBoundary.fallbackState,
1057
fallbackAbortSet,
1058
fallbackKeyPath,
1059
task.formatContext,
1063
// This stack should be the Suspense boundary stack because while the fallback is actually a child segment
1064
// of the parent boundary from a component standpoint the fallback is a child of the Suspense boundary itself
1065
suspenseComponentStack,
1066
+ true,
1067
);
1068
// TODO: This should be queued at a separate lower priority queue so that we only work
1069
// on preparing fallbacks if we don't have any more main content to task on.
1091
const previousReplaySet: ReplaySet = task.replay;
1092
1093
const parentBoundary = task.blockedBoundary;
1094
+ const parentHoistableState = task.hoistableState;
1095
1096
const content: ReactNodeList = props.children;
1097
const fallback: ReactNodeList = props.fallback;
1106
// context switching. We just need to temporarily switch which boundary and replay node
1107
// we're writing to. If something suspends, it'll spawn new suspended task with that context.
1108
task.blockedBoundary = resumedBoundary;
1109
+ task.hoistableState = resumedBoundary.contentState;
1110
task.replay = {nodes: childNodes, slots: childSlots, pendingTasks: 1};
1097
- if (enableFloat) {
1098
- setCurrentlyRenderingBoundaryResourcesTarget(
1099
- request.renderState,
1100
- resumedBoundary.resources,
1101
- );
1102
- }
1111
1112
try {
1113
// We use the safe form because we don't handle suspending here. Only error handling.
1162
// We don't need to schedule any task because we know the parent has written yet.
1163
// We do need to fallthrough to create the fallback though.
1164
} finally {
1157
- if (enableFloat) {
1158
- setCurrentlyRenderingBoundaryResourcesTarget(
1159
- request.renderState,
1160
- parentBoundary ? parentBoundary.resources : null,
1161
- );
1162
- }
1165
task.blockedBoundary = parentBoundary;
1166
+ task.hoistableState = parentHoistableState;
1167
task.replay = previousReplaySet;
1168
task.keyPath = prevKeyPath;
1169
task.componentStack = previousComponentStack;
1185
fallback,
1186
-1,
1187
parentBoundary,
1188
+ resumedBoundary.fallbackState,
1189
fallbackAbortSet,
1190
fallbackKeyPath,
1191
task.formatContext,
1195
// This stack should be the Suspense boundary stack because while the fallback is actually a child segment
1196
// of the parent boundary from a component standpoint the fallback is a child of the Suspense boundary itself
1197
suspenseComponentStack,
1198
+ true,
1199
);
1200
// TODO: This should be queued at a separate lower priority queue so that we only work
1201
// on preparing fallbacks if we don't have any more main content to task on.
1262
props,
1263
request.resumableState,
1264
request.renderState,
1265
+ task.hoistableState,
1266
task.formatContext,
1267
segment.lastPushedText,
1268
+ task.isFallback,
1269
);
1270
segment.lastPushedText = false;
1271
const prevContext = task.formatContext;
2690
task.node,
2691
task.childIndex,
2692
task.blockedBoundary,
2693
+ task.hoistableState,
2694
task.abortSet,
2695
task.keyPath,
2696
task.formatContext,
2700
// We pop one task off the stack because the node that suspended will be tried again,
2701
// which will add it back onto the stack.
2702
task.componentStack !== null ? task.componentStack.parent : null,
2703
+ task.isFallback,
2704
);
2705
2706
const ping = newTask.ping;
2736
task.childIndex,
2737
task.blockedBoundary,
2738
newSegment,
2739
+ task.hoistableState,
2740
task.abortSet,
2741
task.keyPath,
2742
task.formatContext,
2746
// We pop one task off the stack because the node that suspended will be tried again,
2747
// which will add it back onto the stack.
2748
task.componentStack !== null ? task.componentStack.parent : null,
2749
+ task.isFallback,
2750
);
2751
2752
const ping = newTask.ping;
3356
}
3357
3358
function retryTask(request: Request, task: Task): void {
3348
- if (enableFloat) {
3349
- const blockedBoundary = task.blockedBoundary;
3350
- setCurrentlyRenderingBoundaryResourcesTarget(
3351
- request.renderState,
3352
- blockedBoundary ? blockedBoundary.resources : null,
3353
- );
3354
- }
3359
const segment = task.blockedSegment;
3360
if (segment === null) {
3361
retryReplayTask(
3460
erroredTask(request, task.blockedBoundary, x, errorInfo);
3461
return;
3462
} finally {
3459
- if (enableFloat) {
3460
- setCurrentlyRenderingBoundaryResourcesTarget(request.renderState, null);
3461
- }
3463
if (__DEV__) {
3464
currentTaskInDEV = prevTaskInDEV;
3465
}
3542
}
3543
return;
3544
} finally {
3544
- if (enableFloat) {
3545
- setCurrentlyRenderingBoundaryResourcesTarget(request.renderState, null);
3546
- }
3545
if (__DEV__) {
3546
currentTaskInDEV = prevTaskInDEV;
3547
}
3610
}
3611
}
3612
3613
+function flushPreamble(
3614
+ request: Request,
3615
+ destination: Destination,
3616
+ rootSegment: Segment,
3617
+) {
3618
+ const willFlushAllSegments =
3619
+ request.allPendingTasks === 0 && request.trackedPostpones === null;
3620
+ writePreamble(
3621
+ destination,
3622
+ request.resumableState,
3623
+ request.renderState,
3624
+ willFlushAllSegments,
3625
+ );
3626
+}
3627
+
3628
function flushSubtree(
3629
request: Request,
3630
destination: Destination,
3631
segment: Segment,
3632
+ hoistableState: null | HoistableState,
3633
): boolean {
3634
segment.parentFlushed = true;
3635
switch (segment.status) {
3659
for (; chunkIdx < nextChild.index; chunkIdx++) {
3660
writeChunk(destination, chunks[chunkIdx]);
3661
}
3648
- r = flushSegment(request, destination, nextChild);
3662
+ r = flushSegment(request, destination, nextChild, hoistableState);
3663
}
3664
// Finally just write all the remaining chunks
3665
for (; chunkIdx < chunks.length - 1; chunkIdx++) {
3682
request: Request,
3683
destination: Destination,
3684
segment: Segment,
3685
+ hoistableState: null | HoistableState,
3686
): boolean {
3687
const boundary = segment.boundary;
3688
if (boundary === null) {
3689
// Not a suspense boundary.
3675
- return flushSubtree(request, destination, segment);
3690
+ return flushSubtree(request, destination, segment, hoistableState);
3691
}
3692
3693
boundary.parentFlushed = true;
3705
boundary.errorComponentStack,
3706
);
3707
// Flush the fallback.
3693
- flushSubtree(request, destination, segment);
3708
+ flushSubtree(request, destination, segment, hoistableState);
3709
3710
return writeEndClientRenderedSuspenseBoundary(
3711
destination,
3728
const id = boundary.rootSegmentID;
3729
writeStartPendingSuspenseBoundary(destination, request.renderState, id);
3730
3731
+ // We are going to flush the fallback so we need to hoist the fallback
3732
+ // state to the parent boundary
3733
+ if (enableFloat) {
3734
+ if (hoistableState) {
3735
+ hoistHoistables(hoistableState, boundary.fallbackState);
3736
+ }
3737
+ }
3738
// Flush the fallback.
3717
- flushSubtree(request, destination, segment);
3739
+ flushSubtree(request, destination, segment, hoistableState);
3740
3741
return writeEndPendingSuspenseBoundary(destination, request.renderState);
3742
} else if (boundary.byteSize > request.progressiveChunkSize) {
3757
boundary.rootSegmentID,
3758
);
3759
3760
+ // While we are going to flush the fallback we are going to follow it up with
3761
+ // the completed boundary immediately so we make the choice to omit fallback
3762
+ // boundary state from the parent since it will be replaced when the boundary
3763
+ // flushes later in this pass or in a future flush
3764
+
3765
// Flush the fallback.
3739
- flushSubtree(request, destination, segment);
3766
+ flushSubtree(request, destination, segment, hoistableState);
3767
3768
return writeEndPendingSuspenseBoundary(destination, request.renderState);
3769
} else {
3770
if (enableFloat) {
3744
- hoistResources(request.renderState, boundary.resources);
3771
+ if (hoistableState) {
3772
+ hoistHoistables(hoistableState, boundary.contentState);
3773
+ }
3774
}
3775
// We can inline this boundary's content as a complete boundary.
3776
writeStartCompletedSuspenseBoundary(destination, request.renderState);
3784
}
3785
3786
const contentSegment = completedSegments[0];
3758
- flushSegment(request, destination, contentSegment);
3787
+ flushSegment(request, destination, contentSegment, hoistableState);
3788
3789
return writeEndCompletedSuspenseBoundary(destination, request.renderState);
3790
}
3810
request: Request,
3811
destination: Destination,
3812
segment: Segment,
3813
+ hoistableState: HoistableState,
3814
): boolean {
3815
writeStartSegment(
3816
destination,
3818
segment.parentFormatContext,
3819
segment.id,
3820
);
3791
- flushSegment(request, destination, segment);
3821
+ flushSegment(request, destination, segment, hoistableState);
3822
return writeEndSegment(destination, segment.parentFormatContext);
3823
}
3824
3827
destination: Destination,
3828
boundary: SuspenseBoundary,
3829
): boolean {
3800
- if (enableFloat) {
3801
- setCurrentlyRenderingBoundaryResourcesTarget(
3802
- request.renderState,
3803
- boundary.resources,
3804
- );
3805
- }
3830
const completedSegments = boundary.completedSegments;
3831
let i = 0;
3832
for (; i < completedSegments.length; i++) {
3836
completedSegments.length = 0;
3837
3838
if (enableFloat) {
3815
- writeResourcesForBoundary(
3839
+ writeHoistablesForBoundary(
3840
destination,
3817
- boundary.resources,
3841
+ boundary.contentState,
3842
request.renderState,
3843
);
3844
}
3848
request.resumableState,
3849
request.renderState,
3850
boundary.rootSegmentID,
3827
- boundary.resources,
3851
+ boundary.contentState,
3852
);
3853
}
3854
3857
destination: Destination,
3858
boundary: SuspenseBoundary,
3859
): boolean {
3836
- if (enableFloat) {
3837
- setCurrentlyRenderingBoundaryResourcesTarget(
3838
- request.renderState,
3839
- boundary.resources,
3840
- );
3841
- }
3860
const completedSegments = boundary.completedSegments;
3861
let i = 0;
3862
for (; i < completedSegments.length; i++) {
3874
completedSegments.splice(0, i);
3875
3876
if (enableFloat) {
3859
- // The way this is structured we only write resources for partial boundaries
3860
- // if there is no backpressure. Later before we complete the boundary we
3861
- // will write resources regardless of backpressure before we emit the
3862
- // completion instruction
3863
- return writeResourcesForBoundary(
3877
+ return writeHoistablesForBoundary(
3878
destination,
3865
- boundary.resources,
3879
+ boundary.contentState,
3880
request.renderState,
3881
);
3882
} else {
3895
return true;
3896
}
3897
3898
+ const hoistableState = boundary.contentState;
3899
+
3900
const segmentID = segment.id;
3901
if (segmentID === -1) {
3902
// This segment wasn't previously referred to. This happens at the root of
3909
);
3910
}
3911
3896
- return flushSegmentContainer(request, destination, segment);
3912
+ return flushSegmentContainer(request, destination, segment, hoistableState);
3913
} else if (segmentID === boundary.rootSegmentID) {
3914
// When we emit postponed boundaries, we might have assigned the ID already
3915
// but it's still the root segment so we can't inject it into the parent yet.
3900
- return flushSegmentContainer(request, destination, segment);
3916
+ return flushSegmentContainer(request, destination, segment, hoistableState);
3917
} else {
3902
- flushSegmentContainer(request, destination, segment);
3918
+ flushSegmentContainer(request, destination, segment, hoistableState);
3919
return writeCompletedSegmentInstruction(
3920
destination,
3921
request.resumableState,
3944
return;
3945
} else if (request.pendingRootTasks === 0) {
3946
if (enableFloat) {
3931
- writePreamble(
3932
- destination,
3933
- request.resumableState,
3934
- request.renderState,
3935
- request.allPendingTasks === 0 && request.trackedPostpones === null,
3936
- );
3947
+ flushPreamble(request, destination, completedRootSegment);
3948
}
3949
3939
- flushSegment(request, destination, completedRootSegment);
3950
+ flushSegment(request, destination, completedRootSegment, null);
3951
request.completedRootSegment = null;
3952
writeCompletedRoot(destination, request.renderState);
3953
} else {
3955
return;
3956
}
3957
}
3947
-
3958
if (enableFloat) {
3959
writeHoistables(destination, request.resumableState, request.renderState);
3960
}