From b8445db60c7653c87d15186a0bb15d5289989174 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Fri, 29 Sep 2023 14:43:43 -0400 Subject: [PATCH] Update React from 09285d5a7 to d900fadbf. React upstream changes: - https://github.com/facebook/react/pull/27439 - https://github.com/facebook/react/pull/26763 - https://github.com/facebook/react/pull/27434 - https://github.com/facebook/react/pull/27433 - https://github.com/facebook/react/pull/27424 - https://github.com/facebook/react/pull/27428 - https://github.com/facebook/react/pull/27427 - https://github.com/facebook/react/pull/27315 - https://github.com/facebook/react/pull/27314 - https://github.com/facebook/react/pull/27400 - https://github.com/facebook/react/pull/27421 - https://github.com/facebook/react/pull/27419 - https://github.com/facebook/react/pull/27418 --- package.json | 16 +- ...t-dom-server-legacy.browser.development.js | 1328 ++++++++++------- ...om-server-legacy.browser.production.min.js | 286 ++-- ...eact-dom-server-legacy.node.development.js | 1328 ++++++++++------- ...t-dom-server-legacy.node.production.min.js | 311 ++-- ...t-dom-server-rendering-stub.development.js | 50 +- ...om-server-rendering-stub.production.min.js | 14 +- .../react-dom-server.browser.development.js | 1325 +++++++++------- ...react-dom-server.browser.production.min.js | 268 ++-- .../cjs/react-dom-server.edge.development.js | 1325 +++++++++------- .../react-dom-server.edge.production.min.js | 343 ++--- .../cjs/react-dom-server.node.development.js | 1325 +++++++++------- .../react-dom-server.node.production.min.js | 321 ++-- .../react-dom-unstable_testing.development.js | 101 +- ...act-dom-unstable_testing.production.min.js | 24 +- .../cjs/react-dom.development.js | 101 +- .../cjs/react-dom.production.min.js | 24 +- .../cjs/react-dom.profiling.min.js | 124 +- .../react-dom.shared-subset.development.js | 48 +- .../react-dom.shared-subset.production.min.js | 11 +- .../react-dom-experimental/package.json | 4 +- ...t-dom-server-legacy.browser.development.js | 1282 +++++++++------- ...om-server-legacy.browser.production.min.js | 267 ++-- ...eact-dom-server-legacy.node.development.js | 1282 +++++++++------- ...t-dom-server-legacy.node.production.min.js | 265 ++-- ...t-dom-server-rendering-stub.development.js | 50 +- ...om-server-rendering-stub.production.min.js | 14 +- .../react-dom-server.browser.development.js | 1277 +++++++++------- ...react-dom-server.browser.production.min.js | 240 +-- .../cjs/react-dom-server.edge.development.js | 1277 +++++++++------- .../react-dom-server.edge.production.min.js | 254 ++-- .../cjs/react-dom-server.node.development.js | 1277 +++++++++------- .../react-dom-server.node.production.min.js | 247 +-- .../react-dom/cjs/react-dom.development.js | 98 +- .../react-dom/cjs/react-dom.production.min.js | 172 +-- .../react-dom/cjs/react-dom.profiling.min.js | 40 +- .../react-dom.shared-subset.development.js | 48 +- .../react-dom.shared-subset.production.min.js | 11 +- .../next/src/compiled/react-dom/package.json | 4 +- .../cjs/react.development.js | 6 +- .../cjs/react.production.min.js | 2 +- .../cjs/react.shared-subset.development.js | 6 +- .../cjs/react.shared-subset.production.min.js | 2 +- ...-dom-webpack-client.browser.development.js | 141 +- ...m-webpack-client.browser.production.min.js | 45 +- ...ver-dom-webpack-client.edge.development.js | 159 +- ...-dom-webpack-client.edge.production.min.js | 58 +- ...ver-dom-webpack-client.node.development.js | 161 +- ...-dom-webpack-client.node.production.min.js | 60 +- ...bpack-client.node.unbundled.development.js | 119 +- ...ck-client.node.unbundled.production.min.js | 59 +- .../cjs/react-server-dom-webpack-plugin.js | 22 +- ...-dom-webpack-server.browser.development.js | 128 +- ...m-webpack-server.browser.production.min.js | 113 +- ...ver-dom-webpack-server.edge.development.js | 109 +- ...-dom-webpack-server.edge.production.min.js | 97 +- ...ver-dom-webpack-server.node.development.js | 109 +- ...-dom-webpack-server.node.production.min.js | 67 +- ...bpack-server.node.unbundled.development.js | 57 +- ...ck-server.node.unbundled.production.min.js | 60 +- .../package.json | 4 +- ...-dom-webpack-client.browser.development.js | 141 +- ...m-webpack-client.browser.production.min.js | 47 +- ...ver-dom-webpack-client.edge.development.js | 159 +- ...-dom-webpack-client.edge.production.min.js | 54 +- ...ver-dom-webpack-client.node.development.js | 161 +- ...-dom-webpack-client.node.production.min.js | 59 +- ...bpack-client.node.unbundled.development.js | 119 +- ...ck-client.node.unbundled.production.min.js | 55 +- .../cjs/react-server-dom-webpack-plugin.js | 22 +- ...-dom-webpack-server.browser.development.js | 128 +- ...m-webpack-server.browser.production.min.js | 61 +- ...ver-dom-webpack-server.edge.development.js | 109 +- ...-dom-webpack-server.edge.production.min.js | 97 +- ...ver-dom-webpack-server.node.development.js | 109 +- ...-dom-webpack-server.node.production.min.js | 95 +- ...bpack-server.node.unbundled.development.js | 57 +- ...ck-server.node.unbundled.production.min.js | 94 +- .../react-server-dom-webpack/package.json | 4 +- .../compiled/react/cjs/react.development.js | 6 +- .../react/cjs/react.production.min.js | 2 +- .../cjs/react.shared-subset.development.js | 6 +- .../cjs/react.shared-subset.production.min.js | 2 +- pnpm-lock.yaml | 80 +- 84 files changed, 11568 insertions(+), 8465 deletions(-) diff --git a/package.json b/package.json index 7e0d87687836d8..1aa811421fd797 100644 --- a/package.json +++ b/package.json @@ -192,14 +192,14 @@ "random-seed": "0.3.0", "react": "18.2.0", "react-17": "npm:react@17.0.2", - "react-builtin": "npm:react@18.3.0-canary-09285d5a7-20230925", + "react-builtin": "npm:react@18.3.0-canary-d900fadbf-20230929", "react-dom": "18.2.0", "react-dom-17": "npm:react-dom@17.0.2", - "react-dom-builtin": "npm:react-dom@18.3.0-canary-09285d5a7-20230925", - "react-dom-experimental-builtin": "npm:react-dom@0.0.0-experimental-09285d5a7-20230925", - "react-experimental-builtin": "npm:react@0.0.0-experimental-09285d5a7-20230925", - "react-server-dom-webpack": "18.3.0-canary-09285d5a7-20230925", - "react-server-dom-webpack-experimental": "npm:react-server-dom-webpack@0.0.0-experimental-09285d5a7-20230925", + "react-dom-builtin": "npm:react-dom@18.3.0-canary-d900fadbf-20230929", + "react-dom-experimental-builtin": "npm:react-dom@0.0.0-experimental-d900fadbf-20230929", + "react-experimental-builtin": "npm:react@0.0.0-experimental-d900fadbf-20230929", + "react-server-dom-webpack": "18.3.0-canary-d900fadbf-20230929", + "react-server-dom-webpack-experimental": "npm:react-server-dom-webpack@0.0.0-experimental-d900fadbf-20230929", "react-ssr-prepass": "1.0.8", "react-virtualized": "9.22.3", "relay-compiler": "13.0.2", @@ -209,8 +209,8 @@ "resolve-from": "5.0.0", "sass": "1.54.0", "satori": "0.10.6", - "scheduler-builtin": "npm:scheduler@0.24.0-canary-09285d5a7-20230925", - "scheduler-experimental-builtin": "npm:scheduler@0.0.0-experimental-09285d5a7-20230925", + "scheduler-builtin": "npm:scheduler@0.24.0-canary-d900fadbf-20230929", + "scheduler-experimental-builtin": "npm:scheduler@0.0.0-experimental-d900fadbf-20230929", "seedrandom": "3.0.5", "selenium-webdriver": "4.0.0-beta.4", "semver": "7.3.7", diff --git a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.browser.development.js b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.browser.development.js index 63b0610dee700e..d19392ed8d2cdb 100644 --- a/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.browser.development.js +++ b/packages/next/src/compiled/react-dom-experimental/cjs/react-dom-server-legacy.browser.development.js @@ -17,7 +17,7 @@ if (process.env.NODE_ENV !== "production") { var React = require("next/dist/compiled/react-experimental"); var ReactDOM = require('react-dom'); -var ReactVersion = '18.3.0-experimental-09285d5a7-20230925'; +var ReactVersion = '18.3.0-experimental-d900fadbf-20230929'; var ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; @@ -1651,9 +1651,27 @@ var SentFormReplayingRuntime = 16; // Per request, global state that is not contextual to the rendering subtree. // This cannot be resumed and therefore should only contain things that are // temporary working state or are never used in the prerender pass. -// Per response, global state that is not contextual to the rendering subtree. +// Credentials here are things that affect whether a browser will make a request +// as well as things that affect which connection the browser will use for that request. +// We want these to be aligned across preloads and resources because otherwise the preload +// will be wasted. +// We investigated whether referrerPolicy should be included here but from experimentation +// it seems that browsers do not treat this as part of the http cache key and does not affect +// which connection is used. + +var EXISTS = null; // This constant is to mark preloads that have no unique credentials +// to convey. It should never be checked by identity and we should not +// assume Preload values in ResumableState equal this value because they +// will have come from some parsed input. + +var PRELOAD_NO_CREDS = []; + +{ + Object.freeze(PRELOAD_NO_CREDS); +} // Per response, global state that is not contextual to the rendering subtree. // This is resumable and therefore should be serializable. + var dataElementQuotedEnd = stringToPrecomputedChunk('">'); var startInlineScript = stringToPrecomputedChunk(''); @@ -1760,12 +1778,16 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot fontPreloads: new Set(), highImagePreloads: new Set(), // usedImagePreloads: new Set(), - precedences: new Map(), - stylePrecedences: new Map(), + styles: new Map(), bootstrapScripts: new Set(), scripts: new Set(), bulkPreloads: new Set(), - preloadsMap: new Map(), + preloads: { + images: new Map(), + stylesheets: new Map(), + scripts: new Map(), + moduleScripts: new Map() + }, nonce: nonce, // like a module global for currently rendering boundary boundaryResources: null, @@ -1775,17 +1797,32 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot if (bootstrapScripts !== undefined) { for (var i = 0; i < bootstrapScripts.length; i++) { var scriptConfig = bootstrapScripts[i]; - var src = typeof scriptConfig === 'string' ? scriptConfig : scriptConfig.src; - var integrity = typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity; - var crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; - preloadBootstrapScript(resumableState, renderState, src, nonce, integrity, crossOrigin); + var src = void 0, + crossOrigin = void 0, + integrity = void 0; + var props = { + rel: 'preload', + as: 'script', + fetchPriority: 'low', + nonce: nonce + }; + + if (typeof scriptConfig === 'string') { + props.href = src = scriptConfig; + } else { + props.href = src = scriptConfig.src; + props.integrity = integrity = typeof scriptConfig.integrity === 'string' ? scriptConfig.integrity : undefined; + props.crossOrigin = crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } + + preloadBootstrapScriptOrModule(resumableState, renderState, src, props); bootstrapChunks.push(startScriptSrc, stringToChunk(escapeTextForBrowser(src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (integrity) { + if (typeof integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(integrity))); } @@ -1801,20 +1838,32 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot for (var _i = 0; _i < bootstrapModules.length; _i++) { var _scriptConfig = bootstrapModules[_i]; - var _src = typeof _scriptConfig === 'string' ? _scriptConfig : _scriptConfig.src; + var _src = void 0, + _crossOrigin = void 0, + _integrity = void 0; - var _integrity = typeof _scriptConfig === 'string' ? undefined : _scriptConfig.integrity; + var _props = { + rel: 'modulepreload', + fetchPriority: 'low', + nonce: nonce + }; - var _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + if (typeof _scriptConfig === 'string') { + _props.href = _src = _scriptConfig; + } else { + _props.href = _src = _scriptConfig.src; + _props.integrity = _integrity = typeof _scriptConfig.integrity === 'string' ? _scriptConfig.integrity : undefined; + _props.crossOrigin = _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } - preloadBootstrapModule(resumableState, renderState, _src, nonce, _integrity, _crossOrigin); + preloadBootstrapScriptOrModule(resumableState, renderState, _src, _props); bootstrapChunks.push(startModuleSrc, stringToChunk(escapeTextForBrowser(_src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (_integrity) { + if (typeof _integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(_integrity))); } @@ -1847,10 +1896,18 @@ function createResumableState(identifierPrefix, externalRuntimeConfig) { hasHtml: false, // @TODO add bootstrap script to implicit preloads // persistent - preloadsMap: {}, - preconnectsMap: {}, - stylesMap: {}, - scriptsMap: {} + unknownResources: {}, + dnsResources: {}, + connectResources: { + default: {}, + anonymous: {}, + credentials: {} + }, + imageResources: {}, + styleResources: {}, + scriptResources: {}, + moduleUnknownResources: {}, + moduleScriptResources: {} }; } // Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion // modes. We only include the variants as they matter for the sake of our purposes. @@ -3208,7 +3265,7 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse if (props.rel === 'stylesheet') { // This may hoistable as a Stylesheet Resource, otherwise it will emit in place - var key = getResourceKey('style', href); + var key = getResourceKey(href); if (typeof precedence !== 'string' || props.disabled != null || props.onLoad || props.onError) { // This stylesheet is either not opted into Resource semantics or has conflicting properties which @@ -3229,67 +3286,58 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse return pushLinkImpl(target, props); } else { // This stylesheet refers to a Resource and we create a new one if necessary - var stylesInPrecedence = renderState.precedences.get(precedence); - - if (!resumableState.stylesMap.hasOwnProperty(key)) { - var resourceProps = stylesheetPropsFromRawProps(props); - var state = NoState; + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; + + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; // If this is the first time we've encountered this precedence we need + // to create a StyleQueue + + if (!styleQueue) { + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); + } - if (resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = resumableState.preloadsMap[key]; - adoptPreloadPropsForStylesheetProps(resourceProps, preloadProps); - var preloadResource = renderState.preloadsMap.get(key); + var resource = { + state: PENDING$1, + props: stylesheetPropsFromRawProps(props) + }; - if (preloadResource) { - // If we already had a preload we don't want that resource to flush directly. - // We let the newly created resource govern flushing. - preloadResource.state |= Blocked; + if (resourceState) { + // When resourceState is truty it is a Preload state. We cast it for clarity + var preloadState = resourceState; - if (preloadResource.state & Flushed) { - state = PreloadFlushed; - } - } else { - // If we resumed then we assume that this was already flushed - // by the shell. - state = PreloadFlushed; + if (preloadState.length === 2) { + adoptPreloadCredentials(resource.props, preloadState); } - } - var resource = { - type: 'stylesheet', - chunks: [], - state: state, - props: resourceProps - }; - resumableState.stylesMap[key] = null; - - if (!stylesInPrecedence) { - stylesInPrecedence = new Map(); - renderState.precedences.set(precedence, stylesInPrecedence); - var emptyStyleResource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [] - } - }; - stylesInPrecedence.set('', emptyStyleResource); + var preloadResource = renderState.preloads.stylesheets.get(key); - { - if (renderState.stylePrecedences.has(precedence)) { - error('React constructed an empty style resource when a style resource already exists for this precedence: "%s". This is a bug in React.', precedence); - } + if (preloadResource && preloadResource.length > 0) { + // The Preload for this resource was created in this render pass and has not flushed yet so + // we need to clear it to avoid it flushing. + preloadResource.length = 0; + } else { + // Either the preload resource from this render already flushed in this render pass + // or the preload flushed in a prior pass (prerender). In either case we need to mark + // this resource as already having been preloaded. + resource.state = PRELOADED; } + } // We add the newly created resource to our StyleQueue and if necessary + // track the resource with the currently rendering boundary - renderState.stylePrecedences.set(precedence, emptyStyleResource); - } - stylesInPrecedence.set(key, resource); + styleQueue.sheets.set(key, resource); if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.stylesheets.add(resource); } } else { // We need to track whether this boundary should wait on this resource or not. @@ -3297,12 +3345,12 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse // 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 (stylesInPrecedence) { - var _resource = stylesInPrecedence.get(key); + if (styleQueue) { + var _resource = styleQueue.sheets.get(key); if (_resource) { if (renderState.boundaryResources) { - renderState.boundaryResources.add(_resource); + renderState.boundaryResources.stylesheets.add(_resource); } } } @@ -3400,47 +3448,47 @@ function pushStyle(target, props, resumableState, renderState, textEmbedded, ins } } - var key = getResourceKey('style', href); - var resource = renderState.stylePrecedences.get(precedence); + var key = getResourceKey(href); + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; - if (!resumableState.stylesMap.hasOwnProperty(key)) { - if (!resource) { - resource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [href] - } - }; - renderState.stylePrecedences.set(precedence, resource); - var stylesInPrecedence = new Map(); - stylesInPrecedence.set('', resource); + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; - { - if (renderState.precedences.has(precedence)) { - error('React constructed a new style precedence set when one already exists for this precedence: "%s". This is a bug in React.', precedence); - } + { + if (resourceState) { + error('React encountered a hoistable style tag for the same href as a preload: "%s". When using a style tag to inline styles you should not also preload it as a stylsheet.', href); } + } - renderState.precedences.set(precedence, stylesInPrecedence); + if (!styleQueue) { + // This is the first time we've encountered this precedence we need + // to create a StyleQueue. + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [stringToChunk(escapeTextForBrowser(href))], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); } else { - resource.props.hrefs.push(href); + // We have seen this precedence before and need to track this href + styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href))); } - resumableState.stylesMap[key] = null; - pushStyleContents(resource.chunks, props); + pushStyleContents(styleQueue.rules, props); } - if (resource) { + if (styleQueue) { // We need to track whether this boundary should wait on this resource or not. // Typically this resource should always exist since we either had it or just created // 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.add(resource); + renderState.boundaryResources.styles.add(styleQueue); } } @@ -3529,36 +3577,35 @@ function pushStyleContents(target, props) { return; } -function getImagePreloadKey(href, imageSrcSet, imageSizes) { - var uniquePart = ''; - - if (typeof imageSrcSet === 'string' && imageSrcSet !== '') { - uniquePart += '[' + imageSrcSet + ']'; - - if (typeof imageSizes === 'string') { - uniquePart += '[' + imageSizes + ']'; - } - } else { - uniquePart += '[][]' + href; - } - - return getResourceKey('image', uniquePart); -} - function pushImg(target, props, resumableState, renderState, pictureTagInScope) { var src = props.src, srcSet = props.srcSet; - if (props.loading !== 'lazy' && (typeof src === 'string' || typeof srcSet === 'string') && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded + if (props.loading !== 'lazy' && (src || srcSet) && (typeof src === 'string' || src == null) && (typeof srcSet === 'string' || srcSet == null) && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded !(typeof src === 'string' && src[4] === ':' && (src[0] === 'd' || src[0] === 'D') && (src[1] === 'a' || src[1] === 'A') && (src[2] === 't' || src[2] === 'T') && (src[3] === 'a' || src[3] === 'A')) && !(typeof srcSet === 'string' && srcSet[4] === ':' && (srcSet[0] === 'd' || srcSet[0] === 'D') && (srcSet[1] === 'a' || srcSet[1] === 'A') && (srcSet[2] === 't' || srcSet[2] === 'T') && (srcSet[3] === 'a' || srcSet[3] === 'A'))) { // We have a suspensey image and ought to preload it to optimize the loading of display blocking // resumableState. - var sizes = props.sizes; - var key = getImagePreloadKey(src, srcSet, sizes); - var resource; + var sizes = typeof props.sizes === 'string' ? props.sizes : undefined; + var key = getImageResourceKey(src, srcSet, sizes); + var promotablePreloads = renderState.preloads.images; + var resource = promotablePreloads.get(key); + + if (resource) { + // We consider whether this preload can be promoted to higher priority flushing queue. + // The only time a resource will exist here is if it was created during this render + // and was not already in the high priority queue. + if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { + // Delete the resource from the map since we are promoting it and don't want to + // reenter this branch in a second pass for duplicate img hrefs. + promotablePreloads.delete(key); // $FlowFixMe - Flow should understand that this is a Resource if the condition was true - if (!resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = { + renderState.highImagePreloads.add(resource); + } + } else if (!resumableState.imageResources.hasOwnProperty(key)) { + // We must construct a new preload resource + resumableState.imageResources[key] = PRELOAD_NO_CREDS; + resource = []; + pushLinkImpl(resource, { rel: 'preload', as: 'image', // There is a bug in Safari where imageSrcSet is not respected on preload links @@ -3573,25 +3620,15 @@ function pushImg(target, props, resumableState, renderState, pictureTagInScope) type: props.type, fetchPriority: props.fetchPriority, referrerPolicy: props.referrerPolicy - }; - resource = { - type: 'preload', - chunks: [], - state: NoState, - props: preloadProps - }; - resumableState.preloadsMap[key] = preloadProps; - renderState.preloadsMap.set(key, resource); - pushLinkImpl(resource.chunks, preloadProps); - } else { - resource = renderState.preloadsMap.get(key); - } + }); - if (resource) { if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { renderState.highImagePreloads.add(resource); } else { - renderState.bulkPreloads.add(resource); + renderState.bulkPreloads.add(resource); // We can bump the priority up if the same img is rendered later + // with fetchPriority="high" + + promotablePreloads.set(key, resource); } } } @@ -3765,35 +3802,50 @@ function pushScript(target, props, resumableState, renderState, textEmbedded, in } var src = props.src; - var key = getResourceKey('script', src); // We can make this '); @@ -1761,12 +1779,16 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot fontPreloads: new Set(), highImagePreloads: new Set(), // usedImagePreloads: new Set(), - precedences: new Map(), - stylePrecedences: new Map(), + styles: new Map(), bootstrapScripts: new Set(), scripts: new Set(), bulkPreloads: new Set(), - preloadsMap: new Map(), + preloads: { + images: new Map(), + stylesheets: new Map(), + scripts: new Map(), + moduleScripts: new Map() + }, nonce: nonce, // like a module global for currently rendering boundary boundaryResources: null, @@ -1776,17 +1798,32 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot if (bootstrapScripts !== undefined) { for (var i = 0; i < bootstrapScripts.length; i++) { var scriptConfig = bootstrapScripts[i]; - var src = typeof scriptConfig === 'string' ? scriptConfig : scriptConfig.src; - var integrity = typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity; - var crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; - preloadBootstrapScript(resumableState, renderState, src, nonce, integrity, crossOrigin); + var src = void 0, + crossOrigin = void 0, + integrity = void 0; + var props = { + rel: 'preload', + as: 'script', + fetchPriority: 'low', + nonce: nonce + }; + + if (typeof scriptConfig === 'string') { + props.href = src = scriptConfig; + } else { + props.href = src = scriptConfig.src; + props.integrity = integrity = typeof scriptConfig.integrity === 'string' ? scriptConfig.integrity : undefined; + props.crossOrigin = crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } + + preloadBootstrapScriptOrModule(resumableState, renderState, src, props); bootstrapChunks.push(startScriptSrc, stringToChunk(escapeTextForBrowser(src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (integrity) { + if (typeof integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(integrity))); } @@ -1802,20 +1839,32 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot for (var _i = 0; _i < bootstrapModules.length; _i++) { var _scriptConfig = bootstrapModules[_i]; - var _src = typeof _scriptConfig === 'string' ? _scriptConfig : _scriptConfig.src; + var _src = void 0, + _crossOrigin = void 0, + _integrity = void 0; - var _integrity = typeof _scriptConfig === 'string' ? undefined : _scriptConfig.integrity; + var _props = { + rel: 'modulepreload', + fetchPriority: 'low', + nonce: nonce + }; - var _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + if (typeof _scriptConfig === 'string') { + _props.href = _src = _scriptConfig; + } else { + _props.href = _src = _scriptConfig.src; + _props.integrity = _integrity = typeof _scriptConfig.integrity === 'string' ? _scriptConfig.integrity : undefined; + _props.crossOrigin = _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } - preloadBootstrapModule(resumableState, renderState, _src, nonce, _integrity, _crossOrigin); + preloadBootstrapScriptOrModule(resumableState, renderState, _src, _props); bootstrapChunks.push(startModuleSrc, stringToChunk(escapeTextForBrowser(_src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (_integrity) { + if (typeof _integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(_integrity))); } @@ -1848,10 +1897,18 @@ function createResumableState(identifierPrefix, externalRuntimeConfig) { hasHtml: false, // @TODO add bootstrap script to implicit preloads // persistent - preloadsMap: {}, - preconnectsMap: {}, - stylesMap: {}, - scriptsMap: {} + unknownResources: {}, + dnsResources: {}, + connectResources: { + default: {}, + anonymous: {}, + credentials: {} + }, + imageResources: {}, + styleResources: {}, + scriptResources: {}, + moduleUnknownResources: {}, + moduleScriptResources: {} }; } // Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion // modes. We only include the variants as they matter for the sake of our purposes. @@ -3209,7 +3266,7 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse if (props.rel === 'stylesheet') { // This may hoistable as a Stylesheet Resource, otherwise it will emit in place - var key = getResourceKey('style', href); + var key = getResourceKey(href); if (typeof precedence !== 'string' || props.disabled != null || props.onLoad || props.onError) { // This stylesheet is either not opted into Resource semantics or has conflicting properties which @@ -3230,67 +3287,58 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse return pushLinkImpl(target, props); } else { // This stylesheet refers to a Resource and we create a new one if necessary - var stylesInPrecedence = renderState.precedences.get(precedence); - - if (!resumableState.stylesMap.hasOwnProperty(key)) { - var resourceProps = stylesheetPropsFromRawProps(props); - var state = NoState; + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; + + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; // If this is the first time we've encountered this precedence we need + // to create a StyleQueue + + if (!styleQueue) { + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); + } - if (resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = resumableState.preloadsMap[key]; - adoptPreloadPropsForStylesheetProps(resourceProps, preloadProps); - var preloadResource = renderState.preloadsMap.get(key); + var resource = { + state: PENDING$1, + props: stylesheetPropsFromRawProps(props) + }; - if (preloadResource) { - // If we already had a preload we don't want that resource to flush directly. - // We let the newly created resource govern flushing. - preloadResource.state |= Blocked; + if (resourceState) { + // When resourceState is truty it is a Preload state. We cast it for clarity + var preloadState = resourceState; - if (preloadResource.state & Flushed) { - state = PreloadFlushed; - } - } else { - // If we resumed then we assume that this was already flushed - // by the shell. - state = PreloadFlushed; + if (preloadState.length === 2) { + adoptPreloadCredentials(resource.props, preloadState); } - } - var resource = { - type: 'stylesheet', - chunks: [], - state: state, - props: resourceProps - }; - resumableState.stylesMap[key] = null; - - if (!stylesInPrecedence) { - stylesInPrecedence = new Map(); - renderState.precedences.set(precedence, stylesInPrecedence); - var emptyStyleResource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [] - } - }; - stylesInPrecedence.set('', emptyStyleResource); + var preloadResource = renderState.preloads.stylesheets.get(key); - { - if (renderState.stylePrecedences.has(precedence)) { - error('React constructed an empty style resource when a style resource already exists for this precedence: "%s". This is a bug in React.', precedence); - } + if (preloadResource && preloadResource.length > 0) { + // The Preload for this resource was created in this render pass and has not flushed yet so + // we need to clear it to avoid it flushing. + preloadResource.length = 0; + } else { + // Either the preload resource from this render already flushed in this render pass + // or the preload flushed in a prior pass (prerender). In either case we need to mark + // this resource as already having been preloaded. + resource.state = PRELOADED; } + } // We add the newly created resource to our StyleQueue and if necessary + // track the resource with the currently rendering boundary - renderState.stylePrecedences.set(precedence, emptyStyleResource); - } - stylesInPrecedence.set(key, resource); + styleQueue.sheets.set(key, resource); if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.stylesheets.add(resource); } } else { // We need to track whether this boundary should wait on this resource or not. @@ -3298,12 +3346,12 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse // 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 (stylesInPrecedence) { - var _resource = stylesInPrecedence.get(key); + if (styleQueue) { + var _resource = styleQueue.sheets.get(key); if (_resource) { if (renderState.boundaryResources) { - renderState.boundaryResources.add(_resource); + renderState.boundaryResources.stylesheets.add(_resource); } } } @@ -3401,47 +3449,47 @@ function pushStyle(target, props, resumableState, renderState, textEmbedded, ins } } - var key = getResourceKey('style', href); - var resource = renderState.stylePrecedences.get(precedence); + var key = getResourceKey(href); + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; - if (!resumableState.stylesMap.hasOwnProperty(key)) { - if (!resource) { - resource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [href] - } - }; - renderState.stylePrecedences.set(precedence, resource); - var stylesInPrecedence = new Map(); - stylesInPrecedence.set('', resource); + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; - { - if (renderState.precedences.has(precedence)) { - error('React constructed a new style precedence set when one already exists for this precedence: "%s". This is a bug in React.', precedence); - } + { + if (resourceState) { + error('React encountered a hoistable style tag for the same href as a preload: "%s". When using a style tag to inline styles you should not also preload it as a stylsheet.', href); } + } - renderState.precedences.set(precedence, stylesInPrecedence); + if (!styleQueue) { + // This is the first time we've encountered this precedence we need + // to create a StyleQueue. + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [stringToChunk(escapeTextForBrowser(href))], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); } else { - resource.props.hrefs.push(href); + // We have seen this precedence before and need to track this href + styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href))); } - resumableState.stylesMap[key] = null; - pushStyleContents(resource.chunks, props); + pushStyleContents(styleQueue.rules, props); } - if (resource) { + if (styleQueue) { // We need to track whether this boundary should wait on this resource or not. // Typically this resource should always exist since we either had it or just created // 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.add(resource); + renderState.boundaryResources.styles.add(styleQueue); } } @@ -3530,36 +3578,35 @@ function pushStyleContents(target, props) { return; } -function getImagePreloadKey(href, imageSrcSet, imageSizes) { - var uniquePart = ''; - - if (typeof imageSrcSet === 'string' && imageSrcSet !== '') { - uniquePart += '[' + imageSrcSet + ']'; - - if (typeof imageSizes === 'string') { - uniquePart += '[' + imageSizes + ']'; - } - } else { - uniquePart += '[][]' + href; - } - - return getResourceKey('image', uniquePart); -} - function pushImg(target, props, resumableState, renderState, pictureTagInScope) { var src = props.src, srcSet = props.srcSet; - if (props.loading !== 'lazy' && (typeof src === 'string' || typeof srcSet === 'string') && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded + if (props.loading !== 'lazy' && (src || srcSet) && (typeof src === 'string' || src == null) && (typeof srcSet === 'string' || srcSet == null) && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded !(typeof src === 'string' && src[4] === ':' && (src[0] === 'd' || src[0] === 'D') && (src[1] === 'a' || src[1] === 'A') && (src[2] === 't' || src[2] === 'T') && (src[3] === 'a' || src[3] === 'A')) && !(typeof srcSet === 'string' && srcSet[4] === ':' && (srcSet[0] === 'd' || srcSet[0] === 'D') && (srcSet[1] === 'a' || srcSet[1] === 'A') && (srcSet[2] === 't' || srcSet[2] === 'T') && (srcSet[3] === 'a' || srcSet[3] === 'A'))) { // We have a suspensey image and ought to preload it to optimize the loading of display blocking // resumableState. - var sizes = props.sizes; - var key = getImagePreloadKey(src, srcSet, sizes); - var resource; + var sizes = typeof props.sizes === 'string' ? props.sizes : undefined; + var key = getImageResourceKey(src, srcSet, sizes); + var promotablePreloads = renderState.preloads.images; + var resource = promotablePreloads.get(key); + + if (resource) { + // We consider whether this preload can be promoted to higher priority flushing queue. + // The only time a resource will exist here is if it was created during this render + // and was not already in the high priority queue. + if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { + // Delete the resource from the map since we are promoting it and don't want to + // reenter this branch in a second pass for duplicate img hrefs. + promotablePreloads.delete(key); // $FlowFixMe - Flow should understand that this is a Resource if the condition was true - if (!resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = { + renderState.highImagePreloads.add(resource); + } + } else if (!resumableState.imageResources.hasOwnProperty(key)) { + // We must construct a new preload resource + resumableState.imageResources[key] = PRELOAD_NO_CREDS; + resource = []; + pushLinkImpl(resource, { rel: 'preload', as: 'image', // There is a bug in Safari where imageSrcSet is not respected on preload links @@ -3574,25 +3621,15 @@ function pushImg(target, props, resumableState, renderState, pictureTagInScope) type: props.type, fetchPriority: props.fetchPriority, referrerPolicy: props.referrerPolicy - }; - resource = { - type: 'preload', - chunks: [], - state: NoState, - props: preloadProps - }; - resumableState.preloadsMap[key] = preloadProps; - renderState.preloadsMap.set(key, resource); - pushLinkImpl(resource.chunks, preloadProps); - } else { - resource = renderState.preloadsMap.get(key); - } + }); - if (resource) { if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { renderState.highImagePreloads.add(resource); } else { - renderState.bulkPreloads.add(resource); + renderState.bulkPreloads.add(resource); // We can bump the priority up if the same img is rendered later + // with fetchPriority="high" + + promotablePreloads.set(key, resource); } } } @@ -3766,35 +3803,50 @@ function pushScript(target, props, resumableState, renderState, textEmbedded, in } var src = props.src; - var key = getResourceKey('script', src); // We can make this '); @@ -1840,12 +1858,16 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst fontPreloads: new Set(), highImagePreloads: new Set(), // usedImagePreloads: new Set(), - precedences: new Map(), - stylePrecedences: new Map(), + styles: new Map(), bootstrapScripts: new Set(), scripts: new Set(), bulkPreloads: new Set(), - preloadsMap: new Map(), + preloads: { + images: new Map(), + stylesheets: new Map(), + scripts: new Map(), + moduleScripts: new Map() + }, nonce: nonce, // like a module global for currently rendering boundary boundaryResources: null, @@ -1855,17 +1877,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst if (bootstrapScripts !== undefined) { for (var i = 0; i < bootstrapScripts.length; i++) { var scriptConfig = bootstrapScripts[i]; - var src = typeof scriptConfig === 'string' ? scriptConfig : scriptConfig.src; - var integrity = typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity; - var crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; - preloadBootstrapScript(resumableState, renderState, src, nonce, integrity, crossOrigin); + var src = void 0, + crossOrigin = void 0, + integrity = void 0; + var props = { + rel: 'preload', + as: 'script', + fetchPriority: 'low', + nonce: nonce + }; + + if (typeof scriptConfig === 'string') { + props.href = src = scriptConfig; + } else { + props.href = src = scriptConfig.src; + props.integrity = integrity = typeof scriptConfig.integrity === 'string' ? scriptConfig.integrity : undefined; + props.crossOrigin = crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } + + preloadBootstrapScriptOrModule(resumableState, renderState, src, props); bootstrapChunks.push(startScriptSrc, stringToChunk(escapeTextForBrowser(src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (integrity) { + if (typeof integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(integrity))); } @@ -1881,20 +1918,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst for (var _i = 0; _i < bootstrapModules.length; _i++) { var _scriptConfig = bootstrapModules[_i]; - var _src = typeof _scriptConfig === 'string' ? _scriptConfig : _scriptConfig.src; + var _src = void 0, + _crossOrigin = void 0, + _integrity = void 0; - var _integrity = typeof _scriptConfig === 'string' ? undefined : _scriptConfig.integrity; + var _props = { + rel: 'modulepreload', + fetchPriority: 'low', + nonce: nonce + }; - var _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + if (typeof _scriptConfig === 'string') { + _props.href = _src = _scriptConfig; + } else { + _props.href = _src = _scriptConfig.src; + _props.integrity = _integrity = typeof _scriptConfig.integrity === 'string' ? _scriptConfig.integrity : undefined; + _props.crossOrigin = _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } - preloadBootstrapModule(resumableState, renderState, _src, nonce, _integrity, _crossOrigin); + preloadBootstrapScriptOrModule(resumableState, renderState, _src, _props); bootstrapChunks.push(startModuleSrc, stringToChunk(escapeTextForBrowser(_src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (_integrity) { + if (typeof _integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(_integrity))); } @@ -1931,10 +1980,18 @@ function createResumableState(identifierPrefix, externalRuntimeConfig) { hasHtml: false, // @TODO add bootstrap script to implicit preloads // persistent - preloadsMap: {}, - preconnectsMap: {}, - stylesMap: {}, - scriptsMap: {} + unknownResources: {}, + dnsResources: {}, + connectResources: { + default: {}, + anonymous: {}, + credentials: {} + }, + imageResources: {}, + styleResources: {}, + scriptResources: {}, + moduleUnknownResources: {}, + moduleScriptResources: {} }; } // Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion // modes. We only include the variants as they matter for the sake of our purposes. @@ -3292,7 +3349,7 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse if (props.rel === 'stylesheet') { // This may hoistable as a Stylesheet Resource, otherwise it will emit in place - var key = getResourceKey('style', href); + var key = getResourceKey(href); if (typeof precedence !== 'string' || props.disabled != null || props.onLoad || props.onError) { // This stylesheet is either not opted into Resource semantics or has conflicting properties which @@ -3313,67 +3370,58 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse return pushLinkImpl(target, props); } else { // This stylesheet refers to a Resource and we create a new one if necessary - var stylesInPrecedence = renderState.precedences.get(precedence); - - if (!resumableState.stylesMap.hasOwnProperty(key)) { - var resourceProps = stylesheetPropsFromRawProps(props); - var state = NoState; + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; + + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; // If this is the first time we've encountered this precedence we need + // to create a StyleQueue + + if (!styleQueue) { + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); + } - if (resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = resumableState.preloadsMap[key]; - adoptPreloadPropsForStylesheetProps(resourceProps, preloadProps); - var preloadResource = renderState.preloadsMap.get(key); + var resource = { + state: PENDING$1, + props: stylesheetPropsFromRawProps(props) + }; - if (preloadResource) { - // If we already had a preload we don't want that resource to flush directly. - // We let the newly created resource govern flushing. - preloadResource.state |= Blocked; + if (resourceState) { + // When resourceState is truty it is a Preload state. We cast it for clarity + var preloadState = resourceState; - if (preloadResource.state & Flushed) { - state = PreloadFlushed; - } - } else { - // If we resumed then we assume that this was already flushed - // by the shell. - state = PreloadFlushed; + if (preloadState.length === 2) { + adoptPreloadCredentials(resource.props, preloadState); } - } - var resource = { - type: 'stylesheet', - chunks: [], - state: state, - props: resourceProps - }; - resumableState.stylesMap[key] = null; - - if (!stylesInPrecedence) { - stylesInPrecedence = new Map(); - renderState.precedences.set(precedence, stylesInPrecedence); - var emptyStyleResource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [] - } - }; - stylesInPrecedence.set('', emptyStyleResource); + var preloadResource = renderState.preloads.stylesheets.get(key); - { - if (renderState.stylePrecedences.has(precedence)) { - error('React constructed an empty style resource when a style resource already exists for this precedence: "%s". This is a bug in React.', precedence); - } + if (preloadResource && preloadResource.length > 0) { + // The Preload for this resource was created in this render pass and has not flushed yet so + // we need to clear it to avoid it flushing. + preloadResource.length = 0; + } else { + // Either the preload resource from this render already flushed in this render pass + // or the preload flushed in a prior pass (prerender). In either case we need to mark + // this resource as already having been preloaded. + resource.state = PRELOADED; } + } // We add the newly created resource to our StyleQueue and if necessary + // track the resource with the currently rendering boundary - renderState.stylePrecedences.set(precedence, emptyStyleResource); - } - stylesInPrecedence.set(key, resource); + styleQueue.sheets.set(key, resource); if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.stylesheets.add(resource); } } else { // We need to track whether this boundary should wait on this resource or not. @@ -3381,12 +3429,12 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse // 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 (stylesInPrecedence) { - var _resource = stylesInPrecedence.get(key); + if (styleQueue) { + var _resource = styleQueue.sheets.get(key); if (_resource) { if (renderState.boundaryResources) { - renderState.boundaryResources.add(_resource); + renderState.boundaryResources.stylesheets.add(_resource); } } } @@ -3484,47 +3532,47 @@ function pushStyle(target, props, resumableState, renderState, textEmbedded, ins } } - var key = getResourceKey('style', href); - var resource = renderState.stylePrecedences.get(precedence); + var key = getResourceKey(href); + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; - if (!resumableState.stylesMap.hasOwnProperty(key)) { - if (!resource) { - resource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [href] - } - }; - renderState.stylePrecedences.set(precedence, resource); - var stylesInPrecedence = new Map(); - stylesInPrecedence.set('', resource); + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; - { - if (renderState.precedences.has(precedence)) { - error('React constructed a new style precedence set when one already exists for this precedence: "%s". This is a bug in React.', precedence); - } + { + if (resourceState) { + error('React encountered a hoistable style tag for the same href as a preload: "%s". When using a style tag to inline styles you should not also preload it as a stylsheet.', href); } + } - renderState.precedences.set(precedence, stylesInPrecedence); + if (!styleQueue) { + // This is the first time we've encountered this precedence we need + // to create a StyleQueue. + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [stringToChunk(escapeTextForBrowser(href))], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); } else { - resource.props.hrefs.push(href); + // We have seen this precedence before and need to track this href + styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href))); } - resumableState.stylesMap[key] = null; - pushStyleContents(resource.chunks, props); + pushStyleContents(styleQueue.rules, props); } - if (resource) { + if (styleQueue) { // We need to track whether this boundary should wait on this resource or not. // Typically this resource should always exist since we either had it or just created // 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.add(resource); + renderState.boundaryResources.styles.add(styleQueue); } } @@ -3613,36 +3661,35 @@ function pushStyleContents(target, props) { return; } -function getImagePreloadKey(href, imageSrcSet, imageSizes) { - var uniquePart = ''; - - if (typeof imageSrcSet === 'string' && imageSrcSet !== '') { - uniquePart += '[' + imageSrcSet + ']'; - - if (typeof imageSizes === 'string') { - uniquePart += '[' + imageSizes + ']'; - } - } else { - uniquePart += '[][]' + href; - } - - return getResourceKey('image', uniquePart); -} - function pushImg(target, props, resumableState, renderState, pictureTagInScope) { var src = props.src, srcSet = props.srcSet; - if (props.loading !== 'lazy' && (typeof src === 'string' || typeof srcSet === 'string') && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded + if (props.loading !== 'lazy' && (src || srcSet) && (typeof src === 'string' || src == null) && (typeof srcSet === 'string' || srcSet == null) && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded !(typeof src === 'string' && src[4] === ':' && (src[0] === 'd' || src[0] === 'D') && (src[1] === 'a' || src[1] === 'A') && (src[2] === 't' || src[2] === 'T') && (src[3] === 'a' || src[3] === 'A')) && !(typeof srcSet === 'string' && srcSet[4] === ':' && (srcSet[0] === 'd' || srcSet[0] === 'D') && (srcSet[1] === 'a' || srcSet[1] === 'A') && (srcSet[2] === 't' || srcSet[2] === 'T') && (srcSet[3] === 'a' || srcSet[3] === 'A'))) { // We have a suspensey image and ought to preload it to optimize the loading of display blocking // resumableState. - var sizes = props.sizes; - var key = getImagePreloadKey(src, srcSet, sizes); - var resource; + var sizes = typeof props.sizes === 'string' ? props.sizes : undefined; + var key = getImageResourceKey(src, srcSet, sizes); + var promotablePreloads = renderState.preloads.images; + var resource = promotablePreloads.get(key); + + if (resource) { + // We consider whether this preload can be promoted to higher priority flushing queue. + // The only time a resource will exist here is if it was created during this render + // and was not already in the high priority queue. + if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { + // Delete the resource from the map since we are promoting it and don't want to + // reenter this branch in a second pass for duplicate img hrefs. + promotablePreloads.delete(key); // $FlowFixMe - Flow should understand that this is a Resource if the condition was true - if (!resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = { + renderState.highImagePreloads.add(resource); + } + } else if (!resumableState.imageResources.hasOwnProperty(key)) { + // We must construct a new preload resource + resumableState.imageResources[key] = PRELOAD_NO_CREDS; + resource = []; + pushLinkImpl(resource, { rel: 'preload', as: 'image', // There is a bug in Safari where imageSrcSet is not respected on preload links @@ -3657,25 +3704,15 @@ function pushImg(target, props, resumableState, renderState, pictureTagInScope) type: props.type, fetchPriority: props.fetchPriority, referrerPolicy: props.referrerPolicy - }; - resource = { - type: 'preload', - chunks: [], - state: NoState, - props: preloadProps - }; - resumableState.preloadsMap[key] = preloadProps; - renderState.preloadsMap.set(key, resource); - pushLinkImpl(resource.chunks, preloadProps); - } else { - resource = renderState.preloadsMap.get(key); - } + }); - if (resource) { if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { renderState.highImagePreloads.add(resource); } else { - renderState.bulkPreloads.add(resource); + renderState.bulkPreloads.add(resource); // We can bump the priority up if the same img is rendered later + // with fetchPriority="high" + + promotablePreloads.set(key, resource); } } } @@ -3849,35 +3886,50 @@ function pushScript(target, props, resumableState, renderState, textEmbedded, in } var src = props.src; - var key = getResourceKey('script', src); // We can make this '); @@ -1840,12 +1858,16 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst fontPreloads: new Set(), highImagePreloads: new Set(), // usedImagePreloads: new Set(), - precedences: new Map(), - stylePrecedences: new Map(), + styles: new Map(), bootstrapScripts: new Set(), scripts: new Set(), bulkPreloads: new Set(), - preloadsMap: new Map(), + preloads: { + images: new Map(), + stylesheets: new Map(), + scripts: new Map(), + moduleScripts: new Map() + }, nonce: nonce, // like a module global for currently rendering boundary boundaryResources: null, @@ -1855,17 +1877,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst if (bootstrapScripts !== undefined) { for (var i = 0; i < bootstrapScripts.length; i++) { var scriptConfig = bootstrapScripts[i]; - var src = typeof scriptConfig === 'string' ? scriptConfig : scriptConfig.src; - var integrity = typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity; - var crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; - preloadBootstrapScript(resumableState, renderState, src, nonce, integrity, crossOrigin); + var src = void 0, + crossOrigin = void 0, + integrity = void 0; + var props = { + rel: 'preload', + as: 'script', + fetchPriority: 'low', + nonce: nonce + }; + + if (typeof scriptConfig === 'string') { + props.href = src = scriptConfig; + } else { + props.href = src = scriptConfig.src; + props.integrity = integrity = typeof scriptConfig.integrity === 'string' ? scriptConfig.integrity : undefined; + props.crossOrigin = crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } + + preloadBootstrapScriptOrModule(resumableState, renderState, src, props); bootstrapChunks.push(startScriptSrc, stringToChunk(escapeTextForBrowser(src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (integrity) { + if (typeof integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(integrity))); } @@ -1881,20 +1918,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst for (var _i = 0; _i < bootstrapModules.length; _i++) { var _scriptConfig = bootstrapModules[_i]; - var _src = typeof _scriptConfig === 'string' ? _scriptConfig : _scriptConfig.src; + var _src = void 0, + _crossOrigin = void 0, + _integrity = void 0; - var _integrity = typeof _scriptConfig === 'string' ? undefined : _scriptConfig.integrity; + var _props = { + rel: 'modulepreload', + fetchPriority: 'low', + nonce: nonce + }; - var _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + if (typeof _scriptConfig === 'string') { + _props.href = _src = _scriptConfig; + } else { + _props.href = _src = _scriptConfig.src; + _props.integrity = _integrity = typeof _scriptConfig.integrity === 'string' ? _scriptConfig.integrity : undefined; + _props.crossOrigin = _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } - preloadBootstrapModule(resumableState, renderState, _src, nonce, _integrity, _crossOrigin); + preloadBootstrapScriptOrModule(resumableState, renderState, _src, _props); bootstrapChunks.push(startModuleSrc, stringToChunk(escapeTextForBrowser(_src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (_integrity) { + if (typeof _integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(_integrity))); } @@ -1931,10 +1980,18 @@ function createResumableState(identifierPrefix, externalRuntimeConfig) { hasHtml: false, // @TODO add bootstrap script to implicit preloads // persistent - preloadsMap: {}, - preconnectsMap: {}, - stylesMap: {}, - scriptsMap: {} + unknownResources: {}, + dnsResources: {}, + connectResources: { + default: {}, + anonymous: {}, + credentials: {} + }, + imageResources: {}, + styleResources: {}, + scriptResources: {}, + moduleUnknownResources: {}, + moduleScriptResources: {} }; } // Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion // modes. We only include the variants as they matter for the sake of our purposes. @@ -3292,7 +3349,7 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse if (props.rel === 'stylesheet') { // This may hoistable as a Stylesheet Resource, otherwise it will emit in place - var key = getResourceKey('style', href); + var key = getResourceKey(href); if (typeof precedence !== 'string' || props.disabled != null || props.onLoad || props.onError) { // This stylesheet is either not opted into Resource semantics or has conflicting properties which @@ -3313,67 +3370,58 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse return pushLinkImpl(target, props); } else { // This stylesheet refers to a Resource and we create a new one if necessary - var stylesInPrecedence = renderState.precedences.get(precedence); - - if (!resumableState.stylesMap.hasOwnProperty(key)) { - var resourceProps = stylesheetPropsFromRawProps(props); - var state = NoState; + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; + + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; // If this is the first time we've encountered this precedence we need + // to create a StyleQueue + + if (!styleQueue) { + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); + } - if (resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = resumableState.preloadsMap[key]; - adoptPreloadPropsForStylesheetProps(resourceProps, preloadProps); - var preloadResource = renderState.preloadsMap.get(key); + var resource = { + state: PENDING$1, + props: stylesheetPropsFromRawProps(props) + }; - if (preloadResource) { - // If we already had a preload we don't want that resource to flush directly. - // We let the newly created resource govern flushing. - preloadResource.state |= Blocked; + if (resourceState) { + // When resourceState is truty it is a Preload state. We cast it for clarity + var preloadState = resourceState; - if (preloadResource.state & Flushed) { - state = PreloadFlushed; - } - } else { - // If we resumed then we assume that this was already flushed - // by the shell. - state = PreloadFlushed; + if (preloadState.length === 2) { + adoptPreloadCredentials(resource.props, preloadState); } - } - var resource = { - type: 'stylesheet', - chunks: [], - state: state, - props: resourceProps - }; - resumableState.stylesMap[key] = null; - - if (!stylesInPrecedence) { - stylesInPrecedence = new Map(); - renderState.precedences.set(precedence, stylesInPrecedence); - var emptyStyleResource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [] - } - }; - stylesInPrecedence.set('', emptyStyleResource); + var preloadResource = renderState.preloads.stylesheets.get(key); - { - if (renderState.stylePrecedences.has(precedence)) { - error('React constructed an empty style resource when a style resource already exists for this precedence: "%s". This is a bug in React.', precedence); - } + if (preloadResource && preloadResource.length > 0) { + // The Preload for this resource was created in this render pass and has not flushed yet so + // we need to clear it to avoid it flushing. + preloadResource.length = 0; + } else { + // Either the preload resource from this render already flushed in this render pass + // or the preload flushed in a prior pass (prerender). In either case we need to mark + // this resource as already having been preloaded. + resource.state = PRELOADED; } + } // We add the newly created resource to our StyleQueue and if necessary + // track the resource with the currently rendering boundary - renderState.stylePrecedences.set(precedence, emptyStyleResource); - } - stylesInPrecedence.set(key, resource); + styleQueue.sheets.set(key, resource); if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.stylesheets.add(resource); } } else { // We need to track whether this boundary should wait on this resource or not. @@ -3381,12 +3429,12 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse // 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 (stylesInPrecedence) { - var _resource = stylesInPrecedence.get(key); + if (styleQueue) { + var _resource = styleQueue.sheets.get(key); if (_resource) { if (renderState.boundaryResources) { - renderState.boundaryResources.add(_resource); + renderState.boundaryResources.stylesheets.add(_resource); } } } @@ -3484,47 +3532,47 @@ function pushStyle(target, props, resumableState, renderState, textEmbedded, ins } } - var key = getResourceKey('style', href); - var resource = renderState.stylePrecedences.get(precedence); + var key = getResourceKey(href); + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; - if (!resumableState.stylesMap.hasOwnProperty(key)) { - if (!resource) { - resource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [href] - } - }; - renderState.stylePrecedences.set(precedence, resource); - var stylesInPrecedence = new Map(); - stylesInPrecedence.set('', resource); + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; - { - if (renderState.precedences.has(precedence)) { - error('React constructed a new style precedence set when one already exists for this precedence: "%s". This is a bug in React.', precedence); - } + { + if (resourceState) { + error('React encountered a hoistable style tag for the same href as a preload: "%s". When using a style tag to inline styles you should not also preload it as a stylsheet.', href); } + } - renderState.precedences.set(precedence, stylesInPrecedence); + if (!styleQueue) { + // This is the first time we've encountered this precedence we need + // to create a StyleQueue. + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [stringToChunk(escapeTextForBrowser(href))], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); } else { - resource.props.hrefs.push(href); + // We have seen this precedence before and need to track this href + styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href))); } - resumableState.stylesMap[key] = null; - pushStyleContents(resource.chunks, props); + pushStyleContents(styleQueue.rules, props); } - if (resource) { + if (styleQueue) { // We need to track whether this boundary should wait on this resource or not. // Typically this resource should always exist since we either had it or just created // 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.add(resource); + renderState.boundaryResources.styles.add(styleQueue); } } @@ -3613,36 +3661,35 @@ function pushStyleContents(target, props) { return; } -function getImagePreloadKey(href, imageSrcSet, imageSizes) { - var uniquePart = ''; - - if (typeof imageSrcSet === 'string' && imageSrcSet !== '') { - uniquePart += '[' + imageSrcSet + ']'; - - if (typeof imageSizes === 'string') { - uniquePart += '[' + imageSizes + ']'; - } - } else { - uniquePart += '[][]' + href; - } - - return getResourceKey('image', uniquePart); -} - function pushImg(target, props, resumableState, renderState, pictureTagInScope) { var src = props.src, srcSet = props.srcSet; - if (props.loading !== 'lazy' && (typeof src === 'string' || typeof srcSet === 'string') && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded + if (props.loading !== 'lazy' && (src || srcSet) && (typeof src === 'string' || src == null) && (typeof srcSet === 'string' || srcSet == null) && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded !(typeof src === 'string' && src[4] === ':' && (src[0] === 'd' || src[0] === 'D') && (src[1] === 'a' || src[1] === 'A') && (src[2] === 't' || src[2] === 'T') && (src[3] === 'a' || src[3] === 'A')) && !(typeof srcSet === 'string' && srcSet[4] === ':' && (srcSet[0] === 'd' || srcSet[0] === 'D') && (srcSet[1] === 'a' || srcSet[1] === 'A') && (srcSet[2] === 't' || srcSet[2] === 'T') && (srcSet[3] === 'a' || srcSet[3] === 'A'))) { // We have a suspensey image and ought to preload it to optimize the loading of display blocking // resumableState. - var sizes = props.sizes; - var key = getImagePreloadKey(src, srcSet, sizes); - var resource; + var sizes = typeof props.sizes === 'string' ? props.sizes : undefined; + var key = getImageResourceKey(src, srcSet, sizes); + var promotablePreloads = renderState.preloads.images; + var resource = promotablePreloads.get(key); + + if (resource) { + // We consider whether this preload can be promoted to higher priority flushing queue. + // The only time a resource will exist here is if it was created during this render + // and was not already in the high priority queue. + if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { + // Delete the resource from the map since we are promoting it and don't want to + // reenter this branch in a second pass for duplicate img hrefs. + promotablePreloads.delete(key); // $FlowFixMe - Flow should understand that this is a Resource if the condition was true - if (!resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = { + renderState.highImagePreloads.add(resource); + } + } else if (!resumableState.imageResources.hasOwnProperty(key)) { + // We must construct a new preload resource + resumableState.imageResources[key] = PRELOAD_NO_CREDS; + resource = []; + pushLinkImpl(resource, { rel: 'preload', as: 'image', // There is a bug in Safari where imageSrcSet is not respected on preload links @@ -3657,25 +3704,15 @@ function pushImg(target, props, resumableState, renderState, pictureTagInScope) type: props.type, fetchPriority: props.fetchPriority, referrerPolicy: props.referrerPolicy - }; - resource = { - type: 'preload', - chunks: [], - state: NoState, - props: preloadProps - }; - resumableState.preloadsMap[key] = preloadProps; - renderState.preloadsMap.set(key, resource); - pushLinkImpl(resource.chunks, preloadProps); - } else { - resource = renderState.preloadsMap.get(key); - } + }); - if (resource) { if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { renderState.highImagePreloads.add(resource); } else { - renderState.bulkPreloads.add(resource); + renderState.bulkPreloads.add(resource); // We can bump the priority up if the same img is rendered later + // with fetchPriority="high" + + promotablePreloads.set(key, resource); } } } @@ -3849,35 +3886,50 @@ function pushScript(target, props, resumableState, renderState, textEmbedded, in } var src = props.src; - var key = getResourceKey('script', src); // We can make this '); @@ -1823,12 +1841,16 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst fontPreloads: new Set(), highImagePreloads: new Set(), // usedImagePreloads: new Set(), - precedences: new Map(), - stylePrecedences: new Map(), + styles: new Map(), bootstrapScripts: new Set(), scripts: new Set(), bulkPreloads: new Set(), - preloadsMap: new Map(), + preloads: { + images: new Map(), + stylesheets: new Map(), + scripts: new Map(), + moduleScripts: new Map() + }, nonce: nonce, // like a module global for currently rendering boundary boundaryResources: null, @@ -1838,17 +1860,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst if (bootstrapScripts !== undefined) { for (var i = 0; i < bootstrapScripts.length; i++) { var scriptConfig = bootstrapScripts[i]; - var src = typeof scriptConfig === 'string' ? scriptConfig : scriptConfig.src; - var integrity = typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity; - var crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; - preloadBootstrapScript(resumableState, renderState, src, nonce, integrity, crossOrigin); + var src = void 0, + crossOrigin = void 0, + integrity = void 0; + var props = { + rel: 'preload', + as: 'script', + fetchPriority: 'low', + nonce: nonce + }; + + if (typeof scriptConfig === 'string') { + props.href = src = scriptConfig; + } else { + props.href = src = scriptConfig.src; + props.integrity = integrity = typeof scriptConfig.integrity === 'string' ? scriptConfig.integrity : undefined; + props.crossOrigin = crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } + + preloadBootstrapScriptOrModule(resumableState, renderState, src, props); bootstrapChunks.push(startScriptSrc, stringToChunk(escapeTextForBrowser(src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (integrity) { + if (typeof integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(integrity))); } @@ -1864,20 +1901,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst for (var _i = 0; _i < bootstrapModules.length; _i++) { var _scriptConfig = bootstrapModules[_i]; - var _src = typeof _scriptConfig === 'string' ? _scriptConfig : _scriptConfig.src; + var _src = void 0, + _crossOrigin = void 0, + _integrity = void 0; - var _integrity = typeof _scriptConfig === 'string' ? undefined : _scriptConfig.integrity; + var _props = { + rel: 'modulepreload', + fetchPriority: 'low', + nonce: nonce + }; - var _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + if (typeof _scriptConfig === 'string') { + _props.href = _src = _scriptConfig; + } else { + _props.href = _src = _scriptConfig.src; + _props.integrity = _integrity = typeof _scriptConfig.integrity === 'string' ? _scriptConfig.integrity : undefined; + _props.crossOrigin = _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } - preloadBootstrapModule(resumableState, renderState, _src, nonce, _integrity, _crossOrigin); + preloadBootstrapScriptOrModule(resumableState, renderState, _src, _props); bootstrapChunks.push(startModuleSrc, stringToChunk(escapeTextForBrowser(_src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (_integrity) { + if (typeof _integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(_integrity))); } @@ -1914,10 +1963,18 @@ function createResumableState(identifierPrefix, externalRuntimeConfig) { hasHtml: false, // @TODO add bootstrap script to implicit preloads // persistent - preloadsMap: {}, - preconnectsMap: {}, - stylesMap: {}, - scriptsMap: {} + unknownResources: {}, + dnsResources: {}, + connectResources: { + default: {}, + anonymous: {}, + credentials: {} + }, + imageResources: {}, + styleResources: {}, + scriptResources: {}, + moduleUnknownResources: {}, + moduleScriptResources: {} }; } // Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion // modes. We only include the variants as they matter for the sake of our purposes. @@ -3275,7 +3332,7 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse if (props.rel === 'stylesheet') { // This may hoistable as a Stylesheet Resource, otherwise it will emit in place - var key = getResourceKey('style', href); + var key = getResourceKey(href); if (typeof precedence !== 'string' || props.disabled != null || props.onLoad || props.onError) { // This stylesheet is either not opted into Resource semantics or has conflicting properties which @@ -3296,67 +3353,58 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse return pushLinkImpl(target, props); } else { // This stylesheet refers to a Resource and we create a new one if necessary - var stylesInPrecedence = renderState.precedences.get(precedence); - - if (!resumableState.stylesMap.hasOwnProperty(key)) { - var resourceProps = stylesheetPropsFromRawProps(props); - var state = NoState; + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; + + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; // If this is the first time we've encountered this precedence we need + // to create a StyleQueue + + if (!styleQueue) { + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); + } - if (resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = resumableState.preloadsMap[key]; - adoptPreloadPropsForStylesheetProps(resourceProps, preloadProps); - var preloadResource = renderState.preloadsMap.get(key); + var resource = { + state: PENDING$1, + props: stylesheetPropsFromRawProps(props) + }; - if (preloadResource) { - // If we already had a preload we don't want that resource to flush directly. - // We let the newly created resource govern flushing. - preloadResource.state |= Blocked; + if (resourceState) { + // When resourceState is truty it is a Preload state. We cast it for clarity + var preloadState = resourceState; - if (preloadResource.state & Flushed) { - state = PreloadFlushed; - } - } else { - // If we resumed then we assume that this was already flushed - // by the shell. - state = PreloadFlushed; + if (preloadState.length === 2) { + adoptPreloadCredentials(resource.props, preloadState); } - } - var resource = { - type: 'stylesheet', - chunks: [], - state: state, - props: resourceProps - }; - resumableState.stylesMap[key] = null; - - if (!stylesInPrecedence) { - stylesInPrecedence = new Map(); - renderState.precedences.set(precedence, stylesInPrecedence); - var emptyStyleResource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [] - } - }; - stylesInPrecedence.set('', emptyStyleResource); + var preloadResource = renderState.preloads.stylesheets.get(key); - { - if (renderState.stylePrecedences.has(precedence)) { - error('React constructed an empty style resource when a style resource already exists for this precedence: "%s". This is a bug in React.', precedence); - } + if (preloadResource && preloadResource.length > 0) { + // The Preload for this resource was created in this render pass and has not flushed yet so + // we need to clear it to avoid it flushing. + preloadResource.length = 0; + } else { + // Either the preload resource from this render already flushed in this render pass + // or the preload flushed in a prior pass (prerender). In either case we need to mark + // this resource as already having been preloaded. + resource.state = PRELOADED; } + } // We add the newly created resource to our StyleQueue and if necessary + // track the resource with the currently rendering boundary - renderState.stylePrecedences.set(precedence, emptyStyleResource); - } - stylesInPrecedence.set(key, resource); + styleQueue.sheets.set(key, resource); if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.stylesheets.add(resource); } } else { // We need to track whether this boundary should wait on this resource or not. @@ -3364,12 +3412,12 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse // 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 (stylesInPrecedence) { - var _resource = stylesInPrecedence.get(key); + if (styleQueue) { + var _resource = styleQueue.sheets.get(key); if (_resource) { if (renderState.boundaryResources) { - renderState.boundaryResources.add(_resource); + renderState.boundaryResources.stylesheets.add(_resource); } } } @@ -3467,47 +3515,47 @@ function pushStyle(target, props, resumableState, renderState, textEmbedded, ins } } - var key = getResourceKey('style', href); - var resource = renderState.stylePrecedences.get(precedence); + var key = getResourceKey(href); + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; - if (!resumableState.stylesMap.hasOwnProperty(key)) { - if (!resource) { - resource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [href] - } - }; - renderState.stylePrecedences.set(precedence, resource); - var stylesInPrecedence = new Map(); - stylesInPrecedence.set('', resource); + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; - { - if (renderState.precedences.has(precedence)) { - error('React constructed a new style precedence set when one already exists for this precedence: "%s". This is a bug in React.', precedence); - } + { + if (resourceState) { + error('React encountered a hoistable style tag for the same href as a preload: "%s". When using a style tag to inline styles you should not also preload it as a stylsheet.', href); } + } - renderState.precedences.set(precedence, stylesInPrecedence); + if (!styleQueue) { + // This is the first time we've encountered this precedence we need + // to create a StyleQueue. + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [stringToChunk(escapeTextForBrowser(href))], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); } else { - resource.props.hrefs.push(href); + // We have seen this precedence before and need to track this href + styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href))); } - resumableState.stylesMap[key] = null; - pushStyleContents(resource.chunks, props); + pushStyleContents(styleQueue.rules, props); } - if (resource) { + if (styleQueue) { // We need to track whether this boundary should wait on this resource or not. // Typically this resource should always exist since we either had it or just created // 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.add(resource); + renderState.boundaryResources.styles.add(styleQueue); } } @@ -3596,36 +3644,35 @@ function pushStyleContents(target, props) { return; } -function getImagePreloadKey(href, imageSrcSet, imageSizes) { - var uniquePart = ''; - - if (typeof imageSrcSet === 'string' && imageSrcSet !== '') { - uniquePart += '[' + imageSrcSet + ']'; - - if (typeof imageSizes === 'string') { - uniquePart += '[' + imageSizes + ']'; - } - } else { - uniquePart += '[][]' + href; - } - - return getResourceKey('image', uniquePart); -} - function pushImg(target, props, resumableState, renderState, pictureTagInScope) { var src = props.src, srcSet = props.srcSet; - if (props.loading !== 'lazy' && (typeof src === 'string' || typeof srcSet === 'string') && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded + if (props.loading !== 'lazy' && (src || srcSet) && (typeof src === 'string' || src == null) && (typeof srcSet === 'string' || srcSet == null) && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded !(typeof src === 'string' && src[4] === ':' && (src[0] === 'd' || src[0] === 'D') && (src[1] === 'a' || src[1] === 'A') && (src[2] === 't' || src[2] === 'T') && (src[3] === 'a' || src[3] === 'A')) && !(typeof srcSet === 'string' && srcSet[4] === ':' && (srcSet[0] === 'd' || srcSet[0] === 'D') && (srcSet[1] === 'a' || srcSet[1] === 'A') && (srcSet[2] === 't' || srcSet[2] === 'T') && (srcSet[3] === 'a' || srcSet[3] === 'A'))) { // We have a suspensey image and ought to preload it to optimize the loading of display blocking // resumableState. - var sizes = props.sizes; - var key = getImagePreloadKey(src, srcSet, sizes); - var resource; + var sizes = typeof props.sizes === 'string' ? props.sizes : undefined; + var key = getImageResourceKey(src, srcSet, sizes); + var promotablePreloads = renderState.preloads.images; + var resource = promotablePreloads.get(key); + + if (resource) { + // We consider whether this preload can be promoted to higher priority flushing queue. + // The only time a resource will exist here is if it was created during this render + // and was not already in the high priority queue. + if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { + // Delete the resource from the map since we are promoting it and don't want to + // reenter this branch in a second pass for duplicate img hrefs. + promotablePreloads.delete(key); // $FlowFixMe - Flow should understand that this is a Resource if the condition was true - if (!resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = { + renderState.highImagePreloads.add(resource); + } + } else if (!resumableState.imageResources.hasOwnProperty(key)) { + // We must construct a new preload resource + resumableState.imageResources[key] = PRELOAD_NO_CREDS; + resource = []; + pushLinkImpl(resource, { rel: 'preload', as: 'image', // There is a bug in Safari where imageSrcSet is not respected on preload links @@ -3640,25 +3687,15 @@ function pushImg(target, props, resumableState, renderState, pictureTagInScope) type: props.type, fetchPriority: props.fetchPriority, referrerPolicy: props.referrerPolicy - }; - resource = { - type: 'preload', - chunks: [], - state: NoState, - props: preloadProps - }; - resumableState.preloadsMap[key] = preloadProps; - renderState.preloadsMap.set(key, resource); - pushLinkImpl(resource.chunks, preloadProps); - } else { - resource = renderState.preloadsMap.get(key); - } + }); - if (resource) { if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { renderState.highImagePreloads.add(resource); } else { - renderState.bulkPreloads.add(resource); + renderState.bulkPreloads.add(resource); // We can bump the priority up if the same img is rendered later + // with fetchPriority="high" + + promotablePreloads.set(key, resource); } } } @@ -3832,35 +3869,50 @@ function pushScript(target, props, resumableState, renderState, textEmbedded, in } var src = props.src; - var key = getResourceKey('script', src); // We can make this '); @@ -1630,12 +1648,16 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot fontPreloads: new Set(), highImagePreloads: new Set(), // usedImagePreloads: new Set(), - precedences: new Map(), - stylePrecedences: new Map(), + styles: new Map(), bootstrapScripts: new Set(), scripts: new Set(), bulkPreloads: new Set(), - preloadsMap: new Map(), + preloads: { + images: new Map(), + stylesheets: new Map(), + scripts: new Map(), + moduleScripts: new Map() + }, nonce: nonce, // like a module global for currently rendering boundary boundaryResources: null, @@ -1645,17 +1667,32 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot if (bootstrapScripts !== undefined) { for (var i = 0; i < bootstrapScripts.length; i++) { var scriptConfig = bootstrapScripts[i]; - var src = typeof scriptConfig === 'string' ? scriptConfig : scriptConfig.src; - var integrity = typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity; - var crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; - preloadBootstrapScript(resumableState, renderState, src, nonce, integrity, crossOrigin); + var src = void 0, + crossOrigin = void 0, + integrity = void 0; + var props = { + rel: 'preload', + as: 'script', + fetchPriority: 'low', + nonce: nonce + }; + + if (typeof scriptConfig === 'string') { + props.href = src = scriptConfig; + } else { + props.href = src = scriptConfig.src; + props.integrity = integrity = typeof scriptConfig.integrity === 'string' ? scriptConfig.integrity : undefined; + props.crossOrigin = crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } + + preloadBootstrapScriptOrModule(resumableState, renderState, src, props); bootstrapChunks.push(startScriptSrc, stringToChunk(escapeTextForBrowser(src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (integrity) { + if (typeof integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(integrity))); } @@ -1671,20 +1708,32 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot for (var _i = 0; _i < bootstrapModules.length; _i++) { var _scriptConfig = bootstrapModules[_i]; - var _src = typeof _scriptConfig === 'string' ? _scriptConfig : _scriptConfig.src; + var _src = void 0, + _crossOrigin = void 0, + _integrity = void 0; - var _integrity = typeof _scriptConfig === 'string' ? undefined : _scriptConfig.integrity; + var _props = { + rel: 'modulepreload', + fetchPriority: 'low', + nonce: nonce + }; - var _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + if (typeof _scriptConfig === 'string') { + _props.href = _src = _scriptConfig; + } else { + _props.href = _src = _scriptConfig.src; + _props.integrity = _integrity = typeof _scriptConfig.integrity === 'string' ? _scriptConfig.integrity : undefined; + _props.crossOrigin = _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } - preloadBootstrapModule(resumableState, renderState, _src, nonce, _integrity, _crossOrigin); + preloadBootstrapScriptOrModule(resumableState, renderState, _src, _props); bootstrapChunks.push(startModuleSrc, stringToChunk(escapeTextForBrowser(_src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (_integrity) { + if (typeof _integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(_integrity))); } @@ -1717,10 +1766,18 @@ function createResumableState(identifierPrefix, externalRuntimeConfig) { hasHtml: false, // @TODO add bootstrap script to implicit preloads // persistent - preloadsMap: {}, - preconnectsMap: {}, - stylesMap: {}, - scriptsMap: {} + unknownResources: {}, + dnsResources: {}, + connectResources: { + default: {}, + anonymous: {}, + credentials: {} + }, + imageResources: {}, + styleResources: {}, + scriptResources: {}, + moduleUnknownResources: {}, + moduleScriptResources: {} }; } // Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion // modes. We only include the variants as they matter for the sake of our purposes. @@ -2938,7 +2995,7 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse if (props.rel === 'stylesheet') { // This may hoistable as a Stylesheet Resource, otherwise it will emit in place - var key = getResourceKey('style', href); + var key = getResourceKey(href); if (typeof precedence !== 'string' || props.disabled != null || props.onLoad || props.onError) { // This stylesheet is either not opted into Resource semantics or has conflicting properties which @@ -2959,67 +3016,58 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse return pushLinkImpl(target, props); } else { // This stylesheet refers to a Resource and we create a new one if necessary - var stylesInPrecedence = renderState.precedences.get(precedence); - - if (!resumableState.stylesMap.hasOwnProperty(key)) { - var resourceProps = stylesheetPropsFromRawProps(props); - var state = NoState; + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; + + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; // If this is the first time we've encountered this precedence we need + // to create a StyleQueue + + if (!styleQueue) { + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); + } - if (resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = resumableState.preloadsMap[key]; - adoptPreloadPropsForStylesheetProps(resourceProps, preloadProps); - var preloadResource = renderState.preloadsMap.get(key); + var resource = { + state: PENDING$1, + props: stylesheetPropsFromRawProps(props) + }; - if (preloadResource) { - // If we already had a preload we don't want that resource to flush directly. - // We let the newly created resource govern flushing. - preloadResource.state |= Blocked; + if (resourceState) { + // When resourceState is truty it is a Preload state. We cast it for clarity + var preloadState = resourceState; - if (preloadResource.state & Flushed) { - state = PreloadFlushed; - } - } else { - // If we resumed then we assume that this was already flushed - // by the shell. - state = PreloadFlushed; + if (preloadState.length === 2) { + adoptPreloadCredentials(resource.props, preloadState); } - } - var resource = { - type: 'stylesheet', - chunks: [], - state: state, - props: resourceProps - }; - resumableState.stylesMap[key] = null; - - if (!stylesInPrecedence) { - stylesInPrecedence = new Map(); - renderState.precedences.set(precedence, stylesInPrecedence); - var emptyStyleResource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [] - } - }; - stylesInPrecedence.set('', emptyStyleResource); + var preloadResource = renderState.preloads.stylesheets.get(key); - { - if (renderState.stylePrecedences.has(precedence)) { - error('React constructed an empty style resource when a style resource already exists for this precedence: "%s". This is a bug in React.', precedence); - } + if (preloadResource && preloadResource.length > 0) { + // The Preload for this resource was created in this render pass and has not flushed yet so + // we need to clear it to avoid it flushing. + preloadResource.length = 0; + } else { + // Either the preload resource from this render already flushed in this render pass + // or the preload flushed in a prior pass (prerender). In either case we need to mark + // this resource as already having been preloaded. + resource.state = PRELOADED; } + } // We add the newly created resource to our StyleQueue and if necessary + // track the resource with the currently rendering boundary - renderState.stylePrecedences.set(precedence, emptyStyleResource); - } - stylesInPrecedence.set(key, resource); + styleQueue.sheets.set(key, resource); if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.stylesheets.add(resource); } } else { // We need to track whether this boundary should wait on this resource or not. @@ -3027,12 +3075,12 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse // 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 (stylesInPrecedence) { - var _resource = stylesInPrecedence.get(key); + if (styleQueue) { + var _resource = styleQueue.sheets.get(key); if (_resource) { if (renderState.boundaryResources) { - renderState.boundaryResources.add(_resource); + renderState.boundaryResources.stylesheets.add(_resource); } } } @@ -3130,47 +3178,47 @@ function pushStyle(target, props, resumableState, renderState, textEmbedded, ins } } - var key = getResourceKey('style', href); - var resource = renderState.stylePrecedences.get(precedence); + var key = getResourceKey(href); + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; - if (!resumableState.stylesMap.hasOwnProperty(key)) { - if (!resource) { - resource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [href] - } - }; - renderState.stylePrecedences.set(precedence, resource); - var stylesInPrecedence = new Map(); - stylesInPrecedence.set('', resource); + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; - { - if (renderState.precedences.has(precedence)) { - error('React constructed a new style precedence set when one already exists for this precedence: "%s". This is a bug in React.', precedence); - } + { + if (resourceState) { + error('React encountered a hoistable style tag for the same href as a preload: "%s". When using a style tag to inline styles you should not also preload it as a stylsheet.', href); } + } - renderState.precedences.set(precedence, stylesInPrecedence); + if (!styleQueue) { + // This is the first time we've encountered this precedence we need + // to create a StyleQueue. + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [stringToChunk(escapeTextForBrowser(href))], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); } else { - resource.props.hrefs.push(href); + // We have seen this precedence before and need to track this href + styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href))); } - resumableState.stylesMap[key] = null; - pushStyleContents(resource.chunks, props); + pushStyleContents(styleQueue.rules, props); } - if (resource) { + if (styleQueue) { // We need to track whether this boundary should wait on this resource or not. // Typically this resource should always exist since we either had it or just created // 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.add(resource); + renderState.boundaryResources.styles.add(styleQueue); } } @@ -3259,36 +3307,35 @@ function pushStyleContents(target, props) { return; } -function getImagePreloadKey(href, imageSrcSet, imageSizes) { - var uniquePart = ''; - - if (typeof imageSrcSet === 'string' && imageSrcSet !== '') { - uniquePart += '[' + imageSrcSet + ']'; - - if (typeof imageSizes === 'string') { - uniquePart += '[' + imageSizes + ']'; - } - } else { - uniquePart += '[][]' + href; - } - - return getResourceKey('image', uniquePart); -} - function pushImg(target, props, resumableState, renderState, pictureTagInScope) { var src = props.src, srcSet = props.srcSet; - if (props.loading !== 'lazy' && (typeof src === 'string' || typeof srcSet === 'string') && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded + if (props.loading !== 'lazy' && (src || srcSet) && (typeof src === 'string' || src == null) && (typeof srcSet === 'string' || srcSet == null) && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded !(typeof src === 'string' && src[4] === ':' && (src[0] === 'd' || src[0] === 'D') && (src[1] === 'a' || src[1] === 'A') && (src[2] === 't' || src[2] === 'T') && (src[3] === 'a' || src[3] === 'A')) && !(typeof srcSet === 'string' && srcSet[4] === ':' && (srcSet[0] === 'd' || srcSet[0] === 'D') && (srcSet[1] === 'a' || srcSet[1] === 'A') && (srcSet[2] === 't' || srcSet[2] === 'T') && (srcSet[3] === 'a' || srcSet[3] === 'A'))) { // We have a suspensey image and ought to preload it to optimize the loading of display blocking // resumableState. - var sizes = props.sizes; - var key = getImagePreloadKey(src, srcSet, sizes); - var resource; + var sizes = typeof props.sizes === 'string' ? props.sizes : undefined; + var key = getImageResourceKey(src, srcSet, sizes); + var promotablePreloads = renderState.preloads.images; + var resource = promotablePreloads.get(key); - if (!resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = { + if (resource) { + // We consider whether this preload can be promoted to higher priority flushing queue. + // The only time a resource will exist here is if it was created during this render + // and was not already in the high priority queue. + if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { + // Delete the resource from the map since we are promoting it and don't want to + // reenter this branch in a second pass for duplicate img hrefs. + promotablePreloads.delete(key); // $FlowFixMe - Flow should understand that this is a Resource if the condition was true + + renderState.highImagePreloads.add(resource); + } + } else if (!resumableState.imageResources.hasOwnProperty(key)) { + // We must construct a new preload resource + resumableState.imageResources[key] = PRELOAD_NO_CREDS; + resource = []; + pushLinkImpl(resource, { rel: 'preload', as: 'image', // There is a bug in Safari where imageSrcSet is not respected on preload links @@ -3303,25 +3350,15 @@ function pushImg(target, props, resumableState, renderState, pictureTagInScope) type: props.type, fetchPriority: props.fetchPriority, referrerPolicy: props.referrerPolicy - }; - resource = { - type: 'preload', - chunks: [], - state: NoState, - props: preloadProps - }; - resumableState.preloadsMap[key] = preloadProps; - renderState.preloadsMap.set(key, resource); - pushLinkImpl(resource.chunks, preloadProps); - } else { - resource = renderState.preloadsMap.get(key); - } + }); - if (resource) { if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { renderState.highImagePreloads.add(resource); } else { - renderState.bulkPreloads.add(resource); + renderState.bulkPreloads.add(resource); // We can bump the priority up if the same img is rendered later + // with fetchPriority="high" + + promotablePreloads.set(key, resource); } } } @@ -3495,35 +3532,50 @@ function pushScript(target, props, resumableState, renderState, textEmbedded, in } var src = props.src; - var key = getResourceKey('script', src); // We can make this '); @@ -1631,12 +1649,16 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot fontPreloads: new Set(), highImagePreloads: new Set(), // usedImagePreloads: new Set(), - precedences: new Map(), - stylePrecedences: new Map(), + styles: new Map(), bootstrapScripts: new Set(), scripts: new Set(), bulkPreloads: new Set(), - preloadsMap: new Map(), + preloads: { + images: new Map(), + stylesheets: new Map(), + scripts: new Map(), + moduleScripts: new Map() + }, nonce: nonce, // like a module global for currently rendering boundary boundaryResources: null, @@ -1646,17 +1668,32 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot if (bootstrapScripts !== undefined) { for (var i = 0; i < bootstrapScripts.length; i++) { var scriptConfig = bootstrapScripts[i]; - var src = typeof scriptConfig === 'string' ? scriptConfig : scriptConfig.src; - var integrity = typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity; - var crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; - preloadBootstrapScript(resumableState, renderState, src, nonce, integrity, crossOrigin); + var src = void 0, + crossOrigin = void 0, + integrity = void 0; + var props = { + rel: 'preload', + as: 'script', + fetchPriority: 'low', + nonce: nonce + }; + + if (typeof scriptConfig === 'string') { + props.href = src = scriptConfig; + } else { + props.href = src = scriptConfig.src; + props.integrity = integrity = typeof scriptConfig.integrity === 'string' ? scriptConfig.integrity : undefined; + props.crossOrigin = crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } + + preloadBootstrapScriptOrModule(resumableState, renderState, src, props); bootstrapChunks.push(startScriptSrc, stringToChunk(escapeTextForBrowser(src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (integrity) { + if (typeof integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(integrity))); } @@ -1672,20 +1709,32 @@ function createRenderState$1(resumableState, nonce, bootstrapScriptContent, boot for (var _i = 0; _i < bootstrapModules.length; _i++) { var _scriptConfig = bootstrapModules[_i]; - var _src = typeof _scriptConfig === 'string' ? _scriptConfig : _scriptConfig.src; + var _src = void 0, + _crossOrigin = void 0, + _integrity = void 0; - var _integrity = typeof _scriptConfig === 'string' ? undefined : _scriptConfig.integrity; + var _props = { + rel: 'modulepreload', + fetchPriority: 'low', + nonce: nonce + }; - var _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + if (typeof _scriptConfig === 'string') { + _props.href = _src = _scriptConfig; + } else { + _props.href = _src = _scriptConfig.src; + _props.integrity = _integrity = typeof _scriptConfig.integrity === 'string' ? _scriptConfig.integrity : undefined; + _props.crossOrigin = _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } - preloadBootstrapModule(resumableState, renderState, _src, nonce, _integrity, _crossOrigin); + preloadBootstrapScriptOrModule(resumableState, renderState, _src, _props); bootstrapChunks.push(startModuleSrc, stringToChunk(escapeTextForBrowser(_src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (_integrity) { + if (typeof _integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(_integrity))); } @@ -1718,10 +1767,18 @@ function createResumableState(identifierPrefix, externalRuntimeConfig) { hasHtml: false, // @TODO add bootstrap script to implicit preloads // persistent - preloadsMap: {}, - preconnectsMap: {}, - stylesMap: {}, - scriptsMap: {} + unknownResources: {}, + dnsResources: {}, + connectResources: { + default: {}, + anonymous: {}, + credentials: {} + }, + imageResources: {}, + styleResources: {}, + scriptResources: {}, + moduleUnknownResources: {}, + moduleScriptResources: {} }; } // Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion // modes. We only include the variants as they matter for the sake of our purposes. @@ -2939,7 +2996,7 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse if (props.rel === 'stylesheet') { // This may hoistable as a Stylesheet Resource, otherwise it will emit in place - var key = getResourceKey('style', href); + var key = getResourceKey(href); if (typeof precedence !== 'string' || props.disabled != null || props.onLoad || props.onError) { // This stylesheet is either not opted into Resource semantics or has conflicting properties which @@ -2960,67 +3017,58 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse return pushLinkImpl(target, props); } else { // This stylesheet refers to a Resource and we create a new one if necessary - var stylesInPrecedence = renderState.precedences.get(precedence); - - if (!resumableState.stylesMap.hasOwnProperty(key)) { - var resourceProps = stylesheetPropsFromRawProps(props); - var state = NoState; + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; + + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; // If this is the first time we've encountered this precedence we need + // to create a StyleQueue + + if (!styleQueue) { + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); + } - if (resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = resumableState.preloadsMap[key]; - adoptPreloadPropsForStylesheetProps(resourceProps, preloadProps); - var preloadResource = renderState.preloadsMap.get(key); + var resource = { + state: PENDING$1, + props: stylesheetPropsFromRawProps(props) + }; - if (preloadResource) { - // If we already had a preload we don't want that resource to flush directly. - // We let the newly created resource govern flushing. - preloadResource.state |= Blocked; + if (resourceState) { + // When resourceState is truty it is a Preload state. We cast it for clarity + var preloadState = resourceState; - if (preloadResource.state & Flushed) { - state = PreloadFlushed; - } - } else { - // If we resumed then we assume that this was already flushed - // by the shell. - state = PreloadFlushed; + if (preloadState.length === 2) { + adoptPreloadCredentials(resource.props, preloadState); } - } - var resource = { - type: 'stylesheet', - chunks: [], - state: state, - props: resourceProps - }; - resumableState.stylesMap[key] = null; - - if (!stylesInPrecedence) { - stylesInPrecedence = new Map(); - renderState.precedences.set(precedence, stylesInPrecedence); - var emptyStyleResource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [] - } - }; - stylesInPrecedence.set('', emptyStyleResource); + var preloadResource = renderState.preloads.stylesheets.get(key); - { - if (renderState.stylePrecedences.has(precedence)) { - error('React constructed an empty style resource when a style resource already exists for this precedence: "%s". This is a bug in React.', precedence); - } + if (preloadResource && preloadResource.length > 0) { + // The Preload for this resource was created in this render pass and has not flushed yet so + // we need to clear it to avoid it flushing. + preloadResource.length = 0; + } else { + // Either the preload resource from this render already flushed in this render pass + // or the preload flushed in a prior pass (prerender). In either case we need to mark + // this resource as already having been preloaded. + resource.state = PRELOADED; } + } // We add the newly created resource to our StyleQueue and if necessary + // track the resource with the currently rendering boundary - renderState.stylePrecedences.set(precedence, emptyStyleResource); - } - stylesInPrecedence.set(key, resource); + styleQueue.sheets.set(key, resource); if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.stylesheets.add(resource); } } else { // We need to track whether this boundary should wait on this resource or not. @@ -3028,12 +3076,12 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse // 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 (stylesInPrecedence) { - var _resource = stylesInPrecedence.get(key); + if (styleQueue) { + var _resource = styleQueue.sheets.get(key); if (_resource) { if (renderState.boundaryResources) { - renderState.boundaryResources.add(_resource); + renderState.boundaryResources.stylesheets.add(_resource); } } } @@ -3131,47 +3179,47 @@ function pushStyle(target, props, resumableState, renderState, textEmbedded, ins } } - var key = getResourceKey('style', href); - var resource = renderState.stylePrecedences.get(precedence); + var key = getResourceKey(href); + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; - if (!resumableState.stylesMap.hasOwnProperty(key)) { - if (!resource) { - resource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [href] - } - }; - renderState.stylePrecedences.set(precedence, resource); - var stylesInPrecedence = new Map(); - stylesInPrecedence.set('', resource); + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; - { - if (renderState.precedences.has(precedence)) { - error('React constructed a new style precedence set when one already exists for this precedence: "%s". This is a bug in React.', precedence); - } + { + if (resourceState) { + error('React encountered a hoistable style tag for the same href as a preload: "%s". When using a style tag to inline styles you should not also preload it as a stylsheet.', href); } + } - renderState.precedences.set(precedence, stylesInPrecedence); + if (!styleQueue) { + // This is the first time we've encountered this precedence we need + // to create a StyleQueue. + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [stringToChunk(escapeTextForBrowser(href))], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); } else { - resource.props.hrefs.push(href); + // We have seen this precedence before and need to track this href + styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href))); } - resumableState.stylesMap[key] = null; - pushStyleContents(resource.chunks, props); + pushStyleContents(styleQueue.rules, props); } - if (resource) { + if (styleQueue) { // We need to track whether this boundary should wait on this resource or not. // Typically this resource should always exist since we either had it or just created // 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.add(resource); + renderState.boundaryResources.styles.add(styleQueue); } } @@ -3260,36 +3308,35 @@ function pushStyleContents(target, props) { return; } -function getImagePreloadKey(href, imageSrcSet, imageSizes) { - var uniquePart = ''; - - if (typeof imageSrcSet === 'string' && imageSrcSet !== '') { - uniquePart += '[' + imageSrcSet + ']'; - - if (typeof imageSizes === 'string') { - uniquePart += '[' + imageSizes + ']'; - } - } else { - uniquePart += '[][]' + href; - } - - return getResourceKey('image', uniquePart); -} - function pushImg(target, props, resumableState, renderState, pictureTagInScope) { var src = props.src, srcSet = props.srcSet; - if (props.loading !== 'lazy' && (typeof src === 'string' || typeof srcSet === 'string') && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded + if (props.loading !== 'lazy' && (src || srcSet) && (typeof src === 'string' || src == null) && (typeof srcSet === 'string' || srcSet == null) && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded !(typeof src === 'string' && src[4] === ':' && (src[0] === 'd' || src[0] === 'D') && (src[1] === 'a' || src[1] === 'A') && (src[2] === 't' || src[2] === 'T') && (src[3] === 'a' || src[3] === 'A')) && !(typeof srcSet === 'string' && srcSet[4] === ':' && (srcSet[0] === 'd' || srcSet[0] === 'D') && (srcSet[1] === 'a' || srcSet[1] === 'A') && (srcSet[2] === 't' || srcSet[2] === 'T') && (srcSet[3] === 'a' || srcSet[3] === 'A'))) { // We have a suspensey image and ought to preload it to optimize the loading of display blocking // resumableState. - var sizes = props.sizes; - var key = getImagePreloadKey(src, srcSet, sizes); - var resource; + var sizes = typeof props.sizes === 'string' ? props.sizes : undefined; + var key = getImageResourceKey(src, srcSet, sizes); + var promotablePreloads = renderState.preloads.images; + var resource = promotablePreloads.get(key); - if (!resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = { + if (resource) { + // We consider whether this preload can be promoted to higher priority flushing queue. + // The only time a resource will exist here is if it was created during this render + // and was not already in the high priority queue. + if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { + // Delete the resource from the map since we are promoting it and don't want to + // reenter this branch in a second pass for duplicate img hrefs. + promotablePreloads.delete(key); // $FlowFixMe - Flow should understand that this is a Resource if the condition was true + + renderState.highImagePreloads.add(resource); + } + } else if (!resumableState.imageResources.hasOwnProperty(key)) { + // We must construct a new preload resource + resumableState.imageResources[key] = PRELOAD_NO_CREDS; + resource = []; + pushLinkImpl(resource, { rel: 'preload', as: 'image', // There is a bug in Safari where imageSrcSet is not respected on preload links @@ -3304,25 +3351,15 @@ function pushImg(target, props, resumableState, renderState, pictureTagInScope) type: props.type, fetchPriority: props.fetchPriority, referrerPolicy: props.referrerPolicy - }; - resource = { - type: 'preload', - chunks: [], - state: NoState, - props: preloadProps - }; - resumableState.preloadsMap[key] = preloadProps; - renderState.preloadsMap.set(key, resource); - pushLinkImpl(resource.chunks, preloadProps); - } else { - resource = renderState.preloadsMap.get(key); - } + }); - if (resource) { if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { renderState.highImagePreloads.add(resource); } else { - renderState.bulkPreloads.add(resource); + renderState.bulkPreloads.add(resource); // We can bump the priority up if the same img is rendered later + // with fetchPriority="high" + + promotablePreloads.set(key, resource); } } } @@ -3496,35 +3533,50 @@ function pushScript(target, props, resumableState, renderState, textEmbedded, in } var src = props.src; - var key = getResourceKey('script', src); // We can make this '); @@ -1711,12 +1729,16 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst fontPreloads: new Set(), highImagePreloads: new Set(), // usedImagePreloads: new Set(), - precedences: new Map(), - stylePrecedences: new Map(), + styles: new Map(), bootstrapScripts: new Set(), scripts: new Set(), bulkPreloads: new Set(), - preloadsMap: new Map(), + preloads: { + images: new Map(), + stylesheets: new Map(), + scripts: new Map(), + moduleScripts: new Map() + }, nonce: nonce, // like a module global for currently rendering boundary boundaryResources: null, @@ -1726,17 +1748,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst if (bootstrapScripts !== undefined) { for (var i = 0; i < bootstrapScripts.length; i++) { var scriptConfig = bootstrapScripts[i]; - var src = typeof scriptConfig === 'string' ? scriptConfig : scriptConfig.src; - var integrity = typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity; - var crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; - preloadBootstrapScript(resumableState, renderState, src, nonce, integrity, crossOrigin); + var src = void 0, + crossOrigin = void 0, + integrity = void 0; + var props = { + rel: 'preload', + as: 'script', + fetchPriority: 'low', + nonce: nonce + }; + + if (typeof scriptConfig === 'string') { + props.href = src = scriptConfig; + } else { + props.href = src = scriptConfig.src; + props.integrity = integrity = typeof scriptConfig.integrity === 'string' ? scriptConfig.integrity : undefined; + props.crossOrigin = crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } + + preloadBootstrapScriptOrModule(resumableState, renderState, src, props); bootstrapChunks.push(startScriptSrc, stringToChunk(escapeTextForBrowser(src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (integrity) { + if (typeof integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(integrity))); } @@ -1752,20 +1789,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst for (var _i = 0; _i < bootstrapModules.length; _i++) { var _scriptConfig = bootstrapModules[_i]; - var _src = typeof _scriptConfig === 'string' ? _scriptConfig : _scriptConfig.src; + var _src = void 0, + _crossOrigin = void 0, + _integrity = void 0; - var _integrity = typeof _scriptConfig === 'string' ? undefined : _scriptConfig.integrity; + var _props = { + rel: 'modulepreload', + fetchPriority: 'low', + nonce: nonce + }; - var _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + if (typeof _scriptConfig === 'string') { + _props.href = _src = _scriptConfig; + } else { + _props.href = _src = _scriptConfig.src; + _props.integrity = _integrity = typeof _scriptConfig.integrity === 'string' ? _scriptConfig.integrity : undefined; + _props.crossOrigin = _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } - preloadBootstrapModule(resumableState, renderState, _src, nonce, _integrity, _crossOrigin); + preloadBootstrapScriptOrModule(resumableState, renderState, _src, _props); bootstrapChunks.push(startModuleSrc, stringToChunk(escapeTextForBrowser(_src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (_integrity) { + if (typeof _integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(_integrity))); } @@ -1798,10 +1847,18 @@ function createResumableState(identifierPrefix, externalRuntimeConfig) { hasHtml: false, // @TODO add bootstrap script to implicit preloads // persistent - preloadsMap: {}, - preconnectsMap: {}, - stylesMap: {}, - scriptsMap: {} + unknownResources: {}, + dnsResources: {}, + connectResources: { + default: {}, + anonymous: {}, + credentials: {} + }, + imageResources: {}, + styleResources: {}, + scriptResources: {}, + moduleUnknownResources: {}, + moduleScriptResources: {} }; } // Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion // modes. We only include the variants as they matter for the sake of our purposes. @@ -3021,7 +3078,7 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse if (props.rel === 'stylesheet') { // This may hoistable as a Stylesheet Resource, otherwise it will emit in place - var key = getResourceKey('style', href); + var key = getResourceKey(href); if (typeof precedence !== 'string' || props.disabled != null || props.onLoad || props.onError) { // This stylesheet is either not opted into Resource semantics or has conflicting properties which @@ -3042,67 +3099,58 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse return pushLinkImpl(target, props); } else { // This stylesheet refers to a Resource and we create a new one if necessary - var stylesInPrecedence = renderState.precedences.get(precedence); - - if (!resumableState.stylesMap.hasOwnProperty(key)) { - var resourceProps = stylesheetPropsFromRawProps(props); - var state = NoState; + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; + + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; // If this is the first time we've encountered this precedence we need + // to create a StyleQueue + + if (!styleQueue) { + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); + } - if (resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = resumableState.preloadsMap[key]; - adoptPreloadPropsForStylesheetProps(resourceProps, preloadProps); - var preloadResource = renderState.preloadsMap.get(key); + var resource = { + state: PENDING$1, + props: stylesheetPropsFromRawProps(props) + }; - if (preloadResource) { - // If we already had a preload we don't want that resource to flush directly. - // We let the newly created resource govern flushing. - preloadResource.state |= Blocked; + if (resourceState) { + // When resourceState is truty it is a Preload state. We cast it for clarity + var preloadState = resourceState; - if (preloadResource.state & Flushed) { - state = PreloadFlushed; - } - } else { - // If we resumed then we assume that this was already flushed - // by the shell. - state = PreloadFlushed; + if (preloadState.length === 2) { + adoptPreloadCredentials(resource.props, preloadState); } - } - var resource = { - type: 'stylesheet', - chunks: [], - state: state, - props: resourceProps - }; - resumableState.stylesMap[key] = null; - - if (!stylesInPrecedence) { - stylesInPrecedence = new Map(); - renderState.precedences.set(precedence, stylesInPrecedence); - var emptyStyleResource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [] - } - }; - stylesInPrecedence.set('', emptyStyleResource); + var preloadResource = renderState.preloads.stylesheets.get(key); - { - if (renderState.stylePrecedences.has(precedence)) { - error('React constructed an empty style resource when a style resource already exists for this precedence: "%s". This is a bug in React.', precedence); - } + if (preloadResource && preloadResource.length > 0) { + // The Preload for this resource was created in this render pass and has not flushed yet so + // we need to clear it to avoid it flushing. + preloadResource.length = 0; + } else { + // Either the preload resource from this render already flushed in this render pass + // or the preload flushed in a prior pass (prerender). In either case we need to mark + // this resource as already having been preloaded. + resource.state = PRELOADED; } + } // We add the newly created resource to our StyleQueue and if necessary + // track the resource with the currently rendering boundary - renderState.stylePrecedences.set(precedence, emptyStyleResource); - } - stylesInPrecedence.set(key, resource); + styleQueue.sheets.set(key, resource); if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.stylesheets.add(resource); } } else { // We need to track whether this boundary should wait on this resource or not. @@ -3110,12 +3158,12 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse // 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 (stylesInPrecedence) { - var _resource = stylesInPrecedence.get(key); + if (styleQueue) { + var _resource = styleQueue.sheets.get(key); if (_resource) { if (renderState.boundaryResources) { - renderState.boundaryResources.add(_resource); + renderState.boundaryResources.stylesheets.add(_resource); } } } @@ -3213,47 +3261,47 @@ function pushStyle(target, props, resumableState, renderState, textEmbedded, ins } } - var key = getResourceKey('style', href); - var resource = renderState.stylePrecedences.get(precedence); + var key = getResourceKey(href); + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; - if (!resumableState.stylesMap.hasOwnProperty(key)) { - if (!resource) { - resource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [href] - } - }; - renderState.stylePrecedences.set(precedence, resource); - var stylesInPrecedence = new Map(); - stylesInPrecedence.set('', resource); + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; - { - if (renderState.precedences.has(precedence)) { - error('React constructed a new style precedence set when one already exists for this precedence: "%s". This is a bug in React.', precedence); - } + { + if (resourceState) { + error('React encountered a hoistable style tag for the same href as a preload: "%s". When using a style tag to inline styles you should not also preload it as a stylsheet.', href); } + } - renderState.precedences.set(precedence, stylesInPrecedence); + if (!styleQueue) { + // This is the first time we've encountered this precedence we need + // to create a StyleQueue. + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [stringToChunk(escapeTextForBrowser(href))], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); } else { - resource.props.hrefs.push(href); + // We have seen this precedence before and need to track this href + styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href))); } - resumableState.stylesMap[key] = null; - pushStyleContents(resource.chunks, props); + pushStyleContents(styleQueue.rules, props); } - if (resource) { + if (styleQueue) { // We need to track whether this boundary should wait on this resource or not. // Typically this resource should always exist since we either had it or just created // 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.add(resource); + renderState.boundaryResources.styles.add(styleQueue); } } @@ -3342,36 +3390,35 @@ function pushStyleContents(target, props) { return; } -function getImagePreloadKey(href, imageSrcSet, imageSizes) { - var uniquePart = ''; - - if (typeof imageSrcSet === 'string' && imageSrcSet !== '') { - uniquePart += '[' + imageSrcSet + ']'; - - if (typeof imageSizes === 'string') { - uniquePart += '[' + imageSizes + ']'; - } - } else { - uniquePart += '[][]' + href; - } - - return getResourceKey('image', uniquePart); -} - function pushImg(target, props, resumableState, renderState, pictureTagInScope) { var src = props.src, srcSet = props.srcSet; - if (props.loading !== 'lazy' && (typeof src === 'string' || typeof srcSet === 'string') && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded + if (props.loading !== 'lazy' && (src || srcSet) && (typeof src === 'string' || src == null) && (typeof srcSet === 'string' || srcSet == null) && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded !(typeof src === 'string' && src[4] === ':' && (src[0] === 'd' || src[0] === 'D') && (src[1] === 'a' || src[1] === 'A') && (src[2] === 't' || src[2] === 'T') && (src[3] === 'a' || src[3] === 'A')) && !(typeof srcSet === 'string' && srcSet[4] === ':' && (srcSet[0] === 'd' || srcSet[0] === 'D') && (srcSet[1] === 'a' || srcSet[1] === 'A') && (srcSet[2] === 't' || srcSet[2] === 'T') && (srcSet[3] === 'a' || srcSet[3] === 'A'))) { // We have a suspensey image and ought to preload it to optimize the loading of display blocking // resumableState. - var sizes = props.sizes; - var key = getImagePreloadKey(src, srcSet, sizes); - var resource; + var sizes = typeof props.sizes === 'string' ? props.sizes : undefined; + var key = getImageResourceKey(src, srcSet, sizes); + var promotablePreloads = renderState.preloads.images; + var resource = promotablePreloads.get(key); - if (!resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = { + if (resource) { + // We consider whether this preload can be promoted to higher priority flushing queue. + // The only time a resource will exist here is if it was created during this render + // and was not already in the high priority queue. + if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { + // Delete the resource from the map since we are promoting it and don't want to + // reenter this branch in a second pass for duplicate img hrefs. + promotablePreloads.delete(key); // $FlowFixMe - Flow should understand that this is a Resource if the condition was true + + renderState.highImagePreloads.add(resource); + } + } else if (!resumableState.imageResources.hasOwnProperty(key)) { + // We must construct a new preload resource + resumableState.imageResources[key] = PRELOAD_NO_CREDS; + resource = []; + pushLinkImpl(resource, { rel: 'preload', as: 'image', // There is a bug in Safari where imageSrcSet is not respected on preload links @@ -3386,25 +3433,15 @@ function pushImg(target, props, resumableState, renderState, pictureTagInScope) type: props.type, fetchPriority: props.fetchPriority, referrerPolicy: props.referrerPolicy - }; - resource = { - type: 'preload', - chunks: [], - state: NoState, - props: preloadProps - }; - resumableState.preloadsMap[key] = preloadProps; - renderState.preloadsMap.set(key, resource); - pushLinkImpl(resource.chunks, preloadProps); - } else { - resource = renderState.preloadsMap.get(key); - } + }); - if (resource) { if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { renderState.highImagePreloads.add(resource); } else { - renderState.bulkPreloads.add(resource); + renderState.bulkPreloads.add(resource); // We can bump the priority up if the same img is rendered later + // with fetchPriority="high" + + promotablePreloads.set(key, resource); } } } @@ -3578,35 +3615,50 @@ function pushScript(target, props, resumableState, renderState, textEmbedded, in } var src = props.src; - var key = getResourceKey('script', src); // We can make this '); @@ -1711,12 +1729,16 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst fontPreloads: new Set(), highImagePreloads: new Set(), // usedImagePreloads: new Set(), - precedences: new Map(), - stylePrecedences: new Map(), + styles: new Map(), bootstrapScripts: new Set(), scripts: new Set(), bulkPreloads: new Set(), - preloadsMap: new Map(), + preloads: { + images: new Map(), + stylesheets: new Map(), + scripts: new Map(), + moduleScripts: new Map() + }, nonce: nonce, // like a module global for currently rendering boundary boundaryResources: null, @@ -1726,17 +1748,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst if (bootstrapScripts !== undefined) { for (var i = 0; i < bootstrapScripts.length; i++) { var scriptConfig = bootstrapScripts[i]; - var src = typeof scriptConfig === 'string' ? scriptConfig : scriptConfig.src; - var integrity = typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity; - var crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; - preloadBootstrapScript(resumableState, renderState, src, nonce, integrity, crossOrigin); + var src = void 0, + crossOrigin = void 0, + integrity = void 0; + var props = { + rel: 'preload', + as: 'script', + fetchPriority: 'low', + nonce: nonce + }; + + if (typeof scriptConfig === 'string') { + props.href = src = scriptConfig; + } else { + props.href = src = scriptConfig.src; + props.integrity = integrity = typeof scriptConfig.integrity === 'string' ? scriptConfig.integrity : undefined; + props.crossOrigin = crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } + + preloadBootstrapScriptOrModule(resumableState, renderState, src, props); bootstrapChunks.push(startScriptSrc, stringToChunk(escapeTextForBrowser(src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (integrity) { + if (typeof integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(integrity))); } @@ -1752,20 +1789,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst for (var _i = 0; _i < bootstrapModules.length; _i++) { var _scriptConfig = bootstrapModules[_i]; - var _src = typeof _scriptConfig === 'string' ? _scriptConfig : _scriptConfig.src; + var _src = void 0, + _crossOrigin = void 0, + _integrity = void 0; - var _integrity = typeof _scriptConfig === 'string' ? undefined : _scriptConfig.integrity; + var _props = { + rel: 'modulepreload', + fetchPriority: 'low', + nonce: nonce + }; - var _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + if (typeof _scriptConfig === 'string') { + _props.href = _src = _scriptConfig; + } else { + _props.href = _src = _scriptConfig.src; + _props.integrity = _integrity = typeof _scriptConfig.integrity === 'string' ? _scriptConfig.integrity : undefined; + _props.crossOrigin = _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } - preloadBootstrapModule(resumableState, renderState, _src, nonce, _integrity, _crossOrigin); + preloadBootstrapScriptOrModule(resumableState, renderState, _src, _props); bootstrapChunks.push(startModuleSrc, stringToChunk(escapeTextForBrowser(_src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (_integrity) { + if (typeof _integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(_integrity))); } @@ -1798,10 +1847,18 @@ function createResumableState(identifierPrefix, externalRuntimeConfig) { hasHtml: false, // @TODO add bootstrap script to implicit preloads // persistent - preloadsMap: {}, - preconnectsMap: {}, - stylesMap: {}, - scriptsMap: {} + unknownResources: {}, + dnsResources: {}, + connectResources: { + default: {}, + anonymous: {}, + credentials: {} + }, + imageResources: {}, + styleResources: {}, + scriptResources: {}, + moduleUnknownResources: {}, + moduleScriptResources: {} }; } // Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion // modes. We only include the variants as they matter for the sake of our purposes. @@ -3021,7 +3078,7 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse if (props.rel === 'stylesheet') { // This may hoistable as a Stylesheet Resource, otherwise it will emit in place - var key = getResourceKey('style', href); + var key = getResourceKey(href); if (typeof precedence !== 'string' || props.disabled != null || props.onLoad || props.onError) { // This stylesheet is either not opted into Resource semantics or has conflicting properties which @@ -3042,67 +3099,58 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse return pushLinkImpl(target, props); } else { // This stylesheet refers to a Resource and we create a new one if necessary - var stylesInPrecedence = renderState.precedences.get(precedence); - - if (!resumableState.stylesMap.hasOwnProperty(key)) { - var resourceProps = stylesheetPropsFromRawProps(props); - var state = NoState; + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; + + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; // If this is the first time we've encountered this precedence we need + // to create a StyleQueue + + if (!styleQueue) { + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); + } - if (resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = resumableState.preloadsMap[key]; - adoptPreloadPropsForStylesheetProps(resourceProps, preloadProps); - var preloadResource = renderState.preloadsMap.get(key); + var resource = { + state: PENDING$1, + props: stylesheetPropsFromRawProps(props) + }; - if (preloadResource) { - // If we already had a preload we don't want that resource to flush directly. - // We let the newly created resource govern flushing. - preloadResource.state |= Blocked; + if (resourceState) { + // When resourceState is truty it is a Preload state. We cast it for clarity + var preloadState = resourceState; - if (preloadResource.state & Flushed) { - state = PreloadFlushed; - } - } else { - // If we resumed then we assume that this was already flushed - // by the shell. - state = PreloadFlushed; + if (preloadState.length === 2) { + adoptPreloadCredentials(resource.props, preloadState); } - } - var resource = { - type: 'stylesheet', - chunks: [], - state: state, - props: resourceProps - }; - resumableState.stylesMap[key] = null; - - if (!stylesInPrecedence) { - stylesInPrecedence = new Map(); - renderState.precedences.set(precedence, stylesInPrecedence); - var emptyStyleResource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [] - } - }; - stylesInPrecedence.set('', emptyStyleResource); + var preloadResource = renderState.preloads.stylesheets.get(key); - { - if (renderState.stylePrecedences.has(precedence)) { - error('React constructed an empty style resource when a style resource already exists for this precedence: "%s". This is a bug in React.', precedence); - } + if (preloadResource && preloadResource.length > 0) { + // The Preload for this resource was created in this render pass and has not flushed yet so + // we need to clear it to avoid it flushing. + preloadResource.length = 0; + } else { + // Either the preload resource from this render already flushed in this render pass + // or the preload flushed in a prior pass (prerender). In either case we need to mark + // this resource as already having been preloaded. + resource.state = PRELOADED; } + } // We add the newly created resource to our StyleQueue and if necessary + // track the resource with the currently rendering boundary - renderState.stylePrecedences.set(precedence, emptyStyleResource); - } - stylesInPrecedence.set(key, resource); + styleQueue.sheets.set(key, resource); if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.stylesheets.add(resource); } } else { // We need to track whether this boundary should wait on this resource or not. @@ -3110,12 +3158,12 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse // 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 (stylesInPrecedence) { - var _resource = stylesInPrecedence.get(key); + if (styleQueue) { + var _resource = styleQueue.sheets.get(key); if (_resource) { if (renderState.boundaryResources) { - renderState.boundaryResources.add(_resource); + renderState.boundaryResources.stylesheets.add(_resource); } } } @@ -3213,47 +3261,47 @@ function pushStyle(target, props, resumableState, renderState, textEmbedded, ins } } - var key = getResourceKey('style', href); - var resource = renderState.stylePrecedences.get(precedence); + var key = getResourceKey(href); + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; - if (!resumableState.stylesMap.hasOwnProperty(key)) { - if (!resource) { - resource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [href] - } - }; - renderState.stylePrecedences.set(precedence, resource); - var stylesInPrecedence = new Map(); - stylesInPrecedence.set('', resource); + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; - { - if (renderState.precedences.has(precedence)) { - error('React constructed a new style precedence set when one already exists for this precedence: "%s". This is a bug in React.', precedence); - } + { + if (resourceState) { + error('React encountered a hoistable style tag for the same href as a preload: "%s". When using a style tag to inline styles you should not also preload it as a stylsheet.', href); } + } - renderState.precedences.set(precedence, stylesInPrecedence); + if (!styleQueue) { + // This is the first time we've encountered this precedence we need + // to create a StyleQueue. + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [stringToChunk(escapeTextForBrowser(href))], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); } else { - resource.props.hrefs.push(href); + // We have seen this precedence before and need to track this href + styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href))); } - resumableState.stylesMap[key] = null; - pushStyleContents(resource.chunks, props); + pushStyleContents(styleQueue.rules, props); } - if (resource) { + if (styleQueue) { // We need to track whether this boundary should wait on this resource or not. // Typically this resource should always exist since we either had it or just created // 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.add(resource); + renderState.boundaryResources.styles.add(styleQueue); } } @@ -3342,36 +3390,35 @@ function pushStyleContents(target, props) { return; } -function getImagePreloadKey(href, imageSrcSet, imageSizes) { - var uniquePart = ''; - - if (typeof imageSrcSet === 'string' && imageSrcSet !== '') { - uniquePart += '[' + imageSrcSet + ']'; - - if (typeof imageSizes === 'string') { - uniquePart += '[' + imageSizes + ']'; - } - } else { - uniquePart += '[][]' + href; - } - - return getResourceKey('image', uniquePart); -} - function pushImg(target, props, resumableState, renderState, pictureTagInScope) { var src = props.src, srcSet = props.srcSet; - if (props.loading !== 'lazy' && (typeof src === 'string' || typeof srcSet === 'string') && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded + if (props.loading !== 'lazy' && (src || srcSet) && (typeof src === 'string' || src == null) && (typeof srcSet === 'string' || srcSet == null) && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded !(typeof src === 'string' && src[4] === ':' && (src[0] === 'd' || src[0] === 'D') && (src[1] === 'a' || src[1] === 'A') && (src[2] === 't' || src[2] === 'T') && (src[3] === 'a' || src[3] === 'A')) && !(typeof srcSet === 'string' && srcSet[4] === ':' && (srcSet[0] === 'd' || srcSet[0] === 'D') && (srcSet[1] === 'a' || srcSet[1] === 'A') && (srcSet[2] === 't' || srcSet[2] === 'T') && (srcSet[3] === 'a' || srcSet[3] === 'A'))) { // We have a suspensey image and ought to preload it to optimize the loading of display blocking // resumableState. - var sizes = props.sizes; - var key = getImagePreloadKey(src, srcSet, sizes); - var resource; + var sizes = typeof props.sizes === 'string' ? props.sizes : undefined; + var key = getImageResourceKey(src, srcSet, sizes); + var promotablePreloads = renderState.preloads.images; + var resource = promotablePreloads.get(key); - if (!resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = { + if (resource) { + // We consider whether this preload can be promoted to higher priority flushing queue. + // The only time a resource will exist here is if it was created during this render + // and was not already in the high priority queue. + if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { + // Delete the resource from the map since we are promoting it and don't want to + // reenter this branch in a second pass for duplicate img hrefs. + promotablePreloads.delete(key); // $FlowFixMe - Flow should understand that this is a Resource if the condition was true + + renderState.highImagePreloads.add(resource); + } + } else if (!resumableState.imageResources.hasOwnProperty(key)) { + // We must construct a new preload resource + resumableState.imageResources[key] = PRELOAD_NO_CREDS; + resource = []; + pushLinkImpl(resource, { rel: 'preload', as: 'image', // There is a bug in Safari where imageSrcSet is not respected on preload links @@ -3386,25 +3433,15 @@ function pushImg(target, props, resumableState, renderState, pictureTagInScope) type: props.type, fetchPriority: props.fetchPriority, referrerPolicy: props.referrerPolicy - }; - resource = { - type: 'preload', - chunks: [], - state: NoState, - props: preloadProps - }; - resumableState.preloadsMap[key] = preloadProps; - renderState.preloadsMap.set(key, resource); - pushLinkImpl(resource.chunks, preloadProps); - } else { - resource = renderState.preloadsMap.get(key); - } + }); - if (resource) { if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { renderState.highImagePreloads.add(resource); } else { - renderState.bulkPreloads.add(resource); + renderState.bulkPreloads.add(resource); // We can bump the priority up if the same img is rendered later + // with fetchPriority="high" + + promotablePreloads.set(key, resource); } } } @@ -3578,35 +3615,50 @@ function pushScript(target, props, resumableState, renderState, textEmbedded, in } var src = props.src; - var key = getResourceKey('script', src); // We can make this '); @@ -1780,12 +1798,16 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst fontPreloads: new Set(), highImagePreloads: new Set(), // usedImagePreloads: new Set(), - precedences: new Map(), - stylePrecedences: new Map(), + styles: new Map(), bootstrapScripts: new Set(), scripts: new Set(), bulkPreloads: new Set(), - preloadsMap: new Map(), + preloads: { + images: new Map(), + stylesheets: new Map(), + scripts: new Map(), + moduleScripts: new Map() + }, nonce: nonce, // like a module global for currently rendering boundary boundaryResources: null, @@ -1795,17 +1817,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst if (bootstrapScripts !== undefined) { for (var i = 0; i < bootstrapScripts.length; i++) { var scriptConfig = bootstrapScripts[i]; - var src = typeof scriptConfig === 'string' ? scriptConfig : scriptConfig.src; - var integrity = typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity; - var crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; - preloadBootstrapScript(resumableState, renderState, src, nonce, integrity, crossOrigin); + var src = void 0, + crossOrigin = void 0, + integrity = void 0; + var props = { + rel: 'preload', + as: 'script', + fetchPriority: 'low', + nonce: nonce + }; + + if (typeof scriptConfig === 'string') { + props.href = src = scriptConfig; + } else { + props.href = src = scriptConfig.src; + props.integrity = integrity = typeof scriptConfig.integrity === 'string' ? scriptConfig.integrity : undefined; + props.crossOrigin = crossOrigin = typeof scriptConfig === 'string' || scriptConfig.crossOrigin == null ? undefined : scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } + + preloadBootstrapScriptOrModule(resumableState, renderState, src, props); bootstrapChunks.push(startScriptSrc, stringToChunk(escapeTextForBrowser(src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (integrity) { + if (typeof integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(integrity))); } @@ -1821,20 +1858,32 @@ function createRenderState(resumableState, nonce, bootstrapScriptContent, bootst for (var _i = 0; _i < bootstrapModules.length; _i++) { var _scriptConfig = bootstrapModules[_i]; - var _src = typeof _scriptConfig === 'string' ? _scriptConfig : _scriptConfig.src; + var _src = void 0, + _crossOrigin = void 0, + _integrity = void 0; - var _integrity = typeof _scriptConfig === 'string' ? undefined : _scriptConfig.integrity; + var _props = { + rel: 'modulepreload', + fetchPriority: 'low', + nonce: nonce + }; - var _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + if (typeof _scriptConfig === 'string') { + _props.href = _src = _scriptConfig; + } else { + _props.href = _src = _scriptConfig.src; + _props.integrity = _integrity = typeof _scriptConfig.integrity === 'string' ? _scriptConfig.integrity : undefined; + _props.crossOrigin = _crossOrigin = typeof _scriptConfig === 'string' || _scriptConfig.crossOrigin == null ? undefined : _scriptConfig.crossOrigin === 'use-credentials' ? 'use-credentials' : ''; + } - preloadBootstrapModule(resumableState, renderState, _src, nonce, _integrity, _crossOrigin); + preloadBootstrapScriptOrModule(resumableState, renderState, _src, _props); bootstrapChunks.push(startModuleSrc, stringToChunk(escapeTextForBrowser(_src))); if (nonce) { bootstrapChunks.push(scriptNonce, stringToChunk(escapeTextForBrowser(nonce))); } - if (_integrity) { + if (typeof _integrity === 'string') { bootstrapChunks.push(scriptIntegirty, stringToChunk(escapeTextForBrowser(_integrity))); } @@ -1867,10 +1916,18 @@ function createResumableState(identifierPrefix, externalRuntimeConfig) { hasHtml: false, // @TODO add bootstrap script to implicit preloads // persistent - preloadsMap: {}, - preconnectsMap: {}, - stylesMap: {}, - scriptsMap: {} + unknownResources: {}, + dnsResources: {}, + connectResources: { + default: {}, + anonymous: {}, + credentials: {} + }, + imageResources: {}, + styleResources: {}, + scriptResources: {}, + moduleUnknownResources: {}, + moduleScriptResources: {} }; } // Constants for the insertion mode we're currently writing in. We don't encode all HTML5 insertion // modes. We only include the variants as they matter for the sake of our purposes. @@ -3090,7 +3147,7 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse if (props.rel === 'stylesheet') { // This may hoistable as a Stylesheet Resource, otherwise it will emit in place - var key = getResourceKey('style', href); + var key = getResourceKey(href); if (typeof precedence !== 'string' || props.disabled != null || props.onLoad || props.onError) { // This stylesheet is either not opted into Resource semantics or has conflicting properties which @@ -3111,67 +3168,58 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse return pushLinkImpl(target, props); } else { // This stylesheet refers to a Resource and we create a new one if necessary - var stylesInPrecedence = renderState.precedences.get(precedence); - - if (!resumableState.stylesMap.hasOwnProperty(key)) { - var resourceProps = stylesheetPropsFromRawProps(props); - var state = NoState; + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; + + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; // If this is the first time we've encountered this precedence we need + // to create a StyleQueue + + if (!styleQueue) { + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); + } - if (resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = resumableState.preloadsMap[key]; - adoptPreloadPropsForStylesheetProps(resourceProps, preloadProps); - var preloadResource = renderState.preloadsMap.get(key); + var resource = { + state: PENDING$1, + props: stylesheetPropsFromRawProps(props) + }; - if (preloadResource) { - // If we already had a preload we don't want that resource to flush directly. - // We let the newly created resource govern flushing. - preloadResource.state |= Blocked; + if (resourceState) { + // When resourceState is truty it is a Preload state. We cast it for clarity + var preloadState = resourceState; - if (preloadResource.state & Flushed) { - state = PreloadFlushed; - } - } else { - // If we resumed then we assume that this was already flushed - // by the shell. - state = PreloadFlushed; + if (preloadState.length === 2) { + adoptPreloadCredentials(resource.props, preloadState); } - } - var resource = { - type: 'stylesheet', - chunks: [], - state: state, - props: resourceProps - }; - resumableState.stylesMap[key] = null; - - if (!stylesInPrecedence) { - stylesInPrecedence = new Map(); - renderState.precedences.set(precedence, stylesInPrecedence); - var emptyStyleResource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [] - } - }; - stylesInPrecedence.set('', emptyStyleResource); + var preloadResource = renderState.preloads.stylesheets.get(key); - { - if (renderState.stylePrecedences.has(precedence)) { - error('React constructed an empty style resource when a style resource already exists for this precedence: "%s". This is a bug in React.', precedence); - } + if (preloadResource && preloadResource.length > 0) { + // The Preload for this resource was created in this render pass and has not flushed yet so + // we need to clear it to avoid it flushing. + preloadResource.length = 0; + } else { + // Either the preload resource from this render already flushed in this render pass + // or the preload flushed in a prior pass (prerender). In either case we need to mark + // this resource as already having been preloaded. + resource.state = PRELOADED; } + } // We add the newly created resource to our StyleQueue and if necessary + // track the resource with the currently rendering boundary - renderState.stylePrecedences.set(precedence, emptyStyleResource); - } - stylesInPrecedence.set(key, resource); + styleQueue.sheets.set(key, resource); if (renderState.boundaryResources) { - renderState.boundaryResources.add(resource); + renderState.boundaryResources.stylesheets.add(resource); } } else { // We need to track whether this boundary should wait on this resource or not. @@ -3179,12 +3227,12 @@ function pushLink(target, props, resumableState, renderState, textEmbedded, inse // 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 (stylesInPrecedence) { - var _resource = stylesInPrecedence.get(key); + if (styleQueue) { + var _resource = styleQueue.sheets.get(key); if (_resource) { if (renderState.boundaryResources) { - renderState.boundaryResources.add(_resource); + renderState.boundaryResources.stylesheets.add(_resource); } } } @@ -3282,47 +3330,47 @@ function pushStyle(target, props, resumableState, renderState, textEmbedded, ins } } - var key = getResourceKey('style', href); - var resource = renderState.stylePrecedences.get(precedence); + var key = getResourceKey(href); + var styleQueue = renderState.styles.get(precedence); + var hasKey = resumableState.styleResources.hasOwnProperty(key); + var resourceState = hasKey ? resumableState.styleResources[key] : undefined; - if (!resumableState.stylesMap.hasOwnProperty(key)) { - if (!resource) { - resource = { - type: 'style', - chunks: [], - state: NoState, - props: { - precedence: precedence, - hrefs: [href] - } - }; - renderState.stylePrecedences.set(precedence, resource); - var stylesInPrecedence = new Map(); - stylesInPrecedence.set('', resource); + if (resourceState !== EXISTS) { + // We are going to create this resource now so it is marked as Exists + resumableState.styleResources[key] = EXISTS; - { - if (renderState.precedences.has(precedence)) { - error('React constructed a new style precedence set when one already exists for this precedence: "%s". This is a bug in React.', precedence); - } + { + if (resourceState) { + error('React encountered a hoistable style tag for the same href as a preload: "%s". When using a style tag to inline styles you should not also preload it as a stylsheet.', href); } + } - renderState.precedences.set(precedence, stylesInPrecedence); + if (!styleQueue) { + // This is the first time we've encountered this precedence we need + // to create a StyleQueue. + styleQueue = { + precedence: stringToChunk(escapeTextForBrowser(precedence)), + rules: [], + hrefs: [stringToChunk(escapeTextForBrowser(href))], + sheets: new Map() + }; + renderState.styles.set(precedence, styleQueue); } else { - resource.props.hrefs.push(href); + // We have seen this precedence before and need to track this href + styleQueue.hrefs.push(stringToChunk(escapeTextForBrowser(href))); } - resumableState.stylesMap[key] = null; - pushStyleContents(resource.chunks, props); + pushStyleContents(styleQueue.rules, props); } - if (resource) { + if (styleQueue) { // We need to track whether this boundary should wait on this resource or not. // Typically this resource should always exist since we either had it or just created // 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.add(resource); + renderState.boundaryResources.styles.add(styleQueue); } } @@ -3411,36 +3459,35 @@ function pushStyleContents(target, props) { return; } -function getImagePreloadKey(href, imageSrcSet, imageSizes) { - var uniquePart = ''; - - if (typeof imageSrcSet === 'string' && imageSrcSet !== '') { - uniquePart += '[' + imageSrcSet + ']'; - - if (typeof imageSizes === 'string') { - uniquePart += '[' + imageSizes + ']'; - } - } else { - uniquePart += '[][]' + href; - } - - return getResourceKey('image', uniquePart); -} - function pushImg(target, props, resumableState, renderState, pictureTagInScope) { var src = props.src, srcSet = props.srcSet; - if (props.loading !== 'lazy' && (typeof src === 'string' || typeof srcSet === 'string') && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded + if (props.loading !== 'lazy' && (src || srcSet) && (typeof src === 'string' || src == null) && (typeof srcSet === 'string' || srcSet == null) && props.fetchPriority !== 'low' && pictureTagInScope === false && // We exclude data URIs in src and srcSet since these should not be preloaded !(typeof src === 'string' && src[4] === ':' && (src[0] === 'd' || src[0] === 'D') && (src[1] === 'a' || src[1] === 'A') && (src[2] === 't' || src[2] === 'T') && (src[3] === 'a' || src[3] === 'A')) && !(typeof srcSet === 'string' && srcSet[4] === ':' && (srcSet[0] === 'd' || srcSet[0] === 'D') && (srcSet[1] === 'a' || srcSet[1] === 'A') && (srcSet[2] === 't' || srcSet[2] === 'T') && (srcSet[3] === 'a' || srcSet[3] === 'A'))) { // We have a suspensey image and ought to preload it to optimize the loading of display blocking // resumableState. - var sizes = props.sizes; - var key = getImagePreloadKey(src, srcSet, sizes); - var resource; + var sizes = typeof props.sizes === 'string' ? props.sizes : undefined; + var key = getImageResourceKey(src, srcSet, sizes); + var promotablePreloads = renderState.preloads.images; + var resource = promotablePreloads.get(key); - if (!resumableState.preloadsMap.hasOwnProperty(key)) { - var preloadProps = { + if (resource) { + // We consider whether this preload can be promoted to higher priority flushing queue. + // The only time a resource will exist here is if it was created during this render + // and was not already in the high priority queue. + if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { + // Delete the resource from the map since we are promoting it and don't want to + // reenter this branch in a second pass for duplicate img hrefs. + promotablePreloads.delete(key); // $FlowFixMe - Flow should understand that this is a Resource if the condition was true + + renderState.highImagePreloads.add(resource); + } + } else if (!resumableState.imageResources.hasOwnProperty(key)) { + // We must construct a new preload resource + resumableState.imageResources[key] = PRELOAD_NO_CREDS; + resource = []; + pushLinkImpl(resource, { rel: 'preload', as: 'image', // There is a bug in Safari where imageSrcSet is not respected on preload links @@ -3455,25 +3502,15 @@ function pushImg(target, props, resumableState, renderState, pictureTagInScope) type: props.type, fetchPriority: props.fetchPriority, referrerPolicy: props.referrerPolicy - }; - resource = { - type: 'preload', - chunks: [], - state: NoState, - props: preloadProps - }; - resumableState.preloadsMap[key] = preloadProps; - renderState.preloadsMap.set(key, resource); - pushLinkImpl(resource.chunks, preloadProps); - } else { - resource = renderState.preloadsMap.get(key); - } + }); - if (resource) { if (props.fetchPriority === 'high' || renderState.highImagePreloads.size < 10) { renderState.highImagePreloads.add(resource); } else { - renderState.bulkPreloads.add(resource); + renderState.bulkPreloads.add(resource); // We can bump the priority up if the same img is rendered later + // with fetchPriority="high" + + promotablePreloads.set(key, resource); } } } @@ -3647,35 +3684,50 @@ function pushScript(target, props, resumableState, renderState, textEmbedded, in } var src = props.src; - var key = getResourceKey('script', src); // We can make this