From 31034b6de73c7cd9093e92a34e384d84c082aa4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Tue, 22 Aug 2023 15:21:36 -0400 Subject: [PATCH] [Fizz] Split ResponseState/Resources into RenderState/ResumableState (#27268) This exposes a `resume()` API to go with the `prerender()` (only in experimental). It doesn't work yet since we don't yet emit the postponed state so not yet tested. The main thing this does is rename ResponseState->RenderState and Resources->ResumableState. We separated out resources into a separate concept preemptively since it seemed like separate enough but probably doesn't warrant being a separate concept. The result is that we have a per RenderState in the Config which is really just temporary state and things that must be flushed completely in the prerender. Most things should be ResumableState. Most options are specified in the `prerender()` and transferred into the `resume()` but certain options that are unique per request can't be. Notably `nonce` is special. This means that bootstrap scripts and external runtime can't use `nonce` in this mode. They need to have a CSP configured to deal with external scripts, but not inline. We need to be able to restore state of things that we've already emitted in the prerender. We could have separate snapshot/restore methods that does this work when it happens but that means we have to explicitly do that work. This design is trying to keep to the principle that we just work with resumable data structures instead so that we're designing for it with every feature. It also makes restoring faster since it's just straight into the data structure. This is not yet a serializable format. That can be done in a follow up. We also need to vet that each step makes sense. Notably stylesToHoist is a bit unclear how it'll work. --- .eslintrc.js | 1 - .../src/server/ReactFizzConfigDOM.js | 711 ++++++++++-------- .../src/server/ReactFizzConfigDOMLegacy.js | 109 +-- packages/react-dom/npm/server.browser.js | 3 + packages/react-dom/npm/server.bun.js | 3 + packages/react-dom/npm/server.edge.js | 3 + packages/react-dom/npm/server.node.js | 3 + packages/react-dom/server.browser.js | 9 +- packages/react-dom/server.bun.js | 13 +- packages/react-dom/server.edge.js | 13 +- packages/react-dom/server.node.js | 9 +- .../src/__tests__/ReactDOMFizzStatic-test.js | 4 +- .../__tests__/ReactDOMFizzStaticNode-test.js | 28 +- .../src/server/ReactDOMFizzServerBrowser.js | 106 ++- .../src/server/ReactDOMFizzServerBun.js | 25 +- .../src/server/ReactDOMFizzServerEdge.js | 106 ++- .../src/server/ReactDOMFizzServerNode.js | 102 ++- .../src/server/ReactDOMFizzStaticBrowser.js | 27 +- .../src/server/ReactDOMFizzStaticEdge.js | 27 +- .../src/server/ReactDOMFizzStaticNode.js | 33 +- .../src/server/ReactDOMLegacyServerImpl.js | 24 +- .../server/ReactDOMLegacyServerNodeStream.js | 21 +- .../src/server/react-dom-server.browser.js | 10 + .../server/react-dom-server.browser.stable.js | 10 + .../src/server/react-dom-server.bun.js | 10 + .../src/server/react-dom-server.bun.stable.js | 15 + .../src/server/react-dom-server.edge.js | 10 + .../server/react-dom-server.edge.stable.js | 10 + .../src/server/react-dom-server.node.js | 10 + .../server/react-dom-server.node.stable.js | 10 + packages/react-dom/static.node.js | 2 +- .../src/ReactNoopServer.js | 28 +- .../src/ReactDOMServerFB.js | 25 +- packages/react-server/src/ReactFizzHooks.js | 16 +- packages/react-server/src/ReactFizzServer.js | 121 +-- .../src/forks/ReactFizzConfig.custom.js | 4 +- scripts/rollup/bundles.js | 8 +- scripts/shared/inlinedHostConfigs.js | 14 +- 38 files changed, 1050 insertions(+), 633 deletions(-) create mode 100644 packages/react-dom/src/server/react-dom-server.browser.js create mode 100644 packages/react-dom/src/server/react-dom-server.browser.stable.js create mode 100644 packages/react-dom/src/server/react-dom-server.bun.js create mode 100644 packages/react-dom/src/server/react-dom-server.bun.stable.js create mode 100644 packages/react-dom/src/server/react-dom-server.edge.js create mode 100644 packages/react-dom/src/server/react-dom-server.edge.stable.js create mode 100644 packages/react-dom/src/server/react-dom-server.node.js create mode 100644 packages/react-dom/src/server/react-dom-server.node.stable.js diff --git a/.eslintrc.js b/.eslintrc.js index 941c2e3b23..2d0e4abe71 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -492,7 +492,6 @@ module.exports = { ReadableStreamController: 'readonly', RequestInfo: 'readonly', RequestOptions: 'readonly', - ResponseState: 'readonly', StoreAsGlobal: 'readonly', symbol: 'readonly', SyntheticEvent: 'readonly', diff --git a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js index a30a9af90a..4a3c2d157d 100644 --- a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js +++ b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js @@ -50,7 +50,7 @@ import { } from 'react-server/src/ReactServerStreamConfig'; import { resolveRequest, - getResources, + getResumableState, flushResources, } from 'react-server/src/ReactFizzServer'; @@ -117,27 +117,24 @@ const SentClientRenderFunction /* */ = 0b00100; const SentStyleInsertionFunction /* */ = 0b01000; const SentFormReplayingRuntime /* */ = 0b10000; -// Per response, global state that is not contextual to the rendering subtree. -export type ResponseState = { - bootstrapChunks: Array, +// 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. +export type RenderState = { + // These can be recreated from resumable state. placeholderPrefix: PrecomputedChunk, segmentPrefix: PrecomputedChunk, boundaryPrefix: string, - idPrefix: string, - nextSuspenseID: number, - streamingFormat: StreamingFormat, - // state for script streaming format, unused if using external runtime / data + // inline script streaming format, unused if using external runtime / data startInlineScript: PrecomputedChunk, - instructions: InstructionState, - // state for data streaming format - externalRuntimeScript: null | ExternalRuntimeScript, + // the preamble must always flush before resuming, so all these chunks must + // be null or empty when resuming. - // preamble and postamble chunks and state + // preamble chunks htmlChunks: null | Array, headChunks: null | Array, - hasBody: boolean, // Hoistable chunks charsetChunks: Array, @@ -145,6 +142,9 @@ export type ResponseState = { preloadChunks: Array, hoistableChunks: Array, + // Module-global-like reference for current boundary resources + boundaryResources: ?BoundaryResources, + // Module-global-like reference for flushing/hoisting state of style resources // We need to track whether the current request has flushed any style resources // without sending an instruction to hoist them. we do that here @@ -155,6 +155,43 @@ export type ResponseState = { ... }; +// Per response, global state that is not contextual to the rendering subtree. +// This is resumable and therefore should be serializable. +export type ResumableState = { + // external runtime script chunks + externalRuntimeScript: null | ExternalRuntimeScript, // TODO: Move to a serializable format + bootstrapChunks: Array, // TODO: Move to a serializable format. + idPrefix: string, + nextSuspenseID: number, + streamingFormat: StreamingFormat, + + // state for script streaming format, unused if using external runtime / data + instructions: InstructionState, + + // postamble state + hasBody: boolean, + hasHtml: boolean, + + // Resources + + // Request local cache + preloadsMap: Map, + preconnectsMap: Map, + stylesMap: Map, + scriptsMap: Map, + + // Flushing queues for Resource dependencies + preconnects: Set, + fontPreloads: Set, + highImagePreloads: Set, + // usedImagePreloads: Set, + precedences: Map>, + stylePrecedences: Map, + bootstrapScripts: Set, + scripts: Set, + bulkPreloads: Set, +}; + const dataElementQuotedEnd = stringToPrecomputedChunk('">'); const startInlineScript = stringToPrecomputedChunk('