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
Sebastian Markbåge committed
Oct 29, 2019 at 14:45 UTC
f4e974d26e273c4eb58c0ffa9b479435bf598ea1
75 files changed
+1651
-102
fixtures/flight-browser/index.html
new
+84
@@ -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>
packages/react-dom/npm/unstable-flight-client.js
new
+7
@@ -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
+}
packages/react-dom/npm/unstable-flight-server.browser.js
new
+7
@@ -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
+}
packages/react-dom/npm/unstable-flight-server.js
new
+3
@@ -0,0 +1,3 @@
1
+'use strict';
2
+
3
+module.exports = require('./unstable-flight-server.node');
packages/react-dom/npm/unstable-flight-server.node.js
new
+7
@@ -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
+5
-1
@@ -39,13 +39,17 @@
39
"unstable-fizz.js",
40
"unstable-fizz.browser.js",
41
"unstable-fizz.node.js",
42
+ "unstable-flight-server.js",
43
+ "unstable-flight-server.browser.js",
44
+ "unstable-flight-server.node.js",
45
"unstable-native-dependencies.js",
46
"cjs/",
47
"umd/"
48
],
49
"browser": {
50
"./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"
53
},
54
"browserify": {
55
"transform": [
packages/react-dom/src/client/flight/ReactFlightDOMClient.js
new
+34
@@ -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
+};
packages/react-dom/src/client/flight/ReactFlightDOMHostConfig.js
new
+50
@@ -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
+}
packages/react-dom/src/client/flight/__tests__/ReactFlightDOMBrowser-test.js
new
+61
@@ -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
+});
packages/react-dom/src/client/flight/__tests__/ReactFlightDOMNode-test.js
new
+57
@@ -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
+1
-1
@@ -13,7 +13,7 @@ import {
13
createRequest,
14
startWork,
15
startFlowing,
16
-} from 'react-stream/inline.dom-browser';
16
+} from 'react-server/inline.dom-browser';
17
18
function renderToReadableStream(children: ReactNodeList): ReadableStream {
19
let request;
packages/react-dom/src/server/ReactDOMFizzServerNode.js
+1
-1
@@ -10,7 +10,7 @@
10
import type {ReactNodeList} from 'shared/ReactTypes';
11
import type {Writable} from 'stream';
12
13
-import {createRequest, startWork, startFlowing} from 'react-stream/inline.dom';
13
+import {createRequest, startWork, startFlowing} from 'react-server/inline.dom';
14
15
function createDrainHandler(destination, request) {
16
return () => startFlowing(request, 0);
packages/react-dom/src/server/ReactDOMServerFormatConfig.js
renamed
+7
-3
@@ -7,13 +7,17 @@
7
* @flow
8
*/
9
10
-import {convertStringToBuffer} from 'react-stream/src/ReactFizzHostConfig';
10
+import {convertStringToBuffer} from 'react-server/src/ReactServerHostConfig';
11
12
-export function formatChunk(type: string, props: Object): Uint8Array {
12
+export function formatChunkAsString(type: string, props: Object): string {
13
let str = '<' + type + '>';
14
if (typeof props.children === 'string') {
15
str += props.children;
16
}
17
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));
23
}
packages/react-dom/src/server/flight/ReactFlightDOMServerBrowser.js
new
+34
@@ -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-server/flight.inline-typed';
11
+
12
+import {
13
+ createRequest,
14
+ startWork,
15
+ startFlowing,
16
+} from 'react-server/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
+};
packages/react-dom/src/server/flight/ReactFlightDOMServerNode.js
new
+31
@@ -0,0 +1,31 @@
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-server/src/ReactFlightStreamer';
11
+import type {Writable} from 'stream';
12
+
13
+import {
14
+ createRequest,
15
+ startWork,
16
+ startFlowing,
17
+} from 'react-server/flight.inline.dom';
18
+
19
+function createDrainHandler(destination, request) {
20
+ return () => startFlowing(request, 0);
21
+}
22
+
23
+function pipeToNodeWritable(model: ReactModel, destination: Writable): void {
24
+ let request = createRequest(model, destination);
25
+ destination.on('drain', createDrainHandler(destination, request));
26
+ startWork(request);
27
+}
28
+
29
+export default {
30
+ pipeToNodeWritable,
31
+};
packages/react-dom/src/server/flight/__tests__/ReactFlightDOMBrowser-test.js
new
+61
@@ -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
+});
packages/react-dom/src/server/flight/__tests__/ReactFlightDOMNode-test.js
new
+57
@@ -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/unstable-flight-client.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
+'use strict';
11
+
12
+const ReactFlightDOMClient = require('./src/client/flight/ReactFlightDOMClient');
13
+
14
+// TODO: decide on the top-level export form.
15
+// This is hacky but makes it work with both Rollup and Jest
16
+module.exports = ReactFlightDOMClient.default || ReactFlightDOMClient;
packages/react-dom/unstable-flight-server.browser.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
+'use strict';
11
+
12
+const ReactFlightDOMServerBrowser = require('./src/server/flight/ReactFlightDOMServerBrowser');
13
+
14
+// TODO: decide on the top-level export form.
15
+// This is hacky but makes it work with both Rollup and Jest
16
+module.exports =
17
+ ReactFlightDOMServerBrowser.default || ReactFlightDOMServerBrowser;
packages/react-dom/unstable-flight-server.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
+'use strict';
11
+
12
+module.exports = require('./unstable-flight-server.node');
packages/react-dom/unstable-flight-server.node.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
+'use strict';
11
+
12
+const ReactFlightDOMServerNode = require('./src/server/flight/ReactFlightDOMServerNode');
13
+
14
+// TODO: decide on the top-level export form.
15
+// This is hacky but makes it work with both Rollup and Jest
16
+module.exports = ReactFlightDOMServerNode.default || ReactFlightDOMServerNode;
packages/react-flight/README.md
new
+7
@@ -0,0 +1,7 @@
1
+# react-flight
2
+
3
+This is an experimental package for consuming custom React streaming models.
4
+
5
+**Its API is not as stable as that of React, React Native, or React DOM, and does not follow the common versioning scheme.**
6
+
7
+**Use it at your own risk.**
packages/react-flight/index.js
new
+26
@@ -0,0 +1,26 @@
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
+
8
+// This entry point is intentionally not typed. It exists only for third-party
9
+// renderers. The renderers we ship (such as React DOM) instead import a named
10
+// "inline" entry point (for example, `react-server/inline.dom`). It uses
11
+// the same code, but the Flow configuration redirects the host config to its
12
+// real implementation so we can check it against exact intended host types.
13
+//
14
+// Only one renderer (the one you passed to `yarn flow <renderer>`) is fully
15
+// type-checked at any given time. The Flow config maps the
16
+// `react-server/inline.<renderer>` import (which is *not* Flow typed) to
17
+// `react-server/inline-typed` (which *is*) for the current renderer.
18
+// On CI, we run Flow checks for each renderer separately.
19
+
20
+'use strict';
21
+
22
+const ReactFlightClient = require('./src/ReactFlightClient');
23
+
24
+// TODO: decide on the top-level export form.
25
+// This is hacky but makes it work with both Rollup and Jest.
26
+module.exports = ReactFlightClient.default || ReactFlightClient;
packages/react-flight/inline-typed.js
new
+24
@@ -0,0 +1,24 @@
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
+// This file must have the Flow annotation.
11
+//
12
+// This is the Flow-typed entry point for the renderer. It should not be
13
+// imported directly in code. Instead, our Flow configuration uses this entry
14
+// point for the currently checked renderer (the one you passed to `yarn flow`).
15
+//
16
+// For example, if you run `yarn flow dom`, `react-server/inline.dom` points
17
+// to this module (and thus will be considered Flow-typed). But other renderers
18
+// (e.g. `react-test-renderer`) will see stream as untyped during the check.
19
+//
20
+// We can't make all entry points typed at the same time because different
21
+// renderers have different host config types. So we check them one by one.
22
+// We run Flow on all renderers on CI.
23
+
24
+export * from './src/ReactFlightClient';
packages/react-flight/inline.dom-browser.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
+
8
+// This file intentionally does *not* have the Flow annotation.
9
+// Don't add it. See `./inline-typed.js` for an explanation.
10
+
11
+export * from './src/ReactFlightClient';
packages/react-flight/inline.dom.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
+
8
+// This file intentionally does *not* have the Flow annotation.
9
+// Don't add it. See `./inline-typed.js` for an explanation.
10
+
11
+export * from './src/ReactFlightClient';
packages/react-flight/npm/index.js
new
+7
@@ -0,0 +1,7 @@
1
+'use strict';
2
+
3
+if (process.env.NODE_ENV === 'production') {
4
+ module.exports = require('./cjs/react-flight.production.min.js');
5
+} else {
6
+ module.exports = require('./cjs/react-flight.development.js');
7
+}
packages/react-flight/package.json
new
+40
@@ -0,0 +1,40 @@
1
+{
2
+ "name": "react-flight",
3
+ "description": "React package for consuming streaming models.",
4
+ "version": "0.1.0",
5
+ "private": true,
6
+ "keywords": [
7
+ "react"
8
+ ],
9
+ "homepage": "https://reactjs.org/",
10
+ "bugs": "https://github.com/facebook/react/issues",
11
+ "license": "MIT",
12
+ "files": [
13
+ "LICENSE",
14
+ "README.md",
15
+ "index.js",
16
+ "cjs/"
17
+ ],
18
+ "main": "index.js",
19
+ "repository": {
20
+ "type" : "git",
21
+ "url" : "https://github.com/facebook/react.git",
22
+ "directory": "packages/react-flight"
23
+ },
24
+ "engines": {
25
+ "node": ">=0.10.0"
26
+ },
27
+ "peerDependencies": {
28
+ "react": "^16.0.0"
29
+ },
30
+ "dependencies": {
31
+ "loose-envify": "^1.1.0",
32
+ "object-assign": "^4.1.1",
33
+ "prop-types": "^15.6.2"
34
+ },
35
+ "browserify": {
36
+ "transform": [
37
+ "loose-envify"
38
+ ]
39
+ }
40
+}
packages/react-flight/src/ReactFlightClient.js
new
+144
@@ -0,0 +1,144 @@
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 {Destination} from './ReactFlightClientHostConfig';
11
+
12
+import {
13
+ scheduleWork,
14
+ beginWriting,
15
+ writeChunk,
16
+ completeWriting,
17
+ flushBuffered,
18
+ close,
19
+ convertStringToBuffer,
20
+ formatChunkAsString,
21
+} from './ReactFlightClientHostConfig';
22
+import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
23
+
24
+export type ReactModel =
25
+ | React$Element<any>
26
+ | string
27
+ | boolean
28
+ | number
29
+ | null
30
+ | Iterable<ReactModel>
31
+ | ReactModelObject;
32
+
33
+type ReactJSONValue =
34
+ | string
35
+ | boolean
36
+ | number
37
+ | null
38
+ | Array<ReactModel>
39
+ | ReactModelObject;
40
+
41
+type ReactModelObject = {
42
+ +[key: string]: ReactModel,
43
+};
44
+
45
+type OpaqueRequest = {
46
+ destination: Destination,
47
+ model: ReactModel,
48
+ completedChunks: Array<Uint8Array>,
49
+ flowing: boolean,
50
+};
51
+
52
+export function createRequest(
53
+ model: ReactModel,
54
+ destination: Destination,
55
+): OpaqueRequest {
56
+ return {destination, model, completedChunks: [], flowing: false};
57
+}
58
+
59
+function resolveChildToHostFormat(child: ReactJSONValue): string {
60
+ if (typeof child === 'string') {
61
+ return child;
62
+ } else if (typeof child === 'number') {
63
+ return '' + child;
64
+ } else if (typeof child === 'boolean' || child === null) {
65
+ // Booleans are like null when they're React children.
66
+ return '';
67
+ } else if (Array.isArray(child)) {
68
+ return (child: Array<ReactModel>)
69
+ .map(c => resolveChildToHostFormat(resolveModelToJSON('', c)))
70
+ .join('');
71
+ } else {
72
+ throw new Error('Object models are not valid as children of host nodes.');
73
+ }
74
+}
75
+
76
+function resolveElementToHostFormat(type: string, props: Object): string {
77
+ let child = resolveModelToJSON('', props.children);
78
+ let childString = resolveChildToHostFormat(child);
79
+ return formatChunkAsString(
80
+ type,
81
+ Object.assign({}, props, {children: childString}),
82
+ );
83
+}
84
+
85
+function resolveModelToJSON(key: string, value: ReactModel): ReactJSONValue {
86
+ while (value && value.$$typeof === REACT_ELEMENT_TYPE) {
87
+ let element: React$Element<any> = (value: any);
88
+ let type = element.type;
89
+ let props = element.props;
90
+ if (typeof type === 'function') {
91
+ // This is a nested view model.
92
+ value = type(props);
93
+ continue;
94
+ } else if (typeof type === 'string') {
95
+ // This is a host element. E.g. HTML.
96
+ return resolveElementToHostFormat(type, props);
97
+ } else {
98
+ throw new Error('Unsupported type.');
99
+ }
100
+ }
101
+ return value;
102
+}
103
+
104
+function performWork(request: OpaqueRequest): void {
105
+ let rootModel = request.model;
106
+ request.model = null;
107
+ let json = JSON.stringify(rootModel, resolveModelToJSON);
108
+ request.completedChunks.push(convertStringToBuffer(json));
109
+ if (request.flowing) {
110
+ flushCompletedChunks(request);
111
+ }
112
+
113
+ flushBuffered(request.destination);
114
+}
115
+
116
+function flushCompletedChunks(request: OpaqueRequest) {
117
+ let destination = request.destination;
118
+ let chunks = request.completedChunks;
119
+ request.completedChunks = [];
120
+
121
+ beginWriting(destination);
122
+ try {
123
+ for (let i = 0; i < chunks.length; i++) {
124
+ let chunk = chunks[i];
125
+ writeChunk(destination, chunk);
126
+ }
127
+ } finally {
128
+ completeWriting(destination);
129
+ }
130
+ close(destination);
131
+}
132
+
133
+export function startWork(request: OpaqueRequest): void {
134
+ request.flowing = true;
135
+ scheduleWork(() => performWork(request));
136
+}
137
+
138
+export function startFlowing(
139
+ request: OpaqueRequest,
140
+ desiredBytes: number,
141
+): void {
142
+ request.flowing = false;
143
+ flushCompletedChunks(request);
144
+}
packages/react-flight/src/ReactFlightClientHostConfig.js
renamed
packages/react-flight/src/__tests__/ReactFlightClient-test.js
new
+38
@@ -0,0 +1,38 @@
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 React;
14
+let ReactNoopFlightClient;
15
+
16
+describe('ReactFlightClient', () => {
17
+ beforeEach(() => {
18
+ jest.resetModules();
19
+
20
+ React = require('react');
21
+ ReactNoopFlightClient = require('react-noop-renderer/flight-client');
22
+ });
23
+
24
+ it('can resolve a model', () => {
25
+ function Bar({text}) {
26
+ return text.toUpperCase();
27
+ }
28
+ function Foo() {
29
+ return {
30
+ bar: [<Bar text="a" />, <Bar text="b" />],
31
+ };
32
+ }
33
+ let result = ReactNoopFlightClient.render({
34
+ foo: <Foo />,
35
+ });
36
+ expect(result).toEqual([{foo: {bar: ['A', 'B']}}]);
37
+ });
38
+});
packages/react-flight/src/forks/ReactFlightClientHostConfig.custom.js
new
+37
@@ -0,0 +1,37 @@
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
+// This is a host config that's used for the `react-server` package on npm.
11
+// It is only used by third-party renderers.
12
+//
13
+// Its API lets you pass the host config as an argument.
14
+// However, inside the `react-server` we treat host config as a module.
15
+// This file is a shim between two worlds.
16
+//
17
+// It works because the `react-server` bundle is wrapped in something like:
18
+//
19
+// module.exports = function ($$$config) {
20
+// /* renderer code */
21
+// }
22
+//
23
+// So `$$$config` looks like a global variable, but it's
24
+// really an argument to a top-level wrapping function.
25
+
26
+declare var $$$hostConfig: any;
27
+export opaque type Destination = mixed; // eslint-disable-line no-undef
28
+
29
+export const formatChunkAsString = $$$hostConfig.formatChunkAsString;
30
+export const formatChunk = $$$hostConfig.formatChunk;
31
+export const scheduleWork = $$$hostConfig.scheduleWork;
32
+export const beginWriting = $$$hostConfig.beginWriting;
33
+export const writeChunk = $$$hostConfig.writeChunk;
34
+export const completeWriting = $$$hostConfig.completeWriting;
35
+export const flushBuffered = $$$hostConfig.flushBuffered;
36
+export const close = $$$hostConfig.close;
37
+export const convertStringToBuffer = $$$hostConfig.convertStringToBuffer;
packages/react-flight/src/forks/ReactFlightClientHostConfig.dom-browser.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/flight/ReactFlightDOMHostConfig';
packages/react-flight/src/forks/ReactFlightClientHostConfig.dom.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/flight/ReactFlightDOMHostConfig';
packages/react-noop-renderer/flight-client.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
+'use strict';
11
+
12
+const ReactNoopFlightClient = require('./src/ReactNoopFlightClient');
13
+
14
+// TODO: decide on the top-level export form.
15
+// This is hacky but makes it work with both Rollup and Jest.
16
+module.exports = ReactNoopFlightClient.default || ReactNoopFlightClient;
packages/react-noop-renderer/flight-server.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
+'use strict';
11
+
12
+const ReactNoopFlightServer = require('./src/ReactNoopFlightServer');
13
+
14
+// TODO: decide on the top-level export form.
15
+// This is hacky but makes it work with both Rollup and Jest.
16
+module.exports = ReactNoopFlightServer.default || ReactNoopFlightServer;
packages/react-noop-renderer/npm/flight-client.js
new
+7
@@ -0,0 +1,7 @@
1
+'use strict';
2
+
3
+if (process.env.NODE_ENV === 'production') {
4
+ module.exports = require('./cjs/react-noop-renderer-flight-client.production.min.js');
5
+} else {
6
+ module.exports = require('./cjs/react-noop-renderer-flight-client.development.js');
7
+}
packages/react-noop-renderer/npm/flight-server.js
new
+7
@@ -0,0 +1,7 @@
1
+'use strict';
2
+
3
+if (process.env.NODE_ENV === 'production') {
4
+ module.exports = require('./cjs/react-noop-renderer-flight-server.production.min.js');
5
+} else {
6
+ module.exports = require('./cjs/react-noop-renderer-flight-server.development.js');
7
+}
packages/react-noop-renderer/package.json
+5
-2
@@ -2,7 +2,7 @@
2
"name": "react-noop-renderer",
3
"version": "16.0.0",
4
"private": true,
5
- "description": "React package for testing the Fiber reconciler.",
5
+ "description": "React package for testing the Fiber, Fizz and Flight reconcilers.",
6
"main": "index.js",
7
"repository": {
8
"type" : "git",
@@ -15,7 +15,8 @@
15
"prop-types": "^15.6.2",
16
"regenerator-runtime": "^0.11.0",
17
"react-reconciler": "*",
18
- "react-stream": "*"
18
+ "react-flight": "*",
19
+ "react-server": "*"
20
},
21
"peerDependencies": {
22
"react": "^16.0.0"
@@ -27,6 +28,8 @@
28
"index.js",
29
"persistent.js",
30
"server.js",
31
+ "flight-client.js",
32
+ "flight-server.js",
33
"cjs/"
34
]
35
}
packages/react-noop-renderer/src/ReactNoopFlightClient.js
new
+54
@@ -0,0 +1,54 @@
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
+/**
11
+ * This is a renderer of React that doesn't have a render target output.
12
+ * It is useful to demonstrate the internals of the reconciler in isolation
13
+ * and for testing semantics of reconciliation separate from the host
14
+ * environment.
15
+ */
16
+
17
+import type {ReactModel} from 'react-flight/inline-typed';
18
+
19
+import ReactFlightClient from 'react-flight';
20
+
21
+type Destination = Array<string>;
22
+
23
+const ReactNoopFlightClient = ReactFlightClient({
24
+ scheduleWork(callback: () => void) {
25
+ callback();
26
+ },
27
+ beginWriting(destination: Destination): void {},
28
+ writeChunk(destination: Destination, buffer: Uint8Array): void {
29
+ destination.push(JSON.parse(Buffer.from((buffer: any)).toString('utf8')));
30
+ },
31
+ completeWriting(destination: Destination): void {},
32
+ close(destination: Destination): void {},
33
+ flushBuffered(destination: Destination): void {},
34
+ convertStringToBuffer(content: string): Uint8Array {
35
+ return Buffer.from(content, 'utf8');
36
+ },
37
+ formatChunkAsString(type: string, props: Object): string {
38
+ return JSON.stringify({type, props});
39
+ },
40
+ formatChunk(type: string, props: Object): Uint8Array {
41
+ return Buffer.from(JSON.stringify({type, props}), 'utf8');
42
+ },
43
+});
44
+
45
+function render(model: ReactModel): Destination {
46
+ let destination: Destination = [];
47
+ let request = ReactNoopFlightClient.createRequest(model, destination);
48
+ ReactNoopFlightClient.startWork(request);
49
+ return destination;
50
+}
51
+
52
+export default {
53
+ render,
54
+};
packages/react-noop-renderer/src/ReactNoopFlightServer.js
new
+54
@@ -0,0 +1,54 @@
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
+/**
11
+ * This is a renderer of React that doesn't have a render target output.
12
+ * It is useful to demonstrate the internals of the reconciler in isolation
13
+ * and for testing semantics of reconciliation separate from the host
14
+ * environment.
15
+ */
16
+
17
+import type {ReactModel} from 'react-server/flight.inline-typed';
18
+
19
+import ReactFlightStreamer from 'react-server/flight';
20
+
21
+type Destination = Array<string>;
22
+
23
+const ReactNoopFlightServer = ReactFlightStreamer({
24
+ scheduleWork(callback: () => void) {
25
+ callback();
26
+ },
27
+ beginWriting(destination: Destination): void {},
28
+ writeChunk(destination: Destination, buffer: Uint8Array): void {
29
+ destination.push(JSON.parse(Buffer.from((buffer: any)).toString('utf8')));
30
+ },
31
+ completeWriting(destination: Destination): void {},
32
+ close(destination: Destination): void {},
33
+ flushBuffered(destination: Destination): void {},
34
+ convertStringToBuffer(content: string): Uint8Array {
35
+ return Buffer.from(content, 'utf8');
36
+ },
37
+ formatChunkAsString(type: string, props: Object): string {
38
+ return JSON.stringify({type, props});
39
+ },
40
+ formatChunk(type: string, props: Object): Uint8Array {
41
+ return Buffer.from(JSON.stringify({type, props}), 'utf8');
42
+ },
43
+});
44
+
45
+function render(model: ReactModel): Destination {
46
+ let destination: Destination = [];
47
+ let request = ReactNoopFlightServer.createRequest(model, destination);
48
+ ReactNoopFlightServer.startWork(request);
49
+ return destination;
50
+}
51
+
52
+export default {
53
+ render,
54
+};
packages/react-noop-renderer/src/ReactNoopServer.js
+4
-1
@@ -14,7 +14,7 @@
14
* environment.
15
*/
16
17
-import ReactFizzStreamer from 'react-stream';
17
+import ReactFizzStreamer from 'react-server';
18
19
type Destination = Array<string>;
20
@@ -32,6 +32,9 @@ const ReactNoopServer = ReactFizzStreamer({
32
convertStringToBuffer(content: string): Uint8Array {
33
return Buffer.from(content, 'utf8');
34
},
35
+ formatChunkAsString(type: string, props: Object): string {
36
+ return JSON.stringify({type, props});
37
+ },
38
formatChunk(type: string, props: Object): Uint8Array {
39
return Buffer.from(JSON.stringify({type, props}), 'utf8');
40
},
packages/react-server/README.md
new
+7
@@ -0,0 +1,7 @@
1
+# react-server
2
+
3
+This is an experimental package for creating custom React streaming server renderers.
4
+
5
+**Its API is not as stable as that of React, React Native, or React DOM, and does not follow the common versioning scheme.**
6
+
7
+**Use it at your own risk.**
packages/react-server/flight.inline-typed.js
new
+24
@@ -0,0 +1,24 @@
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
+// This file must have the Flow annotation.
11
+//
12
+// This is the Flow-typed entry point for the renderer. It should not be
13
+// imported directly in code. Instead, our Flow configuration uses this entry
14
+// point for the currently checked renderer (the one you passed to `yarn flow`).
15
+//
16
+// For example, if you run `yarn flow dom`, `react-server/flight.inline.dom` points
17
+// to this module (and thus will be considered Flow-typed). But other renderers
18
+// (e.g. `react-test-renderer`) will see stream as untyped during the check.
19
+//
20
+// We can't make all entry points typed at the same time because different
21
+// renderers have different host config types. So we check them one by one.
22
+// We run Flow on all renderers on CI.
23
+
24
+export * from './src/ReactFlightStreamer';
packages/react-server/flight.inline.dom-browser.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
+
8
+// This file intentionally does *not* have the Flow annotation.
9
+// Don't add it. See `./inline-typed.js` for an explanation.
10
+
11
+export * from './src/ReactFlightStreamer';
packages/react-server/flight.inline.dom.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
+
8
+// This file intentionally does *not* have the Flow annotation.
9
+// Don't add it. See `./inline-typed.js` for an explanation.
10
+
11
+export * from './src/ReactFlightStreamer';
packages/react-server/flight.js
new
+26
@@ -0,0 +1,26 @@
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
+
8
+// This entry point is intentionally not typed. It exists only for third-party
9
+// renderers. The renderers we ship (such as React DOM) instead import a named
10
+// "inline" entry point (for example, `react-server/flight.inline.dom`). It uses
11
+// the same code, but the Flow configuration redirects the host config to its
12
+// real implementation so we can check it against exact intended host types.
13
+//
14
+// Only one renderer (the one you passed to `yarn flow <renderer>`) is fully
15
+// type-checked at any given time. The Flow config maps the
16
+// `react-server/flight.inline.<renderer>` import (which is *not* Flow typed) to
17
+// `react-server/flight.inline-typed` (which *is*) for the current renderer.
18
+// On CI, we run Flow checks for each renderer separately.
19
+
20
+'use strict';
21
+
22
+const ReactFlightStreamer = require('./src/ReactFlightStreamer');
23
+
24
+// TODO: decide on the top-level export form.
25
+// This is hacky but makes it work with both Rollup and Jest.
26
+module.exports = ReactFlightStreamer.default || ReactFlightStreamer;
packages/react-server/index.js
renamed
+3
-3
@@ -7,14 +7,14 @@
7
8
// This entry point is intentionally not typed. It exists only for third-party
9
// renderers. The renderers we ship (such as React DOM) instead import a named
10
-// "inline" entry point (for example, `react-stream/inline.dom`). It uses
10
+// "inline" entry point (for example, `react-server/inline.dom`). It uses
11
// the same code, but the Flow configuration redirects the host config to its
12
// real implementation so we can check it against exact intended host types.
13
//
14
// Only one renderer (the one you passed to `yarn flow <renderer>`) is fully
15
// type-checked at any given time. The Flow config maps the
16
-// `react-stream/inline.<renderer>` import (which is *not* Flow typed) to
17
-// `react-stream/inline-typed` (which *is*) for the current renderer.
16
+// `react-server/inline.<renderer>` import (which is *not* Flow typed) to
17
+// `react-server/inline-typed` (which *is*) for the current renderer.
18
// On CI, we run Flow checks for each renderer separately.
19
20
'use strict';
packages/react-server/inline-typed.js
renamed
+1
-1
@@ -13,7 +13,7 @@
13
// imported directly in code. Instead, our Flow configuration uses this entry
14
// point for the currently checked renderer (the one you passed to `yarn flow`).
15
//
16
-// For example, if you run `yarn flow dom`, `react-stream/inline.dom` points
16
+// For example, if you run `yarn flow dom`, `react-server/inline.dom` points
17
// to this module (and thus will be considered Flow-typed). But other renderers
18
// (e.g. `react-test-renderer`) will see stream as untyped during the check.
19
//
packages/react-server/inline.dom-browser.js
renamed
packages/react-server/inline.dom.js
renamed
packages/react-server/npm/flight.js
new
+7
@@ -0,0 +1,7 @@
1
+'use strict';
2
+
3
+if (process.env.NODE_ENV === 'production') {
4
+ module.exports = require('./cjs/react-server-flight.production.min.js');
5
+} else {
6
+ module.exports = require('./cjs/react-server-flight.development.js');
7
+}
packages/react-server/npm/index.js
new
+7
@@ -0,0 +1,7 @@
1
+'use strict';
2
+
3
+if (process.env.NODE_ENV === 'production') {
4
+ module.exports = require('./cjs/react-server.production.min.js');
5
+} else {
6
+ module.exports = require('./cjs/react-server.development.js');
7
+}
packages/react-server/package.json
renamed
+3
-2
@@ -1,5 +1,5 @@
1
{
2
- "name": "react-stream",
2
+ "name": "react-server",
3
"description": "React package for creating custom streaming server renderers.",
4
"version": "0.1.0",
5
"private": true,
@@ -13,13 +13,14 @@
13
"LICENSE",
14
"README.md",
15
"index.js",
16
+ "flight.js",
17
"cjs/"
18
],
19
"main": "index.js",
20
"repository": {
21
"type" : "git",
22
"url" : "https://github.com/facebook/react.git",
22
- "directory": "packages/react-stream"
23
+ "directory": "packages/react-server"
24
},
25
"engines": {
26
"node": ">=0.10.0"
packages/react-server/src/ReactFizzStreamer.js
renamed
+3
-3
@@ -7,7 +7,7 @@
7
* @flow
8
*/
9
10
-import type {Destination} from './ReactFizzHostConfig';
10
+import type {Destination} from './ReactServerHostConfig';
11
import type {ReactNodeList} from 'shared/ReactTypes';
12
13
import {
@@ -17,8 +17,8 @@ import {
17
completeWriting,
18
flushBuffered,
19
close,
20
-} from './ReactFizzHostConfig';
21
-import {formatChunk} from './ReactFizzFormatConfig';
20
+} from './ReactServerHostConfig';
21
+import {formatChunk} from './ReactServerFormatConfig';
22
import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
23
24
type OpaqueRequest = {
packages/react-server/src/ReactFlightStreamer.js
new
+144
@@ -0,0 +1,144 @@
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 {Destination} from './ReactServerHostConfig';
11
+
12
+import {
13
+ scheduleWork,
14
+ beginWriting,
15
+ writeChunk,
16
+ completeWriting,
17
+ flushBuffered,
18
+ close,
19
+ convertStringToBuffer,
20
+} from './ReactServerHostConfig';
21
+import {formatChunkAsString} from './ReactServerFormatConfig';
22
+import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
23
+
24
+export type ReactModel =
25
+ | React$Element<any>
26
+ | string
27
+ | boolean
28
+ | number
29
+ | null
30
+ | Iterable<ReactModel>
31
+ | ReactModelObject;
32
+
33
+type ReactJSONValue =
34
+ | string
35
+ | boolean
36
+ | number
37
+ | null
38
+ | Array<ReactModel>
39
+ | ReactModelObject;
40
+
41
+type ReactModelObject = {
42
+ +[key: string]: ReactModel,
43
+};
44
+
45
+type OpaqueRequest = {
46
+ destination: Destination,
47
+ model: ReactModel,
48
+ completedChunks: Array<Uint8Array>,
49
+ flowing: boolean,
50
+};
51
+
52
+export function createRequest(
53
+ model: ReactModel,
54
+ destination: Destination,
55
+): OpaqueRequest {
56
+ return {destination, model, completedChunks: [], flowing: false};
57
+}
58
+
59
+function resolveChildToHostFormat(child: ReactJSONValue): string {
60
+ if (typeof child === 'string') {
61
+ return child;
62
+ } else if (typeof child === 'number') {
63
+ return '' + child;
64
+ } else if (typeof child === 'boolean' || child === null) {
65
+ // Booleans are like null when they're React children.
66
+ return '';
67
+ } else if (Array.isArray(child)) {
68
+ return (child: Array<ReactModel>)
69
+ .map(c => resolveChildToHostFormat(resolveModelToJSON('', c)))
70
+ .join('');
71
+ } else {
72
+ throw new Error('Object models are not valid as children of host nodes.');
73
+ }
74
+}
75
+
76
+function resolveElementToHostFormat(type: string, props: Object): string {
77
+ let child = resolveModelToJSON('', props.children);
78
+ let childString = resolveChildToHostFormat(child);
79
+ return formatChunkAsString(
80
+ type,
81
+ Object.assign({}, props, {children: childString}),
82
+ );
83
+}
84
+
85
+function resolveModelToJSON(key: string, value: ReactModel): ReactJSONValue {
86
+ while (value && value.$$typeof === REACT_ELEMENT_TYPE) {
87
+ let element: React$Element<any> = (value: any);
88
+ let type = element.type;
89
+ let props = element.props;
90
+ if (typeof type === 'function') {
91
+ // This is a nested view model.
92
+ value = type(props);
93
+ continue;
94
+ } else if (typeof type === 'string') {
95
+ // This is a host element. E.g. HTML.
96
+ return resolveElementToHostFormat(type, props);
97
+ } else {
98
+ throw new Error('Unsupported type.');
99
+ }
100
+ }
101
+ return value;
102
+}
103
+
104
+function performWork(request: OpaqueRequest): void {
105
+ let rootModel = request.model;
106
+ request.model = null;
107
+ let json = JSON.stringify(rootModel, resolveModelToJSON);
108
+ request.completedChunks.push(convertStringToBuffer(json));
109
+ if (request.flowing) {
110
+ flushCompletedChunks(request);
111
+ }
112
+
113
+ flushBuffered(request.destination);
114
+}
115
+
116
+function flushCompletedChunks(request: OpaqueRequest) {
117
+ let destination = request.destination;
118
+ let chunks = request.completedChunks;
119
+ request.completedChunks = [];
120
+
121
+ beginWriting(destination);
122
+ try {
123
+ for (let i = 0; i < chunks.length; i++) {
124
+ let chunk = chunks[i];
125
+ writeChunk(destination, chunk);
126
+ }
127
+ } finally {
128
+ completeWriting(destination);
129
+ }
130
+ close(destination);
131
+}
132
+
133
+export function startWork(request: OpaqueRequest): void {
134
+ request.flowing = true;
135
+ scheduleWork(() => performWork(request));
136
+}
137
+
138
+export function startFlowing(
139
+ request: OpaqueRequest,
140
+ desiredBytes: number,
141
+): void {
142
+ request.flowing = false;
143
+ flushCompletedChunks(request);
144
+}
packages/react-server/src/ReactServerFormatConfig.js
renamed
packages/react-server/src/ReactServerHostConfig.js
new
+22
@@ -0,0 +1,22 @@
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
+/* eslint-disable react-internal/warning-and-invariant-args */
11
+
12
+import invariant from 'shared/invariant';
13
+
14
+// We expect that our Rollup, Jest, and Flow configurations
15
+// always shim this module with the corresponding host config
16
+// (either provided by a renderer, or a generic shim for npm).
17
+//
18
+// We should never resolve to this file, but it exists to make
19
+// sure that if we *do* accidentally break the configuration,
20
+// the failure isn't silent.
21
+
22
+invariant(false, 'This module must be shimmed by a specific renderer.');
packages/react-server/src/ReactServerHostConfigBrowser.js
renamed
packages/react-server/src/ReactServerHostConfigNode.js
renamed
packages/react-server/src/__tests__/ReactFlightServer-test.js
new
+38
@@ -0,0 +1,38 @@
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 React;
14
+let ReactNoopFlight;
15
+
16
+describe('ReactFlightServer', () => {
17
+ beforeEach(() => {
18
+ jest.resetModules();
19
+
20
+ React = require('react');
21
+ ReactNoopFlight = require('react-noop-renderer/flight-server');
22
+ });
23
+
24
+ it('can resolve a model', () => {
25
+ function Bar({text}) {
26
+ return text.toUpperCase();
27
+ }
28
+ function Foo() {
29
+ return {
30
+ bar: [<Bar text="a" />, <Bar text="b" />],
31
+ };
32
+ }
33
+ let result = ReactNoopFlight.render({
34
+ foo: <Foo />,
35
+ });
36
+ expect(result).toEqual([{foo: {bar: ['A', 'B']}}]);
37
+ });
38
+});
packages/react-server/src/__tests__/ReactServer-test.js
renamed
packages/react-server/src/forks/ReactServerFormatConfig.custom.js
renamed
+4
-3
@@ -7,14 +7,14 @@
7
* @flow
8
*/
9
10
-// This is a host config that's used for the `react-stream` package on npm.
10
+// This is a host config that's used for the `react-server` package on npm.
11
// It is only used by third-party renderers.
12
//
13
// Its API lets you pass the host config as an argument.
14
-// However, inside the `react-stream` we treat host config as a module.
14
+// However, inside the `react-server` we treat host config as a module.
15
// This file is a shim between two worlds.
16
//
17
-// It works because the `react-stream` bundle is wrapped in something like:
17
+// It works because the `react-server` bundle is wrapped in something like:
18
//
19
// module.exports = function ($$$config) {
20
// /* renderer code */
@@ -26,4 +26,5 @@
26
declare var $$$hostConfig: any;
27
export opaque type Destination = mixed; // eslint-disable-line no-undef
28
29
+export const formatChunkAsString = $$$hostConfig.formatChunkAsString;
30
export const formatChunk = $$$hostConfig.formatChunk;
packages/react-server/src/forks/ReactServerFormatConfig.dom-browser.js
renamed
+1
-1
@@ -7,4 +7,4 @@
7
* @flow
8
*/
9
10
-export * from 'react-dom/src/server/ReactDOMFizzServerFormatConfig';
10
+export * from 'react-dom/src/server/ReactDOMServerFormatConfig';
packages/react-server/src/forks/ReactServerFormatConfig.dom.js
renamed
+1
-1
@@ -7,4 +7,4 @@
7
* @flow
8
*/
9
10
-export * from 'react-dom/src/server/ReactDOMFizzServerFormatConfig';
10
+export * from 'react-dom/src/server/ReactDOMServerFormatConfig';
packages/react-server/src/forks/ReactServerHostConfig.custom.js
renamed
+3
-3
@@ -7,14 +7,14 @@
7
* @flow
8
*/
9
10
-// This is a host config that's used for the `react-stream` package on npm.
10
+// This is a host config that's used for the `react-server` package on npm.
11
// It is only used by third-party renderers.
12
//
13
// Its API lets you pass the host config as an argument.
14
-// However, inside the `react-stream` we treat host config as a module.
14
+// However, inside the `react-server` we treat host config as a module.
15
// This file is a shim between two worlds.
16
//
17
-// It works because the `react-stream` bundle is wrapped in something like:
17
+// It works because the `react-server` bundle is wrapped in something like:
18
//
19
// module.exports = function ($$$config) {
20
// /* renderer code */
packages/react-server/src/forks/ReactServerHostConfig.dom-browser.js
renamed
+1
-1
@@ -7,4 +7,4 @@
7
* @flow
8
*/
9
10
-export * from '../ReactFizzHostConfigBrowser';
10
+export * from '../ReactServerHostConfigBrowser';
packages/react-server/src/forks/ReactServerHostConfig.dom.js
renamed
+1
-1
@@ -7,4 +7,4 @@
7
* @flow
8
*/
9
10
-export * from '../ReactFizzHostConfigNode';
10
+export * from '../ReactServerHostConfigNode';
packages/react-stream/README.md
deleted
-25
@@ -1,25 +0,0 @@
1
-# react-stream
2
-
3
-This is an experimental package for creating custom React streaming server renderers.
4
-
5
-**Its API is not as stable as that of React, React Native, or React DOM, and does not follow the common versioning scheme.**
6
-
7
-**Use it at your own risk.**
8
-
9
-## API
10
-
11
-```js
12
-var Renderer = require('react-stream');
13
-
14
-var HostConfig = {
15
- // You'll need to implement some methods here.
16
- // See below for more information and examples.
17
-};
18
-
19
-var MyRenderer = Renderer(HostConfig);
20
-
21
-var RendererPublicAPI = {
22
-};
23
-
24
-module.exports = RendererPublicAPI;
25
-```
packages/react-stream/npm/index.js
deleted
-7
@@ -1,7 +0,0 @@
1
-'use strict';
2
-
3
-if (process.env.NODE_ENV === 'production') {
4
- module.exports = require('./cjs/react-stream.production.min.js');
5
-} else {
6
- module.exports = require('./cjs/react-stream.development.js');
7
-}
scripts/flow/createFlowConfigs.js
+9
-6
@@ -18,20 +18,23 @@ const configTemplate = fs
18
.readFileSync(__dirname + '/config/flowconfig')
19
.toString();
20
21
-function writeConfig(renderer, isFizzSupported) {
21
+function writeConfig(renderer, isServerSupported) {
22
const folder = __dirname + '/' + renderer;
23
mkdirp.sync(folder);
24
25
- const fizzRenderer = isFizzSupported ? renderer : 'custom';
25
+ const serverRenderer = isServerSupported ? renderer : 'custom';
26
const config = configTemplate
27
.replace(
28
'%REACT_RENDERER_FLOW_OPTIONS%',
29
`
30
module.name_mapper='react-reconciler/inline.${renderer}$$' -> 'react-reconciler/inline-typed'
31
module.name_mapper='ReactFiberHostConfig$$' -> 'forks/ReactFiberHostConfig.${renderer}'
32
-module.name_mapper='react-stream/inline.${renderer}$$' -> 'react-stream/inline-typed'
33
-module.name_mapper='ReactFizzHostConfig$$' -> 'forks/ReactFizzHostConfig.${fizzRenderer}'
34
-module.name_mapper='ReactFizzFormatConfig$$' -> 'forks/ReactFizzFormatConfig.${fizzRenderer}'
32
+module.name_mapper='react-server/inline.${renderer}$$' -> 'react-server/inline-typed'
33
+module.name_mapper='react-server/flight.inline.${renderer}$$' -> 'react-server/flight.inline-typed'
34
+module.name_mapper='ReactServerHostConfig$$' -> 'forks/ReactServerHostConfig.${serverRenderer}'
35
+module.name_mapper='ReactServerFormatConfig$$' -> 'forks/ReactServerFormatConfig.${serverRenderer}'
36
+module.name_mapper='react-flight/inline.${renderer}$$' -> 'react-flight/inline-typed'
37
+module.name_mapper='ReactFlightClientHostConfig$$' -> 'forks/ReactFlightClientHostConfig.${serverRenderer}'
38
`.trim(),
39
)
40
.replace(
@@ -78,6 +81,6 @@ ${disclaimer}
81
// so that we can run those checks in parallel if we want.
82
inlinedHostConfigs.forEach(rendererInfo => {
83
if (rendererInfo.isFlowTyped) {
81
- writeConfig(rendererInfo.shortName, rendererInfo.isFizzSupported);
84
+ writeConfig(rendererInfo.shortName, rendererInfo.isServerSupported);
85
}
86
});
scripts/jest/setupHostConfigs.js
+96
-13
@@ -17,13 +17,28 @@ jest.mock('react-reconciler/persistent', () => {
17
return require.requireActual('react-reconciler/persistent');
18
};
19
});
20
-const shimFizzHostConfigPath = 'react-stream/src/ReactFizzHostConfig';
21
-const shimFizzFormatConfigPath = 'react-stream/src/ReactFizzFormatConfig';
22
-jest.mock('react-stream', () => {
20
+const shimServerHostConfigPath = 'react-server/src/ReactServerHostConfig';
21
+const shimServerFormatConfigPath = 'react-server/src/ReactServerFormatConfig';
22
+jest.mock('react-server', () => {
23
return config => {
24
- jest.mock(shimFizzHostConfigPath, () => config);
25
- jest.mock(shimFizzFormatConfigPath, () => config);
26
- return require.requireActual('react-stream');
24
+ jest.mock(shimServerHostConfigPath, () => config);
25
+ jest.mock(shimServerFormatConfigPath, () => config);
26
+ return require.requireActual('react-server');
27
+ };
28
+});
29
+jest.mock('react-server/flight', () => {
30
+ return config => {
31
+ jest.mock(shimServerHostConfigPath, () => config);
32
+ jest.mock(shimServerFormatConfigPath, () => config);
33
+ return require.requireActual('react-server/flight');
34
+ };
35
+});
36
+const shimFlightClientHostConfigPath =
37
+ 'react-flight/src/ReactFlightClientHostConfig';
38
+jest.mock('react-flight', () => {
39
+ return config => {
40
+ jest.mock(shimFlightClientHostConfigPath, () => config);
41
+ return require.requireActual('react-flight');
42
};
43
});
44
@@ -65,29 +80,97 @@ inlinedHostConfigs.forEach(rendererInfo => {
80
return renderer;
81
});
82
68
- if (rendererInfo.isFizzSupported) {
69
- jest.mock(`react-stream/inline.${rendererInfo.shortName}`, () => {
83
+ if (rendererInfo.isServerSupported) {
84
+ jest.mock(`react-server/inline.${rendererInfo.shortName}`, () => {
85
+ let hasImportedShimmedConfig = false;
86
+
87
+ // We want the renderer to pick up the host config for this renderer.
88
+ jest.mock(shimServerHostConfigPath, () => {
89
+ hasImportedShimmedConfig = true;
90
+ return require.requireActual(
91
+ `react-server/src/forks/ReactServerHostConfig.${
92
+ rendererInfo.shortName
93
+ }.js`
94
+ );
95
+ });
96
+ jest.mock(shimServerFormatConfigPath, () => {
97
+ hasImportedShimmedConfig = true;
98
+ return require.requireActual(
99
+ `react-server/src/forks/ReactServerFormatConfig.${
100
+ rendererInfo.shortName
101
+ }.js`
102
+ );
103
+ });
104
+
105
+ const renderer = require.requireActual('react-server');
106
+ // If the shimmed config factory function above has not run,
107
+ // it means this test file loads more than one renderer
108
+ // but doesn't reset modules between them. This won't work.
109
+ if (!hasImportedShimmedConfig) {
110
+ throw new Error(
111
+ `Could not import the "${rendererInfo.shortName}" renderer ` +
112
+ `in this suite because another renderer has already been ` +
113
+ `loaded earlier. Call jest.resetModules() before importing any ` +
114
+ `of the following entry points:\n\n` +
115
+ rendererInfo.entryPoints.map(entry => ` * ${entry}`)
116
+ );
117
+ }
118
+
119
+ return renderer;
120
+ });
121
+
122
+ jest.mock(`react-server/flight.inline.${rendererInfo.shortName}`, () => {
123
let hasImportedShimmedConfig = false;
124
125
// We want the renderer to pick up the host config for this renderer.
73
- jest.mock(shimFizzHostConfigPath, () => {
126
+ jest.mock(shimServerHostConfigPath, () => {
127
+ hasImportedShimmedConfig = true;
128
+ return require.requireActual(
129
+ `react-server/src/forks/ReactServerHostConfig.${
130
+ rendererInfo.shortName
131
+ }.js`
132
+ );
133
+ });
134
+ jest.mock(shimServerFormatConfigPath, () => {
135
hasImportedShimmedConfig = true;
136
return require.requireActual(
76
- `react-stream/src/forks/ReactFizzHostConfig.${
137
+ `react-server/src/forks/ReactServerFormatConfig.${
138
rendererInfo.shortName
139
}.js`
140
);
141
});
81
- jest.mock(shimFizzFormatConfigPath, () => {
142
+
143
+ const renderer = require.requireActual('react-server/flight');
144
+ // If the shimmed config factory function above has not run,
145
+ // it means this test file loads more than one renderer
146
+ // but doesn't reset modules between them. This won't work.
147
+ if (!hasImportedShimmedConfig) {
148
+ throw new Error(
149
+ `Could not import the "${rendererInfo.shortName}" renderer ` +
150
+ `in this suite because another renderer has already been ` +
151
+ `loaded earlier. Call jest.resetModules() before importing any ` +
152
+ `of the following entry points:\n\n` +
153
+ rendererInfo.entryPoints.map(entry => ` * ${entry}`)
154
+ );
155
+ }
156
+
157
+ return renderer;
158
+ });
159
+
160
+ jest.mock(`react-flight/inline.${rendererInfo.shortName}`, () => {
161
+ let hasImportedShimmedConfig = false;
162
+
163
+ // We want the renderer to pick up the host config for this renderer.
164
+ jest.mock(shimFlightClientHostConfigPath, () => {
165
hasImportedShimmedConfig = true;
166
return require.requireActual(
84
- `react-stream/src/forks/ReactFizzFormatConfig.${
167
+ `react-flight/src/forks/ReactFlightClientHostConfig.${
168
rendererInfo.shortName
169
}.js`
170
);
171
});
172
90
- const renderer = require.requireActual('react-stream');
173
+ const renderer = require.requireActual('react-flight');
174
// If the shimmed config factory function above has not run,
175
// it means this test file loads more than one renderer
176
// but doesn't reset modules between them. This won't work.
scripts/rollup/bundles.js
+71
-3
@@ -170,6 +170,38 @@ const bundles = [
170
externals: ['react'],
171
},
172
173
+ /******* React DOM Flight Server *******/
174
+ {
175
+ bundleTypes: [NODE_DEV, NODE_PROD, UMD_DEV, UMD_PROD],
176
+ moduleType: RENDERER,
177
+ entry: 'react-dom/unstable-flight-server.browser',
178
+ global: 'ReactFlightDOMServer',
179
+ externals: ['react'],
180
+ },
181
+ {
182
+ bundleTypes: [NODE_DEV, NODE_PROD, FB_WWW_DEV, FB_WWW_PROD],
183
+ moduleType: RENDERER,
184
+ entry: 'react-dom/unstable-flight-server.node',
185
+ global: 'ReactFlightDOMServer',
186
+ externals: ['react'],
187
+ },
188
+
189
+ /******* React DOM Flight Client *******/
190
+ {
191
+ bundleTypes: [
192
+ NODE_DEV,
193
+ NODE_PROD,
194
+ UMD_DEV,
195
+ UMD_PROD,
196
+ FB_WWW_DEV,
197
+ FB_WWW_PROD,
198
+ ],
199
+ moduleType: RENDERER,
200
+ entry: 'react-dom/unstable-flight-client',
201
+ global: 'ReactFlightDOMClient',
202
+ externals: ['react'],
203
+ },
204
+
205
/******* React ART *******/
206
{
207
bundleTypes: [
@@ -308,6 +340,24 @@ const bundles = [
340
externals: ['react', 'scheduler', 'expect'],
341
},
342
343
+ /******* React Noop Flight Server (used for tests) *******/
344
+ {
345
+ bundleTypes: [NODE_DEV, NODE_PROD],
346
+ moduleType: RENDERER,
347
+ entry: 'react-noop-renderer/flight-server',
348
+ global: 'ReactNoopFlightServer',
349
+ externals: ['react', 'scheduler', 'expect'],
350
+ },
351
+
352
+ /******* React Noop Flight Client (used for tests) *******/
353
+ {
354
+ bundleTypes: [NODE_DEV, NODE_PROD],
355
+ moduleType: RENDERER,
356
+ entry: 'react-noop-renderer/flight-client',
357
+ global: 'ReactNoopFlightClient',
358
+ externals: ['react', 'scheduler', 'expect'],
359
+ },
360
+
361
/******* React Reconciler *******/
362
{
363
bundleTypes: [NODE_DEV, NODE_PROD],
@@ -326,12 +376,30 @@ const bundles = [
376
externals: ['react'],
377
},
378
329
- /******* React Stream *******/
379
+ /******* React Server *******/
380
+ {
381
+ bundleTypes: [NODE_DEV, NODE_PROD],
382
+ moduleType: RECONCILER,
383
+ entry: 'react-server',
384
+ global: 'ReactServer',
385
+ externals: ['react'],
386
+ },
387
+
388
+ /******* React Flight Server *******/
389
+ {
390
+ bundleTypes: [NODE_DEV, NODE_PROD],
391
+ moduleType: RECONCILER,
392
+ entry: 'react-server/flight',
393
+ global: 'ReactFlightServer',
394
+ externals: ['react'],
395
+ },
396
+
397
+ /******* React Flight Client *******/
398
{
399
bundleTypes: [NODE_DEV, NODE_PROD],
400
moduleType: RECONCILER,
333
- entry: 'react-stream',
334
- global: 'ReactStream',
401
+ entry: 'react-flight',
402
+ global: 'ReactFlightClient',
403
externals: ['react'],
404
},
405
scripts/rollup/forks.js
+40
-10
@@ -307,13 +307,13 @@ const forks = Object.freeze({
307
);
308
},
309
310
- 'react-stream/src/ReactFizzHostConfig': (
310
+ 'react-server/src/ReactServerHostConfig': (
311
bundleType,
312
entry,
313
dependencies,
314
moduleType
315
) => {
316
- if (dependencies.indexOf('react-stream') !== -1) {
316
+ if (dependencies.indexOf('react-server') !== -1) {
317
return null;
318
}
319
if (moduleType !== RENDERER && moduleType !== RECONCILER) {
@@ -322,28 +322,28 @@ const forks = Object.freeze({
322
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
323
for (let rendererInfo of inlinedHostConfigs) {
324
if (rendererInfo.entryPoints.indexOf(entry) !== -1) {
325
- if (!rendererInfo.isFizzSupported) {
325
+ if (!rendererInfo.isServerSupported) {
326
return null;
327
}
328
- return `react-stream/src/forks/ReactFizzHostConfig.${
328
+ return `react-server/src/forks/ReactServerHostConfig.${
329
rendererInfo.shortName
330
}.js`;
331
}
332
}
333
throw new Error(
334
- 'Expected ReactFizzHostConfig to always be replaced with a shim, but ' +
334
+ 'Expected ReactServerHostConfig to always be replaced with a shim, but ' +
335
`found no mention of "${entry}" entry point in ./scripts/shared/inlinedHostConfigs.js. ` +
336
'Did you mean to add it there to associate it with a specific renderer?'
337
);
338
},
339
340
- 'react-stream/src/ReactFizzFormatConfig': (
340
+ 'react-server/src/ReactServerFormatConfig': (
341
bundleType,
342
entry,
343
dependencies,
344
moduleType
345
) => {
346
- if (dependencies.indexOf('react-stream') !== -1) {
346
+ if (dependencies.indexOf('react-server') !== -1) {
347
return null;
348
}
349
if (moduleType !== RENDERER && moduleType !== RECONCILER) {
@@ -352,16 +352,46 @@ const forks = Object.freeze({
352
// eslint-disable-next-line no-for-of-loops/no-for-of-loops
353
for (let rendererInfo of inlinedHostConfigs) {
354
if (rendererInfo.entryPoints.indexOf(entry) !== -1) {
355
- if (!rendererInfo.isFizzSupported) {
355
+ if (!rendererInfo.isServerSupported) {
356
return null;
357
}
358
- return `react-stream/src/forks/ReactFizzFormatConfig.${
358
+ return `react-server/src/forks/ReactServerFormatConfig.${
359
rendererInfo.shortName
360
}.js`;
361
}
362
}
363
throw new Error(
364
- 'Expected ReactFizzFormatConfig to always be replaced with a shim, but ' +
364
+ 'Expected ReactServerFormatConfig to always be replaced with a shim, but ' +
365
+ `found no mention of "${entry}" entry point in ./scripts/shared/inlinedHostConfigs.js. ` +
366
+ 'Did you mean to add it there to associate it with a specific renderer?'
367
+ );
368
+ },
369
+
370
+ 'react-flight/src/ReactFlightClientHostConfig': (
371
+ bundleType,
372
+ entry,
373
+ dependencies,
374
+ moduleType
375
+ ) => {
376
+ if (dependencies.indexOf('react-flight') !== -1) {
377
+ return null;
378
+ }
379
+ if (moduleType !== RENDERER && moduleType !== RECONCILER) {
380
+ return null;
381
+ }
382
+ // eslint-disable-next-line no-for-of-loops/no-for-of-loops
383
+ for (let rendererInfo of inlinedHostConfigs) {
384
+ if (rendererInfo.entryPoints.indexOf(entry) !== -1) {
385
+ if (!rendererInfo.isServerSupported) {
386
+ return null;
387
+ }
388
+ return `react-flight/src/forks/ReactFlightClientHostConfig.${
389
+ rendererInfo.shortName
390
+ }.js`;
391
+ }
392
+ }
393
+ throw new Error(
394
+ 'Expected ReactFlightClientHostConfig to always be replaced with a shim, but ' +
395
`found no mention of "${entry}" entry point in ./scripts/shared/inlinedHostConfigs.js. ` +
396
'Did you mean to add it there to associate it with a specific renderer?'
397
);
scripts/shared/inlinedHostConfigs.js
+21
-10
@@ -9,48 +9,59 @@
9
module.exports = [
10
{
11
shortName: 'dom',
12
- entryPoints: ['react-dom', 'react-dom/unstable-fizz.node'],
12
+ entryPoints: [
13
+ 'react-dom',
14
+ 'react-dom/unstable-fizz.node',
15
+ 'react-dom/unstable-flight-server.node',
16
+ 'react-dom/unstable-flight-client',
17
+ ],
18
isFlowTyped: true,
14
- isFizzSupported: true,
19
+ isServerSupported: true,
20
},
21
{
22
shortName: 'dom-browser',
18
- entryPoints: ['react-dom/unstable-fizz.browser'],
23
+ entryPoints: [
24
+ 'react-dom/unstable-fizz.browser',
25
+ 'react-dom/unstable-flight-server.browser',
26
+ 'react-dom/unstable-flight-client',
27
+ ],
28
isFlowTyped: true,
20
- isFizzSupported: true,
29
+ isServerSupported: true,
30
},
31
{
32
shortName: 'art',
33
entryPoints: ['react-art'],
34
isFlowTyped: false, // TODO: type it.
26
- isFizzSupported: false,
35
+ isServerSupported: false,
36
},
37
{
38
shortName: 'native',
39
entryPoints: ['react-native-renderer'],
40
isFlowTyped: true,
32
- isFizzSupported: false,
41
+ isServerSupported: false,
42
},
43
{
44
shortName: 'fabric',
45
entryPoints: ['react-native-renderer/fabric'],
46
isFlowTyped: true,
38
- isFizzSupported: false,
47
+ isServerSupported: false,
48
},
49
{
50
shortName: 'test',
51
entryPoints: ['react-test-renderer'],
52
isFlowTyped: true,
44
- isFizzSupported: false,
53
+ isServerSupported: false,
54
},
55
{
56
shortName: 'custom',
57
entryPoints: [
58
'react-reconciler',
59
'react-reconciler/persistent',
51
- 'react-stream',
60
+ 'react-flight',
61
+ 'react-server',
62
+ 'react-server/flight',
63
],
64
isFlowTyped: true,
54
- isFizzSupported: true,
65
+ isServerSupported: true,
66
},
67
];