Skip to content

Commit 83a7b45

Browse files
committed
[Fizz] hoistables should never flush before the preamble (#28802)
Hoistables should never flush before the preamble however there is a surprisingly easy way to trigger this to happen by suspending in the shell of the app. This change modifies the flushing behavior to not emit any hoistables before the preamble has written. It accomplishes this by aborting the flush early if there are any pending root tasks remaining. It's unfortunate we need this extra condition but it's essential that we don't emit anything before the preamble and at the moment I don't see a way to do that without introducing a new condition. There is a test that began to fail with this update. It turns out that in node the root can be blocked during a resume even for a component inside a Suspense boundary if that boundary was part of the prerender. This means that with the current heuristic in this PR boundaries cannot be flushed during resume until the root is unblocked. This is not ideal but this is already how Edge works because the root blocks the stream in that case. This just makes Node deopt in a similar way to edge. We should improve this but we ought to do so in a way that works for edge too and it needs to be more comprehensive. DiffTrain build for [c8a0350](c8a0350)
1 parent 5735300 commit 83a7b45

7 files changed

+452
-431
lines changed

compiled/facebook-www/REVISION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
4f5c812a3c4e52d9ea5d908a27a317ac0f26590a
1+
c8a035036d0f257c514b3628e927dd9dd26e5a09

compiled/facebook-www/ReactDOMServer-dev.classic.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ if (__DEV__) {
1919
var React = require("react");
2020
var ReactDOM = require("react-dom");
2121

22-
var ReactVersion = "19.0.0-www-classic-bad327f1";
22+
var ReactVersion = "19.0.0-www-classic-bf1258d7";
2323

2424
// This refers to a WWW module.
2525
var warningWWW = require("warning");
@@ -14369,22 +14369,24 @@ if (__DEV__) {
1436914369
// until the sink tells us to stop. When we should stop, we still finish writing
1437014370
// that item fully and then yield. At that point we remove the already completed
1437114371
// items up until the point we completed them.
14372+
if (request.pendingRootTasks > 0) {
14373+
// When there are pending root tasks we don't want to flush anything
14374+
return;
14375+
}
14376+
1437214377
var i;
1437314378
var completedRootSegment = request.completedRootSegment;
1437414379

1437514380
if (completedRootSegment !== null) {
1437614381
if (completedRootSegment.status === POSTPONED) {
1437714382
// We postponed the root, so we write nothing.
1437814383
return;
14379-
} else if (request.pendingRootTasks === 0) {
14380-
flushPreamble(request, destination, completedRootSegment);
14381-
flushSegment(request, destination, completedRootSegment, null);
14382-
request.completedRootSegment = null;
14383-
writeCompletedRoot(destination, request.renderState);
14384-
} else {
14385-
// We haven't flushed the root yet so we don't need to check any other branches further down
14386-
return;
1438714384
}
14385+
14386+
flushPreamble(request, destination, completedRootSegment);
14387+
flushSegment(request, destination, completedRootSegment, null);
14388+
request.completedRootSegment = null;
14389+
writeCompletedRoot(destination, request.renderState);
1438814390
}
1438914391

1439014392
writeHoistables(

compiled/facebook-www/ReactDOMServer-dev.modern.js

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ if (__DEV__) {
1919
var React = require("react");
2020
var ReactDOM = require("react-dom");
2121

22-
var ReactVersion = "19.0.0-www-modern-d8e2224b";
22+
var ReactVersion = "19.0.0-www-modern-d9b30156";
2323

2424
// This refers to a WWW module.
2525
var warningWWW = require("warning");
@@ -14274,22 +14274,24 @@ if (__DEV__) {
1427414274
// until the sink tells us to stop. When we should stop, we still finish writing
1427514275
// that item fully and then yield. At that point we remove the already completed
1427614276
// items up until the point we completed them.
14277+
if (request.pendingRootTasks > 0) {
14278+
// When there are pending root tasks we don't want to flush anything
14279+
return;
14280+
}
14281+
1427714282
var i;
1427814283
var completedRootSegment = request.completedRootSegment;
1427914284

1428014285
if (completedRootSegment !== null) {
1428114286
if (completedRootSegment.status === POSTPONED) {
1428214287
// We postponed the root, so we write nothing.
1428314288
return;
14284-
} else if (request.pendingRootTasks === 0) {
14285-
flushPreamble(request, destination, completedRootSegment);
14286-
flushSegment(request, destination, completedRootSegment, null);
14287-
request.completedRootSegment = null;
14288-
writeCompletedRoot(destination, request.renderState);
14289-
} else {
14290-
// We haven't flushed the root yet so we don't need to check any other branches further down
14291-
return;
1429214289
}
14290+
14291+
flushPreamble(request, destination, completedRootSegment);
14292+
flushSegment(request, destination, completedRootSegment, null);
14293+
request.completedRootSegment = null;
14294+
writeCompletedRoot(destination, request.renderState);
1429314295
}
1429414296

1429514297
writeHoistables(

compiled/facebook-www/ReactDOMServer-prod.classic.js

Lines changed: 140 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -5346,10 +5346,11 @@ function flushPartiallyCompletedSegment(
53465346
}
53475347
function flushCompletedQueues(request, destination) {
53485348
try {
5349-
var i,
5350-
completedRootSegment = request.completedRootSegment;
5351-
if (null !== completedRootSegment)
5352-
if (5 !== completedRootSegment.status && 0 === request.pendingRootTasks) {
5349+
if (!(0 < request.pendingRootTasks)) {
5350+
var i,
5351+
completedRootSegment = request.completedRootSegment;
5352+
if (null !== completedRootSegment) {
5353+
if (5 === completedRootSegment.status) return;
53535354
var renderState = request.renderState;
53545355
if (
53555356
(0 !== request.allPendingTasks ||
@@ -5416,141 +5417,145 @@ function flushCompletedQueues(request, destination) {
54165417
flushSegment(request, destination, completedRootSegment, null);
54175418
request.completedRootSegment = null;
54185419
writeBootstrap(destination, request.renderState);
5419-
} else return;
5420-
var renderState$jscomp$0 = request.renderState;
5421-
completedRootSegment = 0;
5422-
var viewportChunks$jscomp$0 = renderState$jscomp$0.viewportChunks;
5423-
for (
5424-
completedRootSegment = 0;
5425-
completedRootSegment < viewportChunks$jscomp$0.length;
5426-
completedRootSegment++
5427-
)
5428-
destination.push(viewportChunks$jscomp$0[completedRootSegment]);
5429-
viewportChunks$jscomp$0.length = 0;
5430-
renderState$jscomp$0.preconnects.forEach(flushResource, destination);
5431-
renderState$jscomp$0.preconnects.clear();
5432-
renderState$jscomp$0.fontPreloads.forEach(flushResource, destination);
5433-
renderState$jscomp$0.fontPreloads.clear();
5434-
renderState$jscomp$0.highImagePreloads.forEach(flushResource, destination);
5435-
renderState$jscomp$0.highImagePreloads.clear();
5436-
renderState$jscomp$0.styles.forEach(preloadLateStyles, destination);
5437-
renderState$jscomp$0.scripts.forEach(flushResource, destination);
5438-
renderState$jscomp$0.scripts.clear();
5439-
renderState$jscomp$0.bulkPreloads.forEach(flushResource, destination);
5440-
renderState$jscomp$0.bulkPreloads.clear();
5441-
var hoistableChunks$jscomp$0 = renderState$jscomp$0.hoistableChunks;
5442-
for (
5443-
completedRootSegment = 0;
5444-
completedRootSegment < hoistableChunks$jscomp$0.length;
5445-
completedRootSegment++
5446-
)
5447-
destination.push(hoistableChunks$jscomp$0[completedRootSegment]);
5448-
hoistableChunks$jscomp$0.length = 0;
5449-
var clientRenderedBoundaries = request.clientRenderedBoundaries;
5450-
for (i = 0; i < clientRenderedBoundaries.length; i++) {
5451-
var boundary = clientRenderedBoundaries[i];
5452-
renderState$jscomp$0 = destination;
5453-
var resumableState$jscomp$0 = request.resumableState,
5454-
renderState$jscomp$1 = request.renderState,
5455-
id = boundary.rootSegmentID,
5456-
errorDigest = boundary.errorDigest,
5457-
scriptFormat = 0 === resumableState$jscomp$0.streamingFormat;
5458-
scriptFormat
5459-
? (renderState$jscomp$0.push(renderState$jscomp$1.startInlineScript),
5460-
0 === (resumableState$jscomp$0.instructions & 4)
5461-
? ((resumableState$jscomp$0.instructions |= 4),
5462-
renderState$jscomp$0.push(
5463-
'$RX=function(b,c,d,e,f){var a=document.getElementById(b);a&&(b=a.previousSibling,b.data="$!",a=a.dataset,c&&(a.dgst=c),d&&(a.msg=d),e&&(a.stck=e),f&&(a.cstck=f),b._reactRetry&&b._reactRetry())};;$RX("'
5464-
))
5465-
: renderState$jscomp$0.push('$RX("'))
5466-
: renderState$jscomp$0.push('<template data-rxi="" data-bid="');
5467-
renderState$jscomp$0.push(renderState$jscomp$1.boundaryPrefix);
5468-
var chunk$jscomp$1 = id.toString(16);
5469-
renderState$jscomp$0.push(chunk$jscomp$1);
5470-
scriptFormat && renderState$jscomp$0.push('"');
5471-
if (errorDigest)
5472-
if (scriptFormat) {
5473-
renderState$jscomp$0.push(",");
5474-
var chunk$jscomp$2 = escapeJSStringsForInstructionScripts(
5475-
errorDigest || ""
5476-
);
5477-
renderState$jscomp$0.push(chunk$jscomp$2);
5478-
} else {
5479-
renderState$jscomp$0.push('" data-dgst="');
5480-
var chunk$jscomp$3 = escapeTextForBrowser(errorDigest || "");
5481-
renderState$jscomp$0.push(chunk$jscomp$3);
5482-
}
5483-
var JSCompiler_inline_result = scriptFormat
5484-
? renderState$jscomp$0.push(")\x3c/script>")
5485-
: renderState$jscomp$0.push('"></template>');
5486-
if (!JSCompiler_inline_result) {
5487-
request.destination = null;
5488-
i++;
5489-
clientRenderedBoundaries.splice(0, i);
5490-
return;
5491-
}
5492-
}
5493-
clientRenderedBoundaries.splice(0, i);
5494-
var completedBoundaries = request.completedBoundaries;
5495-
for (i = 0; i < completedBoundaries.length; i++)
5496-
if (
5497-
!flushCompletedBoundary(request, destination, completedBoundaries[i])
5498-
) {
5499-
request.destination = null;
5500-
i++;
5501-
completedBoundaries.splice(0, i);
5502-
return;
55035420
}
5504-
completedBoundaries.splice(0, i);
5505-
var partialBoundaries = request.partialBoundaries;
5506-
for (i = 0; i < partialBoundaries.length; i++) {
5507-
var boundary$47 = partialBoundaries[i];
5508-
a: {
5509-
clientRenderedBoundaries = request;
5510-
boundary = destination;
5511-
var completedSegments = boundary$47.completedSegments;
5512-
for (
5513-
JSCompiler_inline_result = 0;
5514-
JSCompiler_inline_result < completedSegments.length;
5515-
JSCompiler_inline_result++
5516-
)
5517-
if (
5518-
!flushPartiallyCompletedSegment(
5519-
clientRenderedBoundaries,
5520-
boundary,
5521-
boundary$47,
5522-
completedSegments[JSCompiler_inline_result]
5523-
)
5524-
) {
5525-
JSCompiler_inline_result++;
5526-
completedSegments.splice(0, JSCompiler_inline_result);
5527-
var JSCompiler_inline_result$jscomp$0 = !1;
5528-
break a;
5421+
var renderState$jscomp$0 = request.renderState;
5422+
completedRootSegment = 0;
5423+
var viewportChunks$jscomp$0 = renderState$jscomp$0.viewportChunks;
5424+
for (
5425+
completedRootSegment = 0;
5426+
completedRootSegment < viewportChunks$jscomp$0.length;
5427+
completedRootSegment++
5428+
)
5429+
destination.push(viewportChunks$jscomp$0[completedRootSegment]);
5430+
viewportChunks$jscomp$0.length = 0;
5431+
renderState$jscomp$0.preconnects.forEach(flushResource, destination);
5432+
renderState$jscomp$0.preconnects.clear();
5433+
renderState$jscomp$0.fontPreloads.forEach(flushResource, destination);
5434+
renderState$jscomp$0.fontPreloads.clear();
5435+
renderState$jscomp$0.highImagePreloads.forEach(
5436+
flushResource,
5437+
destination
5438+
);
5439+
renderState$jscomp$0.highImagePreloads.clear();
5440+
renderState$jscomp$0.styles.forEach(preloadLateStyles, destination);
5441+
renderState$jscomp$0.scripts.forEach(flushResource, destination);
5442+
renderState$jscomp$0.scripts.clear();
5443+
renderState$jscomp$0.bulkPreloads.forEach(flushResource, destination);
5444+
renderState$jscomp$0.bulkPreloads.clear();
5445+
var hoistableChunks$jscomp$0 = renderState$jscomp$0.hoistableChunks;
5446+
for (
5447+
completedRootSegment = 0;
5448+
completedRootSegment < hoistableChunks$jscomp$0.length;
5449+
completedRootSegment++
5450+
)
5451+
destination.push(hoistableChunks$jscomp$0[completedRootSegment]);
5452+
hoistableChunks$jscomp$0.length = 0;
5453+
var clientRenderedBoundaries = request.clientRenderedBoundaries;
5454+
for (i = 0; i < clientRenderedBoundaries.length; i++) {
5455+
var boundary = clientRenderedBoundaries[i];
5456+
renderState$jscomp$0 = destination;
5457+
var resumableState$jscomp$0 = request.resumableState,
5458+
renderState$jscomp$1 = request.renderState,
5459+
id = boundary.rootSegmentID,
5460+
errorDigest = boundary.errorDigest,
5461+
scriptFormat = 0 === resumableState$jscomp$0.streamingFormat;
5462+
scriptFormat
5463+
? (renderState$jscomp$0.push(renderState$jscomp$1.startInlineScript),
5464+
0 === (resumableState$jscomp$0.instructions & 4)
5465+
? ((resumableState$jscomp$0.instructions |= 4),
5466+
renderState$jscomp$0.push(
5467+
'$RX=function(b,c,d,e,f){var a=document.getElementById(b);a&&(b=a.previousSibling,b.data="$!",a=a.dataset,c&&(a.dgst=c),d&&(a.msg=d),e&&(a.stck=e),f&&(a.cstck=f),b._reactRetry&&b._reactRetry())};;$RX("'
5468+
))
5469+
: renderState$jscomp$0.push('$RX("'))
5470+
: renderState$jscomp$0.push('<template data-rxi="" data-bid="');
5471+
renderState$jscomp$0.push(renderState$jscomp$1.boundaryPrefix);
5472+
var chunk$jscomp$1 = id.toString(16);
5473+
renderState$jscomp$0.push(chunk$jscomp$1);
5474+
scriptFormat && renderState$jscomp$0.push('"');
5475+
if (errorDigest)
5476+
if (scriptFormat) {
5477+
renderState$jscomp$0.push(",");
5478+
var chunk$jscomp$2 = escapeJSStringsForInstructionScripts(
5479+
errorDigest || ""
5480+
);
5481+
renderState$jscomp$0.push(chunk$jscomp$2);
5482+
} else {
5483+
renderState$jscomp$0.push('" data-dgst="');
5484+
var chunk$jscomp$3 = escapeTextForBrowser(errorDigest || "");
5485+
renderState$jscomp$0.push(chunk$jscomp$3);
55295486
}
5530-
completedSegments.splice(0, JSCompiler_inline_result);
5531-
JSCompiler_inline_result$jscomp$0 = writeHoistablesForBoundary(
5532-
boundary,
5533-
boundary$47.contentState,
5534-
clientRenderedBoundaries.renderState
5535-
);
5487+
var JSCompiler_inline_result = scriptFormat
5488+
? renderState$jscomp$0.push(")\x3c/script>")
5489+
: renderState$jscomp$0.push('"></template>');
5490+
if (!JSCompiler_inline_result) {
5491+
request.destination = null;
5492+
i++;
5493+
clientRenderedBoundaries.splice(0, i);
5494+
return;
5495+
}
55365496
}
5537-
if (!JSCompiler_inline_result$jscomp$0) {
5538-
request.destination = null;
5539-
i++;
5540-
partialBoundaries.splice(0, i);
5541-
return;
5497+
clientRenderedBoundaries.splice(0, i);
5498+
var completedBoundaries = request.completedBoundaries;
5499+
for (i = 0; i < completedBoundaries.length; i++)
5500+
if (
5501+
!flushCompletedBoundary(request, destination, completedBoundaries[i])
5502+
) {
5503+
request.destination = null;
5504+
i++;
5505+
completedBoundaries.splice(0, i);
5506+
return;
5507+
}
5508+
completedBoundaries.splice(0, i);
5509+
var partialBoundaries = request.partialBoundaries;
5510+
for (i = 0; i < partialBoundaries.length; i++) {
5511+
var boundary$47 = partialBoundaries[i];
5512+
a: {
5513+
clientRenderedBoundaries = request;
5514+
boundary = destination;
5515+
var completedSegments = boundary$47.completedSegments;
5516+
for (
5517+
JSCompiler_inline_result = 0;
5518+
JSCompiler_inline_result < completedSegments.length;
5519+
JSCompiler_inline_result++
5520+
)
5521+
if (
5522+
!flushPartiallyCompletedSegment(
5523+
clientRenderedBoundaries,
5524+
boundary,
5525+
boundary$47,
5526+
completedSegments[JSCompiler_inline_result]
5527+
)
5528+
) {
5529+
JSCompiler_inline_result++;
5530+
completedSegments.splice(0, JSCompiler_inline_result);
5531+
var JSCompiler_inline_result$jscomp$0 = !1;
5532+
break a;
5533+
}
5534+
completedSegments.splice(0, JSCompiler_inline_result);
5535+
JSCompiler_inline_result$jscomp$0 = writeHoistablesForBoundary(
5536+
boundary,
5537+
boundary$47.contentState,
5538+
clientRenderedBoundaries.renderState
5539+
);
5540+
}
5541+
if (!JSCompiler_inline_result$jscomp$0) {
5542+
request.destination = null;
5543+
i++;
5544+
partialBoundaries.splice(0, i);
5545+
return;
5546+
}
55425547
}
5548+
partialBoundaries.splice(0, i);
5549+
var largeBoundaries = request.completedBoundaries;
5550+
for (i = 0; i < largeBoundaries.length; i++)
5551+
if (!flushCompletedBoundary(request, destination, largeBoundaries[i])) {
5552+
request.destination = null;
5553+
i++;
5554+
largeBoundaries.splice(0, i);
5555+
return;
5556+
}
5557+
largeBoundaries.splice(0, i);
55435558
}
5544-
partialBoundaries.splice(0, i);
5545-
var largeBoundaries = request.completedBoundaries;
5546-
for (i = 0; i < largeBoundaries.length; i++)
5547-
if (!flushCompletedBoundary(request, destination, largeBoundaries[i])) {
5548-
request.destination = null;
5549-
i++;
5550-
largeBoundaries.splice(0, i);
5551-
return;
5552-
}
5553-
largeBoundaries.splice(0, i);
55545559
} finally {
55555560
0 === request.allPendingTasks &&
55565561
0 === request.pingedTasks.length &&
@@ -5676,4 +5681,4 @@ exports.renderToString = function (children, options) {
56765681
'The server used "renderToString" which does not support Suspense. If you intended for this Suspense boundary to render the fallback content on the server consider throwing an Error somewhere within the Suspense boundary. If you intended to have the server wait for the suspended component please switch to "renderToReadableStream" which supports Suspense on the server'
56775682
);
56785683
};
5679-
exports.version = "19.0.0-www-classic-b2292b3c";
5684+
exports.version = "19.0.0-www-classic-c54d680a";

0 commit comments

Comments
 (0)