Skip to content

Commit 07a270a

Browse files
committed
The prior attempt didn't quite work because resume requests don't follow the same logic that the root tasks will be zero after the preamble has been sent.
I decided to restructure things to track whether there was a shell on the resumable state. I also took the opportunity to collapse the hasBody and hasHTML booleans into a bitfield which also tracks the shell. This is probably the right long term layering too because the fact that hoistables can't be written before the preamble is really a dom limitation not a fizz limitation. Now flushes can attempt to write hoistables every time but for dom specifically these will be noops until there is a shell. We have to set whether there is a shell in two places. first we set it during the preamble write. This covers cases like regular renders. we can't write hoistables until we've flushed the shell so flipping the bit here is the right spot. But during a prerender we will complete the prerender and return a postponed state before we've ever started flushing. In this case we need to flip the bit during the completion of the postponed state. In either case this produces a runtime where during render hositables always come after the preamble and during resumes hoistables only come after the preamble if the prenreder did not cotnain the shell.
1 parent ea441f6 commit 07a270a

File tree

2 files changed

+26
-16
lines changed

2 files changed

+26
-16
lines changed

packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,12 @@ const SentClientRenderFunction /* */ = 0b00100;
119119
const SentStyleInsertionFunction /* */ = 0b01000;
120120
const SentFormReplayingRuntime /* */ = 0b10000;
121121

122+
type Parts = number;
123+
const NoParts /* */ = 0b00000;
124+
const HasShell /* */ = 0b00001;
125+
const HasHTML /* */ = 0b00010;
126+
const HasBody /* */ = 0b00100;
127+
122128
// Per request, global state that is not contextual to the rendering subtree.
123129
// This cannot be resumed and therefore should only contain things that are
124130
// temporary working state or are never used in the prerender pass.
@@ -245,9 +251,8 @@ export type ResumableState = {
245251
// state for script streaming format, unused if using external runtime / data
246252
instructions: InstructionState,
247253

248-
// postamble state
249-
hasBody: boolean,
250-
hasHtml: boolean,
254+
// preamble & postamble state
255+
parts: Parts,
251256

252257
// Resources - Request local cache
253258
unknownResources: {
@@ -637,8 +642,7 @@ export function createResumableState(
637642
bootstrapScripts,
638643
bootstrapModules,
639644
instructions: NothingSent,
640-
hasBody: false,
641-
hasHtml: false,
645+
parts: NoParts,
642646

643647
// @TODO add bootstrap script to implicit preloads
644648

@@ -665,8 +669,8 @@ export function resetResumableState(
665669
// Resets the resumable state based on what didn't manage to fully flush in the render state.
666670
// This currently assumes nothing was flushed.
667671
resumableState.nextFormID = 0;
668-
resumableState.hasBody = false;
669-
resumableState.hasHtml = false;
672+
resumableState.instructions = NothingSent;
673+
resumableState.parts = NoParts;
670674
resumableState.unknownResources = {
671675
font: renderState.resets.font,
672676
};
@@ -681,6 +685,7 @@ export function resetResumableState(
681685

682686
export function completeResumableState(resumableState: ResumableState): void {
683687
// This function is called when we have completed a prerender and there is a shell.
688+
resumableState.parts |= HasShell;
684689
resumableState.bootstrapScriptContent = undefined;
685690
resumableState.bootstrapScripts = undefined;
686691
resumableState.bootstrapModules = undefined;
@@ -3708,14 +3713,14 @@ export function pushEndInstance(
37083713
// we won't emit any more tags
37093714
case 'body': {
37103715
if (formatContext.insertionMode <= HTML_HTML_MODE) {
3711-
resumableState.hasBody = true;
3716+
resumableState.parts |= HasBody;
37123717
return;
37133718
}
37143719
break;
37153720
}
37163721
case 'html':
37173722
if (formatContext.insertionMode === ROOT_HTML_MODE) {
3718-
resumableState.hasHtml = true;
3723+
resumableState.parts |= HasHTML;
37193724
return;
37203725
}
37213726
break;
@@ -4602,6 +4607,8 @@ export function writePreamble(
46024607
renderState: RenderState,
46034608
willFlushAllSegments: boolean,
46044609
): void {
4610+
resumableState.parts |= HasShell;
4611+
46054612
// This function must be called exactly once on every request
46064613
if (
46074614
enableFizzExternalRuntime &&
@@ -4707,6 +4714,11 @@ export function writeHoistables(
47074714
resumableState: ResumableState,
47084715
renderState: RenderState,
47094716
): void {
4717+
if ((resumableState.parts & HasShell) === NoParts) {
4718+
// We can't write anything until we've written the shell.
4719+
return;
4720+
}
4721+
47104722
let i = 0;
47114723

47124724
// Emit high priority Hoistables
@@ -4759,10 +4771,10 @@ export function writePostamble(
47594771
destination: Destination,
47604772
resumableState: ResumableState,
47614773
): void {
4762-
if (resumableState.hasBody) {
4774+
if (resumableState.parts & HasBody) {
47634775
writeChunk(destination, endChunkForTag('body'));
47644776
}
4765-
if (resumableState.hasHtml) {
4777+
if (resumableState.parts & HasHTML) {
47664778
writeChunk(destination, endChunkForTag('html'));
47674779
}
47684780
}

packages/react-server/src/ReactFizzServer.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4057,17 +4057,15 @@ function flushCompletedQueues(
40574057
// that item fully and then yield. At that point we remove the already completed
40584058
// items up until the point we completed them.
40594059

4060-
if (request.pendingRootTasks > 0) {
4061-
// When there are pending root tasks we don't want to flush anything
4062-
return;
4063-
}
4064-
40654060
let i;
40664061
const completedRootSegment = request.completedRootSegment;
40674062
if (completedRootSegment !== null) {
40684063
if (completedRootSegment.status === POSTPONED) {
40694064
// We postponed the root, so we write nothing.
40704065
return;
4066+
} else if (request.pendingRootTasks > 0) {
4067+
// The root is blocked on additional tasks.
4068+
return;
40714069
}
40724070

40734071
flushPreamble(request, destination, completedRootSegment);

0 commit comments

Comments
 (0)