mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[Fizz] Add "Queued" Status to SSR:ed Suspense Boundaries (#33087)
Stacked on #33076. This fixes a bug where we used the "complete" status but the DOMContentLoaded event. This checks for not "loading" instead. We also add a new status where the boundary has been marked as complete by the server but has not yet flushed either due to being throttled, suspended on CSS or animating.
This commit is contained in:
+26
-13
@@ -238,6 +238,7 @@ const ACTIVITY_END_DATA = '/&';
|
||||
const SUSPENSE_START_DATA = '$';
|
||||
const SUSPENSE_END_DATA = '/$';
|
||||
const SUSPENSE_PENDING_START_DATA = '$?';
|
||||
const SUSPENSE_QUEUED_START_DATA = '$~';
|
||||
const SUSPENSE_FALLBACK_START_DATA = '$!';
|
||||
const PREAMBLE_CONTRIBUTION_HTML = 'html';
|
||||
const PREAMBLE_CONTRIBUTION_BODY = 'body';
|
||||
@@ -245,7 +246,7 @@ const PREAMBLE_CONTRIBUTION_HEAD = 'head';
|
||||
const FORM_STATE_IS_MATCHING = 'F!';
|
||||
const FORM_STATE_IS_NOT_MATCHING = 'F';
|
||||
|
||||
const DOCUMENT_READY_STATE_COMPLETE = 'complete';
|
||||
const DOCUMENT_READY_STATE_LOADING = 'loading';
|
||||
|
||||
const STYLE = 'style';
|
||||
|
||||
@@ -1084,6 +1085,7 @@ function clearHydrationBoundary(
|
||||
} else if (
|
||||
data === SUSPENSE_START_DATA ||
|
||||
data === SUSPENSE_PENDING_START_DATA ||
|
||||
data === SUSPENSE_QUEUED_START_DATA ||
|
||||
data === SUSPENSE_FALLBACK_START_DATA ||
|
||||
data === ACTIVITY_START_DATA
|
||||
) {
|
||||
@@ -1205,6 +1207,7 @@ function hideOrUnhideDehydratedBoundary(
|
||||
} else if (
|
||||
data === SUSPENSE_START_DATA ||
|
||||
data === SUSPENSE_PENDING_START_DATA ||
|
||||
data === SUSPENSE_QUEUED_START_DATA ||
|
||||
data === SUSPENSE_FALLBACK_START_DATA
|
||||
) {
|
||||
depth++;
|
||||
@@ -3140,7 +3143,10 @@ export function canHydrateSuspenseInstance(
|
||||
}
|
||||
|
||||
export function isSuspenseInstancePending(instance: SuspenseInstance): boolean {
|
||||
return instance.data === SUSPENSE_PENDING_START_DATA;
|
||||
return (
|
||||
instance.data === SUSPENSE_PENDING_START_DATA ||
|
||||
instance.data === SUSPENSE_QUEUED_START_DATA
|
||||
);
|
||||
}
|
||||
|
||||
export function isSuspenseInstanceFallback(
|
||||
@@ -3149,7 +3155,7 @@ export function isSuspenseInstanceFallback(
|
||||
return (
|
||||
instance.data === SUSPENSE_FALLBACK_START_DATA ||
|
||||
(instance.data === SUSPENSE_PENDING_START_DATA &&
|
||||
instance.ownerDocument.readyState === DOCUMENT_READY_STATE_COMPLETE)
|
||||
instance.ownerDocument.readyState !== DOCUMENT_READY_STATE_LOADING)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3192,7 +3198,11 @@ export function registerSuspenseInstanceRetry(
|
||||
callback: () => void,
|
||||
) {
|
||||
const ownerDocument = instance.ownerDocument;
|
||||
if (
|
||||
if (instance.data === SUSPENSE_QUEUED_START_DATA) {
|
||||
// The Fizz runtime has already queued this boundary for reveal. We wait for it
|
||||
// to be revealed and then retries.
|
||||
instance._reactRetry = callback;
|
||||
} else if (
|
||||
// The Fizz runtime must have put this boundary into client render or complete
|
||||
// state after the render finished but before it committed. We need to call the
|
||||
// callback now rather than wait
|
||||
@@ -3200,7 +3210,7 @@ export function registerSuspenseInstanceRetry(
|
||||
// The boundary is still in pending status but the document has finished loading
|
||||
// before we could register the event handler that would have scheduled the retry
|
||||
// on load so we call teh callback now.
|
||||
ownerDocument.readyState === DOCUMENT_READY_STATE_COMPLETE
|
||||
ownerDocument.readyState !== DOCUMENT_READY_STATE_LOADING
|
||||
) {
|
||||
callback();
|
||||
} else {
|
||||
@@ -3255,18 +3265,19 @@ function getNextHydratable(node: ?Node) {
|
||||
break;
|
||||
}
|
||||
if (nodeType === COMMENT_NODE) {
|
||||
const nodeData = (node: any).data;
|
||||
const data = (node: any).data;
|
||||
if (
|
||||
nodeData === SUSPENSE_START_DATA ||
|
||||
nodeData === SUSPENSE_FALLBACK_START_DATA ||
|
||||
nodeData === SUSPENSE_PENDING_START_DATA ||
|
||||
nodeData === ACTIVITY_START_DATA ||
|
||||
nodeData === FORM_STATE_IS_MATCHING ||
|
||||
nodeData === FORM_STATE_IS_NOT_MATCHING
|
||||
data === SUSPENSE_START_DATA ||
|
||||
data === SUSPENSE_FALLBACK_START_DATA ||
|
||||
data === SUSPENSE_PENDING_START_DATA ||
|
||||
data === SUSPENSE_QUEUED_START_DATA ||
|
||||
data === ACTIVITY_START_DATA ||
|
||||
data === FORM_STATE_IS_MATCHING ||
|
||||
data === FORM_STATE_IS_NOT_MATCHING
|
||||
) {
|
||||
break;
|
||||
}
|
||||
if (nodeData === SUSPENSE_END_DATA || nodeData === ACTIVITY_END_DATA) {
|
||||
if (data === SUSPENSE_END_DATA || data === ACTIVITY_END_DATA) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -3494,6 +3505,7 @@ function getNextHydratableInstanceAfterHydrationBoundary(
|
||||
data === SUSPENSE_START_DATA ||
|
||||
data === SUSPENSE_FALLBACK_START_DATA ||
|
||||
data === SUSPENSE_PENDING_START_DATA ||
|
||||
data === SUSPENSE_QUEUED_START_DATA ||
|
||||
data === ACTIVITY_START_DATA
|
||||
) {
|
||||
depth++;
|
||||
@@ -3535,6 +3547,7 @@ export function getParentHydrationBoundary(
|
||||
data === SUSPENSE_START_DATA ||
|
||||
data === SUSPENSE_FALLBACK_START_DATA ||
|
||||
data === SUSPENSE_PENDING_START_DATA ||
|
||||
data === SUSPENSE_QUEUED_START_DATA ||
|
||||
data === ACTIVITY_START_DATA
|
||||
) {
|
||||
if (depth === 0) {
|
||||
|
||||
+2
-2
@@ -6,9 +6,9 @@ export const markShellTime =
|
||||
export const clientRenderBoundary =
|
||||
'$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())};';
|
||||
export const completeBoundary =
|
||||
'$RB=[];$RC=function(e,c){function m(){$RT=performance.now();var f=$RB;$RB=[];for(var d=0;d<f.length;d+=2){var a=f[d],l=f[d+1],g=a.parentNode;if(g){var h=a.previousSibling,k=0;do{if(a&&8===a.nodeType){var b=a.data;if("/$"===b||"/&"===b)if(0===k)break;else k--;else"$"!==b&&"$?"!==b&&"$!"!==b&&"&"!==b||k++}b=a.nextSibling;g.removeChild(a);a=b}while(a);for(;l.firstChild;)g.insertBefore(l.firstChild,a);h.data="$";h._reactRetry&&h._reactRetry()}}}if(c=document.getElementById(c))if(c.parentNode.removeChild(c),e=\ndocument.getElementById(e))$RB.push(e,c),2===$RB.length&&setTimeout(m,("number"!==typeof $RT?0:$RT)+300-performance.now())};';
|
||||
'$RB=[];$RC=function(d,c){function m(){$RT=performance.now();var f=$RB;$RB=[];for(var e=0;e<f.length;e+=2){var a=f[e],l=f[e+1],g=a.parentNode;if(g){var h=a.previousSibling,k=0;do{if(a&&8===a.nodeType){var b=a.data;if("/$"===b||"/&"===b)if(0===k)break;else k--;else"$"!==b&&"$?"!==b&&"$~"!==b&&"$!"!==b&&"&"!==b||k++}b=a.nextSibling;g.removeChild(a);a=b}while(a);for(;l.firstChild;)g.insertBefore(l.firstChild,a);h.data="$";h._reactRetry&&h._reactRetry()}}}if(c=document.getElementById(c))if(c.parentNode.removeChild(c),d=\ndocument.getElementById(d))d.previousSibling.data="$~",$RB.push(d,c),2===$RB.length&&setTimeout(m,("number"!==typeof $RT?0:$RT)+300-performance.now())};';
|
||||
export const completeBoundaryWithStyles =
|
||||
'$RM=new Map;$RR=function(r,v,w){function t(n){this._p=null;n()}for(var p=new Map,q=document,g,b,h=q.querySelectorAll("link[data-precedence],style[data-precedence]"),u=[],k=0;b=h[k++];)"not all"===b.getAttribute("media")?u.push(b):("LINK"===b.tagName&&$RM.set(b.getAttribute("href"),b),p.set(b.dataset.precedence,g=b));b=0;h=[];var l,a;for(k=!0;;){if(k){var e=w[b++];if(!e){k=!1;b=0;continue}var c=!1,m=0;var d=e[m++];if(a=$RM.get(d)){var f=a._p;c=!0}else{a=q.createElement("link");a.href=d;a.rel=\n"stylesheet";for(a.dataset.precedence=l=e[m++];f=e[m++];)a.setAttribute(f,e[m++]);f=a._p=new Promise(function(n,x){a.onload=t.bind(a,n);a.onerror=t.bind(a,x)});$RM.set(d,a)}d=a.getAttribute("media");!f||d&&!matchMedia(d).matches||h.push(f);if(c)continue}else{a=u[b++];if(!a)break;l=a.getAttribute("data-precedence");a.removeAttribute("media")}c=p.get(l)||g;c===g&&(g=a);p.set(l,a);c?c.parentNode.insertBefore(a,c.nextSibling):(c=q.head,c.insertBefore(a,c.firstChild))}Promise.all(h).then($RC.bind(null,\nr,v),$RX.bind(null,r,"CSS failed to load"))};';
|
||||
'$RM=new Map;$RR=function(n,w,p){function u(q){this._p=null;q()}for(var r=new Map,t=document,h,b,e=t.querySelectorAll("link[data-precedence],style[data-precedence]"),v=[],k=0;b=e[k++];)"not all"===b.getAttribute("media")?v.push(b):("LINK"===b.tagName&&$RM.set(b.getAttribute("href"),b),r.set(b.dataset.precedence,h=b));e=0;b=[];var l,a;for(k=!0;;){if(k){var f=p[e++];if(!f){k=!1;e=0;continue}var c=!1,m=0;var d=f[m++];if(a=$RM.get(d)){var g=a._p;c=!0}else{a=t.createElement("link");a.href=d;a.rel=\n"stylesheet";for(a.dataset.precedence=l=f[m++];g=f[m++];)a.setAttribute(g,f[m++]);g=a._p=new Promise(function(q,x){a.onload=u.bind(a,q);a.onerror=u.bind(a,x)});$RM.set(d,a)}d=a.getAttribute("media");!g||d&&!matchMedia(d).matches||b.push(g);if(c)continue}else{a=v[e++];if(!a)break;l=a.getAttribute("data-precedence");a.removeAttribute("media")}c=r.get(l)||h;c===h&&(h=a);r.set(l,a);c?c.parentNode.insertBefore(a,c.nextSibling):(c=t.head,c.insertBefore(a,c.firstChild))}if(p=document.getElementById(n))p.previousSibling.data=\n"$~";Promise.all(b).then($RC.bind(null,n,w),$RX.bind(null,n,"CSS failed to load"))};';
|
||||
export const completeSegment =
|
||||
'$RS=function(a,b){a=document.getElementById(a);b=document.getElementById(b);for(a.parentNode.removeChild(a);a.firstChild;)b.parentNode.insertBefore(a.firstChild,b);b.parentNode.removeChild(b)};';
|
||||
export const formReplaying =
|
||||
|
||||
Vendored
+14
@@ -9,6 +9,7 @@ const ACTIVITY_END_DATA = '/&';
|
||||
const SUSPENSE_START_DATA = '$';
|
||||
const SUSPENSE_END_DATA = '/$';
|
||||
const SUSPENSE_PENDING_START_DATA = '$?';
|
||||
const SUSPENSE_QUEUED_START_DATA = '$~';
|
||||
const SUSPENSE_FALLBACK_START_DATA = '$!';
|
||||
|
||||
// TODO: Symbols that are referenced outside this module use dynamic accessor
|
||||
@@ -106,6 +107,7 @@ export function completeBoundary(suspenseBoundaryID, contentID) {
|
||||
} else if (
|
||||
data === SUSPENSE_START_DATA ||
|
||||
data === SUSPENSE_PENDING_START_DATA ||
|
||||
data === SUSPENSE_QUEUED_START_DATA ||
|
||||
data === SUSPENSE_FALLBACK_START_DATA ||
|
||||
data === ACTIVITY_START_DATA
|
||||
) {
|
||||
@@ -132,6 +134,10 @@ export function completeBoundary(suspenseBoundaryID, contentID) {
|
||||
}
|
||||
}
|
||||
|
||||
// Mark this Suspense boundary as queued so we know not to client render it
|
||||
// at the end of document load.
|
||||
const suspenseNodeOuter = suspenseIdNodeOuter.previousSibling;
|
||||
suspenseNodeOuter.data = SUSPENSE_QUEUED_START_DATA;
|
||||
// Queue this boundary for the next batch
|
||||
window['$RB'].push(suspenseIdNodeOuter, contentNodeOuter);
|
||||
|
||||
@@ -261,6 +267,14 @@ export function completeBoundaryWithStyles(
|
||||
}
|
||||
}
|
||||
|
||||
const suspenseIdNodeOuter = document.getElementById(suspenseBoundaryID);
|
||||
if (suspenseIdNodeOuter) {
|
||||
// Mark this Suspense boundary as queued so we know not to client render it
|
||||
// at the end of document load.
|
||||
const suspenseNodeOuter = suspenseIdNodeOuter.previousSibling;
|
||||
suspenseNodeOuter.data = SUSPENSE_QUEUED_START_DATA;
|
||||
}
|
||||
|
||||
Promise.all(dependencies).then(
|
||||
window['$RC'].bind(null, suspenseBoundaryID, contentID),
|
||||
window['$RX'].bind(null, suspenseBoundaryID, 'CSS failed to load'),
|
||||
|
||||
Reference in New Issue
Block a user