Skip to content

Commit f4e974d

Browse files
authored
Add Experimental Flight Infrastructure (#16398)
* Add Flight Build and Unify HostFormat Config between Flight and Fizz * Add basic resolution of models * Add basic Flight fixture Demonstrates the streaming protocol. * Rename to flight-server to distinguish from the client parts * Add Flight Client package and entry point * Fix fixture
1 parent 6cd365c commit f4e974d

File tree

75 files changed

+1651
-102
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+1651
-102
lines changed

fixtures/flight-browser/index.html

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<!DOCTYPE html>
2+
<html style="width: 100%; height: 100%; overflow: hidden">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>Flight Example</title>
6+
</head>
7+
<body>
8+
<h1>Flight Example</h1>
9+
<div id="container">
10+
<p>
11+
To install React, follow the instructions on
12+
<a href="https://github.com/facebook/react/">GitHub</a>.
13+
</p>
14+
<p>
15+
If you can see this, React is <strong>not</strong> working right.
16+
If you checked out the source from GitHub make sure to run <code>npm run build</code>.
17+
</p>
18+
</div>
19+
<script src="../../build/dist/react.development.js"></script>
20+
<script src="../../build/dist/react-dom.development.js"></script>
21+
<script src="../../build/dist/react-dom-unstable-flight-client.development.js"></script>
22+
<script src="https://unpkg.com/babel-standalone@6/babel.js"></script>
23+
<script type="text/babel">
24+
function Text({children}) {
25+
return <span>{children}</span>;
26+
}
27+
function HTML() {
28+
return (
29+
<div>
30+
<Text>hello</Text>
31+
<Text>world</Text>
32+
</div>
33+
);
34+
}
35+
36+
let model = {
37+
title: 'Title',
38+
content: {
39+
__html: <HTML />,
40+
}
41+
};
42+
43+
let stream = ReactFlightDOMClient.renderToReadableStream(model);
44+
let response = new Response(stream, {
45+
headers: {'Content-Type': 'text/html'},
46+
});
47+
display(response);
48+
49+
async function display(responseToDisplay) {
50+
let blob = await responseToDisplay.blob();
51+
let url = URL.createObjectURL(blob);
52+
let response = await fetch(url);
53+
let body = await response.body;
54+
55+
let reader = body.getReader();
56+
let charsReceived = 0;
57+
let decoder = new TextDecoder();
58+
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+
});
68+
}
69+
70+
function Shell({ model }) {
71+
return <div>
72+
<h1>{model.title}</h1>
73+
<div dangerouslySetInnerHTML={model.content} />
74+
</div>;
75+
}
76+
77+
function renderResult(json) {
78+
let model = JSON.parse(json);
79+
let container = document.getElementById('container');
80+
ReactDOM.render(<Shell model={model} />, container);
81+
}
82+
</script>
83+
</body>
84+
</html>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
3+
if (process.env.NODE_ENV === 'production') {
4+
module.exports = require('./cjs/react-dom-unstable-flight-client.production.min.js');
5+
} else {
6+
module.exports = require('./cjs/react-dom-unstable-flight-client.development.js');
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
3+
if (process.env.NODE_ENV === 'production') {
4+
module.exports = require('./cjs/react-dom-unstable-flight-server.browser.production.min.js');
5+
} else {
6+
module.exports = require('./cjs/react-dom-unstable-flight-server.browser.development.js');
7+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
'use strict';
2+
3+
module.exports = require('./unstable-flight-server.node');
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
3+
if (process.env.NODE_ENV === 'production') {
4+
module.exports = require('./cjs/react-dom-unstable-flight-server.node.production.min.js');
5+
} else {
6+
module.exports = require('./cjs/react-dom-unstable-flight-server.node.development.js');
7+
}

packages/react-dom/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,17 @@
3939
"unstable-fizz.js",
4040
"unstable-fizz.browser.js",
4141
"unstable-fizz.node.js",
42+
"unstable-flight-server.js",
43+
"unstable-flight-server.browser.js",
44+
"unstable-flight-server.node.js",
4245
"unstable-native-dependencies.js",
4346
"cjs/",
4447
"umd/"
4548
],
4649
"browser": {
4750
"./server.js": "./server.browser.js",
48-
"./unstable-fizz.js": "./unstable-fizz.browser.js"
51+
"./unstable-fizz.js": "./unstable-fizz.browser.js",
52+
"./unstable-flight-server.js": "./unstable-flight-server.browser.js"
4953
},
5054
"browserify": {
5155
"transform": [
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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 {ReactModel} from 'react-flight/src/ReactFlightClient';
11+
12+
import {
13+
createRequest,
14+
startWork,
15+
startFlowing,
16+
} from 'react-flight/inline.dom-browser';
17+
18+
function renderToReadableStream(model: ReactModel): ReadableStream {
19+
let request;
20+
return new ReadableStream({
21+
start(controller) {
22+
request = createRequest(model, controller);
23+
startWork(request);
24+
},
25+
pull(controller) {
26+
startFlowing(request, controller.desiredSize);
27+
},
28+
cancel(reason) {},
29+
});
30+
}
31+
32+
export default {
33+
renderToReadableStream,
34+
};
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
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 = ReadableStreamController;
11+
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) {}
22+
23+
export function writeChunk(destination: Destination, buffer: Uint8Array) {
24+
destination.enqueue(buffer);
25+
}
26+
27+
export function completeWriting(destination: Destination) {}
28+
29+
export function close(destination: Destination) {
30+
destination.close();
31+
}
32+
33+
const textEncoder = new TextEncoder();
34+
35+
export function convertStringToBuffer(content: string): Uint8Array {
36+
return textEncoder.encode(content);
37+
}
38+
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;
46+
}
47+
48+
export function formatChunk(type: string, props: Object): Uint8Array {
49+
return convertStringToBuffer(formatChunkAsString(type, props));
50+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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+
* @emails react-core
8+
*/
9+
10+
'use strict';
11+
12+
// Polyfills for test environment
13+
global.ReadableStream = require('@mattiasbuelens/web-streams-polyfill/ponyfill/es6').ReadableStream;
14+
global.TextEncoder = require('util').TextEncoder;
15+
16+
let React;
17+
let ReactFlightDOMServer;
18+
19+
describe('ReactFlightDOM', () => {
20+
beforeEach(() => {
21+
jest.resetModules();
22+
React = require('react');
23+
ReactFlightDOMServer = require('react-dom/unstable-flight-server.browser');
24+
});
25+
26+
async function readResult(stream) {
27+
let reader = stream.getReader();
28+
let result = '';
29+
while (true) {
30+
let {done, value} = await reader.read();
31+
if (done) {
32+
return result;
33+
}
34+
result += Buffer.from(value).toString('utf8');
35+
}
36+
}
37+
38+
it('should resolve HTML', async () => {
39+
function Text({children}) {
40+
return <span>{children}</span>;
41+
}
42+
function HTML() {
43+
return (
44+
<div>
45+
<Text>hello</Text>
46+
<Text>world</Text>
47+
</div>
48+
);
49+
}
50+
51+
let model = {
52+
html: <HTML />,
53+
};
54+
let stream = ReactFlightDOMServer.renderToReadableStream(model);
55+
jest.runAllTimers();
56+
let result = JSON.parse(await readResult(stream));
57+
expect(result).toEqual({
58+
html: '<div><span>hello</span><span>world</span></div>',
59+
});
60+
});
61+
});
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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+
* @emails react-core
8+
* @jest-environment node
9+
*/
10+
11+
'use strict';
12+
13+
let Stream;
14+
let React;
15+
let ReactFlightDOMServer;
16+
17+
describe('ReactFlightDOM', () => {
18+
beforeEach(() => {
19+
jest.resetModules();
20+
React = require('react');
21+
ReactFlightDOMServer = require('react-dom/unstable-flight-server');
22+
Stream = require('stream');
23+
});
24+
25+
function getTestWritable() {
26+
let writable = new Stream.PassThrough();
27+
writable.setEncoding('utf8');
28+
writable.result = '';
29+
writable.on('data', chunk => (writable.result += chunk));
30+
return writable;
31+
}
32+
33+
it('should resolve HTML', () => {
34+
function Text({children}) {
35+
return <span>{children}</span>;
36+
}
37+
function HTML() {
38+
return (
39+
<div>
40+
<Text>hello</Text>
41+
<Text>world</Text>
42+
</div>
43+
);
44+
}
45+
46+
let writable = getTestWritable();
47+
let model = {
48+
html: <HTML />,
49+
};
50+
ReactFlightDOMServer.pipeToNodeWritable(model, writable);
51+
jest.runAllTimers();
52+
let result = JSON.parse(writable.result);
53+
expect(result).toEqual({
54+
html: '<div><span>hello</span><span>world</span></div>',
55+
});
56+
});
57+
});

packages/react-dom/src/server/ReactDOMFizzServerBrowser.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
createRequest,
1414
startWork,
1515
startFlowing,
16-
} from 'react-stream/inline.dom-browser';
16+
} from 'react-server/inline.dom-browser';
1717

1818
function renderToReadableStream(children: ReactNodeList): ReadableStream {
1919
let request;

packages/react-dom/src/server/ReactDOMFizzServerNode.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import type {ReactNodeList} from 'shared/ReactTypes';
1111
import type {Writable} from 'stream';
1212

13-
import {createRequest, startWork, startFlowing} from 'react-stream/inline.dom';
13+
import {createRequest, startWork, startFlowing} from 'react-server/inline.dom';
1414

1515
function createDrainHandler(destination, request) {
1616
return () => startFlowing(request, 0);

packages/react-dom/src/server/ReactDOMFizzServerFormatConfig.js renamed to packages/react-dom/src/server/ReactDOMServerFormatConfig.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@
77
* @flow
88
*/
99

10-
import {convertStringToBuffer} from 'react-stream/src/ReactFizzHostConfig';
10+
import {convertStringToBuffer} from 'react-server/src/ReactServerHostConfig';
1111

12-
export function formatChunk(type: string, props: Object): Uint8Array {
12+
export function formatChunkAsString(type: string, props: Object): string {
1313
let str = '<' + type + '>';
1414
if (typeof props.children === 'string') {
1515
str += props.children;
1616
}
1717
str += '</' + type + '>';
18-
return convertStringToBuffer(str);
18+
return str;
19+
}
20+
21+
export function formatChunk(type: string, props: Object): Uint8Array {
22+
return convertStringToBuffer(formatChunkAsString(type, props));
1923
}

0 commit comments

Comments
 (0)