Skip to content

Commit

Permalink
The prior attempt didn't quite work because resume requests don't fol…
Browse files Browse the repository at this point in the history
…low 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.
  • Loading branch information
gnoff committed Apr 10, 2024
1 parent ea441f6 commit 07a270a
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 16 deletions.
34 changes: 23 additions & 11 deletions packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@ const SentClientRenderFunction /* */ = 0b00100;
const SentStyleInsertionFunction /* */ = 0b01000;
const SentFormReplayingRuntime /* */ = 0b10000;

type Parts = number;
const NoParts /* */ = 0b00000;
const HasShell /* */ = 0b00001;
const HasHTML /* */ = 0b00010;
const HasBody /* */ = 0b00100;

// Per request, global state that is not contextual to the rendering subtree.
// This cannot be resumed and therefore should only contain things that are
// temporary working state or are never used in the prerender pass.
Expand Down Expand Up @@ -245,9 +251,8 @@ export type ResumableState = {
// state for script streaming format, unused if using external runtime / data
instructions: InstructionState,

// postamble state
hasBody: boolean,
hasHtml: boolean,
// preamble & postamble state
parts: Parts,

// Resources - Request local cache
unknownResources: {
Expand Down Expand Up @@ -637,8 +642,7 @@ export function createResumableState(
bootstrapScripts,
bootstrapModules,
instructions: NothingSent,
hasBody: false,
hasHtml: false,
parts: NoParts,

// @TODO add bootstrap script to implicit preloads

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

export function completeResumableState(resumableState: ResumableState): void {
// This function is called when we have completed a prerender and there is a shell.
resumableState.parts |= HasShell;
resumableState.bootstrapScriptContent = undefined;
resumableState.bootstrapScripts = undefined;
resumableState.bootstrapModules = undefined;
Expand Down Expand Up @@ -3708,14 +3713,14 @@ export function pushEndInstance(
// we won't emit any more tags
case 'body': {
if (formatContext.insertionMode <= HTML_HTML_MODE) {
resumableState.hasBody = true;
resumableState.parts |= HasBody;
return;
}
break;
}
case 'html':
if (formatContext.insertionMode === ROOT_HTML_MODE) {
resumableState.hasHtml = true;
resumableState.parts |= HasHTML;
return;
}
break;
Expand Down Expand Up @@ -4602,6 +4607,8 @@ export function writePreamble(
renderState: RenderState,
willFlushAllSegments: boolean,
): void {
resumableState.parts |= HasShell;

// This function must be called exactly once on every request
if (
enableFizzExternalRuntime &&
Expand Down Expand Up @@ -4707,6 +4714,11 @@ export function writeHoistables(
resumableState: ResumableState,
renderState: RenderState,
): void {
if ((resumableState.parts & HasShell) === NoParts) {
// We can't write anything until we've written the shell.
return;
}

let i = 0;

// Emit high priority Hoistables
Expand Down Expand Up @@ -4759,10 +4771,10 @@ export function writePostamble(
destination: Destination,
resumableState: ResumableState,
): void {
if (resumableState.hasBody) {
if (resumableState.parts & HasBody) {
writeChunk(destination, endChunkForTag('body'));
}
if (resumableState.hasHtml) {
if (resumableState.parts & HasHTML) {
writeChunk(destination, endChunkForTag('html'));
}
}
Expand Down
8 changes: 3 additions & 5 deletions packages/react-server/src/ReactFizzServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -4057,17 +4057,15 @@ function flushCompletedQueues(
// that item fully and then yield. At that point we remove the already completed
// items up until the point we completed them.

if (request.pendingRootTasks > 0) {
// When there are pending root tasks we don't want to flush anything
return;
}

let i;
const completedRootSegment = request.completedRootSegment;
if (completedRootSegment !== null) {
if (completedRootSegment.status === POSTPONED) {
// We postponed the root, so we write nothing.
return;
} else if (request.pendingRootTasks > 0) {
// The root is blocked on additional tasks.
return;
}

flushPreamble(request, destination, completedRootSegment);
Expand Down

0 comments on commit 07a270a

Please sign in to comment.