@samitouri / QOS-React / commits / dbe3363ccd

[Fizz] Implement Legacy renderToString and renderToNodeStream on top of Fizz (#21276)

* Wire up DOM legacy build * Hack to filter extra comments for testing purposes * 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. * 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. * 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. * Enable Fizz legacy mode in stable * 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. * Wrap pushTextInstance to avoid emitting comments for text in static markup * 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. * Wire up generateStaticMarkup to static API entry points * 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.

Sebastian Markbåge committed Jun 14, 2021 at 15:54 UTC dbe3363ccd125f89c25fea234031410408f0df09
25 files changed +680 -40
packages/react-client/src/forks/ReactFlightClientHostConfig.dom-legacy.js new
+12
@@ -0,0 +1,12 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +export * from 'react-client/src/ReactFlightClientHostConfigBrowser';
11 +export * from 'react-client/src/ReactFlightClientHostConfigStream';
12 +export * from 'react-server-dom-webpack/src/ReactFlightClientWebpackBundlerConfig';
packages/react-dom/server.browser.classic.fb.js new
+16
@@ -0,0 +1,16 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +export {
11 + renderToString,
12 + renderToStaticMarkup,
13 + renderToNodeStream,
14 + renderToStaticNodeStream,
15 + version,
16 +} from './src/server/ReactDOMServerBrowser';
packages/react-dom/server.browser.js
+1 -1
@@ -13,4 +13,4 @@ export {
13 renderToNodeStream,
14 renderToStaticNodeStream,
15 version,
16 -} from './src/server/ReactDOMServerBrowser';
16 +} from './src/server/ReactDOMLegacyServerBrowser';
packages/react-dom/server.browser.stable.js new
+16
@@ -0,0 +1,16 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +export {
11 + renderToString,
12 + renderToStaticMarkup,
13 + renderToNodeStream,
14 + renderToStaticNodeStream,
15 + version,
16 +} from './src/server/ReactDOMServerBrowser';
packages/react-dom/server.node.classic.fb.js new
+17
@@ -0,0 +1,17 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +// For some reason Flow doesn't like export * in this file. I don't know why.
11 +export {
12 + renderToString,
13 + renderToStaticMarkup,
14 + renderToNodeStream,
15 + renderToStaticNodeStream,
16 + version,
17 +} from './src/server/ReactDOMServerNode';
packages/react-dom/server.node.js
+1 -1
@@ -14,4 +14,4 @@ export {
14 renderToNodeStream,
15 renderToStaticNodeStream,
16 version,
17 -} from './src/server/ReactDOMServerNode';
17 +} from './src/server/ReactDOMLegacyServerNode';
packages/react-dom/src/__tests__/ReactDOMServerIntegrationNewContext-test.js
+2
@@ -487,6 +487,7 @@ describe('ReactDOMServerIntegration', () => {
487 });
488
489 // Regression test for https://github.com/facebook/react/issues/14705
490 + // @gate !experimental && www
491 it('does not pollute later renders when stream destroyed', () => {
492 const LoggedInUser = React.createContext('default');
493
@@ -529,6 +530,7 @@ describe('ReactDOMServerIntegration', () => {
530 });
531
532 // Regression test for https://github.com/facebook/react/issues/14705
533 + // @gate !experimental && www
534 it('frees context value reference when stream destroyed', () => {
535 const LoggedInUser = React.createContext('default');
536
packages/react-dom/src/server/ReactDOMLegacyServerBrowser.js new
+127
@@ -0,0 +1,127 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import ReactVersion from 'shared/ReactVersion';
11 +import invariant from 'shared/invariant';
12 +
13 +import type {ReactNodeList} from 'shared/ReactTypes';
14 +
15 +import {
16 + createRequest,
17 + startWork,
18 + startFlowing,
19 + abort,
20 +} from 'react-server/src/ReactFizzServer';
21 +
22 +import {
23 + createResponseState,
24 + createRootFormatContext,
25 +} from './ReactDOMServerLegacyFormatConfig';
26 +
27 +type ServerOptions = {
28 + identifierPrefix?: string,
29 +};
30 +
31 +function onError() {
32 + // Non-fatal errors are ignored.
33 +}
34 +
35 +function renderToStringImpl(
36 + children: ReactNodeList,
37 + options: void | ServerOptions,
38 + generateStaticMarkup: boolean,
39 +): string {
40 + let didFatal = false;
41 + let fatalError = null;
42 + let result = '';
43 + const destination = {
44 + push(chunk) {
45 + if (chunk !== null) {
46 + result += chunk;
47 + }
48 + return true;
49 + },
50 + destroy(error) {
51 + didFatal = true;
52 + fatalError = error;
53 + },
54 + };
55 +
56 + let readyToStream = false;
57 + function onReadyToStream() {
58 + readyToStream = true;
59 + }
60 + const request = createRequest(
61 + children,
62 + destination,
63 + createResponseState(
64 + generateStaticMarkup,
65 + options ? options.identifierPrefix : undefined,
66 + ),
67 + createRootFormatContext(undefined),
68 + Infinity,
69 + onError,
70 + undefined,
71 + onReadyToStream,
72 + );
73 + startWork(request);
74 + // If anything suspended and is still pending, we'll abort it before writing.
75 + // That way we write only client-rendered boundaries from the start.
76 + abort(request);
77 + startFlowing(request);
78 + if (didFatal) {
79 + throw fatalError;
80 + }
81 + invariant(
82 + readyToStream,
83 + 'A React component suspended while rendering, but no fallback UI was specified.\n' +
84 + '\n' +
85 + 'Add a <Suspense fallback=...> component higher in the tree to ' +
86 + 'provide a loading indicator or placeholder to display.',
87 + );
88 + return result;
89 +}
90 +
91 +function renderToString(
92 + children: ReactNodeList,
93 + options?: ServerOptions,
94 +): string {
95 + return renderToStringImpl(children, options, false);
96 +}
97 +
98 +function renderToStaticMarkup(
99 + children: ReactNodeList,
100 + options?: ServerOptions,
101 +): string {
102 + return renderToStringImpl(children, options, true);
103 +}
104 +
105 +function renderToNodeStream() {
106 + invariant(
107 + false,
108 + 'ReactDOMServer.renderToNodeStream(): The streaming API is not available ' +
109 + 'in the browser. Use ReactDOMServer.renderToString() instead.',
110 + );
111 +}
112 +
113 +function renderToStaticNodeStream() {
114 + invariant(
115 + false,
116 + 'ReactDOMServer.renderToStaticNodeStream(): The streaming API is not available ' +
117 + 'in the browser. Use ReactDOMServer.renderToStaticMarkup() instead.',
118 + );
119 +}
120 +
121 +export {
122 + renderToString,
123 + renderToStaticMarkup,
124 + renderToNodeStream,
125 + renderToStaticNodeStream,
126 + ReactVersion as version,
127 +};
packages/react-dom/src/server/ReactDOMLegacyServerNode.js new
+113
@@ -0,0 +1,113 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {ReactNodeList} from 'shared/ReactTypes';
11 +
12 +import type {Request} from 'react-server/src/ReactFizzServer';
13 +
14 +import {
15 + createRequest,
16 + startWork,
17 + startFlowing,
18 + abort,
19 +} from 'react-server/src/ReactFizzServer';
20 +
21 +import {
22 + createResponseState,
23 + createRootFormatContext,
24 +} from './ReactDOMServerLegacyFormatConfig';
25 +
26 +import {
27 + version,
28 + renderToString,
29 + renderToStaticMarkup,
30 +} from './ReactDOMLegacyServerBrowser';
31 +
32 +import {Readable} from 'stream';
33 +
34 +type ServerOptions = {
35 + identifierPrefix?: string,
36 +};
37 +
38 +class ReactMarkupReadableStream extends Readable {
39 + request: Request;
40 + startedFlowing: boolean;
41 + constructor() {
42 + // Calls the stream.Readable(options) constructor. Consider exposing built-in
43 + // features like highWaterMark in the future.
44 + super({});
45 + this.request = (null: any);
46 + this.startedFlowing = false;
47 + }
48 +
49 + _destroy(err, callback) {
50 + abort(this.request);
51 + // $FlowFixMe: The type definition for the callback should allow undefined and null.
52 + callback(err);
53 + }
54 +
55 + _read(size) {
56 + if (this.startedFlowing) {
57 + startFlowing(this.request);
58 + }
59 + }
60 +}
61 +
62 +function onError() {
63 + // Non-fatal errors are ignored.
64 +}
65 +
66 +function renderToNodeStreamImpl(
67 + children: ReactNodeList,
68 + options: void | ServerOptions,
69 + generateStaticMarkup: boolean,
70 +): Readable {
71 + function onCompleteAll() {
72 + // We wait until everything has loaded before starting to write.
73 + // That way we only end up with fully resolved HTML even if we suspend.
74 + destination.startedFlowing = true;
75 + startFlowing(request);
76 + }
77 + const destination = new ReactMarkupReadableStream();
78 + const request = createRequest(
79 + children,
80 + destination,
81 + createResponseState(false, options ? options.identifierPrefix : undefined),
82 + createRootFormatContext(undefined),
83 + Infinity,
84 + onError,
85 + onCompleteAll,
86 + undefined,
87 + );
88 + destination.request = request;
89 + startWork(request);
90 + return destination;
91 +}
92 +
93 +function renderToNodeStream(
94 + children: ReactNodeList,
95 + options?: ServerOptions,
96 +): Readable {
97 + return renderToNodeStreamImpl(children, options, false);
98 +}
99 +
100 +function renderToStaticNodeStream(
101 + children: ReactNodeList,
102 + options?: ServerOptions,
103 +): Readable {
104 + return renderToNodeStreamImpl(children, options, true);
105 +}
106 +
107 +export {
108 + renderToString,
109 + renderToStaticMarkup,
110 + renderToNodeStream,
111 + renderToStaticNodeStream,
112 + version,
113 +};
packages/react-dom/src/server/ReactDOMLegacyServerStreamConfig.js new
+62
@@ -0,0 +1,62 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +export type Destination = {
11 + push(chunk: string | null): boolean,
12 + destroy(error: Error): mixed,
13 + ...
14 +};
15 +
16 +export type PrecomputedChunk = string;
17 +export type Chunk = string;
18 +
19 +export function scheduleWork(callback: () => void) {
20 + callback();
21 +}
22 +
23 +export function flushBuffered(destination: Destination) {}
24 +
25 +export function beginWriting(destination: Destination) {}
26 +
27 +let prevWasCommentSegmenter = false;
28 +export function writeChunk(
29 + destination: Destination,
30 + chunk: Chunk | PrecomputedChunk,
31 +): boolean {
32 + if (prevWasCommentSegmenter) {
33 + prevWasCommentSegmenter = false;
34 + if (chunk[0] !== '<') {
35 + destination.push('<!-- -->');
36 + }
37 + }
38 + if (chunk === '<!-- -->') {
39 + prevWasCommentSegmenter = true;
40 + return true;
41 + }
42 + return destination.push(chunk);
43 +}
44 +
45 +export function completeWriting(destination: Destination) {}
46 +
47 +export function close(destination: Destination) {
48 + destination.push(null);
49 +}
50 +
51 +export function stringToChunk(content: string): Chunk {
52 + return content;
53 +}
54 +
55 +export function stringToPrecomputedChunk(content: string): PrecomputedChunk {
56 + return content;
57 +}
58 +
59 +export function closeWithError(destination: Destination, error: mixed): void {
60 + // $FlowFixMe: This is an Error object or the destination accepts other types.
61 + destination.destroy(error);
62 +}
packages/react-dom/src/server/ReactDOMServerFormatConfig.js
+28 -9
@@ -23,7 +23,6 @@ import {
23 writeChunk,
24 stringToChunk,
25 stringToPrecomputedChunk,
26 - isPrimaryStreamConfig,
26 } from 'react-server/src/ReactServerStreamConfig';
27
28 import {
@@ -51,7 +50,7 @@ import isArray from 'shared/isArray';
50
51 // Used to distinguish these contexts from ones used in other renderers.
52 // E.g. this can be used to distinguish legacy renderers from this modern one.
54 -export const isPrimaryRenderer = isPrimaryStreamConfig;
53 +export const isPrimaryRenderer = true;
54
55 // Per response, global state that is not contextual to the rendering subtree.
56 export type ResponseState = {
@@ -63,18 +62,20 @@ export type ResponseState = {
62 nextOpaqueID: number,
63 sentCompleteSegmentFunction: boolean,
64 sentCompleteBoundaryFunction: boolean,
66 - sentClientRenderFunction: boolean,
65 + sentClientRenderFunction: boolean, // We allow the legacy renderer to extend this object.
66 + ...
67 };
68
69 // Allows us to keep track of what we've already written so we can refer back to it.
70 export function createResponseState(
71 - identifierPrefix: string = '',
71 + identifierPrefix: string | void,
72 ): ResponseState {
73 + const idPrefix = identifierPrefix === undefined ? '' : identifierPrefix;
74 return {
74 - placeholderPrefix: stringToPrecomputedChunk(identifierPrefix + 'P:'),
75 - segmentPrefix: stringToPrecomputedChunk(identifierPrefix + 'S:'),
76 - boundaryPrefix: identifierPrefix + 'B:',
77 - opaqueIdentifierPrefix: identifierPrefix + 'R:',
75 + placeholderPrefix: stringToPrecomputedChunk(idPrefix + 'P:'),
76 + segmentPrefix: stringToPrecomputedChunk(idPrefix + 'S:'),
77 + boundaryPrefix: idPrefix + 'B:',
78 + opaqueIdentifierPrefix: idPrefix + 'R:',
79 nextSuspenseID: 0,
80 nextOpaqueID: 0,
81 sentCompleteSegmentFunction: false,
@@ -1459,23 +1460,41 @@ const endSuspenseBoundary = stringToPrecomputedChunk('<!--/$-->');
1460
1461 export function writeStartCompletedSuspenseBoundary(
1462 destination: Destination,
1463 + responseState: ResponseState,
1464 id: SuspenseBoundaryID,
1465 ): boolean {
1466 return writeChunk(destination, startCompletedSuspenseBoundary);
1467 }
1468 export function writeStartPendingSuspenseBoundary(
1469 destination: Destination,
1470 + responseState: ResponseState,
1471 id: SuspenseBoundaryID,
1472 ): boolean {
1473 return writeChunk(destination, startPendingSuspenseBoundary);
1474 }
1475 export function writeStartClientRenderedSuspenseBoundary(
1476 destination: Destination,
1477 + responseState: ResponseState,
1478 id: SuspenseBoundaryID,
1479 ): boolean {
1480 return writeChunk(destination, startClientRenderedSuspenseBoundary);
1481 }
1478 -export function writeEndSuspenseBoundary(destination: Destination): boolean {
1482 +export function writeEndCompletedSuspenseBoundary(
1483 + destination: Destination,
1484 + responseState: ResponseState,
1485 +): boolean {
1486 + return writeChunk(destination, endSuspenseBoundary);
1487 +}
1488 +export function writeEndPendingSuspenseBoundary(
1489 + destination: Destination,
1490 + responseState: ResponseState,
1491 +): boolean {
1492 + return writeChunk(destination, endSuspenseBoundary);
1493 +}
1494 +export function writeEndClientRenderedSuspenseBoundary(
1495 + destination: Destination,
1496 + responseState: ResponseState,
1497 +): boolean {
1498 return writeChunk(destination, endSuspenseBoundary);
1499 }
1500
packages/react-dom/src/server/ReactDOMServerLegacyFormatConfig.js new
+155
@@ -0,0 +1,155 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +import type {SuspenseBoundaryID} from './ReactDOMServerFormatConfig';
11 +
12 +import {
13 + createResponseState as createResponseStateImpl,
14 + pushTextInstance as pushTextInstanceImpl,
15 + writeStartCompletedSuspenseBoundary as writeStartCompletedSuspenseBoundaryImpl,
16 + writeStartClientRenderedSuspenseBoundary as writeStartClientRenderedSuspenseBoundaryImpl,
17 + writeEndCompletedSuspenseBoundary as writeEndCompletedSuspenseBoundaryImpl,
18 + writeEndClientRenderedSuspenseBoundary as writeEndClientRenderedSuspenseBoundaryImpl,
19 +} from './ReactDOMServerFormatConfig';
20 +
21 +import type {
22 + Destination,
23 + Chunk,
24 + PrecomputedChunk,
25 +} from 'react-server/src/ReactServerStreamConfig';
26 +
27 +export const isPrimaryRenderer = false;
28 +
29 +export type ResponseState = {
30 + // Keep this in sync with ReactDOMServerFormatConfig
31 + placeholderPrefix: PrecomputedChunk,
32 + segmentPrefix: PrecomputedChunk,
33 + boundaryPrefix: string,
34 + opaqueIdentifierPrefix: string,
35 + nextSuspenseID: number,
36 + nextOpaqueID: number,
37 + sentCompleteSegmentFunction: boolean,
38 + sentCompleteBoundaryFunction: boolean,
39 + sentClientRenderFunction: boolean,
40 + // This is an extra field for the legacy renderer
41 + generateStaticMarkup: boolean,
42 +};
43 +
44 +export function createResponseState(
45 + generateStaticMarkup: boolean,
46 + identifierPrefix: string | void,
47 +): ResponseState {
48 + const responseState = createResponseStateImpl(identifierPrefix);
49 + return {
50 + // Keep this in sync with ReactDOMServerFormatConfig
51 + placeholderPrefix: responseState.placeholderPrefix,
52 + segmentPrefix: responseState.segmentPrefix,
53 + boundaryPrefix: responseState.boundaryPrefix,
54 + opaqueIdentifierPrefix: responseState.opaqueIdentifierPrefix,
55 + nextSuspenseID: responseState.nextSuspenseID,
56 + nextOpaqueID: responseState.nextOpaqueID,
57 + sentCompleteSegmentFunction: responseState.sentCompleteSegmentFunction,
58 + sentCompleteBoundaryFunction: responseState.sentCompleteBoundaryFunction,
59 + sentClientRenderFunction: responseState.sentClientRenderFunction,
60 + // This is an extra field for the legacy renderer
61 + generateStaticMarkup,
62 + };
63 +}
64 +
65 +export type {
66 + FormatContext,
67 + SuspenseBoundaryID,
68 + OpaqueIDType,
69 +} from './ReactDOMServerFormatConfig';
70 +
71 +export {
72 + createRootFormatContext,
73 + getChildFormatContext,
74 + createSuspenseBoundaryID,
75 + makeServerID,
76 + pushEmpty,
77 + pushStartInstance,
78 + pushEndInstance,
79 + writeStartSegment,
80 + writeEndSegment,
81 + writeCompletedSegmentInstruction,
82 + writeCompletedBoundaryInstruction,
83 + writeClientRenderBoundaryInstruction,
84 + writeStartPendingSuspenseBoundary,
85 + writeEndPendingSuspenseBoundary,
86 + writePlaceholder,
87 +} from './ReactDOMServerFormatConfig';
88 +
89 +import {stringToChunk} from 'react-server/src/ReactServerStreamConfig';
90 +
91 +import escapeTextForBrowser from './escapeTextForBrowser';
92 +
93 +export function pushTextInstance(
94 + target: Array<Chunk | PrecomputedChunk>,
95 + text: string,
96 + responseState: ResponseState,
97 + assignID: null | SuspenseBoundaryID,
98 +): void {
99 + if (responseState.generateStaticMarkup) {
100 + target.push(stringToChunk(escapeTextForBrowser(text)));
101 + } else {
102 + pushTextInstanceImpl(target, text, responseState, assignID);
103 + }
104 +}
105 +
106 +export function writeStartCompletedSuspenseBoundary(
107 + destination: Destination,
108 + responseState: ResponseState,
109 + id: SuspenseBoundaryID,
110 +): boolean {
111 + if (responseState.generateStaticMarkup) {
112 + // A completed boundary is done and doesn't need a representation in the HTML
113 + // if we're not going to be hydrating it.
114 + return true;
115 + }
116 + return writeStartCompletedSuspenseBoundaryImpl(
117 + destination,
118 + responseState,
119 + id,
120 + );
121 +}
122 +export function writeStartClientRenderedSuspenseBoundary(
123 + destination: Destination,
124 + responseState: ResponseState,
125 + id: SuspenseBoundaryID,
126 +): boolean {
127 + if (responseState.generateStaticMarkup) {
128 + // A client rendered boundary is done and doesn't need a representation in the HTML
129 + // since we'll never hydrate it. This is arguably an error in static generation.
130 + return true;
131 + }
132 + return writeStartClientRenderedSuspenseBoundaryImpl(
133 + destination,
134 + responseState,
135 + id,
136 + );
137 +}
138 +export function writeEndCompletedSuspenseBoundary(
139 + destination: Destination,
140 + responseState: ResponseState,
141 +): boolean {
142 + if (responseState.generateStaticMarkup) {
143 + return true;
144 + }
145 + return writeEndCompletedSuspenseBoundaryImpl(destination, responseState);
146 +}
147 +export function writeEndClientRenderedSuspenseBoundary(
148 + destination: Destination,
149 + responseState: ResponseState,
150 +): boolean {
151 + if (responseState.generateStaticMarkup) {
152 + return true;
153 + }
154 + return writeEndClientRenderedSuspenseBoundaryImpl(destination, responseState);
155 +}
packages/react-native-renderer/src/server/ReactNativeServerFormatConfig.js
+19 -1
@@ -203,6 +203,7 @@ export function writePlaceholder(
203 // Suspense boundaries are encoded as comments.
204 export function writeStartCompletedSuspenseBoundary(
205 destination: Destination,
206 + responseState: ResponseState,
207 id: SuspenseBoundaryID,
208 ): boolean {
209 writeChunk(destination, SUSPENSE_COMPLETE);
@@ -210,6 +211,7 @@ export function writeStartCompletedSuspenseBoundary(
211 }
212 export function writeStartPendingSuspenseBoundary(
213 destination: Destination,
214 + responseState: ResponseState,
215 id: SuspenseBoundaryID,
216 ): boolean {
217 writeChunk(destination, SUSPENSE_PENDING);
@@ -217,12 +219,28 @@ export function writeStartPendingSuspenseBoundary(
219 }
220 export function writeStartClientRenderedSuspenseBoundary(
221 destination: Destination,
222 + responseState: ResponseState,
223 id: SuspenseBoundaryID,
224 ): boolean {
225 writeChunk(destination, SUSPENSE_CLIENT_RENDER);
226 return writeChunk(destination, formatID(id));
227 }
225 -export function writeEndSuspenseBoundary(destination: Destination): boolean {
228 +export function writeEndCompletedSuspenseBoundary(
229 + destination: Destination,
230 + responseState: ResponseState,
231 +): boolean {
232 + return writeChunk(destination, END);
233 +}
234 +export function writeEndPendingSuspenseBoundary(
235 + destination: Destination,
236 + responseState: ResponseState,
237 +): boolean {
238 + return writeChunk(destination, END);
239 +}
240 +export function writeEndClientRenderedSuspenseBoundary(
241 + destination: Destination,
242 + responseState: ResponseState,
243 +): boolean {
244 return writeChunk(destination, END);
245 }
246
packages/react-noop-renderer/src/ReactNoopServer.js
+10 -1
@@ -138,6 +138,7 @@ const ReactNoopServer = ReactFizzServer({
138
139 writeStartCompletedSuspenseBoundary(
140 destination: Destination,
141 + responseState: ResponseState,
142 suspenseInstance: SuspenseInstance,
143 ): boolean {
144 suspenseInstance.state = 'complete';
@@ -147,6 +148,7 @@ const ReactNoopServer = ReactFizzServer({
148 },
149 writeStartPendingSuspenseBoundary(
150 destination: Destination,
151 + responseState: ResponseState,
152 suspenseInstance: SuspenseInstance,
153 ): boolean {
154 suspenseInstance.state = 'pending';
@@ -156,6 +158,7 @@ const ReactNoopServer = ReactFizzServer({
158 },
159 writeStartClientRenderedSuspenseBoundary(
160 destination: Destination,
161 + responseState: ResponseState,
162 suspenseInstance: SuspenseInstance,
163 ): boolean {
164 suspenseInstance.state = 'client-render';
@@ -163,7 +166,13 @@ const ReactNoopServer = ReactFizzServer({
166 parent.children.push(suspenseInstance);
167 destination.stack.push(suspenseInstance);
168 },
166 - writeEndSuspenseBoundary(destination: Destination): boolean {
169 + writeEndCompletedSuspenseBoundary(destination: Destination): boolean {
170 + destination.stack.pop();
171 + },
172 + writeEndPendingSuspenseBoundary(destination: Destination): boolean {
173 + destination.stack.pop();
174 + },
175 + writeEndClientRenderedSuspenseBoundary(destination: Destination): boolean {
176 destination.stack.pop();
177 },
178
packages/react-reconciler/src/forks/ReactFiberHostConfig.dom-legacy.js new
+10
@@ -0,0 +1,10 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +export * from 'react-dom/src/client/ReactDOMHostConfig';
packages/react-server-dom-relay/src/ReactServerStreamConfigFB.js
-2
@@ -17,8 +17,6 @@ export type Destination = {
17 export type PrecomputedChunk = string;
18 export type Chunk = string;
19
20 -export const isPrimaryStreamConfig = true;
21 -
20 export function scheduleWork(callback: () => void) {
21 // We don't schedule work in this model, and instead expect performWork to always be called repeatedly.
22 }
packages/react-server/src/ReactFizzServer.js
+33 -9
@@ -40,7 +40,9 @@ import {
40 writeStartCompletedSuspenseBoundary,
41 writeStartPendingSuspenseBoundary,
42 writeStartClientRenderedSuspenseBoundary,
43 - writeEndSuspenseBoundary,
43 + writeEndCompletedSuspenseBoundary,
44 + writeEndPendingSuspenseBoundary,
45 + writeEndClientRenderedSuspenseBoundary,
46 writeStartSegment,
47 writeEndSegment,
48 writeClientRenderBoundaryInstruction,
@@ -1609,12 +1611,19 @@ function flushSegment(
1611 // Emit a client rendered suspense boundary wrapper.
1612 // We never queue the inner boundary so we'll never emit its content or partial segments.
1613
1612 - writeStartClientRenderedSuspenseBoundary(destination, boundary.id);
1614 + writeStartClientRenderedSuspenseBoundary(
1615 + destination,
1616 + request.responseState,
1617 + boundary.id,
1618 + );
1619
1620 // Flush the fallback.
1621 flushSubtree(request, destination, segment);
1622
1617 - return writeEndSuspenseBoundary(destination);
1623 + return writeEndClientRenderedSuspenseBoundary(
1624 + destination,
1625 + request.responseState,
1626 + );
1627 } else if (boundary.pendingTasks > 0) {
1628 // This boundary is still loading. Emit a pending suspense boundary wrapper.
1629
@@ -1625,12 +1634,16 @@ function flushSegment(
1634 request.partialBoundaries.push(boundary);
1635 }
1636
1628 - writeStartPendingSuspenseBoundary(destination, boundary.id);
1637 + writeStartPendingSuspenseBoundary(
1638 + destination,
1639 + request.responseState,
1640 + boundary.id,
1641 + );
1642
1643 // Flush the fallback.
1644 flushSubtree(request, destination, segment);
1645
1633 - return writeEndSuspenseBoundary(destination);
1646 + return writeEndPendingSuspenseBoundary(destination, request.responseState);
1647 } else if (boundary.byteSize > request.progressiveChunkSize) {
1648 // This boundary is large and will be emitted separately so that we can progressively show
1649 // other content. We add it to the queue during the flush because we have to ensure that
@@ -1643,16 +1656,24 @@ function flushSegment(
1656
1657 request.completedBoundaries.push(boundary);
1658 // Emit a pending rendered suspense boundary wrapper.
1646 - writeStartPendingSuspenseBoundary(destination, boundary.id);
1659 + writeStartPendingSuspenseBoundary(
1660 + destination,
1661 + request.responseState,
1662 + boundary.id,
1663 + );
1664
1665 // Flush the fallback.
1666 flushSubtree(request, destination, segment);
1667
1651 - return writeEndSuspenseBoundary(destination);
1668 + return writeEndPendingSuspenseBoundary(destination, request.responseState);
1669 } else {
1670 // We can inline this boundary's content as a complete boundary.
1671
1655 - writeStartCompletedSuspenseBoundary(destination, boundary.id);
1672 + writeStartCompletedSuspenseBoundary(
1673 + destination,
1674 + request.responseState,
1675 + boundary.id,
1676 + );
1677
1678 const completedSegments = boundary.completedSegments;
1679 invariant(
@@ -1662,7 +1683,10 @@ function flushSegment(
1683 const contentSegment = completedSegments[0];
1684 flushSegment(request, destination, contentSegment);
1685
1665 - return writeEndSuspenseBoundary(destination);
1686 + return writeEndCompletedSuspenseBoundary(
1687 + destination,
1688 + request.responseState,
1689 + );
1690 }
1691 }
1692
packages/react-server/src/ReactServerStreamConfigBrowser.js
-2
@@ -12,8 +12,6 @@ export type Destination = ReadableStreamController;
12 export type PrecomputedChunk = Uint8Array;
13 export type Chunk = Uint8Array;
14
15 -export const isPrimaryStreamConfig = true;
16 -
15 export function scheduleWork(callback: () => void) {
16 callback();
17 }
packages/react-server/src/ReactServerStreamConfigNode.js
-2
@@ -19,8 +19,6 @@ export type Destination = Writable & MightBeFlushable;
19 export type PrecomputedChunk = Uint8Array;
20 export type Chunk = string;
21
22 -export const isPrimaryStreamConfig = true;
23 -
22 export function scheduleWork(callback: () => void) {
23 setImmediate(callback);
24 }
packages/react-server/src/forks/ReactFlightServerConfig.dom-legacy.js new
+11
@@ -0,0 +1,11 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +export * from '../ReactFlightServerConfigStream';
11 +export * from 'react-server-dom-webpack/src/ReactFlightServerWebpackBundlerConfig';
packages/react-server/src/forks/ReactServerFormatConfig.custom.js
+6 -1
@@ -46,7 +46,12 @@ export const writeStartPendingSuspenseBoundary =
46 $$$hostConfig.writeStartPendingSuspenseBoundary;
47 export const writeStartClientRenderedSuspenseBoundary =
48 $$$hostConfig.writeStartClientRenderedSuspenseBoundary;
49 -export const writeEndSuspenseBoundary = $$$hostConfig.writeEndSuspenseBoundary;
49 +export const writeEndCompletedSuspenseBoundary =
50 + $$$hostConfig.writeEndCompletedSuspenseBoundary;
51 +export const writeEndPendingSuspenseBoundary =
52 + $$$hostConfig.writeEndPendingSuspenseBoundary;
53 +export const writeEndClientRenderedSuspenseBoundary =
54 + $$$hostConfig.writeEndClientRenderedSuspenseBoundary;
55 export const writeStartSegment = $$$hostConfig.writeStartSegment;
56 export const writeEndSegment = $$$hostConfig.writeEndSegment;
57 export const writeCompletedSegmentInstruction =
packages/react-server/src/forks/ReactServerFormatConfig.dom-legacy.js new
+10
@@ -0,0 +1,10 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +export * from 'react-dom/src/server/ReactDOMServerLegacyFormatConfig';
packages/react-server/src/forks/ReactServerStreamConfig.dom-legacy.js new
+10
@@ -0,0 +1,10 @@
1 +/**
2 + * Copyright (c) Facebook, Inc. and its affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @flow
8 + */
9 +
10 +export * from 'react-dom/src/server/ReactDOMLegacyServerStreamConfig';
scripts/rollup/bundles.js
+3 -11
@@ -56,17 +56,9 @@ const moduleTypes = {
56 RENDERER_UTILS: 'RENDERER_UTILS',
57 // Standalone reconciler for third-party renderers.
58 RECONCILER: 'RECONCILER',
59 - // Non-Fiber implementations like SSR and Shallow renderers.
60 - NON_FIBER_RENDERER: 'NON_FIBER_RENDERER',
59 };
60
63 -const {
64 - ISOMORPHIC,
65 - RENDERER,
66 - RENDERER_UTILS,
67 - RECONCILER,
68 - NON_FIBER_RENDERER,
69 -} = moduleTypes;
61 +const {ISOMORPHIC, RENDERER, RENDERER_UTILS, RECONCILER} = moduleTypes;
62
63 const bundles = [
64 /******* Isomorphic *******/
@@ -241,7 +233,7 @@ const bundles = [
233 bundleTypes: __EXPERIMENTAL__
234 ? [UMD_DEV, UMD_PROD, NODE_DEV, NODE_PROD]
235 : [UMD_DEV, UMD_PROD, NODE_DEV, NODE_PROD, FB_WWW_DEV, FB_WWW_PROD],
244 - moduleType: NON_FIBER_RENDERER,
236 + moduleType: RENDERER,
237 entry: 'react-dom/server.browser',
238 global: 'ReactDOMServer',
239 externals: ['react'],
@@ -254,7 +246,7 @@ const bundles = [
246 },
247 {
248 bundleTypes: [NODE_DEV, NODE_PROD],
257 - moduleType: NON_FIBER_RENDERER,
249 + moduleType: RENDERER,
250 entry: 'react-dom/server.node',
251 externals: ['react', 'stream'],
252 babel: opts =>
scripts/shared/inlinedHostConfigs.js
+18
@@ -20,6 +20,7 @@ module.exports = [
20 'react-dom',
21 'react-dom/unstable-fizz',
22 'react-dom/unstable-fizz.node',
23 + 'react-dom/server.node.stable',
24 'react-dom/src/server/ReactDOMFizzServerNode.js', // react-dom/unstable-fizz.node
25 'react-server-dom-webpack',
26 'react-server-dom-webpack/writer',
@@ -44,6 +45,7 @@ module.exports = [
45 'react-dom',
46 'react-dom/testing',
47 'react-dom/unstable-fizz.browser',
48 + 'react-dom/server.browser.stable',
49 'react-dom/src/server/ReactDOMFizzServerBrowser.js', // react-dom/unstable-fizz.browser
50 'react-server-dom-webpack',
51 'react-server-dom-webpack/writer.browser.server',
@@ -53,6 +55,22 @@ module.exports = [
55 isFlowTyped: true,
56 isServerSupported: true,
57 },
58 + {
59 + shortName: 'dom-legacy',
60 + entryPoints: ['react-dom/server.browser', 'react-dom/server.node'],
61 + paths: [
62 + 'react-dom',
63 + 'react-dom/server',
64 + 'react-dom/server.browser',
65 + 'react-dom/server.node',
66 + 'react-server-dom-webpack',
67 + 'react-dom/src/server/ReactDOMLegacyServerBrowser.js', // react-dom/server.browser
68 + 'react-dom/src/server/ReactDOMLegacyServerNode.js', // react-dom/server.node
69 + 'react-client/src/ReactFlightClientStream.js', // We can only type check this in streaming configurations.
70 + ],
71 + isFlowTyped: true,
72 + isServerSupported: true,
73 + },
74 {
75 shortName: 'art',
76 entryPoints: ['react-art'],