Skip to content

Commit fadc971

Browse files
authored
[Flight] Add Client Infrastructure (#17234)
* Change demo to server * Expose client in package.json * Reorganize tests We don't want unit tests but instead test how both server and clients work together. So this merges server/client test files. * Fill in the client implementation a bit * Use new client in fixture * Add Promise/Uint8Array to lint rule I'll probably end up deleting these deps later but they're here for now.
1 parent 36fd29f commit fadc971

File tree

17 files changed

+239
-377
lines changed

17 files changed

+239
-377
lines changed

fixtures/flight-browser/index.html

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@ <h1>Flight Example</h1>
1818
</div>
1919
<script src="../../build/dist/react.development.js"></script>
2020
<script src="../../build/dist/react-dom.development.js"></script>
21+
<script src="../../build/dist/react-dom-unstable-flight-server.browser.development.js"></script>
2122
<script src="../../build/dist/react-dom-unstable-flight-client.development.js"></script>
2223
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
2324
<script type="text/babel">
25+
let Suspense = React.Suspense;
26+
2427
function Text({children}) {
2528
return <span>{children}</span>;
2629
}
@@ -40,7 +43,7 @@ <h1>Flight Example</h1>
4043
}
4144
};
4245

43-
let stream = ReactFlightDOMClient.renderToReadableStream(model);
46+
let stream = ReactFlightDOMServer.renderToReadableStream(model);
4447
let response = new Response(stream, {
4548
headers: {'Content-Type': 'text/html'},
4649
});
@@ -49,35 +52,35 @@ <h1>Flight Example</h1>
4952
async function display(responseToDisplay) {
5053
let blob = await responseToDisplay.blob();
5154
let url = URL.createObjectURL(blob);
52-
let response = await fetch(url);
53-
let body = await response.body;
5455

55-
let reader = body.getReader();
56-
let charsReceived = 0;
57-
let decoder = new TextDecoder();
56+
let data = ReactFlightDOMClient.readFromFetch(
57+
fetch(url)
58+
);
59+
// The client also supports XHR streaming.
60+
// var xhr = new XMLHttpRequest();
61+
// xhr.open('GET', url);
62+
// let data = ReactFlightDOMClient.readFromXHR(xhr);
63+
// xhr.send();
5864

59-
let json = '';
60-
reader.read().then(function processChunk({ done, value }) {
61-
if (done) {
62-
renderResult(json);
63-
return;
64-
}
65-
json += decoder.decode(value);
66-
return reader.read().then(processChunk);
67-
});
65+
renderResult(data);
6866
}
6967

70-
function Shell({ model }) {
68+
function Shell({ data }) {
69+
let model = data.model;
7170
return <div>
7271
<h1>{model.title}</h1>
7372
<div dangerouslySetInnerHTML={model.content} />
7473
</div>;
7574
}
7675

77-
function renderResult(json) {
78-
let model = JSON.parse(json);
76+
function renderResult(data) {
7977
let container = document.getElementById('container');
80-
ReactDOM.render(<Shell model={model} />, container);
78+
ReactDOM.render(
79+
<Suspense fallback="Loading...">
80+
<Shell data={data} />
81+
</Suspense>,
82+
container
83+
);
8184
}
8285
</script>
8386
</body>

packages/react-dom/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"unstable-fizz.js",
4040
"unstable-fizz.browser.js",
4141
"unstable-fizz.node.js",
42+
"unstable-flight-client.js",
4243
"unstable-flight-server.js",
4344
"unstable-flight-server.browser.js",
4445
"unstable-flight-server.node.js",

packages/react-dom/src/client/flight/ReactFlightDOMClient.js

Lines changed: 68 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,28 +7,80 @@
77
* @flow
88
*/
99

10-
import type {ReactModel} from 'react-flight/src/ReactFlightClient';
10+
import type {ReactModelRoot} from 'react-flight/src/ReactFlightClient';
1111

1212
import {
13-
createRequest,
14-
startWork,
15-
startFlowing,
16-
} from 'react-flight/inline.dom-browser';
13+
createResponse,
14+
getModelRoot,
15+
reportGlobalError,
16+
processStringChunk,
17+
processBinaryChunk,
18+
complete,
19+
} from 'react-flight/inline.dom';
1720

18-
function renderToReadableStream(model: ReactModel): ReadableStream {
19-
let request;
20-
return new ReadableStream({
21-
start(controller) {
22-
request = createRequest(model, controller);
23-
startWork(request);
21+
function startReadingFromStream(response, stream: ReadableStream): void {
22+
let reader = stream.getReader();
23+
function progress({done, value}) {
24+
if (done) {
25+
complete(response);
26+
return;
27+
}
28+
let buffer: Uint8Array = (value: any);
29+
processBinaryChunk(response, buffer, 0);
30+
return reader.read().then(progress, error);
31+
}
32+
function error(e) {
33+
reportGlobalError(response, e);
34+
}
35+
reader.read().then(progress, error);
36+
}
37+
38+
function readFromReadableStream<T>(stream: ReadableStream): ReactModelRoot<T> {
39+
let response = createResponse(stream);
40+
startReadingFromStream(response, stream);
41+
return getModelRoot(response);
42+
}
43+
44+
function readFromFetch<T>(
45+
promiseForResponse: Promise<Response>,
46+
): ReactModelRoot<T> {
47+
let response = createResponse(promiseForResponse);
48+
promiseForResponse.then(
49+
function(r) {
50+
startReadingFromStream(response, (r.body: any));
2451
},
25-
pull(controller) {
26-
startFlowing(request, controller.desiredSize);
52+
function(e) {
53+
reportGlobalError(response, e);
2754
},
28-
cancel(reason) {},
29-
});
55+
);
56+
return getModelRoot(response);
57+
}
58+
59+
function readFromXHR<T>(request: XMLHttpRequest): ReactModelRoot<T> {
60+
let response = createResponse(request);
61+
let processedLength = 0;
62+
function progress(e: ProgressEvent): void {
63+
let chunk = request.responseText;
64+
processStringChunk(response, chunk, processedLength);
65+
processedLength = chunk.length;
66+
}
67+
function load(e: ProgressEvent): void {
68+
progress(e);
69+
complete(response);
70+
}
71+
function error(e: ProgressEvent): void {
72+
reportGlobalError(response, new TypeError('Network error'));
73+
}
74+
request.addEventListener('progress', progress);
75+
request.addEventListener('load', load);
76+
request.addEventListener('error', error);
77+
request.addEventListener('abort', error);
78+
request.addEventListener('timeout', error);
79+
return getModelRoot(response);
3080
}
3181

3282
export default {
33-
renderToReadableStream,
83+
readFromXHR,
84+
readFromFetch,
85+
readFromReadableStream,
3486
};

packages/react-dom/src/client/flight/ReactFlightDOMHostConfig.js

Lines changed: 16 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,44 +7,28 @@
77
* @flow
88
*/
99

10-
export type Destination = ReadableStreamController;
10+
export type Source = Promise<Response> | ReadableStream | XMLHttpRequest;
1111

12-
export function scheduleWork(callback: () => void) {
13-
callback();
14-
}
15-
16-
export function flushBuffered(destination: Destination) {
17-
// WHATWG Streams do not yet have a way to flush the underlying
18-
// transform streams. https://github.com/whatwg/streams/issues/960
19-
}
20-
21-
export function beginWriting(destination: Destination) {}
12+
export type StringDecoder = TextDecoder;
2213

23-
export function writeChunk(destination: Destination, buffer: Uint8Array) {
24-
destination.enqueue(buffer);
25-
}
26-
27-
export function completeWriting(destination: Destination) {}
14+
export const supportsBinaryStreams = true;
2815

29-
export function close(destination: Destination) {
30-
destination.close();
16+
export function createStringDecoder(): StringDecoder {
17+
return new TextDecoder();
3118
}
3219

33-
const textEncoder = new TextEncoder();
34-
35-
export function convertStringToBuffer(content: string): Uint8Array {
36-
return textEncoder.encode(content);
37-
}
20+
const decoderOptions = {stream: true};
3821

39-
export function formatChunkAsString(type: string, props: Object): string {
40-
let str = '<' + type + '>';
41-
if (typeof props.children === 'string') {
42-
str += props.children;
43-
}
44-
str += '</' + type + '>';
45-
return str;
22+
export function readPartialStringChunk(
23+
decoder: StringDecoder,
24+
buffer: Uint8Array,
25+
): string {
26+
return decoder.decode(buffer, decoderOptions);
4627
}
4728

48-
export function formatChunk(type: string, props: Object): Uint8Array {
49-
return convertStringToBuffer(formatChunkAsString(type, props));
29+
export function readFinalStringChunk(
30+
decoder: StringDecoder,
31+
buffer: Uint8Array,
32+
): string {
33+
return decoder.decode(buffer);
5034
}

packages/react-dom/src/server/flight/__tests__/ReactFlightDOMBrowser-test.js

Lines changed: 0 additions & 61 deletions
This file was deleted.

packages/react-dom/src/server/flight/__tests__/ReactFlightDOMNode-test.js

Lines changed: 0 additions & 57 deletions
This file was deleted.

0 commit comments

Comments
 (0)