@samitouri / QOS-React-2 / commits / f4e974d26e

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 ];