Add Relay Flight Build (#18242)

* Rename to clarify that it's client-only

* Rename FizzStreamer to FizzServer for consistency

* Rename react-flight to react-client/flight

For consistency with react-server. Currently this just includes flight
but it could be expanded to include the whole reconciler.

* Add Relay Flight Build

* Rename ReactServerHostConfig to ReactServerStreamConfig

This will be the config specifically for streaming purposes.
There will be other configs for other purposes.
This commit is contained in:
Sebastian Markbåge 2020-03-07 11:23:30 -08:00 committed by GitHub
parent 7a1691cdff
commit bdc5cc4635
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 311 additions and 48 deletions

View File

@ -1,4 +1,4 @@
# react-flight # react-client
This is an experimental package for consuming custom React streaming models. This is an experimental package for consuming custom React streaming models.

7
packages/react-client/npm/flight.js vendored Normal file
View File

@ -0,0 +1,7 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-client-flight.production.min.js');
} else {
module.exports = require('./cjs/react-client-flight.development.js');
}

View File

@ -1,5 +1,5 @@
{ {
"name": "react-flight", "name": "react-client",
"description": "React package for consuming streaming models.", "description": "React package for consuming streaming models.",
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
@ -12,14 +12,13 @@
"files": [ "files": [
"LICENSE", "LICENSE",
"README.md", "README.md",
"index.js", "flight.js",
"cjs/" "cjs/"
], ],
"main": "index.js",
"repository": { "repository": {
"type" : "git", "type" : "git",
"url" : "https://github.com/facebook/react.git", "url" : "https://github.com/facebook/react.git",
"directory": "packages/react-flight" "directory": "packages/react-client"
}, },
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"

View File

@ -0,0 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export * from 'react-client/src/ReactFlightClientHostConfigBrowser';

View File

@ -0,0 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export * from 'react-flight-dom-relay/src/ReactFlightDOMRelayClientHostConfig';

View File

@ -0,0 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export * from 'react-client/src/ReactFlightClientHostConfigBrowser';

View File

@ -13,7 +13,7 @@ import {
createRequest, createRequest,
startWork, startWork,
startFlowing, startFlowing,
} from 'react-server/src/ReactFizzStreamer'; } from 'react-server/src/ReactFizzServer';
function renderToReadableStream(children: ReactNodeList): ReadableStream { function renderToReadableStream(children: ReactNodeList): ReadableStream {
let request; let request;

View File

@ -14,7 +14,7 @@ import {
createRequest, createRequest,
startWork, startWork,
startFlowing, startFlowing,
} from 'react-server/src/ReactFizzStreamer'; } from 'react-server/src/ReactFizzServer';
function createDrainHandler(destination, request) { function createDrainHandler(destination, request) {
return () => startFlowing(request); return () => startFlowing(request);

View File

@ -7,4 +7,4 @@
* @flow * @flow
*/ */
export * from '../ReactServerHostConfigBrowser'; export * from './src/ReactFlightDOMRelayClient';

View File

@ -0,0 +1,18 @@
{
"name": "react-flight-dom-relay",
"version": "0.1.0",
"private": true,
"repository": {
"type" : "git",
"url" : "https://github.com/facebook/react.git",
"directory": "packages/react-flight-dom-relay"
},
"dependencies": {
"object-assign": "^4.1.1",
"scheduler": "^0.11.0"
},
"peerDependencies": {
"react": "^16.0.0",
"react-dom": "^16.0.0"
}
}

View File

@ -7,4 +7,4 @@
* @flow * @flow
*/ */
export * from 'react-flight/src/ReactFlightHostConfigBrowser'; export * from './src/ReactFlightDOMRelayServer';

View File

@ -0,0 +1,30 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {ReactModelRoot} from 'react-client/src/ReactFlightClient';
import {
createResponse,
getModelRoot,
processStringChunk,
complete,
} from 'react-client/src/ReactFlightClient';
type EncodedData = Array<string>;
function read<T>(data: EncodedData): ReactModelRoot<T> {
let response = createResponse(data);
for (let i = 0; i < data.length; i++) {
processStringChunk(response, data[i], 0);
}
complete(response);
return getModelRoot(response);
}
export {read};

View File

@ -0,0 +1,32 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export type Source = Array<string>;
export type StringDecoder = void;
export const supportsBinaryStreams = false;
export function createStringDecoder(): void {
throw new Error('Should never be called');
}
export function readPartialStringChunk(
decoder: StringDecoder,
buffer: Uint8Array,
): string {
throw new Error('Should never be called');
}
export function readFinalStringChunk(
decoder: StringDecoder,
buffer: Uint8Array,
): string {
throw new Error('Should never be called');
}

View File

@ -0,0 +1,23 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
import type {ReactModel} from 'react-server/src/ReactFlightServer';
import {createRequest, startWork} from 'react-server/src/ReactFlightServer';
type EncodedData = Array<string>;
function render(model: ReactModel): EncodedData {
let data: EncodedData = [];
let request = createRequest(model, data);
startWork(request);
return data;
}
export {render};

View File

@ -0,0 +1,34 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export type Destination = Array<string>;
export function scheduleWork(callback: () => void) {
callback();
}
export function flushBuffered(destination: Destination) {}
export function beginWriting(destination: Destination) {}
export function writeChunk(
destination: Destination,
buffer: Uint8Array,
): boolean {
destination.push(Buffer.from((buffer: any)).toString('utf8'));
return true;
}
export function completeWriting(destination: Destination) {}
export function close(destination: Destination) {}
export function convertStringToBuffer(content: string): Uint8Array {
return Buffer.from(content, 'utf8');
}

View File

@ -0,0 +1,42 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
'use strict';
// Polyfills for test environment
global.TextDecoder = require('util').TextDecoder;
let React;
let ReactDOMFlightRelayServer;
let ReactDOMFlightRelayClient;
describe('ReactFlightDOMRelay', () => {
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactDOMFlightRelayServer = require('react-flight-dom-relay/server');
ReactDOMFlightRelayClient = require('react-flight-dom-relay');
});
it('can resolve a model', () => {
function Bar({text}) {
return text.toUpperCase();
}
function Foo() {
return {
bar: [<Bar text="a" />, <Bar text="b" />],
};
}
let data = ReactDOMFlightRelayServer.render({
foo: <Foo />,
});
let root = ReactDOMFlightRelayClient.read(data);
let model = root.model;
expect(model).toEqual({foo: {bar: ['A', 'B']}});
});
});

View File

@ -7,7 +7,7 @@
* @flow * @flow
*/ */
import type {ReactModelRoot} from 'react-flight/src/ReactFlightClient'; import type {ReactModelRoot} from 'react-client/src/ReactFlightClient';
import { import {
createResponse, createResponse,
@ -16,7 +16,7 @@ import {
processStringChunk, processStringChunk,
processBinaryChunk, processBinaryChunk,
complete, complete,
} from 'react-flight/src/ReactFlightClient'; } from 'react-client/src/ReactFlightClient';
function startReadingFromStream(response, stream: ReadableStream): void { function startReadingFromStream(response, stream: ReadableStream): void {
let reader = stream.getReader(); let reader = stream.getReader();

View File

@ -1,7 +0,0 @@
'use strict';
if (process.env.NODE_ENV === 'production') {
module.exports = require('./cjs/react-flight.production.min.js');
} else {
module.exports = require('./cjs/react-flight.development.js');
}

View File

@ -14,7 +14,7 @@
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
"regenerator-runtime": "^0.11.0", "regenerator-runtime": "^0.11.0",
"react-reconciler": "*", "react-reconciler": "*",
"react-flight": "*", "react-client": "*",
"react-server": "*" "react-server": "*"
}, },
"peerDependencies": { "peerDependencies": {

View File

@ -14,9 +14,9 @@
* environment. * environment.
*/ */
import type {ReactModelRoot} from 'react-flight'; import type {ReactModelRoot} from 'react-client/flight';
import ReactFlightClient from 'react-flight'; import ReactFlightClient from 'react-client/flight';
type Source = Array<string>; type Source = Array<string>;

View File

@ -14,11 +14,11 @@
* environment. * environment.
*/ */
import ReactFizzStreamer from 'react-server'; import ReactFizzServer from 'react-server';
type Destination = Array<string>; type Destination = Array<string>;
const ReactNoopServer = ReactFizzStreamer({ const ReactNoopServer = ReactFizzServer({
scheduleWork(callback: () => void) { scheduleWork(callback: () => void) {
callback(); callback();
}, },

View File

@ -7,4 +7,4 @@
* @flow * @flow
*/ */
export * from 'react-flight/src/ReactFlightHostConfigBrowser'; export * from 'react-dom/src/client/ReactDOMHostConfig';

View File

@ -7,4 +7,4 @@
* @flow * @flow
*/ */
export * from './src/ReactFizzStreamer'; export * from './src/ReactFizzServer';

View File

@ -7,7 +7,7 @@
* @flow * @flow
*/ */
import {convertStringToBuffer} from 'react-server/src/ReactServerHostConfig'; import {convertStringToBuffer} from 'react-server/src/ReactServerStreamConfig';
import {renderToStaticMarkup} from 'react-dom/server'; import {renderToStaticMarkup} from 'react-dom/server';

View File

@ -7,7 +7,7 @@
* @flow * @flow
*/ */
import type {Destination} from './ReactServerHostConfig'; import type {Destination} from './ReactServerStreamConfig';
import type {ReactNodeList} from 'shared/ReactTypes'; import type {ReactNodeList} from 'shared/ReactTypes';
import { import {
@ -17,7 +17,7 @@ import {
completeWriting, completeWriting,
flushBuffered, flushBuffered,
close, close,
} from './ReactServerHostConfig'; } from './ReactServerStreamConfig';
import {formatChunk} from './ReactServerFormatConfig'; import {formatChunk} from './ReactServerFormatConfig';
import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols'; import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';

View File

@ -7,7 +7,7 @@
* @flow * @flow
*/ */
import type {Destination} from './ReactServerHostConfig'; import type {Destination} from './ReactServerStreamConfig';
import { import {
scheduleWork, scheduleWork,
@ -17,7 +17,7 @@ import {
flushBuffered, flushBuffered,
close, close,
convertStringToBuffer, convertStringToBuffer,
} from './ReactServerHostConfig'; } from './ReactServerStreamConfig';
import {renderHostChildrenToString} from './ReactServerFormatConfig'; import {renderHostChildrenToString} from './ReactServerFormatConfig';
import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols'; import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';

View File

@ -7,4 +7,4 @@
* @flow * @flow
*/ */
export * from '../ReactServerHostConfigNode'; export * from '../ReactDOMServerFormatConfig';

View File

@ -0,0 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export * from '../ReactServerStreamConfigBrowser';

View File

@ -0,0 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export * from 'react-flight-dom-relay/src/ReactFlightDOMRelayServerHostConfig';

View File

@ -0,0 +1,10 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
export * from '../ReactServerStreamConfigNode';

View File

@ -49,7 +49,7 @@ function writeConfig(renderer, rendererInfo, isServerSupported) {
'%REACT_RENDERER_FLOW_OPTIONS%', '%REACT_RENDERER_FLOW_OPTIONS%',
` `
module.name_mapper='ReactFiberHostConfig$$' -> 'forks/ReactFiberHostConfig.${renderer}' module.name_mapper='ReactFiberHostConfig$$' -> 'forks/ReactFiberHostConfig.${renderer}'
module.name_mapper='ReactServerHostConfig$$' -> 'forks/ReactServerHostConfig.${serverRenderer}' module.name_mapper='ReactServerStreamConfig$$' -> 'forks/ReactServerStreamConfig.${serverRenderer}'
module.name_mapper='ReactServerFormatConfig$$' -> 'forks/ReactServerFormatConfig.${serverRenderer}' module.name_mapper='ReactServerFormatConfig$$' -> 'forks/ReactServerFormatConfig.${serverRenderer}'
module.name_mapper='ReactFlightClientHostConfig$$' -> 'forks/ReactFlightClientHostConfig.${serverRenderer}' module.name_mapper='ReactFlightClientHostConfig$$' -> 'forks/ReactFlightClientHostConfig.${serverRenderer}'
`.trim(), `.trim(),

View File

@ -11,35 +11,35 @@ jest.mock('react-reconciler', () => {
return require.requireActual('react-reconciler'); return require.requireActual('react-reconciler');
}; };
}); });
const shimServerHostConfigPath = 'react-server/src/ReactServerHostConfig'; const shimServerStreamConfigPath = 'react-server/src/ReactServerStreamConfig';
const shimServerFormatConfigPath = 'react-server/src/ReactServerFormatConfig'; const shimServerFormatConfigPath = 'react-server/src/ReactServerFormatConfig';
jest.mock('react-server', () => { jest.mock('react-server', () => {
return config => { return config => {
jest.mock(shimServerHostConfigPath, () => config); jest.mock(shimServerStreamConfigPath, () => config);
jest.mock(shimServerFormatConfigPath, () => config); jest.mock(shimServerFormatConfigPath, () => config);
return require.requireActual('react-server'); return require.requireActual('react-server');
}; };
}); });
jest.mock('react-server/flight', () => { jest.mock('react-server/flight', () => {
return config => { return config => {
jest.mock(shimServerHostConfigPath, () => config); jest.mock(shimServerStreamConfigPath, () => config);
jest.mock(shimServerFormatConfigPath, () => config); jest.mock(shimServerFormatConfigPath, () => config);
return require.requireActual('react-server/flight'); return require.requireActual('react-server/flight');
}; };
}); });
const shimFlightClientHostConfigPath = const shimFlightClientHostConfigPath =
'react-flight/src/ReactFlightClientHostConfig'; 'react-client/src/ReactFlightClientHostConfig';
jest.mock('react-flight', () => { jest.mock('react-client/flight', () => {
return config => { return config => {
jest.mock(shimFlightClientHostConfigPath, () => config); jest.mock(shimFlightClientHostConfigPath, () => config);
return require.requireActual('react-flight'); return require.requireActual('react-client/flight');
}; };
}); });
const configPaths = [ const configPaths = [
'react-reconciler/src/ReactFiberHostConfig', 'react-reconciler/src/ReactFiberHostConfig',
'react-flight/src/ReactFlightClientHostConfig', 'react-client/src/ReactFlightClientHostConfig',
'react-server/src/ReactServerHostConfig', 'react-server/src/ReactServerStreamConfig',
'react-server/src/ReactServerFormatConfig', 'react-server/src/ReactServerFormatConfig',
]; ];

View File

@ -197,6 +197,24 @@ const bundles = [
externals: ['react'], externals: ['react'],
}, },
/******* React DOM Flight Server Relay *******/
{
bundleTypes: [FB_WWW_DEV, FB_WWW_PROD],
moduleType: RENDERER,
entry: 'react-flight-dom-relay/server',
global: 'ReactFlightDOMRelayServer',
externals: ['react', 'react-dom/server'],
},
/******* React DOM Flight Client Relay *******/
{
bundleTypes: [FB_WWW_DEV, FB_WWW_PROD],
moduleType: RENDERER,
entry: 'react-flight-dom-relay',
global: 'ReactFlightDOMRelayClient',
externals: ['react'],
},
/******* React ART *******/ /******* React ART *******/
{ {
bundleTypes: [ bundleTypes: [
@ -384,7 +402,7 @@ const bundles = [
{ {
bundleTypes: [NODE_DEV, NODE_PROD], bundleTypes: [NODE_DEV, NODE_PROD],
moduleType: RECONCILER, moduleType: RECONCILER,
entry: 'react-flight', entry: 'react-client/flight',
global: 'ReactFlightClient', global: 'ReactFlightClient',
externals: ['react'], externals: ['react'],
}, },

View File

@ -298,7 +298,7 @@ const forks = Object.freeze({
); );
}, },
'react-server/src/ReactServerHostConfig': ( 'react-server/src/ReactServerStreamConfig': (
bundleType, bundleType,
entry, entry,
dependencies, dependencies,
@ -316,11 +316,11 @@ const forks = Object.freeze({
if (!rendererInfo.isServerSupported) { if (!rendererInfo.isServerSupported) {
return null; return null;
} }
return `react-server/src/forks/ReactServerHostConfig.${rendererInfo.shortName}.js`; return `react-server/src/forks/ReactServerStreamConfig.${rendererInfo.shortName}.js`;
} }
} }
throw new Error( throw new Error(
'Expected ReactServerHostConfig to always be replaced with a shim, but ' + 'Expected ReactServerStreamConfig to always be replaced with a shim, but ' +
`found no mention of "${entry}" entry point in ./scripts/shared/inlinedHostConfigs.js. ` + `found no mention of "${entry}" entry point in ./scripts/shared/inlinedHostConfigs.js. ` +
'Did you mean to add it there to associate it with a specific renderer?' 'Did you mean to add it there to associate it with a specific renderer?'
); );
@ -354,13 +354,13 @@ const forks = Object.freeze({
); );
}, },
'react-flight/src/ReactFlightClientHostConfig': ( 'react-client/src/ReactFlightClientHostConfig': (
bundleType, bundleType,
entry, entry,
dependencies, dependencies,
moduleType moduleType
) => { ) => {
if (dependencies.indexOf('react-flight') !== -1) { if (dependencies.indexOf('react-client') !== -1) {
return null; return null;
} }
if (moduleType !== RENDERER && moduleType !== RECONCILER) { if (moduleType !== RENDERER && moduleType !== RECONCILER) {
@ -372,7 +372,7 @@ const forks = Object.freeze({
if (!rendererInfo.isServerSupported) { if (!rendererInfo.isServerSupported) {
return null; return null;
} }
return `react-flight/src/forks/ReactFlightClientHostConfig.${rendererInfo.shortName}.js`; return `react-client/src/forks/ReactFlightClientHostConfig.${rendererInfo.shortName}.js`;
} }
} }
throw new Error( throw new Error(

View File

@ -78,11 +78,18 @@ module.exports = [
isFlowTyped: true, isFlowTyped: true,
isServerSupported: false, isServerSupported: false,
}, },
{
shortName: 'dom-relay',
entryPoints: ['react-flight-dom-relay', 'react-flight-dom-relay/server'],
paths: ['react-dom', 'react-flight-dom-relay'],
isFlowTyped: true,
isServerSupported: true,
},
{ {
shortName: 'custom', shortName: 'custom',
entryPoints: [ entryPoints: [
'react-reconciler', 'react-reconciler',
'react-flight', 'react-client/flight',
'react-server', 'react-server',
'react-server/flight', 'react-server/flight',
], ],