diff --git a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js index 91baeef4a1bff..515145f486c47 100644 --- a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js +++ b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js @@ -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. @@ -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: { @@ -637,8 +642,7 @@ export function createResumableState( bootstrapScripts, bootstrapModules, instructions: NothingSent, - hasBody: false, - hasHtml: false, + parts: NoParts, // @TODO add bootstrap script to implicit preloads @@ -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, }; @@ -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; @@ -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; @@ -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 && @@ -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 @@ -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')); } } diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index 7ba6f334e759a..af2b85d543bd6 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -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);