From fa09a710fbee96a9cb2d8d4b89856e6a33804f3a Mon Sep 17 00:00:00 2001 From: gnoff Date: Tue, 30 Jan 2024 18:20:40 +0000 Subject: [PATCH] [Fizz] improve Hoistable handling for Elements and Resources inside Suspense Boundaries (#28069) Updates Fizz to handle Hoistables (Resources and Elements) in a way that better aligns with Suspense fallbacks 1. Hoistable Elements inside a fallback (regardless of how deep and how many additional boundaries are intermediate) will be ignored. The reasoning is fallbacks are transient and since there is not good way to clean up hoistables because they escape their Suspense container its better to not emit them in the first place. SSR fallbacks are already not full fidelity because they never hydrate so this aligns with that somewhat. 2. Hoistable stylesheets in fallbacks will only block the reveal of a parent suspense boundary if the fallback is going to flush with that completed parent suspense boundary. Previously if you rendered a stylesheet Resource inside a fallback any parent suspense boundaries that completed after the shell flushed would include that resource in the set required to resolve before the boundary reveal happens on the client. This is not a semantic change, just a performance optimization 3. preconnect and preload hoistable queues are gone, if you want to optimize resource loading you shoudl use `ReactDOM.preconnect` and `ReactDOM.preload`. `viewport` meta tags get their own queue because they need to go before any preloads since they affect the media state. In addition to those functional changes this PR also refactors the boundary resource tracking by moving it to the task rather than using function calls at the start of each render and flush. Tasks also now track whether they are a fallback task supercedes prior work here: https://github.com/facebook/react/pull/27534 DiffTrain build for [554fc49f41465d914b15dc8eb2ec094f37824f7e](https://github.com/facebook/react/commit/554fc49f41465d914b15dc8eb2ec094f37824f7e) --- compiled/facebook-www/REVISION | 2 +- compiled/facebook-www/React-dev.modern.js | 2 +- .../ReactDOMServer-dev.classic.js | 420 ++++++------ .../facebook-www/ReactDOMServer-dev.modern.js | 420 ++++++------ .../ReactDOMServer-prod.classic.js | 640 ++++++++---------- .../ReactDOMServer-prod.modern.js | 632 ++++++++--------- .../ReactDOMServerStreaming-dev.modern.js | 412 ++++++----- .../ReactDOMServerStreaming-prod.modern.js | 639 +++++++++-------- .../facebook-www/ReactServer-prod.modern.js | 2 +- 9 files changed, 1495 insertions(+), 1674 deletions(-) diff --git a/compiled/facebook-www/REVISION b/compiled/facebook-www/REVISION index d1100a6727..abc52c1de2 100644 --- a/compiled/facebook-www/REVISION +++ b/compiled/facebook-www/REVISION @@ -1 +1 @@ -1c958aa4abf9e6b638489b1d73cdb1b6dc7c3ab6 +554fc49f41465d914b15dc8eb2ec094f37824f7e diff --git a/compiled/facebook-www/React-dev.modern.js b/compiled/facebook-www/React-dev.modern.js index 115fe4d5a6..b3640cb69a 100644 --- a/compiled/facebook-www/React-dev.modern.js +++ b/compiled/facebook-www/React-dev.modern.js @@ -24,7 +24,7 @@ if (__DEV__) { ) { __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error()); } - var ReactVersion = "18.3.0-www-modern-2c842b55"; + var ReactVersion = "18.3.0-www-modern-afe290cd"; // ATTENTION // When adding new symbols to this file, diff --git a/compiled/facebook-www/ReactDOMServer-dev.classic.js b/compiled/facebook-www/ReactDOMServer-dev.classic.js index e09b988fa7..13da746fd9 100644 --- a/compiled/facebook-www/ReactDOMServer-dev.classic.js +++ b/compiled/facebook-www/ReactDOMServer-dev.classic.js @@ -19,7 +19,7 @@ if (__DEV__) { var React = require("react"); var ReactDOM = require("react-dom"); - var ReactVersion = "18.3.0-www-classic-12c301cf"; + var ReactVersion = "18.3.0-www-classic-ac038d0e"; // This refers to a WWW module. var warningWWW = require("warning"); @@ -2221,6 +2221,7 @@ if (__DEV__) { headChunks: null, externalRuntimeScript: externalRuntimeScript, bootstrapChunks: bootstrapChunks, + importMapChunks: importMapChunks, onHeaders: onHeaders, headers: headers, resets: { @@ -2235,9 +2236,7 @@ if (__DEV__) { style: {} }, charsetChunks: [], - preconnectChunks: [], - importMapChunks: importMapChunks, - preloadChunks: [], + viewportChunks: [], hoistableChunks: [], // cleared on flush preconnects: new Set(), @@ -2256,7 +2255,7 @@ if (__DEV__) { }, nonce: nonce, // like a module global for currently rendering boundary - boundaryResources: null, + hoistableState: null, stylesToHoist: false }; @@ -3903,7 +3902,8 @@ if (__DEV__) { renderState, textEmbedded, insertionMode, - noscriptTagInScope + noscriptTagInScope, + isFallback ) { { if ( @@ -3919,11 +3919,24 @@ if (__DEV__) { target.push(textSeparator); } - if (typeof props.charSet === "string") { + if (isFallback) { + // Hoistable Elements for fallbacks are simply omitted. we don't want to emit them early + // because they are likely superceded by primary content and we want to avoid needing to clean + // them up when the primary content is ready. They are never hydrated on the client anyway because + // boundaries in fallback are awaited or client render, in either case there is never hydration + return null; + } else if (typeof props.charSet === "string") { + // "charset" Should really be config and not picked up from tags however since this is + // the only way to embed the tag today we flush it on a special queue on the Request so it + // can go before everything else. Like viewport this means that the tag will escape it's + // parent container. return pushSelfClosing(renderState.charsetChunks, props, "meta"); } else if (props.name === "viewport") { - // "viewport" isn't related to preconnect but it has the right priority - return pushSelfClosing(renderState.preconnectChunks, props, "meta"); + // "viewport" is flushed on the Request so it can go earlier that Float resources that + // might be affected by it. This means it can escape the boundary it is rendered within. + // This is a pragmatic solution to viewport being incredibly sensitive to document order + // without requiring all hoistables to be flushed too early. + return pushSelfClosing(renderState.viewportChunks, props, "meta"); } else { return pushSelfClosing(renderState.hoistableChunks, props, "meta"); } @@ -3936,9 +3949,11 @@ if (__DEV__) { props, resumableState, renderState, + hoistableState, textEmbedded, insertionMode, - noscriptTagInScope + noscriptTagInScope, + isFallback ) { { var rel = props.rel; @@ -4058,8 +4073,8 @@ if (__DEV__) { styleQueue.sheets.set(key, resource); - if (renderState.boundaryResources) { - renderState.boundaryResources.stylesheets.add(resource); + if (hoistableState) { + hoistableState.stylesheets.add(resource); } } else { // We need to track whether this boundary should wait on this resource or not. @@ -4071,8 +4086,8 @@ if (__DEV__) { var _resource = styleQueue.sheets.get(key); if (_resource) { - if (renderState.boundaryResources) { - renderState.boundaryResources.stylesheets.add(_resource); + if (hoistableState) { + hoistableState.stylesheets.add(_resource); } } } @@ -4099,16 +4114,14 @@ if (__DEV__) { target.push(textSeparator); } - switch (props.rel) { - case "preconnect": - case "dns-prefetch": - return pushLinkImpl(renderState.preconnectChunks, props); - - case "preload": - return pushLinkImpl(renderState.preloadChunks, props); - - default: - return pushLinkImpl(renderState.hoistableChunks, props); + if (isFallback) { + // Hoistable Elements for fallbacks are simply omitted. we don't want to emit them early + // because they are likely superceded by primary content and we want to avoid needing to clean + // them up when the primary content is ready. They are never hydrated on the client anyway because + // boundaries in fallback are awaited or client render, in either case there is never hydration + return null; + } else { + return pushLinkImpl(renderState.hoistableChunks, props); } } } @@ -4150,6 +4163,7 @@ if (__DEV__) { props, resumableState, renderState, + hoistableState, textEmbedded, insertionMode, noscriptTagInScope @@ -4253,8 +4267,8 @@ if (__DEV__) { // it. However, it's possible when you resume that the style has already been emitted // and then it wouldn't be recreated in the RenderState and there's no need to track // it again since we should've hoisted it to the shell already. - if (renderState.boundaryResources) { - renderState.boundaryResources.styles.add(styleQueue); + if (hoistableState) { + hoistableState.styles.add(styleQueue); } } @@ -4566,7 +4580,8 @@ if (__DEV__) { props, renderState, insertionMode, - noscriptTagInScope + noscriptTagInScope, + isFallback ) { { if (hasOwnProperty.call(props, "children")) { @@ -4622,8 +4637,15 @@ if (__DEV__) { !noscriptTagInScope && props.itemProp == null ) { - pushTitleImpl(renderState.hoistableChunks, props); - return null; + if (isFallback) { + // Hoistable Elements for fallbacks are simply omitted. we don't want to emit them early + // because they are likely superceded by primary content and we want to avoid needing to clean + // them up when the primary content is ready. They are never hydrated on the client anyway because + // boundaries in fallback are awaited or client render, in either case there is never hydration + return null; + } else { + pushTitleImpl(renderState.hoistableChunks, props); + } } else { return pushTitleImpl(target, props); } @@ -5083,8 +5105,10 @@ if (__DEV__) { props, resumableState, renderState, + hoistableState, formatContext, - textEmbedded + textEmbedded, + isFallback ) { { validateProperties$2(type, props); @@ -5159,7 +5183,8 @@ if (__DEV__) { props, renderState, formatContext.insertionMode, - !!(formatContext.tagScope & NOSCRIPT_SCOPE) + !!(formatContext.tagScope & NOSCRIPT_SCOPE), + isFallback ); case "link": @@ -5168,9 +5193,11 @@ if (__DEV__) { props, resumableState, renderState, + hoistableState, textEmbedded, formatContext.insertionMode, - !!(formatContext.tagScope & NOSCRIPT_SCOPE) + !!(formatContext.tagScope & NOSCRIPT_SCOPE), + isFallback ); case "script": @@ -5190,6 +5217,7 @@ if (__DEV__) { props, resumableState, renderState, + hoistableState, textEmbedded, formatContext.insertionMode, !!(formatContext.tagScope & NOSCRIPT_SCOPE) @@ -5202,7 +5230,8 @@ if (__DEV__) { renderState, textEmbedded, formatContext.insertionMode, - !!(formatContext.tagScope & NOSCRIPT_SCOPE) + !!(formatContext.tagScope & NOSCRIPT_SCOPE), + isFallback ); // Newline eating tags @@ -5711,7 +5740,7 @@ if (__DEV__) { resumableState, renderState, id, - boundaryResources + hoistableState ) { var requiresStyleInsertion; @@ -5792,12 +5821,12 @@ if (__DEV__) { // - data writer emits a string literal, which is escaped as html // e.g. ["A", "B"] if (scriptFormat) { - writeChunk(destination, completeBoundaryScript3a); // boundaryResources encodes an array literal + writeChunk(destination, completeBoundaryScript3a); // hoistableState encodes an array literal - writeStyleResourceDependenciesInJS(destination, boundaryResources); + writeStyleResourceDependenciesInJS(destination, hoistableState); } else { writeChunk(destination, completeBoundaryData3a); - writeStyleResourceDependenciesInAttr(destination, boundaryResources); + writeStyleResourceDependenciesInAttr(destination, hoistableState); } } else { if (scriptFormat) { @@ -6067,21 +6096,21 @@ if (__DEV__) { return false; } - function writeResourcesForBoundary( + function writeHoistablesForBoundary( destination, - boundaryResources, + hoistableState, renderState ) { // Reset these on each invocation, they are only safe to read in this function currentlyRenderingBoundaryHasStylesToHoist = false; destinationHasCapacity = true; // Flush style tags for each precedence this boundary depends on - boundaryResources.styles.forEach( - flushStyleTagsLateForBoundary, - destination - ); // Determine if this boundary has stylesheets that need to be awaited upon completion + hoistableState.styles.forEach(flushStyleTagsLateForBoundary, destination); // Determine if this boundary has stylesheets that need to be awaited upon completion - boundaryResources.stylesheets.forEach(hasStylesToHoist); + hoistableState.stylesheets.forEach(hasStylesToHoist); // We don't actually want to flush any hoistables until the boundary is complete so we omit + // any further writing here. This is becuase unlike Resources, Hoistable Elements act more like + // regular elements, each rendered element has a unique representation in the DOM. We don't want + // these elements to appear in the DOM early, before the boundary has actually completed if (currentlyRenderingBoundaryHasStylesToHoist) { renderState.stylesToHoist = true; @@ -6243,13 +6272,13 @@ if (__DEV__) { renderState.preconnects.forEach(flushResource, destination); renderState.preconnects.clear(); - var preconnectChunks = renderState.preconnectChunks; + var viewportChunks = renderState.viewportChunks; - for (i = 0; i < preconnectChunks.length; i++) { - writeChunk(destination, preconnectChunks[i]); + for (i = 0; i < viewportChunks.length; i++) { + writeChunk(destination, viewportChunks[i]); } - preconnectChunks.length = 0; + viewportChunks.length = 0; renderState.fontPreloads.forEach(flushResource, destination); renderState.fontPreloads.clear(); renderState.highImagePreloads.forEach(flushResource, destination); @@ -6267,15 +6296,7 @@ if (__DEV__) { renderState.scripts.forEach(flushResource, destination); renderState.scripts.clear(); renderState.bulkPreloads.forEach(flushResource, destination); - renderState.bulkPreloads.clear(); // Write embedding preloadChunks - - var preloadChunks = renderState.preloadChunks; - - for (i = 0; i < preloadChunks.length; i++) { - writeChunk(destination, preloadChunks[i]); - } - - preloadChunks.length = 0; // Write embedding hoistableChunks + renderState.bulkPreloads.clear(); // Write embedding hoistableChunks var hoistableChunks = renderState.hoistableChunks; @@ -6283,14 +6304,11 @@ if (__DEV__) { writeChunk(destination, hoistableChunks[i]); } - hoistableChunks.length = 0; // Flush closing head if necessary + hoistableChunks.length = 0; if (htmlChunks && headChunks === null) { - // We have an rendered but no rendered. We however inserted - // a up above so we need to emit the now. This is safe because - // if the main content contained the it would also have provided a - // . This means that all the content inside is either or - // invalid HTML + // we have an but we inserted an implicit tag. We need + // to close it since the main content won't have it writeChunk(destination, endChunkForTag("head")); } } // We don't bother reporting backpressure at the moment because we expect to @@ -6303,15 +6321,15 @@ if (__DEV__) { // We omit charsetChunks because we have already sent the shell and if it wasn't // already sent it is too late now. - renderState.preconnects.forEach(flushResource, destination); - renderState.preconnects.clear(); - var preconnectChunks = renderState.preconnectChunks; + var viewportChunks = renderState.viewportChunks; - for (i = 0; i < preconnectChunks.length; i++) { - writeChunk(destination, preconnectChunks[i]); + for (i = 0; i < viewportChunks.length; i++) { + writeChunk(destination, viewportChunks[i]); } - preconnectChunks.length = 0; + viewportChunks.length = 0; + renderState.preconnects.forEach(flushResource, destination); + renderState.preconnects.clear(); renderState.fontPreloads.forEach(flushResource, destination); renderState.fontPreloads.clear(); renderState.highImagePreloads.forEach(flushResource, destination); @@ -6328,15 +6346,7 @@ if (__DEV__) { renderState.scripts.forEach(flushResource, destination); renderState.scripts.clear(); renderState.bulkPreloads.forEach(flushResource, destination); - renderState.bulkPreloads.clear(); // Write embedding preloadChunks - - var preloadChunks = renderState.preloadChunks; - - for (i = 0; i < preloadChunks.length; i++) { - writeChunk(destination, preloadChunks[i]); - } - - preloadChunks.length = 0; // Write embedding hoistableChunks + renderState.bulkPreloads.clear(); // Write embedding hoistableChunks var hoistableChunks = renderState.hoistableChunks; @@ -6362,13 +6372,10 @@ if (__DEV__) { // E.g. // [["JS_escaped_string1", "JS_escaped_string2"]] - function writeStyleResourceDependenciesInJS( - destination, - boundaryResources - ) { + function writeStyleResourceDependenciesInJS(destination, hoistableState) { writeChunk(destination, arrayFirstOpenBracket); var nextArrayOpenBrackChunk = arrayFirstOpenBracket; - boundaryResources.stylesheets.forEach(function (resource) { + hoistableState.stylesheets.forEach(function (resource) { if (resource.state === PREAMBLE); else if (resource.state === LATE) { // We only need to emit the href because this resource flushed in an earlier @@ -6565,13 +6572,10 @@ if (__DEV__) { // E.g. // [["JSON_escaped_string1", "JSON_escaped_string2"]] - function writeStyleResourceDependenciesInAttr( - destination, - boundaryResources - ) { + function writeStyleResourceDependenciesInAttr(destination, hoistableState) { writeChunk(destination, arrayFirstOpenBracket); var nextArrayOpenBrackChunk = arrayFirstOpenBracket; - boundaryResources.stylesheets.forEach(function (resource) { + hoistableState.stylesheets.forEach(function (resource) { if (resource.state === PREAMBLE); else if (resource.state === LATE) { // We only need to emit the href because this resource flushed in an earlier @@ -6776,18 +6780,12 @@ if (__DEV__) { var PRELOADED = 1; var PREAMBLE = 2; var LATE = 3; - function createBoundaryResources() { + function createHoistableState() { return { styles: new Set(), stylesheets: new Set() }; } - function setCurrentlyRenderingBoundaryResourcesTarget( - renderState, - boundaryResources - ) { - renderState.boundaryResources = boundaryResources; - } function getResourceKey(href) { return href; @@ -7677,19 +7675,9 @@ if (__DEV__) { this.stylesheets.add(stylesheet); } - function hoistResources(renderState, source) { - var currentBoundaryResources = renderState.boundaryResources; - - if (currentBoundaryResources) { - source.styles.forEach( - hoistStyleQueueDependency, - currentBoundaryResources - ); - source.stylesheets.forEach( - hoistStylesheetDependency, - currentBoundaryResources - ); - } + function hoistHoistables(parentState, childState) { + childState.styles.forEach(hoistStyleQueueDependency, parentState); + childState.stylesheets.forEach(hoistStylesheetDependency, parentState); } // This function is called at various times depending on whether we are rendering // or prerendering. In this implementation we only actually emit headers once and // subsequent calls are ignored. We track whether the request has a completed shell @@ -7815,13 +7803,12 @@ if (__DEV__) { headChunks: renderState.headChunks, externalRuntimeScript: renderState.externalRuntimeScript, bootstrapChunks: renderState.bootstrapChunks, + importMapChunks: renderState.importMapChunks, onHeaders: renderState.onHeaders, headers: renderState.headers, resets: renderState.resets, charsetChunks: renderState.charsetChunks, - preconnectChunks: renderState.preconnectChunks, - importMapChunks: renderState.importMapChunks, - preloadChunks: renderState.preloadChunks, + viewportChunks: renderState.viewportChunks, hoistableChunks: renderState.hoistableChunks, preconnects: renderState.preconnects, fontPreloads: renderState.fontPreloads, @@ -7832,7 +7819,6 @@ if (__DEV__) { scripts: renderState.scripts, bulkPreloads: renderState.bulkPreloads, preloads: renderState.preloads, - boundaryResources: renderState.boundaryResources, stylesToHoist: renderState.stylesToHoist, // This is an extra field for the legacy renderer generateStaticMarkup: generateStaticMarkup @@ -10835,13 +10821,15 @@ if (__DEV__) { -1, null, rootSegment, + null, abortSet, null, rootFormatContext, emptyContextObject, rootContextSnapshot, emptyTreeContext, - null + null, + false ); pingedTasks.push(rootTask); return request; @@ -10875,7 +10863,8 @@ if (__DEV__) { byteSize: 0, fallbackAbortableTasks: fallbackAbortableTasks, errorDigest: null, - resources: createBoundaryResources(), + contentState: createHoistableState(), + fallbackState: createHoistableState(), trackedContentKeyPath: null, trackedFallbackNode: null }; @@ -10888,13 +10877,15 @@ if (__DEV__) { childIndex, blockedBoundary, blockedSegment, + hoistableState, abortSet, keyPath, formatContext, legacyContext, context, treeContext, - componentStack + componentStack, + isFallback ) { request.allPendingTasks++; @@ -10913,6 +10904,7 @@ if (__DEV__) { }, blockedBoundary: blockedBoundary, blockedSegment: blockedSegment, + hoistableState: hoistableState, abortSet: abortSet, keyPath: keyPath, formatContext: formatContext, @@ -10920,7 +10912,8 @@ if (__DEV__) { context: context, treeContext: treeContext, componentStack: componentStack, - thenableState: thenableState + thenableState: thenableState, + isFallback: isFallback }; abortSet.add(task); return task; @@ -10933,13 +10926,15 @@ if (__DEV__) { node, childIndex, blockedBoundary, + hoistableState, abortSet, keyPath, formatContext, legacyContext, context, treeContext, - componentStack + componentStack, + isFallback ) { request.allPendingTasks++; @@ -10959,6 +10954,7 @@ if (__DEV__) { }, blockedBoundary: blockedBoundary, blockedSegment: null, + hoistableState: hoistableState, abortSet: abortSet, keyPath: keyPath, formatContext: formatContext, @@ -10966,7 +10962,8 @@ if (__DEV__) { context: context, treeContext: treeContext, componentStack: componentStack, - thenableState: thenableState + thenableState: thenableState, + isFallback: isFallback }; abortSet.add(task); return task; @@ -11140,6 +11137,7 @@ if (__DEV__) { createBuiltInComponentStack(task, "Suspense")); var prevKeyPath = task.keyPath; var parentBoundary = task.blockedBoundary; + var parentHoistableState = task.hoistableState; var parentSegment = task.blockedSegment; // Each time we enter a suspense boundary, we split out into a new segment for // the fallback so that we can later replace that segment with the content. // This also lets us split out the main content even if it doesn't suspend, @@ -11187,15 +11185,8 @@ if (__DEV__) { // we're writing to. If something suspends, it'll spawn new suspended task with that context. task.blockedBoundary = newBoundary; + task.hoistableState = newBoundary.contentState; task.blockedSegment = contentRootSegment; - - { - setCurrentlyRenderingBoundaryResourcesTarget( - request.renderState, - newBoundary.resources - ); - } - task.keyPath = keyPath; try { @@ -11234,14 +11225,8 @@ if (__DEV__) { // We don't need to schedule any task because we know the parent has written yet. // We do need to fallthrough to create the fallback though. } finally { - { - setCurrentlyRenderingBoundaryResourcesTarget( - request.renderState, - parentBoundary ? parentBoundary.resources : null - ); - } - task.blockedBoundary = parentBoundary; + task.hoistableState = parentHoistableState; task.blockedSegment = parentSegment; task.keyPath = prevKeyPath; task.componentStack = previousComponentStack; @@ -11279,6 +11264,7 @@ if (__DEV__) { -1, parentBoundary, boundarySegment, + newBoundary.fallbackState, fallbackAbortSet, fallbackKeyPath, task.formatContext, @@ -11286,7 +11272,8 @@ if (__DEV__) { task.context, task.treeContext, // This stack should be the Suspense boundary stack because while the fallback is actually a child segment // of the parent boundary from a component standpoint the fallback is a child of the Suspense boundary itself - suspenseComponentStack + suspenseComponentStack, + true ); // TODO: This should be queued at a separate lower priority queue so that we only work // on preparing fallbacks if we don't have any more main content to task on. @@ -11312,6 +11299,7 @@ if (__DEV__) { var prevKeyPath = task.keyPath; var previousReplaySet = task.replay; var parentBoundary = task.blockedBoundary; + var parentHoistableState = task.hoistableState; var content = props.children; var fallback = props.fallback; var fallbackAbortSet = new Set(); @@ -11323,19 +11311,13 @@ if (__DEV__) { // we're writing to. If something suspends, it'll spawn new suspended task with that context. task.blockedBoundary = resumedBoundary; + task.hoistableState = resumedBoundary.contentState; task.replay = { nodes: childNodes, slots: childSlots, pendingTasks: 1 }; - { - setCurrentlyRenderingBoundaryResourcesTarget( - request.renderState, - resumedBoundary.resources - ); - } - try { // We use the safe form because we don't handle suspending here. Only error handling. renderNode(request, task, content, -1); @@ -11379,14 +11361,8 @@ if (__DEV__) { // We don't need to schedule any task because we know the parent has written yet. // We do need to fallthrough to create the fallback though. } finally { - { - setCurrentlyRenderingBoundaryResourcesTarget( - request.renderState, - parentBoundary ? parentBoundary.resources : null - ); - } - task.blockedBoundary = parentBoundary; + task.hoistableState = parentHoistableState; task.replay = previousReplaySet; task.keyPath = prevKeyPath; task.componentStack = previousComponentStack; @@ -11407,6 +11383,7 @@ if (__DEV__) { fallback, -1, parentBoundary, + resumedBoundary.fallbackState, fallbackAbortSet, fallbackKeyPath, task.formatContext, @@ -11414,7 +11391,8 @@ if (__DEV__) { task.context, task.treeContext, // This stack should be the Suspense boundary stack because while the fallback is actually a child segment // of the parent boundary from a component standpoint the fallback is a child of the Suspense boundary itself - suspenseComponentStack + suspenseComponentStack, + true ); // TODO: This should be queued at a separate lower priority queue so that we only work // on preparing fallbacks if we don't have any more main content to task on. @@ -11449,8 +11427,10 @@ if (__DEV__) { props, request.resumableState, request.renderState, + task.hoistableState, task.formatContext, - segment.lastPushedText + segment.lastPushedText, + task.isFallback ); segment.lastPushedText = false; @@ -12657,6 +12637,7 @@ if (__DEV__) { task.node, task.childIndex, task.blockedBoundary, + task.hoistableState, task.abortSet, task.keyPath, task.formatContext, @@ -12664,7 +12645,8 @@ if (__DEV__) { task.context, task.treeContext, // We pop one task off the stack because the node that suspended will be tried again, // which will add it back onto the stack. - task.componentStack !== null ? task.componentStack.parent : null + task.componentStack !== null ? task.componentStack.parent : null, + task.isFallback ); var ping = newTask.ping; x.then(ping, ping); @@ -12692,6 +12674,7 @@ if (__DEV__) { task.childIndex, task.blockedBoundary, newSegment, + task.hoistableState, task.abortSet, task.keyPath, task.formatContext, @@ -12699,7 +12682,8 @@ if (__DEV__) { task.context, task.treeContext, // We pop one task off the stack because the node that suspended will be tried again, // which will add it back onto the stack. - task.componentStack !== null ? task.componentStack.parent : null + task.componentStack !== null ? task.componentStack.parent : null, + task.isFallback ); var ping = newTask.ping; x.then(ping, ping); @@ -13254,14 +13238,6 @@ if (__DEV__) { } function retryTask(request, task) { - { - var blockedBoundary = task.blockedBoundary; - setCurrentlyRenderingBoundaryResourcesTarget( - request.renderState, - blockedBoundary ? blockedBoundary.resources : null - ); - } - var segment = task.blockedSegment; if (segment === null) { @@ -13341,13 +13317,6 @@ if (__DEV__) { erroredTask(request, task.blockedBoundary, x, errorInfo); return; } finally { - { - setCurrentlyRenderingBoundaryResourcesTarget( - request.renderState, - null - ); - } - { currentTaskInDEV = prevTaskInDEV; } @@ -13432,13 +13401,6 @@ if (__DEV__) { return; } finally { - { - setCurrentlyRenderingBoundaryResourcesTarget( - request.renderState, - null - ); - } - { currentTaskInDEV = prevTaskInDEV; } @@ -13517,7 +13479,18 @@ if (__DEV__) { } } - function flushSubtree(request, destination, segment) { + function flushPreamble(request, destination, rootSegment) { + var willFlushAllSegments = + request.allPendingTasks === 0 && request.trackedPostpones === null; + writePreamble( + destination, + request.resumableState, + request.renderState, + willFlushAllSegments + ); + } + + function flushSubtree(request, destination, segment, hoistableState) { segment.parentFlushed = true; switch (segment.status) { @@ -13549,7 +13522,7 @@ if (__DEV__) { writeChunk(destination, chunks[chunkIdx]); } - r = flushSegment(request, destination, nextChild); + r = flushSegment(request, destination, nextChild, hoistableState); } // Finally just write all the remaining chunks for (; chunkIdx < chunks.length - 1; chunkIdx++) { @@ -13571,12 +13544,12 @@ if (__DEV__) { } } - function flushSegment(request, destination, segment) { + function flushSegment(request, destination, segment, hoistableState) { var boundary = segment.boundary; if (boundary === null) { // Not a suspense boundary. - return flushSubtree(request, destination, segment); + return flushSubtree(request, destination, segment, hoistableState); } boundary.parentFlushed = true; // This segment is a Suspense boundary. We need to decide whether to @@ -13593,7 +13566,7 @@ if (__DEV__) { boundary.errorComponentStack ); // Flush the fallback. - flushSubtree(request, destination, segment); + flushSubtree(request, destination, segment, hoistableState); return writeEndClientRenderedSuspenseBoundary( destination, request.renderState @@ -13611,9 +13584,16 @@ if (__DEV__) { } // This boundary is still loading. Emit a pending suspense boundary wrapper. var id = boundary.rootSegmentID; - writeStartPendingSuspenseBoundary(destination, request.renderState, id); // Flush the fallback. + writeStartPendingSuspenseBoundary(destination, request.renderState, id); // We are going to flush the fallback so we need to hoist the fallback + // state to the parent boundary - flushSubtree(request, destination, segment); + { + if (hoistableState) { + hoistHoistables(hoistableState, boundary.fallbackState); + } + } // Flush the fallback. + + flushSubtree(request, destination, segment, hoistableState); return writeEndPendingSuspenseBoundary(destination); } else if (boundary.byteSize > request.progressiveChunkSize) { // This boundary is large and will be emitted separately so that we can progressively show @@ -13629,13 +13609,19 @@ if (__DEV__) { destination, request.renderState, boundary.rootSegmentID - ); // Flush the fallback. + ); // While we are going to flush the fallback we are going to follow it up with + // the completed boundary immediately so we make the choice to omit fallback + // boundary state from the parent since it will be replaced when the boundary + // flushes later in this pass or in a future flush + // Flush the fallback. - flushSubtree(request, destination, segment); + flushSubtree(request, destination, segment, hoistableState); return writeEndPendingSuspenseBoundary(destination); } else { { - hoistResources(request.renderState, boundary.resources); + if (hoistableState) { + hoistHoistables(hoistableState, boundary.contentState); + } } // We can inline this boundary's content as a complete boundary. writeStartCompletedSuspenseBoundary(destination, request.renderState); @@ -13648,7 +13634,7 @@ if (__DEV__) { } var contentSegment = completedSegments[0]; - flushSegment(request, destination, contentSegment); + flushSegment(request, destination, contentSegment, hoistableState); return writeEndCompletedSuspenseBoundary( destination, request.renderState @@ -13668,25 +13654,23 @@ if (__DEV__) { ); } - function flushSegmentContainer(request, destination, segment) { + function flushSegmentContainer( + request, + destination, + segment, + hoistableState + ) { writeStartSegment( destination, request.renderState, segment.parentFormatContext, segment.id ); - flushSegment(request, destination, segment); + flushSegment(request, destination, segment, hoistableState); return writeEndSegment(destination, segment.parentFormatContext); } function flushCompletedBoundary(request, destination, boundary) { - { - setCurrentlyRenderingBoundaryResourcesTarget( - request.renderState, - boundary.resources - ); - } - var completedSegments = boundary.completedSegments; var i = 0; @@ -13698,9 +13682,9 @@ if (__DEV__) { completedSegments.length = 0; { - writeResourcesForBoundary( + writeHoistablesForBoundary( destination, - boundary.resources, + boundary.contentState, request.renderState ); } @@ -13710,18 +13694,11 @@ if (__DEV__) { request.resumableState, request.renderState, boundary.rootSegmentID, - boundary.resources + boundary.contentState ); } function flushPartialBoundary(request, destination, boundary) { - { - setCurrentlyRenderingBoundaryResourcesTarget( - request.renderState, - boundary.resources - ); - } - var completedSegments = boundary.completedSegments; var i = 0; @@ -13747,13 +13724,9 @@ if (__DEV__) { completedSegments.splice(0, i); { - // The way this is structured we only write resources for partial boundaries - // if there is no backpressure. Later before we complete the boundary we - // will write resources regardless of backpressure before we emit the - // completion instruction - return writeResourcesForBoundary( + return writeHoistablesForBoundary( destination, - boundary.resources, + boundary.contentState, request.renderState ); } @@ -13770,6 +13743,7 @@ if (__DEV__) { return true; } + var hoistableState = boundary.contentState; var segmentID = segment.id; if (segmentID === -1) { @@ -13783,13 +13757,23 @@ if (__DEV__) { ); } - return flushSegmentContainer(request, destination, segment); + return flushSegmentContainer( + request, + destination, + segment, + hoistableState + ); } else if (segmentID === boundary.rootSegmentID) { // When we emit postponed boundaries, we might have assigned the ID already // but it's still the root segment so we can't inject it into the parent yet. - return flushSegmentContainer(request, destination, segment); + return flushSegmentContainer( + request, + destination, + segment, + hoistableState + ); } else { - flushSegmentContainer(request, destination, segment); + flushSegmentContainer(request, destination, segment, hoistableState); return writeCompletedSegmentInstruction( destination, request.resumableState, @@ -13814,16 +13798,10 @@ if (__DEV__) { return; } else if (request.pendingRootTasks === 0) { if (enableFloat) { - writePreamble( - destination, - request.resumableState, - request.renderState, - request.allPendingTasks === 0 && - request.trackedPostpones === null - ); + flushPreamble(request, destination, completedRootSegment); } - flushSegment(request, destination, completedRootSegment); + flushSegment(request, destination, completedRootSegment, null); request.completedRootSegment = null; writeCompletedRoot(destination, request.renderState); } else { diff --git a/compiled/facebook-www/ReactDOMServer-dev.modern.js b/compiled/facebook-www/ReactDOMServer-dev.modern.js index fac1cf5665..23bbb2a263 100644 --- a/compiled/facebook-www/ReactDOMServer-dev.modern.js +++ b/compiled/facebook-www/ReactDOMServer-dev.modern.js @@ -19,7 +19,7 @@ if (__DEV__) { var React = require("react"); var ReactDOM = require("react-dom"); - var ReactVersion = "18.3.0-www-modern-4a47c827"; + var ReactVersion = "18.3.0-www-modern-c10a0d4f"; // This refers to a WWW module. var warningWWW = require("warning"); @@ -2221,6 +2221,7 @@ if (__DEV__) { headChunks: null, externalRuntimeScript: externalRuntimeScript, bootstrapChunks: bootstrapChunks, + importMapChunks: importMapChunks, onHeaders: onHeaders, headers: headers, resets: { @@ -2235,9 +2236,7 @@ if (__DEV__) { style: {} }, charsetChunks: [], - preconnectChunks: [], - importMapChunks: importMapChunks, - preloadChunks: [], + viewportChunks: [], hoistableChunks: [], // cleared on flush preconnects: new Set(), @@ -2256,7 +2255,7 @@ if (__DEV__) { }, nonce: nonce, // like a module global for currently rendering boundary - boundaryResources: null, + hoistableState: null, stylesToHoist: false }; @@ -3903,7 +3902,8 @@ if (__DEV__) { renderState, textEmbedded, insertionMode, - noscriptTagInScope + noscriptTagInScope, + isFallback ) { { if ( @@ -3919,11 +3919,24 @@ if (__DEV__) { target.push(textSeparator); } - if (typeof props.charSet === "string") { + if (isFallback) { + // Hoistable Elements for fallbacks are simply omitted. we don't want to emit them early + // because they are likely superceded by primary content and we want to avoid needing to clean + // them up when the primary content is ready. They are never hydrated on the client anyway because + // boundaries in fallback are awaited or client render, in either case there is never hydration + return null; + } else if (typeof props.charSet === "string") { + // "charset" Should really be config and not picked up from tags however since this is + // the only way to embed the tag today we flush it on a special queue on the Request so it + // can go before everything else. Like viewport this means that the tag will escape it's + // parent container. return pushSelfClosing(renderState.charsetChunks, props, "meta"); } else if (props.name === "viewport") { - // "viewport" isn't related to preconnect but it has the right priority - return pushSelfClosing(renderState.preconnectChunks, props, "meta"); + // "viewport" is flushed on the Request so it can go earlier that Float resources that + // might be affected by it. This means it can escape the boundary it is rendered within. + // This is a pragmatic solution to viewport being incredibly sensitive to document order + // without requiring all hoistables to be flushed too early. + return pushSelfClosing(renderState.viewportChunks, props, "meta"); } else { return pushSelfClosing(renderState.hoistableChunks, props, "meta"); } @@ -3936,9 +3949,11 @@ if (__DEV__) { props, resumableState, renderState, + hoistableState, textEmbedded, insertionMode, - noscriptTagInScope + noscriptTagInScope, + isFallback ) { { var rel = props.rel; @@ -4058,8 +4073,8 @@ if (__DEV__) { styleQueue.sheets.set(key, resource); - if (renderState.boundaryResources) { - renderState.boundaryResources.stylesheets.add(resource); + if (hoistableState) { + hoistableState.stylesheets.add(resource); } } else { // We need to track whether this boundary should wait on this resource or not. @@ -4071,8 +4086,8 @@ if (__DEV__) { var _resource = styleQueue.sheets.get(key); if (_resource) { - if (renderState.boundaryResources) { - renderState.boundaryResources.stylesheets.add(_resource); + if (hoistableState) { + hoistableState.stylesheets.add(_resource); } } } @@ -4099,16 +4114,14 @@ if (__DEV__) { target.push(textSeparator); } - switch (props.rel) { - case "preconnect": - case "dns-prefetch": - return pushLinkImpl(renderState.preconnectChunks, props); - - case "preload": - return pushLinkImpl(renderState.preloadChunks, props); - - default: - return pushLinkImpl(renderState.hoistableChunks, props); + if (isFallback) { + // Hoistable Elements for fallbacks are simply omitted. we don't want to emit them early + // because they are likely superceded by primary content and we want to avoid needing to clean + // them up when the primary content is ready. They are never hydrated on the client anyway because + // boundaries in fallback are awaited or client render, in either case there is never hydration + return null; + } else { + return pushLinkImpl(renderState.hoistableChunks, props); } } } @@ -4150,6 +4163,7 @@ if (__DEV__) { props, resumableState, renderState, + hoistableState, textEmbedded, insertionMode, noscriptTagInScope @@ -4253,8 +4267,8 @@ if (__DEV__) { // it. However, it's possible when you resume that the style has already been emitted // and then it wouldn't be recreated in the RenderState and there's no need to track // it again since we should've hoisted it to the shell already. - if (renderState.boundaryResources) { - renderState.boundaryResources.styles.add(styleQueue); + if (hoistableState) { + hoistableState.styles.add(styleQueue); } } @@ -4566,7 +4580,8 @@ if (__DEV__) { props, renderState, insertionMode, - noscriptTagInScope + noscriptTagInScope, + isFallback ) { { if (hasOwnProperty.call(props, "children")) { @@ -4622,8 +4637,15 @@ if (__DEV__) { !noscriptTagInScope && props.itemProp == null ) { - pushTitleImpl(renderState.hoistableChunks, props); - return null; + if (isFallback) { + // Hoistable Elements for fallbacks are simply omitted. we don't want to emit them early + // because they are likely superceded by primary content and we want to avoid needing to clean + // them up when the primary content is ready. They are never hydrated on the client anyway because + // boundaries in fallback are awaited or client render, in either case there is never hydration + return null; + } else { + pushTitleImpl(renderState.hoistableChunks, props); + } } else { return pushTitleImpl(target, props); } @@ -5083,8 +5105,10 @@ if (__DEV__) { props, resumableState, renderState, + hoistableState, formatContext, - textEmbedded + textEmbedded, + isFallback ) { { validateProperties$2(type, props); @@ -5159,7 +5183,8 @@ if (__DEV__) { props, renderState, formatContext.insertionMode, - !!(formatContext.tagScope & NOSCRIPT_SCOPE) + !!(formatContext.tagScope & NOSCRIPT_SCOPE), + isFallback ); case "link": @@ -5168,9 +5193,11 @@ if (__DEV__) { props, resumableState, renderState, + hoistableState, textEmbedded, formatContext.insertionMode, - !!(formatContext.tagScope & NOSCRIPT_SCOPE) + !!(formatContext.tagScope & NOSCRIPT_SCOPE), + isFallback ); case "script": @@ -5190,6 +5217,7 @@ if (__DEV__) { props, resumableState, renderState, + hoistableState, textEmbedded, formatContext.insertionMode, !!(formatContext.tagScope & NOSCRIPT_SCOPE) @@ -5202,7 +5230,8 @@ if (__DEV__) { renderState, textEmbedded, formatContext.insertionMode, - !!(formatContext.tagScope & NOSCRIPT_SCOPE) + !!(formatContext.tagScope & NOSCRIPT_SCOPE), + isFallback ); // Newline eating tags @@ -5711,7 +5740,7 @@ if (__DEV__) { resumableState, renderState, id, - boundaryResources + hoistableState ) { var requiresStyleInsertion; @@ -5792,12 +5821,12 @@ if (__DEV__) { // - data writer emits a string literal, which is escaped as html // e.g. ["A", "B"] if (scriptFormat) { - writeChunk(destination, completeBoundaryScript3a); // boundaryResources encodes an array literal + writeChunk(destination, completeBoundaryScript3a); // hoistableState encodes an array literal - writeStyleResourceDependenciesInJS(destination, boundaryResources); + writeStyleResourceDependenciesInJS(destination, hoistableState); } else { writeChunk(destination, completeBoundaryData3a); - writeStyleResourceDependenciesInAttr(destination, boundaryResources); + writeStyleResourceDependenciesInAttr(destination, hoistableState); } } else { if (scriptFormat) { @@ -6067,21 +6096,21 @@ if (__DEV__) { return false; } - function writeResourcesForBoundary( + function writeHoistablesForBoundary( destination, - boundaryResources, + hoistableState, renderState ) { // Reset these on each invocation, they are only safe to read in this function currentlyRenderingBoundaryHasStylesToHoist = false; destinationHasCapacity = true; // Flush style tags for each precedence this boundary depends on - boundaryResources.styles.forEach( - flushStyleTagsLateForBoundary, - destination - ); // Determine if this boundary has stylesheets that need to be awaited upon completion + hoistableState.styles.forEach(flushStyleTagsLateForBoundary, destination); // Determine if this boundary has stylesheets that need to be awaited upon completion - boundaryResources.stylesheets.forEach(hasStylesToHoist); + hoistableState.stylesheets.forEach(hasStylesToHoist); // We don't actually want to flush any hoistables until the boundary is complete so we omit + // any further writing here. This is becuase unlike Resources, Hoistable Elements act more like + // regular elements, each rendered element has a unique representation in the DOM. We don't want + // these elements to appear in the DOM early, before the boundary has actually completed if (currentlyRenderingBoundaryHasStylesToHoist) { renderState.stylesToHoist = true; @@ -6243,13 +6272,13 @@ if (__DEV__) { renderState.preconnects.forEach(flushResource, destination); renderState.preconnects.clear(); - var preconnectChunks = renderState.preconnectChunks; + var viewportChunks = renderState.viewportChunks; - for (i = 0; i < preconnectChunks.length; i++) { - writeChunk(destination, preconnectChunks[i]); + for (i = 0; i < viewportChunks.length; i++) { + writeChunk(destination, viewportChunks[i]); } - preconnectChunks.length = 0; + viewportChunks.length = 0; renderState.fontPreloads.forEach(flushResource, destination); renderState.fontPreloads.clear(); renderState.highImagePreloads.forEach(flushResource, destination); @@ -6267,15 +6296,7 @@ if (__DEV__) { renderState.scripts.forEach(flushResource, destination); renderState.scripts.clear(); renderState.bulkPreloads.forEach(flushResource, destination); - renderState.bulkPreloads.clear(); // Write embedding preloadChunks - - var preloadChunks = renderState.preloadChunks; - - for (i = 0; i < preloadChunks.length; i++) { - writeChunk(destination, preloadChunks[i]); - } - - preloadChunks.length = 0; // Write embedding hoistableChunks + renderState.bulkPreloads.clear(); // Write embedding hoistableChunks var hoistableChunks = renderState.hoistableChunks; @@ -6283,14 +6304,11 @@ if (__DEV__) { writeChunk(destination, hoistableChunks[i]); } - hoistableChunks.length = 0; // Flush closing head if necessary + hoistableChunks.length = 0; if (htmlChunks && headChunks === null) { - // We have an rendered but no rendered. We however inserted - // a up above so we need to emit the now. This is safe because - // if the main content contained the it would also have provided a - // . This means that all the content inside is either or - // invalid HTML + // we have an but we inserted an implicit tag. We need + // to close it since the main content won't have it writeChunk(destination, endChunkForTag("head")); } } // We don't bother reporting backpressure at the moment because we expect to @@ -6303,15 +6321,15 @@ if (__DEV__) { // We omit charsetChunks because we have already sent the shell and if it wasn't // already sent it is too late now. - renderState.preconnects.forEach(flushResource, destination); - renderState.preconnects.clear(); - var preconnectChunks = renderState.preconnectChunks; + var viewportChunks = renderState.viewportChunks; - for (i = 0; i < preconnectChunks.length; i++) { - writeChunk(destination, preconnectChunks[i]); + for (i = 0; i < viewportChunks.length; i++) { + writeChunk(destination, viewportChunks[i]); } - preconnectChunks.length = 0; + viewportChunks.length = 0; + renderState.preconnects.forEach(flushResource, destination); + renderState.preconnects.clear(); renderState.fontPreloads.forEach(flushResource, destination); renderState.fontPreloads.clear(); renderState.highImagePreloads.forEach(flushResource, destination); @@ -6328,15 +6346,7 @@ if (__DEV__) { renderState.scripts.forEach(flushResource, destination); renderState.scripts.clear(); renderState.bulkPreloads.forEach(flushResource, destination); - renderState.bulkPreloads.clear(); // Write embedding preloadChunks - - var preloadChunks = renderState.preloadChunks; - - for (i = 0; i < preloadChunks.length; i++) { - writeChunk(destination, preloadChunks[i]); - } - - preloadChunks.length = 0; // Write embedding hoistableChunks + renderState.bulkPreloads.clear(); // Write embedding hoistableChunks var hoistableChunks = renderState.hoistableChunks; @@ -6362,13 +6372,10 @@ if (__DEV__) { // E.g. // [["JS_escaped_string1", "JS_escaped_string2"]] - function writeStyleResourceDependenciesInJS( - destination, - boundaryResources - ) { + function writeStyleResourceDependenciesInJS(destination, hoistableState) { writeChunk(destination, arrayFirstOpenBracket); var nextArrayOpenBrackChunk = arrayFirstOpenBracket; - boundaryResources.stylesheets.forEach(function (resource) { + hoistableState.stylesheets.forEach(function (resource) { if (resource.state === PREAMBLE); else if (resource.state === LATE) { // We only need to emit the href because this resource flushed in an earlier @@ -6565,13 +6572,10 @@ if (__DEV__) { // E.g. // [["JSON_escaped_string1", "JSON_escaped_string2"]] - function writeStyleResourceDependenciesInAttr( - destination, - boundaryResources - ) { + function writeStyleResourceDependenciesInAttr(destination, hoistableState) { writeChunk(destination, arrayFirstOpenBracket); var nextArrayOpenBrackChunk = arrayFirstOpenBracket; - boundaryResources.stylesheets.forEach(function (resource) { + hoistableState.stylesheets.forEach(function (resource) { if (resource.state === PREAMBLE); else if (resource.state === LATE) { // We only need to emit the href because this resource flushed in an earlier @@ -6776,18 +6780,12 @@ if (__DEV__) { var PRELOADED = 1; var PREAMBLE = 2; var LATE = 3; - function createBoundaryResources() { + function createHoistableState() { return { styles: new Set(), stylesheets: new Set() }; } - function setCurrentlyRenderingBoundaryResourcesTarget( - renderState, - boundaryResources - ) { - renderState.boundaryResources = boundaryResources; - } function getResourceKey(href) { return href; @@ -7677,19 +7675,9 @@ if (__DEV__) { this.stylesheets.add(stylesheet); } - function hoistResources(renderState, source) { - var currentBoundaryResources = renderState.boundaryResources; - - if (currentBoundaryResources) { - source.styles.forEach( - hoistStyleQueueDependency, - currentBoundaryResources - ); - source.stylesheets.forEach( - hoistStylesheetDependency, - currentBoundaryResources - ); - } + function hoistHoistables(parentState, childState) { + childState.styles.forEach(hoistStyleQueueDependency, parentState); + childState.stylesheets.forEach(hoistStylesheetDependency, parentState); } // This function is called at various times depending on whether we are rendering // or prerendering. In this implementation we only actually emit headers once and // subsequent calls are ignored. We track whether the request has a completed shell @@ -7815,13 +7803,12 @@ if (__DEV__) { headChunks: renderState.headChunks, externalRuntimeScript: renderState.externalRuntimeScript, bootstrapChunks: renderState.bootstrapChunks, + importMapChunks: renderState.importMapChunks, onHeaders: renderState.onHeaders, headers: renderState.headers, resets: renderState.resets, charsetChunks: renderState.charsetChunks, - preconnectChunks: renderState.preconnectChunks, - importMapChunks: renderState.importMapChunks, - preloadChunks: renderState.preloadChunks, + viewportChunks: renderState.viewportChunks, hoistableChunks: renderState.hoistableChunks, preconnects: renderState.preconnects, fontPreloads: renderState.fontPreloads, @@ -7832,7 +7819,6 @@ if (__DEV__) { scripts: renderState.scripts, bulkPreloads: renderState.bulkPreloads, preloads: renderState.preloads, - boundaryResources: renderState.boundaryResources, stylesToHoist: renderState.stylesToHoist, // This is an extra field for the legacy renderer generateStaticMarkup: generateStaticMarkup @@ -10574,13 +10560,15 @@ if (__DEV__) { -1, null, rootSegment, + null, abortSet, null, rootFormatContext, emptyContextObject, rootContextSnapshot, emptyTreeContext, - null + null, + false ); pingedTasks.push(rootTask); return request; @@ -10614,7 +10602,8 @@ if (__DEV__) { byteSize: 0, fallbackAbortableTasks: fallbackAbortableTasks, errorDigest: null, - resources: createBoundaryResources(), + contentState: createHoistableState(), + fallbackState: createHoistableState(), trackedContentKeyPath: null, trackedFallbackNode: null }; @@ -10627,13 +10616,15 @@ if (__DEV__) { childIndex, blockedBoundary, blockedSegment, + hoistableState, abortSet, keyPath, formatContext, legacyContext, context, treeContext, - componentStack + componentStack, + isFallback ) { request.allPendingTasks++; @@ -10652,6 +10643,7 @@ if (__DEV__) { }, blockedBoundary: blockedBoundary, blockedSegment: blockedSegment, + hoistableState: hoistableState, abortSet: abortSet, keyPath: keyPath, formatContext: formatContext, @@ -10659,7 +10651,8 @@ if (__DEV__) { context: context, treeContext: treeContext, componentStack: componentStack, - thenableState: thenableState + thenableState: thenableState, + isFallback: isFallback }; abortSet.add(task); return task; @@ -10672,13 +10665,15 @@ if (__DEV__) { node, childIndex, blockedBoundary, + hoistableState, abortSet, keyPath, formatContext, legacyContext, context, treeContext, - componentStack + componentStack, + isFallback ) { request.allPendingTasks++; @@ -10698,6 +10693,7 @@ if (__DEV__) { }, blockedBoundary: blockedBoundary, blockedSegment: null, + hoistableState: hoistableState, abortSet: abortSet, keyPath: keyPath, formatContext: formatContext, @@ -10705,7 +10701,8 @@ if (__DEV__) { context: context, treeContext: treeContext, componentStack: componentStack, - thenableState: thenableState + thenableState: thenableState, + isFallback: isFallback }; abortSet.add(task); return task; @@ -10879,6 +10876,7 @@ if (__DEV__) { createBuiltInComponentStack(task, "Suspense")); var prevKeyPath = task.keyPath; var parentBoundary = task.blockedBoundary; + var parentHoistableState = task.hoistableState; var parentSegment = task.blockedSegment; // Each time we enter a suspense boundary, we split out into a new segment for // the fallback so that we can later replace that segment with the content. // This also lets us split out the main content even if it doesn't suspend, @@ -10926,15 +10924,8 @@ if (__DEV__) { // we're writing to. If something suspends, it'll spawn new suspended task with that context. task.blockedBoundary = newBoundary; + task.hoistableState = newBoundary.contentState; task.blockedSegment = contentRootSegment; - - { - setCurrentlyRenderingBoundaryResourcesTarget( - request.renderState, - newBoundary.resources - ); - } - task.keyPath = keyPath; try { @@ -10973,14 +10964,8 @@ if (__DEV__) { // We don't need to schedule any task because we know the parent has written yet. // We do need to fallthrough to create the fallback though. } finally { - { - setCurrentlyRenderingBoundaryResourcesTarget( - request.renderState, - parentBoundary ? parentBoundary.resources : null - ); - } - task.blockedBoundary = parentBoundary; + task.hoistableState = parentHoistableState; task.blockedSegment = parentSegment; task.keyPath = prevKeyPath; task.componentStack = previousComponentStack; @@ -11018,6 +11003,7 @@ if (__DEV__) { -1, parentBoundary, boundarySegment, + newBoundary.fallbackState, fallbackAbortSet, fallbackKeyPath, task.formatContext, @@ -11025,7 +11011,8 @@ if (__DEV__) { task.context, task.treeContext, // This stack should be the Suspense boundary stack because while the fallback is actually a child segment // of the parent boundary from a component standpoint the fallback is a child of the Suspense boundary itself - suspenseComponentStack + suspenseComponentStack, + true ); // TODO: This should be queued at a separate lower priority queue so that we only work // on preparing fallbacks if we don't have any more main content to task on. @@ -11051,6 +11038,7 @@ if (__DEV__) { var prevKeyPath = task.keyPath; var previousReplaySet = task.replay; var parentBoundary = task.blockedBoundary; + var parentHoistableState = task.hoistableState; var content = props.children; var fallback = props.fallback; var fallbackAbortSet = new Set(); @@ -11062,19 +11050,13 @@ if (__DEV__) { // we're writing to. If something suspends, it'll spawn new suspended task with that context. task.blockedBoundary = resumedBoundary; + task.hoistableState = resumedBoundary.contentState; task.replay = { nodes: childNodes, slots: childSlots, pendingTasks: 1 }; - { - setCurrentlyRenderingBoundaryResourcesTarget( - request.renderState, - resumedBoundary.resources - ); - } - try { // We use the safe form because we don't handle suspending here. Only error handling. renderNode(request, task, content, -1); @@ -11118,14 +11100,8 @@ if (__DEV__) { // We don't need to schedule any task because we know the parent has written yet. // We do need to fallthrough to create the fallback though. } finally { - { - setCurrentlyRenderingBoundaryResourcesTarget( - request.renderState, - parentBoundary ? parentBoundary.resources : null - ); - } - task.blockedBoundary = parentBoundary; + task.hoistableState = parentHoistableState; task.replay = previousReplaySet; task.keyPath = prevKeyPath; task.componentStack = previousComponentStack; @@ -11146,6 +11122,7 @@ if (__DEV__) { fallback, -1, parentBoundary, + resumedBoundary.fallbackState, fallbackAbortSet, fallbackKeyPath, task.formatContext, @@ -11153,7 +11130,8 @@ if (__DEV__) { task.context, task.treeContext, // This stack should be the Suspense boundary stack because while the fallback is actually a child segment // of the parent boundary from a component standpoint the fallback is a child of the Suspense boundary itself - suspenseComponentStack + suspenseComponentStack, + true ); // TODO: This should be queued at a separate lower priority queue so that we only work // on preparing fallbacks if we don't have any more main content to task on. @@ -11188,8 +11166,10 @@ if (__DEV__) { props, request.resumableState, request.renderState, + task.hoistableState, task.formatContext, - segment.lastPushedText + segment.lastPushedText, + task.isFallback ); segment.lastPushedText = false; @@ -12385,6 +12365,7 @@ if (__DEV__) { task.node, task.childIndex, task.blockedBoundary, + task.hoistableState, task.abortSet, task.keyPath, task.formatContext, @@ -12392,7 +12373,8 @@ if (__DEV__) { task.context, task.treeContext, // We pop one task off the stack because the node that suspended will be tried again, // which will add it back onto the stack. - task.componentStack !== null ? task.componentStack.parent : null + task.componentStack !== null ? task.componentStack.parent : null, + task.isFallback ); var ping = newTask.ping; x.then(ping, ping); @@ -12420,6 +12402,7 @@ if (__DEV__) { task.childIndex, task.blockedBoundary, newSegment, + task.hoistableState, task.abortSet, task.keyPath, task.formatContext, @@ -12427,7 +12410,8 @@ if (__DEV__) { task.context, task.treeContext, // We pop one task off the stack because the node that suspended will be tried again, // which will add it back onto the stack. - task.componentStack !== null ? task.componentStack.parent : null + task.componentStack !== null ? task.componentStack.parent : null, + task.isFallback ); var ping = newTask.ping; x.then(ping, ping); @@ -12982,14 +12966,6 @@ if (__DEV__) { } function retryTask(request, task) { - { - var blockedBoundary = task.blockedBoundary; - setCurrentlyRenderingBoundaryResourcesTarget( - request.renderState, - blockedBoundary ? blockedBoundary.resources : null - ); - } - var segment = task.blockedSegment; if (segment === null) { @@ -13069,13 +13045,6 @@ if (__DEV__) { erroredTask(request, task.blockedBoundary, x, errorInfo); return; } finally { - { - setCurrentlyRenderingBoundaryResourcesTarget( - request.renderState, - null - ); - } - { currentTaskInDEV = prevTaskInDEV; } @@ -13160,13 +13129,6 @@ if (__DEV__) { return; } finally { - { - setCurrentlyRenderingBoundaryResourcesTarget( - request.renderState, - null - ); - } - { currentTaskInDEV = prevTaskInDEV; } @@ -13245,7 +13207,18 @@ if (__DEV__) { } } - function flushSubtree(request, destination, segment) { + function flushPreamble(request, destination, rootSegment) { + var willFlushAllSegments = + request.allPendingTasks === 0 && request.trackedPostpones === null; + writePreamble( + destination, + request.resumableState, + request.renderState, + willFlushAllSegments + ); + } + + function flushSubtree(request, destination, segment, hoistableState) { segment.parentFlushed = true; switch (segment.status) { @@ -13277,7 +13250,7 @@ if (__DEV__) { writeChunk(destination, chunks[chunkIdx]); } - r = flushSegment(request, destination, nextChild); + r = flushSegment(request, destination, nextChild, hoistableState); } // Finally just write all the remaining chunks for (; chunkIdx < chunks.length - 1; chunkIdx++) { @@ -13299,12 +13272,12 @@ if (__DEV__) { } } - function flushSegment(request, destination, segment) { + function flushSegment(request, destination, segment, hoistableState) { var boundary = segment.boundary; if (boundary === null) { // Not a suspense boundary. - return flushSubtree(request, destination, segment); + return flushSubtree(request, destination, segment, hoistableState); } boundary.parentFlushed = true; // This segment is a Suspense boundary. We need to decide whether to @@ -13321,7 +13294,7 @@ if (__DEV__) { boundary.errorComponentStack ); // Flush the fallback. - flushSubtree(request, destination, segment); + flushSubtree(request, destination, segment, hoistableState); return writeEndClientRenderedSuspenseBoundary( destination, request.renderState @@ -13339,9 +13312,16 @@ if (__DEV__) { } // This boundary is still loading. Emit a pending suspense boundary wrapper. var id = boundary.rootSegmentID; - writeStartPendingSuspenseBoundary(destination, request.renderState, id); // Flush the fallback. + writeStartPendingSuspenseBoundary(destination, request.renderState, id); // We are going to flush the fallback so we need to hoist the fallback + // state to the parent boundary - flushSubtree(request, destination, segment); + { + if (hoistableState) { + hoistHoistables(hoistableState, boundary.fallbackState); + } + } // Flush the fallback. + + flushSubtree(request, destination, segment, hoistableState); return writeEndPendingSuspenseBoundary(destination); } else if (boundary.byteSize > request.progressiveChunkSize) { // This boundary is large and will be emitted separately so that we can progressively show @@ -13357,13 +13337,19 @@ if (__DEV__) { destination, request.renderState, boundary.rootSegmentID - ); // Flush the fallback. + ); // While we are going to flush the fallback we are going to follow it up with + // the completed boundary immediately so we make the choice to omit fallback + // boundary state from the parent since it will be replaced when the boundary + // flushes later in this pass or in a future flush + // Flush the fallback. - flushSubtree(request, destination, segment); + flushSubtree(request, destination, segment, hoistableState); return writeEndPendingSuspenseBoundary(destination); } else { { - hoistResources(request.renderState, boundary.resources); + if (hoistableState) { + hoistHoistables(hoistableState, boundary.contentState); + } } // We can inline this boundary's content as a complete boundary. writeStartCompletedSuspenseBoundary(destination, request.renderState); @@ -13376,7 +13362,7 @@ if (__DEV__) { } var contentSegment = completedSegments[0]; - flushSegment(request, destination, contentSegment); + flushSegment(request, destination, contentSegment, hoistableState); return writeEndCompletedSuspenseBoundary( destination, request.renderState @@ -13396,25 +13382,23 @@ if (__DEV__) { ); } - function flushSegmentContainer(request, destination, segment) { + function flushSegmentContainer( + request, + destination, + segment, + hoistableState + ) { writeStartSegment( destination, request.renderState, segment.parentFormatContext, segment.id ); - flushSegment(request, destination, segment); + flushSegment(request, destination, segment, hoistableState); return writeEndSegment(destination, segment.parentFormatContext); } function flushCompletedBoundary(request, destination, boundary) { - { - setCurrentlyRenderingBoundaryResourcesTarget( - request.renderState, - boundary.resources - ); - } - var completedSegments = boundary.completedSegments; var i = 0; @@ -13426,9 +13410,9 @@ if (__DEV__) { completedSegments.length = 0; { - writeResourcesForBoundary( + writeHoistablesForBoundary( destination, - boundary.resources, + boundary.contentState, request.renderState ); } @@ -13438,18 +13422,11 @@ if (__DEV__) { request.resumableState, request.renderState, boundary.rootSegmentID, - boundary.resources + boundary.contentState ); } function flushPartialBoundary(request, destination, boundary) { - { - setCurrentlyRenderingBoundaryResourcesTarget( - request.renderState, - boundary.resources - ); - } - var completedSegments = boundary.completedSegments; var i = 0; @@ -13475,13 +13452,9 @@ if (__DEV__) { completedSegments.splice(0, i); { - // The way this is structured we only write resources for partial boundaries - // if there is no backpressure. Later before we complete the boundary we - // will write resources regardless of backpressure before we emit the - // completion instruction - return writeResourcesForBoundary( + return writeHoistablesForBoundary( destination, - boundary.resources, + boundary.contentState, request.renderState ); } @@ -13498,6 +13471,7 @@ if (__DEV__) { return true; } + var hoistableState = boundary.contentState; var segmentID = segment.id; if (segmentID === -1) { @@ -13511,13 +13485,23 @@ if (__DEV__) { ); } - return flushSegmentContainer(request, destination, segment); + return flushSegmentContainer( + request, + destination, + segment, + hoistableState + ); } else if (segmentID === boundary.rootSegmentID) { // When we emit postponed boundaries, we might have assigned the ID already // but it's still the root segment so we can't inject it into the parent yet. - return flushSegmentContainer(request, destination, segment); + return flushSegmentContainer( + request, + destination, + segment, + hoistableState + ); } else { - flushSegmentContainer(request, destination, segment); + flushSegmentContainer(request, destination, segment, hoistableState); return writeCompletedSegmentInstruction( destination, request.resumableState, @@ -13542,16 +13526,10 @@ if (__DEV__) { return; } else if (request.pendingRootTasks === 0) { if (enableFloat) { - writePreamble( - destination, - request.resumableState, - request.renderState, - request.allPendingTasks === 0 && - request.trackedPostpones === null - ); + flushPreamble(request, destination, completedRootSegment); } - flushSegment(request, destination, completedRootSegment); + flushSegment(request, destination, completedRootSegment, null); request.completedRootSegment = null; writeCompletedRoot(destination, request.renderState); } else { diff --git a/compiled/facebook-www/ReactDOMServer-prod.classic.js b/compiled/facebook-www/ReactDOMServer-prod.classic.js index 79028d5862..07232e7695 100644 --- a/compiled/facebook-www/ReactDOMServer-prod.classic.js +++ b/compiled/facebook-www/ReactDOMServer-prod.classic.js @@ -613,85 +613,6 @@ function flattenOptionChildren(children) { }); return content; } -function pushLink( - target, - props, - resumableState, - renderState, - textEmbedded, - insertionMode, - noscriptTagInScope -) { - var rel = props.rel, - href = props.href, - precedence = props.precedence; - if ( - 3 === insertionMode || - noscriptTagInScope || - null != props.itemProp || - "string" !== typeof rel || - "string" !== typeof href || - "" === href - ) - return pushLinkImpl(target, props), null; - if ("stylesheet" === props.rel) { - if ( - "string" !== typeof precedence || - null != props.disabled || - props.onLoad || - props.onError - ) - return pushLinkImpl(target, props); - insertionMode = renderState.styles.get(precedence); - noscriptTagInScope = resumableState.styleResources.hasOwnProperty(href) - ? resumableState.styleResources[href] - : void 0; - null !== noscriptTagInScope - ? ((resumableState.styleResources[href] = null), - insertionMode || - ((insertionMode = { - precedence: escapeTextForBrowser(precedence), - rules: [], - hrefs: [], - sheets: new Map() - }), - renderState.styles.set(precedence, insertionMode)), - (props = { - state: 0, - props: assign({}, props, { - "data-precedence": props.precedence, - precedence: null - }) - }), - noscriptTagInScope && - (2 === noscriptTagInScope.length && - adoptPreloadCredentials(props.props, noscriptTagInScope), - (resumableState = renderState.preloads.stylesheets.get(href)) && - 0 < resumableState.length - ? (resumableState.length = 0) - : (props.state = 1)), - insertionMode.sheets.set(href, props), - renderState.boundaryResources && - renderState.boundaryResources.stylesheets.add(props)) - : insertionMode && - (href = insertionMode.sheets.get(href)) && - renderState.boundaryResources && - renderState.boundaryResources.stylesheets.add(href); - textEmbedded && target.push("\x3c!-- --\x3e"); - return null; - } - if (props.onLoad || props.onError) return pushLinkImpl(target, props); - textEmbedded && target.push("\x3c!-- --\x3e"); - switch (props.rel) { - case "preconnect": - case "dns-prefetch": - return pushLinkImpl(renderState.preconnectChunks, props); - case "preload": - return pushLinkImpl(renderState.preloadChunks, props); - default: - return pushLinkImpl(renderState.hoistableChunks, props); - } -} function pushLinkImpl(target, props) { target.push(startChunkForTag("link")); for (var propKey in props) @@ -830,8 +751,10 @@ function pushStartInstance( props, resumableState, renderState, + hoistableState, formatContext, - textEmbedded + textEmbedded, + isFallback ) { switch (type) { case "div": @@ -1195,19 +1118,88 @@ function pushStartInstance( props ); else - pushTitleImpl(renderState.hoistableChunks, props), - (JSCompiler_inline_result$jscomp$1 = null); + isFallback + ? (JSCompiler_inline_result$jscomp$1 = null) + : (pushTitleImpl(renderState.hoistableChunks, props), + (JSCompiler_inline_result$jscomp$1 = void 0)); return JSCompiler_inline_result$jscomp$1; case "link": - return pushLink( - target$jscomp$0, - props, - resumableState, - renderState, - textEmbedded, - formatContext.insertionMode, - !!(formatContext.tagScope & 1) - ); + var rel = props.rel, + href = props.href, + precedence = props.precedence; + if ( + 3 === formatContext.insertionMode || + formatContext.tagScope & 1 || + null != props.itemProp || + "string" !== typeof rel || + "string" !== typeof href || + "" === href + ) { + pushLinkImpl(target$jscomp$0, props); + var JSCompiler_inline_result$jscomp$2 = null; + } else if ("stylesheet" === props.rel) + if ( + "string" !== typeof precedence || + null != props.disabled || + props.onLoad || + props.onError + ) + JSCompiler_inline_result$jscomp$2 = pushLinkImpl( + target$jscomp$0, + props + ); + else { + var styleQueue = renderState.styles.get(precedence), + resourceState = resumableState.styleResources.hasOwnProperty(href) + ? resumableState.styleResources[href] + : void 0; + if (null !== resourceState) { + resumableState.styleResources[href] = null; + styleQueue || + ((styleQueue = { + precedence: escapeTextForBrowser(precedence), + rules: [], + hrefs: [], + sheets: new Map() + }), + renderState.styles.set(precedence, styleQueue)); + var resource = { + state: 0, + props: assign({}, props, { + "data-precedence": props.precedence, + precedence: null + }) + }; + if (resourceState) { + 2 === resourceState.length && + adoptPreloadCredentials(resource.props, resourceState); + var preloadResource = renderState.preloads.stylesheets.get(href); + preloadResource && 0 < preloadResource.length + ? (preloadResource.length = 0) + : (resource.state = 1); + } + styleQueue.sheets.set(href, resource); + hoistableState && hoistableState.stylesheets.add(resource); + } else if (styleQueue) { + var resource$8 = styleQueue.sheets.get(href); + resource$8 && + hoistableState && + hoistableState.stylesheets.add(resource$8); + } + textEmbedded && target$jscomp$0.push("\x3c!-- --\x3e"); + JSCompiler_inline_result$jscomp$2 = null; + } + else + props.onLoad || props.onError + ? (JSCompiler_inline_result$jscomp$2 = pushLinkImpl( + target$jscomp$0, + props + )) + : (textEmbedded && target$jscomp$0.push("\x3c!-- --\x3e"), + (JSCompiler_inline_result$jscomp$2 = isFallback + ? null + : pushLinkImpl(renderState.hoistableChunks, props))); + return JSCompiler_inline_result$jscomp$2; case "script": var asyncProp = props.async; if ( @@ -1222,7 +1214,7 @@ function pushStartInstance( formatContext.tagScope & 1 || null != props.itemProp ) - var JSCompiler_inline_result$jscomp$2 = pushScriptImpl( + var JSCompiler_inline_result$jscomp$3 = pushScriptImpl( target$jscomp$0, props ); @@ -1234,37 +1226,37 @@ function pushStartInstance( } else (resources = resumableState.scriptResources), (preloads = renderState.preloads.scripts); - var resourceState = resources.hasOwnProperty(key) + var resourceState$jscomp$0 = resources.hasOwnProperty(key) ? resources[key] : void 0; - if (null !== resourceState) { + if (null !== resourceState$jscomp$0) { resources[key] = null; var scriptProps = props; - if (resourceState) { - 2 === resourceState.length && + if (resourceState$jscomp$0) { + 2 === resourceState$jscomp$0.length && ((scriptProps = assign({}, props)), - adoptPreloadCredentials(scriptProps, resourceState)); - var preloadResource = preloads.get(key); - preloadResource && (preloadResource.length = 0); + adoptPreloadCredentials(scriptProps, resourceState$jscomp$0)); + var preloadResource$jscomp$0 = preloads.get(key); + preloadResource$jscomp$0 && (preloadResource$jscomp$0.length = 0); } - var resource = []; - renderState.scripts.add(resource); - pushScriptImpl(resource, scriptProps); + var resource$jscomp$0 = []; + renderState.scripts.add(resource$jscomp$0); + pushScriptImpl(resource$jscomp$0, scriptProps); } textEmbedded && target$jscomp$0.push("\x3c!-- --\x3e"); - JSCompiler_inline_result$jscomp$2 = null; + JSCompiler_inline_result$jscomp$3 = null; } - return JSCompiler_inline_result$jscomp$2; + return JSCompiler_inline_result$jscomp$3; case "style": - var precedence = props.precedence, - href = props.href; + var precedence$jscomp$0 = props.precedence, + href$jscomp$0 = props.href; if ( 3 === formatContext.insertionMode || formatContext.tagScope & 1 || null != props.itemProp || - "string" !== typeof precedence || - "string" !== typeof href || - "" === href + "string" !== typeof precedence$jscomp$0 || + "string" !== typeof href$jscomp$0 || + "" === href$jscomp$0 ) { target$jscomp$0.push(startChunkForTag("style")); var children$jscomp$4 = null, @@ -1302,26 +1294,28 @@ function pushStartInstance( target$jscomp$0.push(escapeTextForBrowser("" + child)); pushInnerHTML(target$jscomp$0, innerHTML$jscomp$3, children$jscomp$4); target$jscomp$0.push(endChunkForTag("style")); - var JSCompiler_inline_result$jscomp$3 = null; + var JSCompiler_inline_result$jscomp$4 = null; } else { - var styleQueue = renderState.styles.get(precedence); + var styleQueue$jscomp$0 = renderState.styles.get(precedence$jscomp$0); if ( null !== - (resumableState.styleResources.hasOwnProperty(href) - ? resumableState.styleResources[href] + (resumableState.styleResources.hasOwnProperty(href$jscomp$0) + ? resumableState.styleResources[href$jscomp$0] : void 0) ) { - resumableState.styleResources[href] = null; - styleQueue - ? styleQueue.hrefs.push(escapeTextForBrowser(href)) - : ((styleQueue = { - precedence: escapeTextForBrowser(precedence), + resumableState.styleResources[href$jscomp$0] = null; + styleQueue$jscomp$0 + ? styleQueue$jscomp$0.hrefs.push( + escapeTextForBrowser(href$jscomp$0) + ) + : ((styleQueue$jscomp$0 = { + precedence: escapeTextForBrowser(precedence$jscomp$0), rules: [], - hrefs: [escapeTextForBrowser(href)], + hrefs: [escapeTextForBrowser(href$jscomp$0)], sheets: new Map() }), - renderState.styles.set(precedence, styleQueue)); - var target = styleQueue.rules, + renderState.styles.set(precedence$jscomp$0, styleQueue$jscomp$0)); + var target = styleQueue$jscomp$0.rules, children$jscomp$5 = null, innerHTML$jscomp$4 = null, propKey$jscomp$7; @@ -1349,33 +1343,34 @@ function pushStartInstance( target.push(escapeTextForBrowser("" + child$jscomp$0)); pushInnerHTML(target, innerHTML$jscomp$4, children$jscomp$5); } - styleQueue && - renderState.boundaryResources && - renderState.boundaryResources.styles.add(styleQueue); + styleQueue$jscomp$0 && + hoistableState && + hoistableState.styles.add(styleQueue$jscomp$0); textEmbedded && target$jscomp$0.push("\x3c!-- --\x3e"); - JSCompiler_inline_result$jscomp$3 = void 0; + JSCompiler_inline_result$jscomp$4 = void 0; } - return JSCompiler_inline_result$jscomp$3; + return JSCompiler_inline_result$jscomp$4; case "meta": if ( 3 === formatContext.insertionMode || formatContext.tagScope & 1 || null != props.itemProp ) - var JSCompiler_inline_result$jscomp$4 = pushSelfClosing( + var JSCompiler_inline_result$jscomp$5 = pushSelfClosing( target$jscomp$0, props, "meta" ); else textEmbedded && target$jscomp$0.push("\x3c!-- --\x3e"), - (JSCompiler_inline_result$jscomp$4 = - "string" === typeof props.charSet - ? pushSelfClosing(renderState.charsetChunks, props, "meta") - : "viewport" === props.name - ? pushSelfClosing(renderState.preconnectChunks, props, "meta") - : pushSelfClosing(renderState.hoistableChunks, props, "meta")); - return JSCompiler_inline_result$jscomp$4; + (JSCompiler_inline_result$jscomp$5 = isFallback + ? null + : "string" === typeof props.charSet + ? pushSelfClosing(renderState.charsetChunks, props, "meta") + : "viewport" === props.name + ? pushSelfClosing(renderState.viewportChunks, props, "meta") + : pushSelfClosing(renderState.hoistableChunks, props, "meta")); + return JSCompiler_inline_result$jscomp$5; case "listing": case "pre": target$jscomp$0.push(startChunkForTag(type)); @@ -1448,20 +1443,20 @@ function pushStartInstance( var sizes = "string" === typeof props.sizes ? props.sizes : void 0, key$jscomp$0 = srcSet ? srcSet + "\n" + (sizes || "") : src, promotablePreloads = renderState.preloads.images, - resource$jscomp$0 = promotablePreloads.get(key$jscomp$0); - if (resource$jscomp$0) { + resource$jscomp$1 = promotablePreloads.get(key$jscomp$0); + if (resource$jscomp$1) { if ( "high" === props.fetchPriority || 10 > renderState.highImagePreloads.size ) promotablePreloads.delete(key$jscomp$0), - renderState.highImagePreloads.add(resource$jscomp$0); + renderState.highImagePreloads.add(resource$jscomp$1); } else if ( !resumableState.imageResources.hasOwnProperty(key$jscomp$0) ) { resumableState.imageResources[key$jscomp$0] = PRELOAD_NO_CREDS; var input = props.crossOrigin; - var JSCompiler_inline_result$jscomp$5 = + var JSCompiler_inline_result$jscomp$6 = "string" === typeof input ? "use-credentials" === input ? input @@ -1476,7 +1471,7 @@ function pushStartInstance( ((header = getPreloadAsHeader(src, "image", { imageSrcSet: props.srcSet, imageSizes: props.sizes, - crossOrigin: JSCompiler_inline_result$jscomp$5, + crossOrigin: JSCompiler_inline_result$jscomp$6, integrity: props.integrity, nonce: props.nonce, type: props.type, @@ -1487,14 +1482,14 @@ function pushStartInstance( ? ((renderState.resets.image[key$jscomp$0] = PRELOAD_NO_CREDS), headers.highImagePreloads && (headers.highImagePreloads += ", "), (headers.highImagePreloads += header)) - : ((resource$jscomp$0 = []), - pushLinkImpl(resource$jscomp$0, { + : ((resource$jscomp$1 = []), + pushLinkImpl(resource$jscomp$1, { rel: "preload", as: "image", href: srcSet ? void 0 : src, imageSrcSet: srcSet, imageSizes: sizes, - crossOrigin: JSCompiler_inline_result$jscomp$5, + crossOrigin: JSCompiler_inline_result$jscomp$6, integrity: props.integrity, type: props.type, fetchPriority: props.fetchPriority, @@ -1502,9 +1497,9 @@ function pushStartInstance( }), "high" === props.fetchPriority || 10 > renderState.highImagePreloads.size - ? renderState.highImagePreloads.add(resource$jscomp$0) - : (renderState.bulkPreloads.add(resource$jscomp$0), - promotablePreloads.set(key$jscomp$0, resource$jscomp$0))); + ? renderState.highImagePreloads.add(resource$jscomp$1) + : (renderState.bulkPreloads.add(resource$jscomp$1), + promotablePreloads.set(key$jscomp$0, resource$jscomp$1))); } } return pushSelfClosing(target$jscomp$0, props, "img"); @@ -1532,36 +1527,36 @@ function pushStartInstance( case "head": if (2 > formatContext.insertionMode && null === renderState.headChunks) { renderState.headChunks = []; - var JSCompiler_inline_result$jscomp$6 = pushStartGenericElement( + var JSCompiler_inline_result$jscomp$7 = pushStartGenericElement( renderState.headChunks, props, "head" ); } else - JSCompiler_inline_result$jscomp$6 = pushStartGenericElement( + JSCompiler_inline_result$jscomp$7 = pushStartGenericElement( target$jscomp$0, props, "head" ); - return JSCompiler_inline_result$jscomp$6; + return JSCompiler_inline_result$jscomp$7; case "html": if ( 0 === formatContext.insertionMode && null === renderState.htmlChunks ) { renderState.htmlChunks = [""]; - var JSCompiler_inline_result$jscomp$7 = pushStartGenericElement( + var JSCompiler_inline_result$jscomp$8 = pushStartGenericElement( renderState.htmlChunks, props, "html" ); } else - JSCompiler_inline_result$jscomp$7 = pushStartGenericElement( + JSCompiler_inline_result$jscomp$8 = pushStartGenericElement( target$jscomp$0, props, "html" ); - return JSCompiler_inline_result$jscomp$7; + return JSCompiler_inline_result$jscomp$8; default: if (-1 !== type.indexOf("-")) { target$jscomp$0.push(startChunkForTag(type)); @@ -1795,15 +1790,11 @@ function hasStylesToHoist(stylesheet) { ? (currentlyRenderingBoundaryHasStylesToHoist = !0) : !1; } -function writeResourcesForBoundary( - destination, - boundaryResources, - renderState -) { +function writeHoistablesForBoundary(destination, hoistableState, renderState) { currentlyRenderingBoundaryHasStylesToHoist = !1; destinationHasCapacity = !0; - boundaryResources.styles.forEach(flushStyleTagsLateForBoundary, destination); - boundaryResources.stylesheets.forEach(hasStylesToHoist); + hoistableState.styles.forEach(flushStyleTagsLateForBoundary, destination); + hoistableState.stylesheets.forEach(hasStylesToHoist); currentlyRenderingBoundaryHasStylesToHoist && (renderState.stylesToHoist = !0); return destinationHasCapacity; @@ -1875,10 +1866,10 @@ function preloadLateStyles(styleQueue) { styleQueue.sheets.forEach(preloadLateStyle, this); styleQueue.sheets.clear(); } -function writeStyleResourceDependenciesInJS(destination, boundaryResources) { +function writeStyleResourceDependenciesInJS(destination, hoistableState) { destination.push("["); var nextArrayOpenBrackChunk = "["; - boundaryResources.stylesheets.forEach(function (resource) { + hoistableState.stylesheets.forEach(function (resource) { if (2 !== resource.state) if (3 === resource.state) destination.push(nextArrayOpenBrackChunk), @@ -1971,10 +1962,10 @@ function writeStyleResourceAttributeInJS(destination, name, value) { attributeName = escapeJSObjectForInstructionScripts(name); destination.push(attributeName); } -function writeStyleResourceDependenciesInAttr(destination, boundaryResources) { +function writeStyleResourceDependenciesInAttr(destination, hoistableState) { destination.push("["); var nextArrayOpenBrackChunk = "["; - boundaryResources.stylesheets.forEach(function (resource) { + hoistableState.stylesheets.forEach(function (resource) { if (2 !== resource.state) if (3 === resource.state) destination.push(nextArrayOpenBrackChunk), @@ -2067,6 +2058,9 @@ function writeStyleResourceAttributeInAttr(destination, name, value) { attributeName = escapeTextForBrowser(JSON.stringify(name)); destination.push(attributeName); } +function createHoistableState() { + return { styles: new Set(), stylesheets: new Set() }; +} function prefetchDNS(href) { var request = currentRequest ? currentRequest : null; if (request) { @@ -2482,16 +2476,16 @@ function createRenderState(resumableState, generateStaticMarkup) { "\x3c/script>" ); bootstrapScriptContent = idPrefix + "P:"; - var JSCompiler_object_inline_segmentPrefix_1645 = idPrefix + "S:"; + var JSCompiler_object_inline_segmentPrefix_1594 = idPrefix + "S:"; idPrefix += "B:"; - var JSCompiler_object_inline_preconnects_1660 = new Set(), - JSCompiler_object_inline_fontPreloads_1661 = new Set(), - JSCompiler_object_inline_highImagePreloads_1662 = new Set(), - JSCompiler_object_inline_styles_1663 = new Map(), - JSCompiler_object_inline_bootstrapScripts_1664 = new Set(), - JSCompiler_object_inline_scripts_1665 = new Set(), - JSCompiler_object_inline_bulkPreloads_1666 = new Set(), - JSCompiler_object_inline_preloads_1667 = { + var JSCompiler_object_inline_preconnects_1608 = new Set(), + JSCompiler_object_inline_fontPreloads_1609 = new Set(), + JSCompiler_object_inline_highImagePreloads_1610 = new Set(), + JSCompiler_object_inline_styles_1611 = new Map(), + JSCompiler_object_inline_bootstrapScripts_1612 = new Set(), + JSCompiler_object_inline_scripts_1613 = new Set(), + JSCompiler_object_inline_bulkPreloads_1614 = new Set(), + JSCompiler_object_inline_preloads_1615 = { images: new Map(), stylesheets: new Map(), scripts: new Map(), @@ -2528,7 +2522,7 @@ function createRenderState(resumableState, generateStaticMarkup) { scriptConfig.moduleScriptResources[href] = null; scriptConfig = []; pushLinkImpl(scriptConfig, props); - JSCompiler_object_inline_bootstrapScripts_1664.add(scriptConfig); + JSCompiler_object_inline_bootstrapScripts_1612.add(scriptConfig); bootstrapChunks.push('