From 35e53b4653fdfb9c1712ce4c07037f5f276b4e91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Tue, 10 Nov 2020 22:54:42 -0500 Subject: [PATCH] [Flight] Simplify Relay row protocol (#20168) * Simplify Relay protocol integration * Encode Relay rows as tuples instead of objects This is slightly more compact and more ressembles more closely the encoding we use for the raw stream protocol. --- .../src/ReactFlightDOMRelayClient.js | 19 +++++- .../ReactFlightDOMRelayClientHostConfig.js | 2 +- .../src/ReactFlightDOMRelayProtocol.js | 31 +++++++++ .../ReactFlightDOMRelayServerHostConfig.js | 65 ++++--------------- .../ReactFlightDOMRelayServerIntegration.js | 22 +------ .../ReactFlightDOMRelay-test.internal.js | 13 +--- .../src/ReactFlightNativeRelayClient.js | 19 +++++- .../ReactFlightNativeRelayClientHostConfig.js | 2 +- .../src/ReactFlightNativeRelayProtocol.js | 31 +++++++++ .../ReactFlightNativeRelayServerHostConfig.js | 65 ++++--------------- ...ReactFlightNativeRelayServerIntegration.js | 22 +------ .../ReactFlightNativeRelay-test.internal.js | 21 +----- scripts/flow/react-relay-hooks.js | 28 +------- 13 files changed, 132 insertions(+), 208 deletions(-) create mode 100644 packages/react-transport-dom-relay/src/ReactFlightDOMRelayProtocol.js create mode 100644 packages/react-transport-native-relay/src/ReactFlightNativeRelayProtocol.js diff --git a/packages/react-transport-dom-relay/src/ReactFlightDOMRelayClient.js b/packages/react-transport-dom-relay/src/ReactFlightDOMRelayClient.js index 7a5e51ff63112..b42f52097f547 100644 --- a/packages/react-transport-dom-relay/src/ReactFlightDOMRelayClient.js +++ b/packages/react-transport-dom-relay/src/ReactFlightDOMRelayClient.js @@ -7,10 +7,27 @@ * @flow */ -export { +import type {RowEncoding} from './ReactFlightDOMRelayProtocol'; + +import type {Response} from 'react-client/src/ReactFlightClient'; + +import { createResponse, resolveModel, resolveModule, resolveError, close, } from 'react-client/src/ReactFlightClient'; + +export {createResponse, close}; + +export function resolveRow(response: Response, chunk: RowEncoding): void { + if (chunk[0] === 'J') { + resolveModel(response, chunk[1], chunk[2]); + } else if (chunk[0] === 'M') { + resolveModule(response, chunk[1], chunk[2]); + } else { + // $FlowFixMe: Flow doesn't support disjoint unions on tuples. + resolveError(response, chunk[1], chunk[2].message, chunk[2].stack); + } +} diff --git a/packages/react-transport-dom-relay/src/ReactFlightDOMRelayClientHostConfig.js b/packages/react-transport-dom-relay/src/ReactFlightDOMRelayClientHostConfig.js index 1dde710b4af81..f816443781700 100644 --- a/packages/react-transport-dom-relay/src/ReactFlightDOMRelayClientHostConfig.js +++ b/packages/react-transport-dom-relay/src/ReactFlightDOMRelayClientHostConfig.js @@ -26,7 +26,7 @@ export { export type {ModuleMetaData} from 'ReactFlightDOMRelayClientIntegration'; -export opaque type UninitializedModel = JSONValue; +export type UninitializedModel = JSONValue; export type Response = ResponseBase; diff --git a/packages/react-transport-dom-relay/src/ReactFlightDOMRelayProtocol.js b/packages/react-transport-dom-relay/src/ReactFlightDOMRelayProtocol.js new file mode 100644 index 0000000000000..263161d53852d --- /dev/null +++ b/packages/react-transport-dom-relay/src/ReactFlightDOMRelayProtocol.js @@ -0,0 +1,31 @@ +/** + * 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 {ModuleMetaData} from 'ReactFlightDOMRelayServerIntegration'; + +export type JSONValue = + | string + | number + | boolean + | null + | {+[key: string]: JSONValue} + | Array; + +export type RowEncoding = + | ['J', number, JSONValue] + | ['M', number, ModuleMetaData] + | [ + 'E', + number, + { + message: string, + stack: string, + ... + }, + ]; diff --git a/packages/react-transport-dom-relay/src/ReactFlightDOMRelayServerHostConfig.js b/packages/react-transport-dom-relay/src/ReactFlightDOMRelayServerHostConfig.js index 73bc60d4eb896..d3fc134176a96 100644 --- a/packages/react-transport-dom-relay/src/ReactFlightDOMRelayServerHostConfig.js +++ b/packages/react-transport-dom-relay/src/ReactFlightDOMRelayServerHostConfig.js @@ -7,6 +7,8 @@ * @flow */ +import type {RowEncoding, JSONValue} from './ReactFlightDOMRelayProtocol'; + import type {Request, ReactModel} from 'react-server/src/ReactFlightServer'; import JSResourceReference from 'JSResourceReference'; @@ -22,9 +24,7 @@ import type { import {resolveModelToJSON} from 'react-server/src/ReactFlightServer'; import { - emitModel, - emitModule, - emitError, + emitRow, resolveModuleMetaData as resolveModuleMetaDataImpl, } from 'ReactFlightDOMRelayServerIntegration'; @@ -45,34 +45,7 @@ export function resolveModuleMetaData( return resolveModuleMetaDataImpl(config, resource); } -type JSONValue = - | string - | number - | boolean - | null - | {+[key: string]: JSONValue} - | Array; - -export type Chunk = - | { - type: 'json', - id: number, - json: JSONValue, - } - | { - type: 'module', - id: number, - json: ModuleMetaData, - } - | { - type: 'error', - id: number, - json: { - message: string, - stack: string, - ... - }, - }; +export type Chunk = RowEncoding; export function processErrorChunk( request: Request, @@ -80,14 +53,14 @@ export function processErrorChunk( message: string, stack: string, ): Chunk { - return { - type: 'error', - id: id, - json: { + return [ + 'E', + id, + { message, stack, }, - }; + ]; } function convertModelToJSON( @@ -126,11 +99,7 @@ export function processModelChunk( model: ReactModel, ): Chunk { const json = convertModelToJSON(request, {}, '', model); - return { - type: 'json', - id: id, - json: json, - }; + return ['J', id, json]; } export function processModuleChunk( @@ -139,11 +108,7 @@ export function processModuleChunk( moduleMetaData: ModuleMetaData, ): Chunk { // The moduleMetaData is already a JSON serializable value. - return { - type: 'module', - id: id, - json: moduleMetaData, - }; + return ['M', id, moduleMetaData]; } export function scheduleWork(callback: () => void) { @@ -155,13 +120,7 @@ export function flushBuffered(destination: Destination) {} export function beginWriting(destination: Destination) {} export function writeChunk(destination: Destination, chunk: Chunk): boolean { - if (chunk.type === 'json') { - emitModel(destination, chunk.id, chunk.json); - } else if (chunk.type === 'module') { - emitModule(destination, chunk.id, chunk.json); - } else { - emitError(destination, chunk.id, chunk.json.message, chunk.json.stack); - } + emitRow(destination, chunk); return true; } diff --git a/packages/react-transport-dom-relay/src/__mocks__/ReactFlightDOMRelayServerIntegration.js b/packages/react-transport-dom-relay/src/__mocks__/ReactFlightDOMRelayServerIntegration.js index 3a873ffcad392..cc755f8eaee71 100644 --- a/packages/react-transport-dom-relay/src/__mocks__/ReactFlightDOMRelayServerIntegration.js +++ b/packages/react-transport-dom-relay/src/__mocks__/ReactFlightDOMRelayServerIntegration.js @@ -8,26 +8,8 @@ 'use strict'; const ReactFlightDOMRelayServerIntegration = { - emitModel(destination, id, json) { - destination.push({ - type: 'json', - id: id, - json: json, - }); - }, - emitModule(destination, id, json) { - destination.push({ - type: 'module', - id: id, - json: json, - }); - }, - emitError(destination, id, message, stack) { - destination.push({ - type: 'error', - id: id, - json: {message, stack}, - }); + emitRow(destination, json) { + destination.push(json); }, close(destination) {}, resolveModuleMetaData(config, resource) { diff --git a/packages/react-transport-dom-relay/src/__tests__/ReactFlightDOMRelay-test.internal.js b/packages/react-transport-dom-relay/src/__tests__/ReactFlightDOMRelay-test.internal.js index 035f5c1abd0b8..092e12abac9d0 100644 --- a/packages/react-transport-dom-relay/src/__tests__/ReactFlightDOMRelay-test.internal.js +++ b/packages/react-transport-dom-relay/src/__tests__/ReactFlightDOMRelay-test.internal.js @@ -30,18 +30,7 @@ describe('ReactFlightDOMRelay', () => { const response = ReactDOMFlightRelayClient.createResponse(); for (let i = 0; i < data.length; i++) { const chunk = data[i]; - if (chunk.type === 'json') { - ReactDOMFlightRelayClient.resolveModel(response, chunk.id, chunk.json); - } else if (chunk.type === 'module') { - ReactDOMFlightRelayClient.resolveModule(response, chunk.id, chunk.json); - } else { - ReactDOMFlightRelayClient.resolveError( - response, - chunk.id, - chunk.json.message, - chunk.json.stack, - ); - } + ReactDOMFlightRelayClient.resolveRow(response, chunk); } ReactDOMFlightRelayClient.close(response); const model = response.readRoot(); diff --git a/packages/react-transport-native-relay/src/ReactFlightNativeRelayClient.js b/packages/react-transport-native-relay/src/ReactFlightNativeRelayClient.js index 7a5e51ff63112..94222c0ca7136 100644 --- a/packages/react-transport-native-relay/src/ReactFlightNativeRelayClient.js +++ b/packages/react-transport-native-relay/src/ReactFlightNativeRelayClient.js @@ -7,10 +7,27 @@ * @flow */ -export { +import type {RowEncoding} from './ReactFlightNativeRelayProtocol'; + +import type {Response} from 'react-client/src/ReactFlightClient'; + +import { createResponse, resolveModel, resolveModule, resolveError, close, } from 'react-client/src/ReactFlightClient'; + +export {createResponse, close}; + +export function resolveRow(response: Response, chunk: RowEncoding): void { + if (chunk[0] === 'J') { + resolveModel(response, chunk[1], chunk[2]); + } else if (chunk[0] === 'M') { + resolveModule(response, chunk[1], chunk[2]); + } else { + // $FlowFixMe: Flow doesn't support disjoint unions on tuples. + resolveError(response, chunk[1], chunk[2].message, chunk[2].stack); + } +} diff --git a/packages/react-transport-native-relay/src/ReactFlightNativeRelayClientHostConfig.js b/packages/react-transport-native-relay/src/ReactFlightNativeRelayClientHostConfig.js index 035dcaf9f4c19..b13d18b3c4098 100644 --- a/packages/react-transport-native-relay/src/ReactFlightNativeRelayClientHostConfig.js +++ b/packages/react-transport-native-relay/src/ReactFlightNativeRelayClientHostConfig.js @@ -26,7 +26,7 @@ export { export type {ModuleMetaData} from 'ReactFlightNativeRelayClientIntegration'; -export opaque type UninitializedModel = JSONValue; +export type UninitializedModel = JSONValue; export type Response = ResponseBase; diff --git a/packages/react-transport-native-relay/src/ReactFlightNativeRelayProtocol.js b/packages/react-transport-native-relay/src/ReactFlightNativeRelayProtocol.js new file mode 100644 index 0000000000000..5689f8c0f974c --- /dev/null +++ b/packages/react-transport-native-relay/src/ReactFlightNativeRelayProtocol.js @@ -0,0 +1,31 @@ +/** + * 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 {ModuleMetaData} from 'ReactFlightNativeRelayServerIntegration'; + +export type JSONValue = + | string + | number + | boolean + | null + | {+[key: string]: JSONValue} + | Array; + +export type RowEncoding = + | ['J', number, JSONValue] + | ['M', number, ModuleMetaData] + | [ + 'E', + number, + { + message: string, + stack: string, + ... + }, + ]; diff --git a/packages/react-transport-native-relay/src/ReactFlightNativeRelayServerHostConfig.js b/packages/react-transport-native-relay/src/ReactFlightNativeRelayServerHostConfig.js index 3992b670e1fa0..bd28ee0337521 100644 --- a/packages/react-transport-native-relay/src/ReactFlightNativeRelayServerHostConfig.js +++ b/packages/react-transport-native-relay/src/ReactFlightNativeRelayServerHostConfig.js @@ -7,6 +7,8 @@ * @flow */ +import type {RowEncoding, JSONValue} from './ReactFlightNativeRelayProtocol'; + import type {Request, ReactModel} from 'react-server/src/ReactFlightServer'; import JSResourceReferenceImpl from 'JSResourceReferenceImpl'; @@ -22,9 +24,7 @@ import type { import {resolveModelToJSON} from 'react-server/src/ReactFlightServer'; import { - emitModel, - emitModule, - emitError, + emitRow, resolveModuleMetaData as resolveModuleMetaDataImpl, } from 'ReactFlightNativeRelayServerIntegration'; @@ -45,34 +45,7 @@ export function resolveModuleMetaData( return resolveModuleMetaDataImpl(config, resource); } -type JSONValue = - | string - | number - | boolean - | null - | {+[key: string]: JSONValue} - | Array; - -export type Chunk = - | { - type: 'json', - id: number, - json: JSONValue, - } - | { - type: 'module', - id: number, - json: ModuleMetaData, - } - | { - type: 'error', - id: number, - json: { - message: string, - stack: string, - ... - }, - }; +export type Chunk = RowEncoding; export function processErrorChunk( request: Request, @@ -80,14 +53,14 @@ export function processErrorChunk( message: string, stack: string, ): Chunk { - return { - type: 'error', - id: id, - json: { + return [ + 'E', + id, + { message, stack, }, - }; + ]; } function convertModelToJSON( @@ -126,11 +99,7 @@ export function processModelChunk( model: ReactModel, ): Chunk { const json = convertModelToJSON(request, {}, '', model); - return { - type: 'json', - id: id, - json: json, - }; + return ['J', id, json]; } export function processModuleChunk( @@ -139,11 +108,7 @@ export function processModuleChunk( moduleMetaData: ModuleMetaData, ): Chunk { // The moduleMetaData is already a JSON serializable value. - return { - type: 'module', - id: id, - json: moduleMetaData, - }; + return ['M', id, moduleMetaData]; } export function scheduleWork(callback: () => void) { @@ -155,13 +120,7 @@ export function flushBuffered(destination: Destination) {} export function beginWriting(destination: Destination) {} export function writeChunk(destination: Destination, chunk: Chunk): boolean { - if (chunk.type === 'json') { - emitModel(destination, chunk.id, chunk.json); - } else if (chunk.type === 'module') { - emitModule(destination, chunk.id, chunk.json); - } else { - emitError(destination, chunk.id, chunk.json.message, chunk.json.stack); - } + emitRow(destination, chunk); return true; } diff --git a/packages/react-transport-native-relay/src/__mocks__/ReactFlightNativeRelayServerIntegration.js b/packages/react-transport-native-relay/src/__mocks__/ReactFlightNativeRelayServerIntegration.js index 197048cc50c89..eec41c718881a 100644 --- a/packages/react-transport-native-relay/src/__mocks__/ReactFlightNativeRelayServerIntegration.js +++ b/packages/react-transport-native-relay/src/__mocks__/ReactFlightNativeRelayServerIntegration.js @@ -8,26 +8,8 @@ 'use strict'; const ReactFlightNativeRelayServerIntegration = { - emitModel(destination, id, json) { - destination.push({ - type: 'json', - id: id, - json: json, - }); - }, - emitModule(destination, id, json) { - destination.push({ - type: 'module', - id: id, - json: json, - }); - }, - emitError(destination, id, message, stack) { - destination.push({ - type: 'error', - id: id, - json: {message, stack}, - }); + emitRow(destination, json) { + destination.push(json); }, close(destination) {}, resolveModuleMetaData(config, resource) { diff --git a/packages/react-transport-native-relay/src/__tests__/ReactFlightNativeRelay-test.internal.js b/packages/react-transport-native-relay/src/__tests__/ReactFlightNativeRelay-test.internal.js index 48742e6bc1cbf..cf893bca760e6 100644 --- a/packages/react-transport-native-relay/src/__tests__/ReactFlightNativeRelay-test.internal.js +++ b/packages/react-transport-native-relay/src/__tests__/ReactFlightNativeRelay-test.internal.js @@ -45,26 +45,7 @@ describe('ReactFlightNativeRelay', () => { const response = ReactNativeFlightRelayClient.createResponse(); for (let i = 0; i < data.length; i++) { const chunk = data[i]; - if (chunk.type === 'json') { - ReactNativeFlightRelayClient.resolveModel( - response, - chunk.id, - chunk.json, - ); - } else if (chunk.type === 'module') { - ReactNativeFlightRelayClient.resolveModule( - response, - chunk.id, - chunk.json, - ); - } else { - ReactNativeFlightRelayClient.resolveError( - response, - chunk.id, - chunk.json.message, - chunk.json.stack, - ); - } + ReactNativeFlightRelayClient.resolveRow(response, chunk); } ReactNativeFlightRelayClient.close(response); const model = response.readRoot(); diff --git a/scripts/flow/react-relay-hooks.js b/scripts/flow/react-relay-hooks.js index 00fdbc839741a..73eb20ef41272 100644 --- a/scripts/flow/react-relay-hooks.js +++ b/scripts/flow/react-relay-hooks.js @@ -35,22 +35,10 @@ declare module 'JSResourceReferenceImpl' { declare module 'ReactFlightDOMRelayServerIntegration' { declare export opaque type Destination; declare export opaque type BundlerConfig; - declare export function emitModel( + declare export function emitRow( destination: Destination, - id: number, json: JSONValue, ): void; - declare export function emitModule( - destination: Destination, - id: number, - json: ModuleMetaData, - ): void; - declare export function emitError( - destination: Destination, - id: number, - message: string, - stack: string, - ): void; declare export function close(destination: Destination): void; declare export type ModuleMetaData = JSONValue; @@ -76,22 +64,10 @@ declare module 'ReactFlightDOMRelayClientIntegration' { declare module 'ReactFlightNativeRelayServerIntegration' { declare export opaque type Destination; declare export opaque type BundlerConfig; - declare export function emitModel( + declare export function emitRow( destination: Destination, - id: number, json: JSONValue, ): void; - declare export function emitModule( - destination: Destination, - id: number, - json: ModuleMetaData, - ): void; - declare export function emitError( - destination: Destination, - id: number, - message: string, - stack: string, - ): void; declare export function close(destination: Destination): void; declare export type ModuleMetaData = JSONValue;