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 0000000000..59e1171c14 --- /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 0000000000..c57063649a --- /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 c57063649a..bd63acc868 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 0000000000..c57063649a --- /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 0000000000..e610a8818f --- /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 e610a8818f..92b69c03bc 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/src/__tests__/ReactDOMServerIntegrationNewContext-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js index cbba195910..601a42e725 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'); diff --git a/packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js b/packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js new file mode 100644 index 0000000000..4f0d619417 --- /dev/null +++ b/packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js @@ -0,0 +1,127 @@ +/** + * 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 './ReactDOMServerLegacyFormatConfig'; + +type ServerOptions = { + identifierPrefix?: string, +}; + +function onError() { + // Non-fatal errors are ignored. +} + +function renderToStringImpl( + children: ReactNodeList, + options: void | ServerOptions, + generateStaticMarkup: boolean, +): string { + let didFatal = false; + let fatalError = null; + let result = ''; + const destination = { + push(chunk) { + if (chunk !== null) { + result += chunk; + } + return true; + }, + destroy(error) { + didFatal = true; + fatalError = error; + }, + }; + + let readyToStream = false; + function onReadyToStream() { + readyToStream = true; + } + const request = createRequest( + children, + destination, + createResponseState( + generateStaticMarkup, + options ? options.identifierPrefix : undefined, + ), + createRootFormatContext(undefined), + Infinity, + onError, + undefined, + onReadyToStream, + ); + 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; + } + 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; +} + +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, + '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, + 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 0000000000..bc1e132209 --- /dev/null +++ b/packages/react-dom/src/server/ReactDOMLegacyServerNode.js @@ -0,0 +1,113 @@ +/** + * 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 './ReactDOMServerLegacyFormatConfig'; + +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 renderToNodeStreamImpl( + children: ReactNodeList, + options: void | ServerOptions, + generateStaticMarkup: boolean, +): 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(false, options ? options.identifierPrefix : undefined), + createRootFormatContext(undefined), + Infinity, + onError, + onCompleteAll, + undefined, + ); + destination.request = request; + startWork(request); + 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, + 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 0000000000..4cfe16091b --- /dev/null +++ b/packages/react-dom/src/server/ReactDOMLegacyServerStreamConfig.js @@ -0,0 +1,62 @@ +/** + * 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 function scheduleWork(callback: () => void) { + callback(); +} + +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); +} + +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-dom/src/server/ReactDOMServerFormatConfig.js b/packages/react-dom/src/server/ReactDOMServerFormatConfig.js index 7d705a6abe..6dfa245ae4 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, @@ -1459,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 new file mode 100644 index 0000000000..dbca4b43ab --- /dev/null +++ b/packages/react-dom/src/server/ReactDOMServerLegacyFormatConfig.js @@ -0,0 +1,155 @@ +/** + * 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 {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'; + +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, + pushStartInstance, + pushEndInstance, + writeStartSegment, + writeEndSegment, + writeCompletedSegmentInstruction, + writeCompletedBoundaryInstruction, + writeClientRenderBoundaryInstruction, + writeStartPendingSuspenseBoundary, + writeEndPendingSuspenseBoundary, + writePlaceholder, +} 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); + } +} + +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 98779768f4..9f7501007e 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 5a8ff56d6c..e183a8ce71 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-reconciler/src/forks/ReactFiberHostConfig.dom-legacy.js b/packages/react-reconciler/src/forks/ReactFiberHostConfig.dom-legacy.js new file mode 100644 index 0000000000..d830c8501b --- /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-dom-relay/src/ReactServerStreamConfigFB.js b/packages/react-server-dom-relay/src/ReactServerStreamConfigFB.js index b13e5f3792..e0477fd619 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/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index 927aef27f4..411eaf7618 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/ReactServerStreamConfigBrowser.js b/packages/react-server/src/ReactServerStreamConfigBrowser.js index b669002aff..3714be4302 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 58ce127ba9..348deb7605 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/ReactFlightServerConfig.dom-legacy.js b/packages/react-server/src/forks/ReactFlightServerConfig.dom-legacy.js new file mode 100644 index 0000000000..07b5f345f1 --- /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.custom.js b/packages/react-server/src/forks/ReactServerFormatConfig.custom.js index 7296697cc9..5458a4f50a 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 = 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 0000000000..acbac5042b --- /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/ReactDOMServerLegacyFormatConfig'; 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 0000000000..160efd5a92 --- /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 12302671c0..9823df3142 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: 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: NON_FIBER_RENDERER, + moduleType: RENDERER, entry: 'react-dom/server.node', externals: ['react', 'stream'], babel: opts => diff --git a/scripts/shared/inlinedHostConfigs.js b/scripts/shared/inlinedHostConfigs.js index eef4d1fa9a..146bc4d64f 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'],