Skip to content

Commit 5a7d35a

Browse files
committed
Configure Flight rendering
We render into an RSC payload using FlightServer, then parse it with FlightClient and then render the result using Fizz.
1 parent 23a7c2d commit 5a7d35a

File tree

8 files changed

+318
-39
lines changed

8 files changed

+318
-39
lines changed

packages/react-client/src/forks/ReactFlightClientConfig.dom-bun.js

-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
1111
export * from 'react-client/src/ReactClientConsoleConfigPlain';
1212
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
1313

14-
export type Response = any;
1514
export opaque type ModuleLoading = mixed;
1615
export opaque type SSRModuleMap = mixed;
1716
export opaque type ServerManifest = mixed;

packages/react-client/src/forks/ReactFlightClientConfig.dom-legacy.js

+77-15
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,82 @@
77
* @flow
88
*/
99

10-
export * from 'react-client/src/ReactFlightClientStreamConfigWeb';
11-
export * from 'react-client/src/ReactClientConsoleConfigBrowser';
12-
export * from 'react-dom-bindings/src/shared/ReactFlightClientConfigDOM';
13-
14-
export type Response = any;
15-
export opaque type ModuleLoading = mixed;
16-
export opaque type SSRModuleMap = mixed;
17-
export opaque type ServerManifest = mixed;
10+
import type {Thenable} from 'shared/ReactTypes';
11+
12+
export * from 'react-html/src/ReactHTMLLegacyClientStreamConfig.js';
13+
export * from 'react-client/src/ReactClientConsoleConfigPlain';
14+
15+
export type ModuleLoading = null;
16+
export type SSRModuleMap = null;
17+
export opaque type ServerManifest = null;
1818
export opaque type ServerReferenceId = string;
19-
export opaque type ClientReferenceMetadata = mixed;
20-
export opaque type ClientReference<T> = mixed; // eslint-disable-line no-unused-vars
21-
export const resolveClientReference: any = null;
22-
export const resolveServerReference: any = null;
23-
export const preloadModule: any = null;
24-
export const requireModule: any = null;
25-
export const prepareDestinationForModule: any = null;
19+
export opaque type ClientReferenceMetadata = null;
20+
export opaque type ClientReference<T> = null; // eslint-disable-line no-unused-vars
21+
22+
export function prepareDestinationForModule(
23+
moduleLoading: ModuleLoading,
24+
nonce: ?string,
25+
metadata: ClientReferenceMetadata,
26+
) {
27+
throw new Error(
28+
'renderToMarkup should not have emitted Client References. This is a bug in React.',
29+
);
30+
}
31+
32+
export function resolveClientReference<T>(
33+
bundlerConfig: SSRModuleMap,
34+
metadata: ClientReferenceMetadata,
35+
): ClientReference<T> {
36+
throw new Error(
37+
'renderToMarkup should not have emitted Client References. This is a bug in React.',
38+
);
39+
}
40+
41+
export function resolveServerReference<T>(
42+
config: ServerManifest,
43+
id: ServerReferenceId,
44+
): ClientReference<T> {
45+
throw new Error(
46+
'renderToMarkup should not have emitted Server References. This is a bug in React.',
47+
);
48+
}
49+
50+
export function preloadModule<T>(
51+
metadata: ClientReference<T>,
52+
): null | Thenable<T> {
53+
return null;
54+
}
55+
56+
export function requireModule<T>(metadata: ClientReference<T>): T {
57+
throw new Error(
58+
'renderToMarkup should not have emitted Client References. This is a bug in React.',
59+
);
60+
}
61+
2662
export const usedWithSSR = true;
63+
64+
type HintCode = string;
65+
type HintModel<T: HintCode> = null; // eslint-disable-line no-unused-vars
66+
67+
export function dispatchHint<Code: HintCode>(
68+
code: Code,
69+
model: HintModel<Code>,
70+
): void {
71+
// Should never happen.
72+
}
73+
74+
export function preinitModuleForSSR(
75+
href: string,
76+
nonce: ?string,
77+
crossOrigin: ?string,
78+
) {
79+
// Should never happen.
80+
}
81+
82+
export function preinitScriptForSSR(
83+
href: string,
84+
nonce: ?string,
85+
crossOrigin: ?string,
86+
) {
87+
// Should never happen.
88+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and 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+
// TODO: The legacy one should not use binary.
11+
12+
export type StringDecoder = TextDecoder;
13+
14+
export function createStringDecoder(): StringDecoder {
15+
return new TextDecoder();
16+
}
17+
18+
const decoderOptions = {stream: true};
19+
20+
export function readPartialStringChunk(
21+
decoder: StringDecoder,
22+
buffer: Uint8Array,
23+
): string {
24+
return decoder.decode(buffer, decoderOptions);
25+
}
26+
27+
export function readFinalStringChunk(
28+
decoder: StringDecoder,
29+
buffer: Uint8Array,
30+
): string {
31+
return decoder.decode(buffer);
32+
}

packages/react-html/src/ReactHTMLServer.js

+95-20
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,29 @@
88
*/
99

1010
import type {ReactNodeList} from 'shared/ReactTypes';
11-
12-
import type {
13-
Request,
14-
PostponedState,
15-
ErrorInfo,
16-
} from 'react-server/src/ReactFizzServer';
11+
import type {LazyComponent} from 'react/src/ReactLazy';
1712

1813
import ReactVersion from 'shared/ReactVersion';
1914

2015
import {
21-
createRequest,
22-
startWork,
23-
startFlowing,
24-
abort,
16+
createRequest as createFlightRequest,
17+
startWork as startFlightWork,
18+
startFlowing as startFlightFlowing,
19+
abort as abortFlight,
20+
} from 'react-server/src/ReactFlightServer';
21+
22+
import {
23+
createResponse as createFlightResponse,
24+
getRoot as getFlightRoot,
25+
processBinaryChunk as processFlightBinaryChunk,
26+
close as closeFlight,
27+
} from 'react-client/src/ReactFlightClient';
28+
29+
import {
30+
createRequest as createFizzRequest,
31+
startWork as startFizzWork,
32+
startFlowing as startFizzFlowing,
33+
abort as abortFizz,
2534
} from 'react-server/src/ReactFizzServer';
2635

2736
import {
@@ -30,20 +39,60 @@ import {
3039
createRootFormatContext,
3140
} from 'react-dom-bindings/src/server/ReactFizzConfigDOMLegacy';
3241

42+
type ReactMarkupNodeList =
43+
// This is the intersection of ReactNodeList and ReactClientValue minus
44+
// Client/ServerReferences.
45+
| React$Element<React$AbstractComponent<any, any>>
46+
| LazyComponent<ReactMarkupNodeList, any>
47+
| React$Element<string>
48+
| string
49+
| boolean
50+
| number
51+
| symbol
52+
| null
53+
| void
54+
| bigint
55+
| $AsyncIterable<ReactMarkupNodeList, ReactMarkupNodeList, void>
56+
| $AsyncIterator<ReactMarkupNodeList, ReactMarkupNodeList, void>
57+
| Iterable<ReactMarkupNodeList>
58+
| Iterator<ReactMarkupNodeList>
59+
| Array<ReactMarkupNodeList>
60+
| Promise<ReactMarkupNodeList>; // Thenable<ReactMarkupNodeList>
61+
3362
type MarkupOptions = {
3463
identifierPrefix?: string,
3564
signal?: AbortSignal,
3665
};
3766

67+
function noServerCallOrFormAction() {
68+
throw new Error(
69+
'renderToMarkup should not have emitted Server References. This is a bug in React.',
70+
);
71+
}
72+
3873
export function renderToMarkup(
39-
children: ReactNodeList,
74+
children: ReactMarkupNodeList,
4075
options?: MarkupOptions,
4176
): Promise<string> {
4277
return new Promise((resolve, reject) => {
43-
let didFatal = false;
44-
let fatalError = null;
78+
const textEncoder = new TextEncoder();
79+
const flightDestination = {
80+
push(chunk: string | null): boolean {
81+
if (chunk !== null) {
82+
// TODO: Legacy should not use binary streams.
83+
processFlightBinaryChunk(flightResponse, textEncoder.encode(chunk));
84+
} else {
85+
closeFlight(flightResponse);
86+
}
87+
return true;
88+
},
89+
destroy(error: mixed): void {
90+
abortFizz(fizzRequest, error);
91+
reject(error);
92+
},
93+
};
4594
let buffer = '';
46-
const destination = {
95+
const fizzDestination = {
4796
// $FlowFixMe[missing-local-annot]
4897
push(chunk) {
4998
if (chunk !== null) {
@@ -56,6 +105,7 @@ export function renderToMarkup(
56105
},
57106
// $FlowFixMe[missing-local-annot]
58107
destroy(error) {
108+
abortFlight(flightRequest, error);
59109
reject(error);
60110
},
61111
};
@@ -65,12 +115,33 @@ export function renderToMarkup(
65115
// client rendering mode because there's no client rendering here.
66116
reject(error);
67117
}
118+
const flightRequest = createFlightRequest(
119+
// $FlowFixMe: This should be a subtype but not everything is typed covariant.
120+
children,
121+
null,
122+
onError,
123+
options ? options.identifierPrefix : undefined,
124+
undefined,
125+
'Markup',
126+
undefined,
127+
);
128+
const flightResponse = createFlightResponse(
129+
null,
130+
null,
131+
noServerCallOrFormAction,
132+
noServerCallOrFormAction,
133+
undefined,
134+
undefined,
135+
undefined,
136+
);
68137
const resumableState = createResumableState(
69138
options ? options.identifierPrefix : undefined,
70139
undefined,
71140
);
72-
const request = createRequest(
73-
children,
141+
const root = getFlightRoot<ReactNodeList>(flightResponse);
142+
const fizzRequest = createFizzRequest(
143+
// $FlowFixMe: Thenables as children are supported.
144+
root,
74145
resumableState,
75146
createRenderState(resumableState, true),
76147
createRootFormatContext(),
@@ -86,17 +157,21 @@ export function renderToMarkup(
86157
if (options && options.signal) {
87158
const signal = options.signal;
88159
if (signal.aborted) {
89-
abort(request, (signal: any).reason);
160+
abortFlight(flightRequest, (signal: any).reason);
161+
abortFizz(fizzRequest, (signal: any).reason);
90162
} else {
91163
const listener = () => {
92-
abort(request, (signal: any).reason);
164+
abortFlight(flightRequest, (signal: any).reason);
165+
abortFizz(fizzRequest, (signal: any).reason);
93166
signal.removeEventListener('abort', listener);
94167
};
95168
signal.addEventListener('abort', listener);
96169
}
97170
}
98-
startWork(request);
99-
startFlowing(request, destination);
171+
startFlightWork(flightRequest);
172+
startFlightFlowing(flightRequest, flightDestination);
173+
startFizzWork(fizzRequest);
174+
startFizzFlowing(fizzRequest, fizzDestination);
100175
});
101176
}
102177

packages/react-html/src/__tests__/ReactHTMLServer-test.js

+3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99

1010
'use strict';
1111

12+
global.TextDecoder = require('util').TextDecoder;
13+
global.TextEncoder = require('util').TextEncoder;
14+
1215
let React;
1316
let ReactHTML;
1417

0 commit comments

Comments
 (0)