From b2b1a1b881665423b58b67a2fb6050ff19900286 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Wed, 14 Apr 2021 18:38:24 -0400 Subject: [PATCH 01/11] Wire up DOM legacy build --- .../ReactFlightClientHostConfig.dom-legacy.js | 12 +++ .../react-dom/server.browser.classic.fb.js | 16 +++ packages/react-dom/server.browser.js | 2 +- packages/react-dom/server.browser.stable.js | 16 +++ packages/react-dom/server.node.classic.fb.js | 17 ++++ packages/react-dom/server.node.js | 2 +- packages/react-dom/server.node.stable.js | 17 ++++ .../src/server/ReactDOMLegacyServerBrowser.js | 97 ++++++++++++++++++ .../src/server/ReactDOMLegacyServerNode.js | 98 +++++++++++++++++++ .../ReactDOMLegacyServerStreamConfig.js | 53 ++++++++++ .../forks/ReactFiberHostConfig.dom-legacy.js | 10 ++ .../ReactFlightServerConfig.dom-legacy.js | 11 +++ .../ReactServerFormatConfig.dom-legacy.js | 10 ++ .../ReactServerStreamConfig.dom-legacy.js | 10 ++ scripts/rollup/bundles.js | 4 +- scripts/shared/inlinedHostConfigs.js | 18 ++++ 16 files changed, 389 insertions(+), 4 deletions(-) create mode 100644 packages/react-client/src/forks/ReactFlightClientHostConfig.dom-legacy.js create mode 100644 packages/react-dom/server.browser.classic.fb.js create mode 100644 packages/react-dom/server.browser.stable.js create mode 100644 packages/react-dom/server.node.classic.fb.js create mode 100644 packages/react-dom/server.node.stable.js create mode 100644 packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js create mode 100644 packages/react-dom/src/server/ReactDOMLegacyServerNode.js create mode 100644 packages/react-dom/src/server/ReactDOMLegacyServerStreamConfig.js create mode 100644 packages/react-reconciler/src/forks/ReactFiberHostConfig.dom-legacy.js create mode 100644 packages/react-server/src/forks/ReactFlightServerConfig.dom-legacy.js create mode 100644 packages/react-server/src/forks/ReactServerFormatConfig.dom-legacy.js create mode 100644 packages/react-server/src/forks/ReactServerStreamConfig.dom-legacy.js diff --git a/packages/react-client/src/forks/ReactFlightClientHostConfig.dom-legacy.js b/packages/react-client/src/forks/ReactFlightClientHostConfig.dom-legacy.js new file mode 100644 index 0000000000000..59e1171c144ea --- /dev/null +++ b/packages/react-client/src/forks/ReactFlightClientHostConfig.dom-legacy.js @@ -0,0 +1,12 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export * from 'react-client/src/ReactFlightClientHostConfigBrowser'; +export * from 'react-client/src/ReactFlightClientHostConfigStream'; +export * from 'react-server-dom-webpack/src/ReactFlightClientWebpackBundlerConfig'; diff --git a/packages/react-dom/server.browser.classic.fb.js b/packages/react-dom/server.browser.classic.fb.js new file mode 100644 index 0000000000000..c57063649a9f0 --- /dev/null +++ b/packages/react-dom/server.browser.classic.fb.js @@ -0,0 +1,16 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export { + renderToString, + renderToStaticMarkup, + renderToNodeStream, + renderToStaticNodeStream, + version, +} from './src/server/ReactDOMServerBrowser'; diff --git a/packages/react-dom/server.browser.js b/packages/react-dom/server.browser.js index c57063649a9f0..bd63acc8685f7 100644 --- a/packages/react-dom/server.browser.js +++ b/packages/react-dom/server.browser.js @@ -13,4 +13,4 @@ export { renderToNodeStream, renderToStaticNodeStream, version, -} from './src/server/ReactDOMServerBrowser'; +} from './src/server/ReactDOMLegacyServerBrowser'; diff --git a/packages/react-dom/server.browser.stable.js b/packages/react-dom/server.browser.stable.js new file mode 100644 index 0000000000000..c57063649a9f0 --- /dev/null +++ b/packages/react-dom/server.browser.stable.js @@ -0,0 +1,16 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export { + renderToString, + renderToStaticMarkup, + renderToNodeStream, + renderToStaticNodeStream, + version, +} from './src/server/ReactDOMServerBrowser'; diff --git a/packages/react-dom/server.node.classic.fb.js b/packages/react-dom/server.node.classic.fb.js new file mode 100644 index 0000000000000..e610a8818f961 --- /dev/null +++ b/packages/react-dom/server.node.classic.fb.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +// For some reason Flow doesn't like export * in this file. I don't know why. +export { + renderToString, + renderToStaticMarkup, + renderToNodeStream, + renderToStaticNodeStream, + version, +} from './src/server/ReactDOMServerNode'; diff --git a/packages/react-dom/server.node.js b/packages/react-dom/server.node.js index e610a8818f961..92b69c03bc34e 100644 --- a/packages/react-dom/server.node.js +++ b/packages/react-dom/server.node.js @@ -14,4 +14,4 @@ export { renderToNodeStream, renderToStaticNodeStream, version, -} from './src/server/ReactDOMServerNode'; +} from './src/server/ReactDOMLegacyServerNode'; diff --git a/packages/react-dom/server.node.stable.js b/packages/react-dom/server.node.stable.js new file mode 100644 index 0000000000000..e610a8818f961 --- /dev/null +++ b/packages/react-dom/server.node.stable.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +// For some reason Flow doesn't like export * in this file. I don't know why. +export { + renderToString, + renderToStaticMarkup, + renderToNodeStream, + renderToStaticNodeStream, + version, +} from './src/server/ReactDOMServerNode'; diff --git a/packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js b/packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js new file mode 100644 index 0000000000000..2ea0b22d10abc --- /dev/null +++ b/packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js @@ -0,0 +1,97 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import ReactVersion from 'shared/ReactVersion'; +import invariant from 'shared/invariant'; + +import type {ReactNodeList} from 'shared/ReactTypes'; + +import { + createRequest, + startWork, + startFlowing, + abort, +} from 'react-server/src/ReactFizzServer'; + +import { + createResponseState, + createRootFormatContext, +} from './ReactDOMServerFormatConfig'; + +type ServerOptions = { + identifierPrefix?: string, +}; + +function onError() { + // Non-fatal errors are ignored. +} + +function renderToString( + children: ReactNodeList, + options?: ServerOptions, +): string { + let didFatal = false; + let fatalError = null; + const result = []; + const destination = { + push(chunk) { + if (chunk) { + result.push(chunk); + } + return true; + }, + destroy(error) { + didFatal = true; + fatalError = error; + }, + }; + const request = createRequest( + children, + destination, + createResponseState(options ? options.identifierPrefix : undefined), + createRootFormatContext(undefined), + Infinity, + onError, + undefined, + undefined, + ); + startWork(request); + // If anything suspended and is still pending, we'll abort it before writing. + // That way we write only client-rendered boundaries from the start. + abort(request); + startFlowing(request); + if (didFatal) { + throw fatalError; + } + return result.join(''); +} + +function renderToNodeStream() { + invariant( + false, + 'ReactDOMServer.renderToNodeStream(): The streaming API is not available ' + + 'in the browser. Use ReactDOMServer.renderToString() instead.', + ); +} + +function renderToStaticNodeStream() { + invariant( + false, + 'ReactDOMServer.renderToStaticNodeStream(): The streaming API is not available ' + + 'in the browser. Use ReactDOMServer.renderToStaticMarkup() instead.', + ); +} + +export { + renderToString, + renderToString as renderToStaticMarkup, + renderToNodeStream, + renderToStaticNodeStream, + ReactVersion as version, +}; diff --git a/packages/react-dom/src/server/ReactDOMLegacyServerNode.js b/packages/react-dom/src/server/ReactDOMLegacyServerNode.js new file mode 100644 index 0000000000000..29ba74f8ffaf7 --- /dev/null +++ b/packages/react-dom/src/server/ReactDOMLegacyServerNode.js @@ -0,0 +1,98 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {ReactNodeList} from 'shared/ReactTypes'; + +import type {Request} from 'react-server/src/ReactFizzServer'; + +import { + createRequest, + startWork, + startFlowing, + abort, +} from 'react-server/src/ReactFizzServer'; + +import { + createResponseState, + createRootFormatContext, +} from './ReactDOMServerFormatConfig'; + +import { + version, + renderToString, + renderToStaticMarkup, +} from './ReactDOMLegacyServerBrowser'; + +import {Readable} from 'stream'; + +type ServerOptions = { + identifierPrefix?: string, +}; + +class ReactMarkupReadableStream extends Readable { + request: Request; + startedFlowing: boolean; + constructor() { + // Calls the stream.Readable(options) constructor. Consider exposing built-in + // features like highWaterMark in the future. + super({}); + this.request = (null: any); + this.startedFlowing = false; + } + + _destroy(err, callback) { + abort(this.request); + // $FlowFixMe: The type definition for the callback should allow undefined and null. + callback(err); + } + + _read(size) { + if (this.startedFlowing) { + startFlowing(this.request); + } + } +} + +function onError() { + // Non-fatal errors are ignored. +} + +function renderToNodeStream( + children: ReactNodeList, + options?: ServerOptions, +): Readable { + function onCompleteAll() { + // We wait until everything has loaded before starting to write. + // That way we only end up with fully resolved HTML even if we suspend. + destination.startedFlowing = true; + startFlowing(request); + } + const destination = new ReactMarkupReadableStream(); + const request = createRequest( + children, + destination, + createResponseState(options ? options.identifierPrefix : undefined), + createRootFormatContext(undefined), + Infinity, + onError, + onCompleteAll, + undefined, + ); + destination.request = request; + startWork(request); + return destination; +} + +export { + renderToString, + renderToStaticMarkup, + renderToNodeStream, + renderToNodeStream as renderToStaticNodeStream, + version, +}; diff --git a/packages/react-dom/src/server/ReactDOMLegacyServerStreamConfig.js b/packages/react-dom/src/server/ReactDOMLegacyServerStreamConfig.js new file mode 100644 index 0000000000000..e8a106c3673d8 --- /dev/null +++ b/packages/react-dom/src/server/ReactDOMLegacyServerStreamConfig.js @@ -0,0 +1,53 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export type Destination = { + push(chunk: string | null): boolean, + destroy(error: Error): mixed, + ... +}; + +export type PrecomputedChunk = string; +export type Chunk = string; + +export const isPrimaryStreamConfig = false; + +export function scheduleWork(callback: () => void) { + callback(); +} + +export function flushBuffered(destination: Destination) {} + +export function beginWriting(destination: Destination) {} + +export function writeChunk( + destination: Destination, + chunk: Chunk | PrecomputedChunk, +): boolean { + return destination.push(chunk); +} + +export function completeWriting(destination: Destination) {} + +export function close(destination: Destination) { + destination.push(null); +} + +export function stringToChunk(content: string): Chunk { + return content; +} + +export function stringToPrecomputedChunk(content: string): PrecomputedChunk { + return content; +} + +export function closeWithError(destination: Destination, error: mixed): void { + // $FlowFixMe: This is an Error object or the destination accepts other types. + destination.destroy(error); +} diff --git a/packages/react-reconciler/src/forks/ReactFiberHostConfig.dom-legacy.js b/packages/react-reconciler/src/forks/ReactFiberHostConfig.dom-legacy.js new file mode 100644 index 0000000000000..d830c8501be27 --- /dev/null +++ b/packages/react-reconciler/src/forks/ReactFiberHostConfig.dom-legacy.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export * from 'react-dom/src/client/ReactDOMHostConfig'; diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.dom-legacy.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-legacy.js new file mode 100644 index 0000000000000..07b5f345f1936 --- /dev/null +++ b/packages/react-server/src/forks/ReactFlightServerConfig.dom-legacy.js @@ -0,0 +1,11 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export * from '../ReactFlightServerConfigStream'; +export * from 'react-server-dom-webpack/src/ReactFlightServerWebpackBundlerConfig'; diff --git a/packages/react-server/src/forks/ReactServerFormatConfig.dom-legacy.js b/packages/react-server/src/forks/ReactServerFormatConfig.dom-legacy.js new file mode 100644 index 0000000000000..c6e482efeb60c --- /dev/null +++ b/packages/react-server/src/forks/ReactServerFormatConfig.dom-legacy.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export * from 'react-dom/src/server/ReactDOMServerFormatConfig'; diff --git a/packages/react-server/src/forks/ReactServerStreamConfig.dom-legacy.js b/packages/react-server/src/forks/ReactServerStreamConfig.dom-legacy.js new file mode 100644 index 0000000000000..160efd5a92b23 --- /dev/null +++ b/packages/react-server/src/forks/ReactServerStreamConfig.dom-legacy.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export * from 'react-dom/src/server/ReactDOMLegacyServerStreamConfig'; diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 12302671c00ea..624c34bd2d9af 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -241,7 +241,7 @@ const bundles = [ bundleTypes: __EXPERIMENTAL__ ? [UMD_DEV, UMD_PROD, NODE_DEV, NODE_PROD] : [UMD_DEV, UMD_PROD, NODE_DEV, NODE_PROD, FB_WWW_DEV, FB_WWW_PROD], - moduleType: NON_FIBER_RENDERER, + moduleType: __EXPERIMENTAL__ ? RENDERER : NON_FIBER_RENDERER, entry: 'react-dom/server.browser', global: 'ReactDOMServer', externals: ['react'], @@ -254,7 +254,7 @@ const bundles = [ }, { bundleTypes: [NODE_DEV, NODE_PROD], - moduleType: NON_FIBER_RENDERER, + moduleType: __EXPERIMENTAL__ ? RENDERER : NON_FIBER_RENDERER, entry: 'react-dom/server.node', externals: ['react', 'stream'], babel: opts => diff --git a/scripts/shared/inlinedHostConfigs.js b/scripts/shared/inlinedHostConfigs.js index eef4d1fa9a0ec..146bc4d64ffe2 100644 --- a/scripts/shared/inlinedHostConfigs.js +++ b/scripts/shared/inlinedHostConfigs.js @@ -20,6 +20,7 @@ module.exports = [ 'react-dom', 'react-dom/unstable-fizz', 'react-dom/unstable-fizz.node', + 'react-dom/server.node.stable', 'react-dom/src/server/ReactDOMFizzServerNode.js', // react-dom/unstable-fizz.node 'react-server-dom-webpack', 'react-server-dom-webpack/writer', @@ -44,6 +45,7 @@ module.exports = [ 'react-dom', 'react-dom/testing', 'react-dom/unstable-fizz.browser', + 'react-dom/server.browser.stable', 'react-dom/src/server/ReactDOMFizzServerBrowser.js', // react-dom/unstable-fizz.browser 'react-server-dom-webpack', 'react-server-dom-webpack/writer.browser.server', @@ -53,6 +55,22 @@ module.exports = [ isFlowTyped: true, isServerSupported: true, }, + { + shortName: 'dom-legacy', + entryPoints: ['react-dom/server.browser', 'react-dom/server.node'], + paths: [ + 'react-dom', + 'react-dom/server', + 'react-dom/server.browser', + 'react-dom/server.node', + 'react-server-dom-webpack', + 'react-dom/src/server/ReactDOMLegacyServerBrowser.js', // react-dom/server.browser + 'react-dom/src/server/ReactDOMLegacyServerNode.js', // react-dom/server.node + 'react-client/src/ReactFlightClientStream.js', // We can only type check this in streaming configurations. + ], + isFlowTyped: true, + isServerSupported: true, + }, { shortName: 'art', entryPoints: ['react-art'], From d194f022472a7b25b1ad17c37cf0ffc38e4d2bff Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Wed, 14 Apr 2021 22:01:55 -0400 Subject: [PATCH 02/11] Hack to filter extra comments for testing purposes --- .../src/server/ReactDOMLegacyServerStreamConfig.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/react-dom/src/server/ReactDOMLegacyServerStreamConfig.js b/packages/react-dom/src/server/ReactDOMLegacyServerStreamConfig.js index e8a106c3673d8..c1d6c1df07ef9 100644 --- a/packages/react-dom/src/server/ReactDOMLegacyServerStreamConfig.js +++ b/packages/react-dom/src/server/ReactDOMLegacyServerStreamConfig.js @@ -26,10 +26,21 @@ export function flushBuffered(destination: Destination) {} export function beginWriting(destination: Destination) {} +let prevWasCommentSegmenter = false; export function writeChunk( destination: Destination, chunk: Chunk | PrecomputedChunk, ): boolean { + if (prevWasCommentSegmenter) { + prevWasCommentSegmenter = false; + if (chunk[0] !== '<') { + destination.push(''); + } + } + if (chunk === '') { + prevWasCommentSegmenter = true; + return true; + } return destination.push(chunk); } From 17503185f7fcb11f15eba5f87c57a04debcec1f2 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 19 Apr 2021 12:42:38 -0400 Subject: [PATCH 03/11] Use string concat in renderToString I think this might be faster. We could probably use a combination of this technique in the stream too to lower the overhead. --- .../react-dom/src/server/ReactDOMLegacyServerBrowser.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js b/packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js index 2ea0b22d10abc..190c22cd6a60d 100644 --- a/packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js +++ b/packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js @@ -38,11 +38,11 @@ function renderToString( ): string { let didFatal = false; let fatalError = null; - const result = []; + let result = ''; const destination = { push(chunk) { - if (chunk) { - result.push(chunk); + if (chunk !== null) { + result += chunk; } return true; }, @@ -69,7 +69,7 @@ function renderToString( if (didFatal) { throw fatalError; } - return result.join(''); + return result; } function renderToNodeStream() { From e29a5064167d62b81c1f82a5a577a3bf6738e170 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 26 Apr 2021 21:11:17 -0400 Subject: [PATCH 04/11] Error if we can't complete the root synchronously Maybe this should always error but in the async forms we can just delay the stream until it resolves so it does have some useful semantics. In the synchronous form it's never useful though. I'm mostly adding the error because we're testing this behavior for renderToString specifically. --- .../src/server/ReactDOMLegacyServerBrowser.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js b/packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js index 190c22cd6a60d..5435c3b6449c7 100644 --- a/packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js +++ b/packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js @@ -51,6 +51,11 @@ function renderToString( fatalError = error; }, }; + + let readyToStream = false; + function onReadyToStream() { + readyToStream = true; + } const request = createRequest( children, destination, @@ -59,7 +64,7 @@ function renderToString( Infinity, onError, undefined, - undefined, + onReadyToStream, ); startWork(request); // If anything suspended and is still pending, we'll abort it before writing. @@ -69,6 +74,13 @@ function renderToString( if (didFatal) { throw fatalError; } + invariant( + readyToStream, + 'A React component suspended while rendering, but no fallback UI was specified.\n' + + '\n' + + 'Add a component higher in the tree to ' + + 'provide a loading indicator or placeholder to display.', + ); return result; } From b8234038bb89770b47ad0d8e89924e1040854611 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Wed, 5 May 2021 11:19:57 -0400 Subject: [PATCH 05/11] Gate memory leak tests of internals These tests don't translate as is to the new implementation and have been ported to the Fizz tests separately. --- .../src/__tests__/ReactDOMServerIntegrationNewContext-test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js index cbba195910459..601a42e725240 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js @@ -487,6 +487,7 @@ describe('ReactDOMServerIntegration', () => { }); // Regression test for https://github.com/facebook/react/issues/14705 + // @gate !experimental && www it('does not pollute later renders when stream destroyed', () => { const LoggedInUser = React.createContext('default'); @@ -529,6 +530,7 @@ describe('ReactDOMServerIntegration', () => { }); // Regression test for https://github.com/facebook/react/issues/14705 + // @gate !experimental && www it('frees context value reference when stream destroyed', () => { const LoggedInUser = React.createContext('default'); From 0e3bcb79973babb00ae95484cede03a4301fc2e9 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Tue, 1 Jun 2021 23:04:29 -0400 Subject: [PATCH 06/11] Enable Fizz legacy mode in stable --- packages/react-dom/server.node.stable.js | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 packages/react-dom/server.node.stable.js diff --git a/packages/react-dom/server.node.stable.js b/packages/react-dom/server.node.stable.js deleted file mode 100644 index e610a8818f961..0000000000000 --- a/packages/react-dom/server.node.stable.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - */ - -// For some reason Flow doesn't like export * in this file. I don't know why. -export { - renderToString, - renderToStaticMarkup, - renderToNodeStream, - renderToStaticNodeStream, - version, -} from './src/server/ReactDOMServerNode'; From 1fcd29c6da01a469d049d7172c1464007f9902f0 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 14 Jun 2021 13:54:27 -0400 Subject: [PATCH 07/11] Add wrapper around the ServerFormatConfig for legacy mode This ensures that we can inject custom overrides without negatively affecting the new implementation. This adds another field for static mark up for example. --- .../src/server/ReactDOMLegacyServerBrowser.js | 4 +- .../src/server/ReactDOMLegacyServerNode.js | 4 +- .../ReactDOMLegacyServerStreamConfig.js | 2 - .../src/server/ReactDOMServerFormatConfig.js | 17 ++-- .../ReactDOMServerLegacyFormatConfig.js | 77 +++++++++++++++++++ .../src/ReactServerStreamConfigFB.js | 2 - .../src/ReactServerStreamConfigBrowser.js | 2 - .../src/ReactServerStreamConfigNode.js | 2 - .../ReactServerFormatConfig.dom-legacy.js | 2 +- 9 files changed, 91 insertions(+), 21 deletions(-) create mode 100644 packages/react-dom/src/server/ReactDOMServerLegacyFormatConfig.js diff --git a/packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js b/packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js index 5435c3b6449c7..5a01fa9327955 100644 --- a/packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js +++ b/packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js @@ -22,7 +22,7 @@ import { import { createResponseState, createRootFormatContext, -} from './ReactDOMServerFormatConfig'; +} from './ReactDOMServerLegacyFormatConfig'; type ServerOptions = { identifierPrefix?: string, @@ -59,7 +59,7 @@ function renderToString( const request = createRequest( children, destination, - createResponseState(options ? options.identifierPrefix : undefined), + createResponseState(false, options ? options.identifierPrefix : undefined), createRootFormatContext(undefined), Infinity, onError, diff --git a/packages/react-dom/src/server/ReactDOMLegacyServerNode.js b/packages/react-dom/src/server/ReactDOMLegacyServerNode.js index 29ba74f8ffaf7..55b05d90c4753 100644 --- a/packages/react-dom/src/server/ReactDOMLegacyServerNode.js +++ b/packages/react-dom/src/server/ReactDOMLegacyServerNode.js @@ -21,7 +21,7 @@ import { import { createResponseState, createRootFormatContext, -} from './ReactDOMServerFormatConfig'; +} from './ReactDOMServerLegacyFormatConfig'; import { version, @@ -77,7 +77,7 @@ function renderToNodeStream( const request = createRequest( children, destination, - createResponseState(options ? options.identifierPrefix : undefined), + createResponseState(false, options ? options.identifierPrefix : undefined), createRootFormatContext(undefined), Infinity, onError, diff --git a/packages/react-dom/src/server/ReactDOMLegacyServerStreamConfig.js b/packages/react-dom/src/server/ReactDOMLegacyServerStreamConfig.js index c1d6c1df07ef9..4cfe16091bc58 100644 --- a/packages/react-dom/src/server/ReactDOMLegacyServerStreamConfig.js +++ b/packages/react-dom/src/server/ReactDOMLegacyServerStreamConfig.js @@ -16,8 +16,6 @@ export type Destination = { export type PrecomputedChunk = string; export type Chunk = string; -export const isPrimaryStreamConfig = false; - export function scheduleWork(callback: () => void) { callback(); } diff --git a/packages/react-dom/src/server/ReactDOMServerFormatConfig.js b/packages/react-dom/src/server/ReactDOMServerFormatConfig.js index 7d705a6abe1d3..1e77b80184d22 100644 --- a/packages/react-dom/src/server/ReactDOMServerFormatConfig.js +++ b/packages/react-dom/src/server/ReactDOMServerFormatConfig.js @@ -23,7 +23,6 @@ import { writeChunk, stringToChunk, stringToPrecomputedChunk, - isPrimaryStreamConfig, } from 'react-server/src/ReactServerStreamConfig'; import { @@ -51,7 +50,7 @@ import isArray from 'shared/isArray'; // Used to distinguish these contexts from ones used in other renderers. // E.g. this can be used to distinguish legacy renderers from this modern one. -export const isPrimaryRenderer = isPrimaryStreamConfig; +export const isPrimaryRenderer = true; // Per response, global state that is not contextual to the rendering subtree. export type ResponseState = { @@ -63,18 +62,20 @@ export type ResponseState = { nextOpaqueID: number, sentCompleteSegmentFunction: boolean, sentCompleteBoundaryFunction: boolean, - sentClientRenderFunction: boolean, + sentClientRenderFunction: boolean, // We allow the legacy renderer to extend this object. + ... }; // Allows us to keep track of what we've already written so we can refer back to it. export function createResponseState( - identifierPrefix: string = '', + identifierPrefix: string | void, ): ResponseState { + const idPrefix = identifierPrefix === undefined ? '' : identifierPrefix; return { - placeholderPrefix: stringToPrecomputedChunk(identifierPrefix + 'P:'), - segmentPrefix: stringToPrecomputedChunk(identifierPrefix + 'S:'), - boundaryPrefix: identifierPrefix + 'B:', - opaqueIdentifierPrefix: identifierPrefix + 'R:', + placeholderPrefix: stringToPrecomputedChunk(idPrefix + 'P:'), + segmentPrefix: stringToPrecomputedChunk(idPrefix + 'S:'), + boundaryPrefix: idPrefix + 'B:', + opaqueIdentifierPrefix: idPrefix + 'R:', nextSuspenseID: 0, nextOpaqueID: 0, sentCompleteSegmentFunction: false, diff --git a/packages/react-dom/src/server/ReactDOMServerLegacyFormatConfig.js b/packages/react-dom/src/server/ReactDOMServerLegacyFormatConfig.js new file mode 100644 index 0000000000000..09e4f84a4a153 --- /dev/null +++ b/packages/react-dom/src/server/ReactDOMServerLegacyFormatConfig.js @@ -0,0 +1,77 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import {createResponseState as createResponseStateImpl} from './ReactDOMServerFormatConfig'; + +import type {PrecomputedChunk} from 'react-server/src/ReactServerStreamConfig'; + +export const isPrimaryRenderer = false; + +export type ResponseState = { + // Keep this in sync with ReactDOMServerFormatConfig + placeholderPrefix: PrecomputedChunk, + segmentPrefix: PrecomputedChunk, + boundaryPrefix: string, + opaqueIdentifierPrefix: string, + nextSuspenseID: number, + nextOpaqueID: number, + sentCompleteSegmentFunction: boolean, + sentCompleteBoundaryFunction: boolean, + sentClientRenderFunction: boolean, + // This is an extra field for the legacy renderer + generateStaticMarkup: boolean, +}; + +export function createResponseState( + generateStaticMarkup: boolean, + identifierPrefix: string | void, +): ResponseState { + const responseState = createResponseStateImpl(identifierPrefix); + return { + // Keep this in sync with ReactDOMServerFormatConfig + placeholderPrefix: responseState.placeholderPrefix, + segmentPrefix: responseState.segmentPrefix, + boundaryPrefix: responseState.boundaryPrefix, + opaqueIdentifierPrefix: responseState.opaqueIdentifierPrefix, + nextSuspenseID: responseState.nextSuspenseID, + nextOpaqueID: responseState.nextOpaqueID, + sentCompleteSegmentFunction: responseState.sentCompleteSegmentFunction, + sentCompleteBoundaryFunction: responseState.sentCompleteBoundaryFunction, + sentClientRenderFunction: responseState.sentClientRenderFunction, + // This is an extra field for the legacy renderer + generateStaticMarkup, + }; +} + +export type { + FormatContext, + SuspenseBoundaryID, + OpaqueIDType, +} from './ReactDOMServerFormatConfig'; + +export { + createRootFormatContext, + getChildFormatContext, + createSuspenseBoundaryID, + makeServerID, + pushEmpty, + pushTextInstance, + pushStartInstance, + pushEndInstance, + writePlaceholder, + writeStartCompletedSuspenseBoundary, + writeStartPendingSuspenseBoundary, + writeStartClientRenderedSuspenseBoundary, + writeEndSuspenseBoundary, + writeStartSegment, + writeEndSegment, + writeCompletedSegmentInstruction, + writeCompletedBoundaryInstruction, + writeClientRenderBoundaryInstruction, +} from './ReactDOMServerFormatConfig'; diff --git a/packages/react-server-dom-relay/src/ReactServerStreamConfigFB.js b/packages/react-server-dom-relay/src/ReactServerStreamConfigFB.js index b13e5f3792601..e0477fd6197e7 100644 --- a/packages/react-server-dom-relay/src/ReactServerStreamConfigFB.js +++ b/packages/react-server-dom-relay/src/ReactServerStreamConfigFB.js @@ -17,8 +17,6 @@ export type Destination = { export type PrecomputedChunk = string; export type Chunk = string; -export const isPrimaryStreamConfig = true; - export function scheduleWork(callback: () => void) { // We don't schedule work in this model, and instead expect performWork to always be called repeatedly. } diff --git a/packages/react-server/src/ReactServerStreamConfigBrowser.js b/packages/react-server/src/ReactServerStreamConfigBrowser.js index b669002aff771..3714be4302765 100644 --- a/packages/react-server/src/ReactServerStreamConfigBrowser.js +++ b/packages/react-server/src/ReactServerStreamConfigBrowser.js @@ -12,8 +12,6 @@ export type Destination = ReadableStreamController; export type PrecomputedChunk = Uint8Array; export type Chunk = Uint8Array; -export const isPrimaryStreamConfig = true; - export function scheduleWork(callback: () => void) { callback(); } diff --git a/packages/react-server/src/ReactServerStreamConfigNode.js b/packages/react-server/src/ReactServerStreamConfigNode.js index 58ce127ba9659..348deb7605039 100644 --- a/packages/react-server/src/ReactServerStreamConfigNode.js +++ b/packages/react-server/src/ReactServerStreamConfigNode.js @@ -19,8 +19,6 @@ export type Destination = Writable & MightBeFlushable; export type PrecomputedChunk = Uint8Array; export type Chunk = string; -export const isPrimaryStreamConfig = true; - export function scheduleWork(callback: () => void) { setImmediate(callback); } diff --git a/packages/react-server/src/forks/ReactServerFormatConfig.dom-legacy.js b/packages/react-server/src/forks/ReactServerFormatConfig.dom-legacy.js index c6e482efeb60c..acbac5042b62a 100644 --- a/packages/react-server/src/forks/ReactServerFormatConfig.dom-legacy.js +++ b/packages/react-server/src/forks/ReactServerFormatConfig.dom-legacy.js @@ -7,4 +7,4 @@ * @flow */ -export * from 'react-dom/src/server/ReactDOMServerFormatConfig'; +export * from 'react-dom/src/server/ReactDOMServerLegacyFormatConfig'; From 9e7d2f5c099faafbf5e8ec7bfe1abedf832ef271 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 14 Jun 2021 14:08:54 -0400 Subject: [PATCH 08/11] Wrap pushTextInstance to avoid emitting comments for text in static markup --- .../ReactDOMServerLegacyFormatConfig.js | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/packages/react-dom/src/server/ReactDOMServerLegacyFormatConfig.js b/packages/react-dom/src/server/ReactDOMServerLegacyFormatConfig.js index 09e4f84a4a153..4c35d44c30a70 100644 --- a/packages/react-dom/src/server/ReactDOMServerLegacyFormatConfig.js +++ b/packages/react-dom/src/server/ReactDOMServerLegacyFormatConfig.js @@ -7,9 +7,21 @@ * @flow */ -import {createResponseState as createResponseStateImpl} from './ReactDOMServerFormatConfig'; +import type { + FormatContext, + SuspenseBoundaryID, + OpaqueIDType, +} from './ReactDOMServerFormatConfig'; + +import { + createResponseState as createResponseStateImpl, + pushTextInstance as pushTextInstanceImpl, +} from './ReactDOMServerFormatConfig'; -import type {PrecomputedChunk} from 'react-server/src/ReactServerStreamConfig'; +import type { + Chunk, + PrecomputedChunk, +} from 'react-server/src/ReactServerStreamConfig'; export const isPrimaryRenderer = false; @@ -61,7 +73,6 @@ export { createSuspenseBoundaryID, makeServerID, pushEmpty, - pushTextInstance, pushStartInstance, pushEndInstance, writePlaceholder, @@ -75,3 +86,20 @@ export { writeCompletedBoundaryInstruction, writeClientRenderBoundaryInstruction, } from './ReactDOMServerFormatConfig'; + +import {stringToChunk} from 'react-server/src/ReactServerStreamConfig'; + +import escapeTextForBrowser from './escapeTextForBrowser'; + +export function pushTextInstance( + target: Array, + text: string, + responseState: ResponseState, + assignID: null | SuspenseBoundaryID, +): void { + if (responseState.generateStaticMarkup) { + target.push(stringToChunk(escapeTextForBrowser(text))); + } else { + pushTextInstanceImpl(target, text, responseState, assignID); + } +} From 38cdc26bde3243f1015ff3ae7769b2cf77bf6947 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 14 Jun 2021 14:38:06 -0400 Subject: [PATCH 09/11] Don't emit static mark up for completed suspense boundaries Completed and client rendered boundaries are only marked for the client to take over. Pending boundaries are still supported in case you stream non-hydratable mark up. --- .../src/server/ReactDOMServerFormatConfig.js | 20 +++++- .../ReactDOMServerLegacyFormatConfig.js | 70 ++++++++++++++++--- .../server/ReactNativeServerFormatConfig.js | 20 +++++- .../src/ReactNoopServer.js | 11 ++- packages/react-server/src/ReactFizzServer.js | 42 ++++++++--- .../forks/ReactServerFormatConfig.custom.js | 7 +- 6 files changed, 147 insertions(+), 23 deletions(-) diff --git a/packages/react-dom/src/server/ReactDOMServerFormatConfig.js b/packages/react-dom/src/server/ReactDOMServerFormatConfig.js index 1e77b80184d22..6dfa245ae4bd8 100644 --- a/packages/react-dom/src/server/ReactDOMServerFormatConfig.js +++ b/packages/react-dom/src/server/ReactDOMServerFormatConfig.js @@ -1460,23 +1460,41 @@ const endSuspenseBoundary = stringToPrecomputedChunk(''); export function writeStartCompletedSuspenseBoundary( destination: Destination, + responseState: ResponseState, id: SuspenseBoundaryID, ): boolean { return writeChunk(destination, startCompletedSuspenseBoundary); } export function writeStartPendingSuspenseBoundary( destination: Destination, + responseState: ResponseState, id: SuspenseBoundaryID, ): boolean { return writeChunk(destination, startPendingSuspenseBoundary); } export function writeStartClientRenderedSuspenseBoundary( destination: Destination, + responseState: ResponseState, id: SuspenseBoundaryID, ): boolean { return writeChunk(destination, startClientRenderedSuspenseBoundary); } -export function writeEndSuspenseBoundary(destination: Destination): boolean { +export function writeEndCompletedSuspenseBoundary( + destination: Destination, + responseState: ResponseState, +): boolean { + return writeChunk(destination, endSuspenseBoundary); +} +export function writeEndPendingSuspenseBoundary( + destination: Destination, + responseState: ResponseState, +): boolean { + return writeChunk(destination, endSuspenseBoundary); +} +export function writeEndClientRenderedSuspenseBoundary( + destination: Destination, + responseState: ResponseState, +): boolean { return writeChunk(destination, endSuspenseBoundary); } diff --git a/packages/react-dom/src/server/ReactDOMServerLegacyFormatConfig.js b/packages/react-dom/src/server/ReactDOMServerLegacyFormatConfig.js index 4c35d44c30a70..dbca4b43ab032 100644 --- a/packages/react-dom/src/server/ReactDOMServerLegacyFormatConfig.js +++ b/packages/react-dom/src/server/ReactDOMServerLegacyFormatConfig.js @@ -7,18 +7,19 @@ * @flow */ -import type { - FormatContext, - SuspenseBoundaryID, - OpaqueIDType, -} from './ReactDOMServerFormatConfig'; +import type {SuspenseBoundaryID} from './ReactDOMServerFormatConfig'; import { createResponseState as createResponseStateImpl, pushTextInstance as pushTextInstanceImpl, + writeStartCompletedSuspenseBoundary as writeStartCompletedSuspenseBoundaryImpl, + writeStartClientRenderedSuspenseBoundary as writeStartClientRenderedSuspenseBoundaryImpl, + writeEndCompletedSuspenseBoundary as writeEndCompletedSuspenseBoundaryImpl, + writeEndClientRenderedSuspenseBoundary as writeEndClientRenderedSuspenseBoundaryImpl, } from './ReactDOMServerFormatConfig'; import type { + Destination, Chunk, PrecomputedChunk, } from 'react-server/src/ReactServerStreamConfig'; @@ -75,16 +76,14 @@ export { pushEmpty, pushStartInstance, pushEndInstance, - writePlaceholder, - writeStartCompletedSuspenseBoundary, - writeStartPendingSuspenseBoundary, - writeStartClientRenderedSuspenseBoundary, - writeEndSuspenseBoundary, writeStartSegment, writeEndSegment, writeCompletedSegmentInstruction, writeCompletedBoundaryInstruction, writeClientRenderBoundaryInstruction, + writeStartPendingSuspenseBoundary, + writeEndPendingSuspenseBoundary, + writePlaceholder, } from './ReactDOMServerFormatConfig'; import {stringToChunk} from 'react-server/src/ReactServerStreamConfig'; @@ -103,3 +102,54 @@ export function pushTextInstance( pushTextInstanceImpl(target, text, responseState, assignID); } } + +export function writeStartCompletedSuspenseBoundary( + destination: Destination, + responseState: ResponseState, + id: SuspenseBoundaryID, +): boolean { + if (responseState.generateStaticMarkup) { + // A completed boundary is done and doesn't need a representation in the HTML + // if we're not going to be hydrating it. + return true; + } + return writeStartCompletedSuspenseBoundaryImpl( + destination, + responseState, + id, + ); +} +export function writeStartClientRenderedSuspenseBoundary( + destination: Destination, + responseState: ResponseState, + id: SuspenseBoundaryID, +): boolean { + if (responseState.generateStaticMarkup) { + // A client rendered boundary is done and doesn't need a representation in the HTML + // since we'll never hydrate it. This is arguably an error in static generation. + return true; + } + return writeStartClientRenderedSuspenseBoundaryImpl( + destination, + responseState, + id, + ); +} +export function writeEndCompletedSuspenseBoundary( + destination: Destination, + responseState: ResponseState, +): boolean { + if (responseState.generateStaticMarkup) { + return true; + } + return writeEndCompletedSuspenseBoundaryImpl(destination, responseState); +} +export function writeEndClientRenderedSuspenseBoundary( + destination: Destination, + responseState: ResponseState, +): boolean { + if (responseState.generateStaticMarkup) { + return true; + } + return writeEndClientRenderedSuspenseBoundaryImpl(destination, responseState); +} diff --git a/packages/react-native-renderer/src/server/ReactNativeServerFormatConfig.js b/packages/react-native-renderer/src/server/ReactNativeServerFormatConfig.js index 98779768f486c..9f7501007ed87 100644 --- a/packages/react-native-renderer/src/server/ReactNativeServerFormatConfig.js +++ b/packages/react-native-renderer/src/server/ReactNativeServerFormatConfig.js @@ -203,6 +203,7 @@ export function writePlaceholder( // Suspense boundaries are encoded as comments. export function writeStartCompletedSuspenseBoundary( destination: Destination, + responseState: ResponseState, id: SuspenseBoundaryID, ): boolean { writeChunk(destination, SUSPENSE_COMPLETE); @@ -210,6 +211,7 @@ export function writeStartCompletedSuspenseBoundary( } export function writeStartPendingSuspenseBoundary( destination: Destination, + responseState: ResponseState, id: SuspenseBoundaryID, ): boolean { writeChunk(destination, SUSPENSE_PENDING); @@ -217,12 +219,28 @@ export function writeStartPendingSuspenseBoundary( } export function writeStartClientRenderedSuspenseBoundary( destination: Destination, + responseState: ResponseState, id: SuspenseBoundaryID, ): boolean { writeChunk(destination, SUSPENSE_CLIENT_RENDER); return writeChunk(destination, formatID(id)); } -export function writeEndSuspenseBoundary(destination: Destination): boolean { +export function writeEndCompletedSuspenseBoundary( + destination: Destination, + responseState: ResponseState, +): boolean { + return writeChunk(destination, END); +} +export function writeEndPendingSuspenseBoundary( + destination: Destination, + responseState: ResponseState, +): boolean { + return writeChunk(destination, END); +} +export function writeEndClientRenderedSuspenseBoundary( + destination: Destination, + responseState: ResponseState, +): boolean { return writeChunk(destination, END); } diff --git a/packages/react-noop-renderer/src/ReactNoopServer.js b/packages/react-noop-renderer/src/ReactNoopServer.js index 5a8ff56d6ce23..e183a8ce71906 100644 --- a/packages/react-noop-renderer/src/ReactNoopServer.js +++ b/packages/react-noop-renderer/src/ReactNoopServer.js @@ -138,6 +138,7 @@ const ReactNoopServer = ReactFizzServer({ writeStartCompletedSuspenseBoundary( destination: Destination, + responseState: ResponseState, suspenseInstance: SuspenseInstance, ): boolean { suspenseInstance.state = 'complete'; @@ -147,6 +148,7 @@ const ReactNoopServer = ReactFizzServer({ }, writeStartPendingSuspenseBoundary( destination: Destination, + responseState: ResponseState, suspenseInstance: SuspenseInstance, ): boolean { suspenseInstance.state = 'pending'; @@ -156,6 +158,7 @@ const ReactNoopServer = ReactFizzServer({ }, writeStartClientRenderedSuspenseBoundary( destination: Destination, + responseState: ResponseState, suspenseInstance: SuspenseInstance, ): boolean { suspenseInstance.state = 'client-render'; @@ -163,7 +166,13 @@ const ReactNoopServer = ReactFizzServer({ parent.children.push(suspenseInstance); destination.stack.push(suspenseInstance); }, - writeEndSuspenseBoundary(destination: Destination): boolean { + writeEndCompletedSuspenseBoundary(destination: Destination): boolean { + destination.stack.pop(); + }, + writeEndPendingSuspenseBoundary(destination: Destination): boolean { + destination.stack.pop(); + }, + writeEndClientRenderedSuspenseBoundary(destination: Destination): boolean { destination.stack.pop(); }, diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index 927aef27f4a7f..411eaf7618c62 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -40,7 +40,9 @@ import { writeStartCompletedSuspenseBoundary, writeStartPendingSuspenseBoundary, writeStartClientRenderedSuspenseBoundary, - writeEndSuspenseBoundary, + writeEndCompletedSuspenseBoundary, + writeEndPendingSuspenseBoundary, + writeEndClientRenderedSuspenseBoundary, writeStartSegment, writeEndSegment, writeClientRenderBoundaryInstruction, @@ -1609,12 +1611,19 @@ function flushSegment( // Emit a client rendered suspense boundary wrapper. // We never queue the inner boundary so we'll never emit its content or partial segments. - writeStartClientRenderedSuspenseBoundary(destination, boundary.id); + writeStartClientRenderedSuspenseBoundary( + destination, + request.responseState, + boundary.id, + ); // Flush the fallback. flushSubtree(request, destination, segment); - return writeEndSuspenseBoundary(destination); + return writeEndClientRenderedSuspenseBoundary( + destination, + request.responseState, + ); } else if (boundary.pendingTasks > 0) { // This boundary is still loading. Emit a pending suspense boundary wrapper. @@ -1625,12 +1634,16 @@ function flushSegment( request.partialBoundaries.push(boundary); } - writeStartPendingSuspenseBoundary(destination, boundary.id); + writeStartPendingSuspenseBoundary( + destination, + request.responseState, + boundary.id, + ); // Flush the fallback. flushSubtree(request, destination, segment); - return writeEndSuspenseBoundary(destination); + return writeEndPendingSuspenseBoundary(destination, request.responseState); } else if (boundary.byteSize > request.progressiveChunkSize) { // This boundary is large and will be emitted separately so that we can progressively show // other content. We add it to the queue during the flush because we have to ensure that @@ -1643,16 +1656,24 @@ function flushSegment( request.completedBoundaries.push(boundary); // Emit a pending rendered suspense boundary wrapper. - writeStartPendingSuspenseBoundary(destination, boundary.id); + writeStartPendingSuspenseBoundary( + destination, + request.responseState, + boundary.id, + ); // Flush the fallback. flushSubtree(request, destination, segment); - return writeEndSuspenseBoundary(destination); + return writeEndPendingSuspenseBoundary(destination, request.responseState); } else { // We can inline this boundary's content as a complete boundary. - writeStartCompletedSuspenseBoundary(destination, boundary.id); + writeStartCompletedSuspenseBoundary( + destination, + request.responseState, + boundary.id, + ); const completedSegments = boundary.completedSegments; invariant( @@ -1662,7 +1683,10 @@ function flushSegment( const contentSegment = completedSegments[0]; flushSegment(request, destination, contentSegment); - return writeEndSuspenseBoundary(destination); + return writeEndCompletedSuspenseBoundary( + destination, + request.responseState, + ); } } diff --git a/packages/react-server/src/forks/ReactServerFormatConfig.custom.js b/packages/react-server/src/forks/ReactServerFormatConfig.custom.js index 7296697cc9c88..5458a4f50a9d5 100644 --- a/packages/react-server/src/forks/ReactServerFormatConfig.custom.js +++ b/packages/react-server/src/forks/ReactServerFormatConfig.custom.js @@ -46,7 +46,12 @@ export const writeStartPendingSuspenseBoundary = $$$hostConfig.writeStartPendingSuspenseBoundary; export const writeStartClientRenderedSuspenseBoundary = $$$hostConfig.writeStartClientRenderedSuspenseBoundary; -export const writeEndSuspenseBoundary = $$$hostConfig.writeEndSuspenseBoundary; +export const writeEndCompletedSuspenseBoundary = + $$$hostConfig.writeEndCompletedSuspenseBoundary; +export const writeEndPendingSuspenseBoundary = + $$$hostConfig.writeEndPendingSuspenseBoundary; +export const writeEndClientRenderedSuspenseBoundary = + $$$hostConfig.writeEndClientRenderedSuspenseBoundary; export const writeStartSegment = $$$hostConfig.writeStartSegment; export const writeEndSegment = $$$hostConfig.writeEndSegment; export const writeCompletedSegmentInstruction = From b24fbf8396e553aa09b43d1778919b35b2477dd2 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 14 Jun 2021 14:48:06 -0400 Subject: [PATCH 10/11] Wire up generateStaticMarkup to static API entry points --- .../src/server/ReactDOMLegacyServerBrowser.js | 26 ++++++++++++++++--- .../src/server/ReactDOMLegacyServerNode.js | 21 ++++++++++++--- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js b/packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js index 5a01fa9327955..4f0d619417611 100644 --- a/packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js +++ b/packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js @@ -32,9 +32,10 @@ function onError() { // Non-fatal errors are ignored. } -function renderToString( +function renderToStringImpl( children: ReactNodeList, - options?: ServerOptions, + options: void | ServerOptions, + generateStaticMarkup: boolean, ): string { let didFatal = false; let fatalError = null; @@ -59,7 +60,10 @@ function renderToString( const request = createRequest( children, destination, - createResponseState(false, options ? options.identifierPrefix : undefined), + createResponseState( + generateStaticMarkup, + options ? options.identifierPrefix : undefined, + ), createRootFormatContext(undefined), Infinity, onError, @@ -84,6 +88,20 @@ function renderToString( return result; } +function renderToString( + children: ReactNodeList, + options?: ServerOptions, +): string { + return renderToStringImpl(children, options, false); +} + +function renderToStaticMarkup( + children: ReactNodeList, + options?: ServerOptions, +): string { + return renderToStringImpl(children, options, true); +} + function renderToNodeStream() { invariant( false, @@ -102,7 +120,7 @@ function renderToStaticNodeStream() { export { renderToString, - renderToString as renderToStaticMarkup, + renderToStaticMarkup, renderToNodeStream, renderToStaticNodeStream, ReactVersion as version, diff --git a/packages/react-dom/src/server/ReactDOMLegacyServerNode.js b/packages/react-dom/src/server/ReactDOMLegacyServerNode.js index 55b05d90c4753..bc1e13220963d 100644 --- a/packages/react-dom/src/server/ReactDOMLegacyServerNode.js +++ b/packages/react-dom/src/server/ReactDOMLegacyServerNode.js @@ -63,9 +63,10 @@ function onError() { // Non-fatal errors are ignored. } -function renderToNodeStream( +function renderToNodeStreamImpl( children: ReactNodeList, - options?: ServerOptions, + options: void | ServerOptions, + generateStaticMarkup: boolean, ): Readable { function onCompleteAll() { // We wait until everything has loaded before starting to write. @@ -89,10 +90,24 @@ function renderToNodeStream( return destination; } +function renderToNodeStream( + children: ReactNodeList, + options?: ServerOptions, +): Readable { + return renderToNodeStreamImpl(children, options, false); +} + +function renderToStaticNodeStream( + children: ReactNodeList, + options?: ServerOptions, +): Readable { + return renderToNodeStreamImpl(children, options, true); +} + export { renderToString, renderToStaticMarkup, renderToNodeStream, - renderToNodeStream as renderToStaticNodeStream, + renderToStaticNodeStream, version, }; From c24b569588033061c5814473fb0ed2f321467e12 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Mon, 14 Jun 2021 15:35:46 -0400 Subject: [PATCH 11/11] Mark as renderer for stable This shouldn't affect the FB one ideally but it's done with the same build so let's hope this works. --- scripts/rollup/bundles.js | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 624c34bd2d9af..9823df3142f29 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -56,17 +56,9 @@ const moduleTypes = { RENDERER_UTILS: 'RENDERER_UTILS', // Standalone reconciler for third-party renderers. RECONCILER: 'RECONCILER', - // Non-Fiber implementations like SSR and Shallow renderers. - NON_FIBER_RENDERER: 'NON_FIBER_RENDERER', }; -const { - ISOMORPHIC, - RENDERER, - RENDERER_UTILS, - RECONCILER, - NON_FIBER_RENDERER, -} = moduleTypes; +const {ISOMORPHIC, RENDERER, RENDERER_UTILS, RECONCILER} = moduleTypes; const bundles = [ /******* Isomorphic *******/ @@ -241,7 +233,7 @@ const bundles = [ bundleTypes: __EXPERIMENTAL__ ? [UMD_DEV, UMD_PROD, NODE_DEV, NODE_PROD] : [UMD_DEV, UMD_PROD, NODE_DEV, NODE_PROD, FB_WWW_DEV, FB_WWW_PROD], - moduleType: __EXPERIMENTAL__ ? RENDERER : NON_FIBER_RENDERER, + moduleType: RENDERER, entry: 'react-dom/server.browser', global: 'ReactDOMServer', externals: ['react'], @@ -254,7 +246,7 @@ const bundles = [ }, { bundleTypes: [NODE_DEV, NODE_PROD], - moduleType: __EXPERIMENTAL__ ? RENDERER : NON_FIBER_RENDERER, + moduleType: RENDERER, entry: 'react-dom/server.node', externals: ['react', 'stream'], babel: opts =>