Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Flight Encoding #26082

Merged
merged 6 commits into from
Jan 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 24 additions & 47 deletions packages/react-client/src/ReactFlightClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,14 +201,6 @@ function createErrorChunk<T>(
return new Chunk(ERRORED, null, error, response);
}

function createInitializedChunk<T>(
response: Response,
value: T,
): InitializedChunk<T> {
// $FlowFixMe Flow doesn't support functions as constructors
return new Chunk(INITIALIZED, value, null, response);
}

function wakeChunk<T>(listeners: Array<(T) => mixed>, value: T): void {
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i];
Expand Down Expand Up @@ -483,14 +475,32 @@ export function parseModelString(
key: string,
value: string,
): any {
switch (value[0]) {
case '$': {
if (value === '$') {
return REACT_ELEMENT_TYPE;
} else if (value[1] === '$' || value[1] === '@') {
if (value[0] === '$') {
if (value === '$') {
// A very common symbol.
return REACT_ELEMENT_TYPE;
}
switch (value[1]) {
case '$': {
// This was an escaped string value.
return value.substring(1);
} else {
}
case 'L': {
// Lazy node
const id = parseInt(value.substring(2), 16);
const chunk = getChunk(response, id);
// We create a React.lazy wrapper around any lazy values.
// When passed into React, we'll know how to suspend on this.
return createLazyChunkWrapper(chunk);
}
case 'S': {
return Symbol.for(value.substring(2));
}
case 'P': {
return getOrCreateServerContext(value.substring(2)).Provider;
}
default: {
// We assume that anything else is a reference ID.
const id = parseInt(value.substring(1), 16);
const chunk = getChunk(response, id);
switch (chunk.status) {
Expand Down Expand Up @@ -518,13 +528,6 @@ export function parseModelString(
}
}
}
case '@': {
const id = parseInt(value.substring(1), 16);
const chunk = getChunk(response, id);
// We create a React.lazy wrapper around any lazy values.
// When passed into React, we'll know how to suspend on this.
return createLazyChunkWrapper(chunk);
}
}
return value;
}
Expand Down Expand Up @@ -566,21 +569,6 @@ export function resolveModel(
}
}

export function resolveProvider(
response: Response,
id: number,
contextName: string,
): void {
const chunks = response._chunks;
chunks.set(
id,
createInitializedChunk(
response,
getOrCreateServerContext(contextName).Provider,
),
);
}

export function resolveModule(
response: Response,
id: number,
Expand Down Expand Up @@ -626,17 +614,6 @@ export function resolveModule(
}
}

export function resolveSymbol(
response: Response,
id: number,
name: string,
): void {
const chunks = response._chunks;
// We assume that we'll always emit the symbol before anything references it
// to save a few bytes.
chunks.set(id, createInitializedChunk(response, Symbol.for(name)));
}

type ErrorWithDigest = Error & {digest?: string};
export function resolveErrorProd(
response: Response,
Expand Down
33 changes: 9 additions & 24 deletions packages/react-client/src/ReactFlightClientStream.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@ import type {BundlerConfig} from './ReactFlightClientHostConfig';
import {
resolveModule,
resolveModel,
resolveProvider,
resolveSymbol,
resolveErrorProd,
resolveErrorDev,
createResponse as createResponseBase,
Expand All @@ -36,33 +34,20 @@ function processFullRow(response: Response, row: string): void {
if (row === '') {
return;
}
const tag = row[0];
const colon = row.indexOf(':', 0);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are you leaving the position argument in place to be emphatic that it searches the whole string?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No reason really other than why not be explicit and I’m not sure this will last. Eg might be continuing from a partial string when we optimize this a bit.

const id = parseInt(row.substring(0, colon), 16);
const tag = row[colon + 1];
// When tags that are not text are added, check them here before
// parsing the row as text.
// switch (tag) {
// }
const colon = row.indexOf(':', 1);
const id = parseInt(row.substring(1, colon), 16);
const text = row.substring(colon + 1);
switch (tag) {
case 'J': {
resolveModel(response, id, text);
return;
}
case 'M': {
resolveModule(response, id, text);
return;
}
case 'P': {
resolveProvider(response, id, text);
return;
}
case 'S': {
resolveSymbol(response, id, JSON.parse(text));
case 'I': {
resolveModule(response, id, row.substring(colon + 2));
return;
}
case 'E': {
const errorInfo = JSON.parse(text);
const errorInfo = JSON.parse(row.substring(colon + 2));
if (__DEV__) {
resolveErrorDev(
response,
Expand All @@ -77,9 +62,9 @@ function processFullRow(response: Response, row: string): void {
return;
}
default: {
throw new Error(
"Error parsing the data. It's probably an error code or network corruption.",
);
// We assume anything else is JSON.
resolveModel(response, id, row.substring(colon + 1));
return;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
createResponse,
resolveModel,
resolveModule,
resolveSymbol,
resolveErrorDev,
resolveErrorProd,
close,
Expand All @@ -25,15 +24,12 @@ import {
export {createResponse, close, getRoot};

export function resolveRow(response: Response, chunk: RowEncoding): void {
if (chunk[0] === 'J') {
if (chunk[0] === 'O') {
// $FlowFixMe unable to refine on array indices
resolveModel(response, chunk[1], chunk[2]);
} else if (chunk[0] === 'M') {
} else if (chunk[0] === 'I') {
// $FlowFixMe unable to refine on array indices
resolveModule(response, chunk[1], chunk[2]);
} else if (chunk[0] === 'S') {
// $FlowFixMe: Flow doesn't support disjoint unions on tuples.
resolveSymbol(response, chunk[1], chunk[2]);
} else {
if (__DEV__) {
resolveErrorDev(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export type JSONValue =
| $ReadOnlyArray<JSONValue>;

export type RowEncoding =
| ['J', number, JSONValue]
| ['M', number, ModuleMetaData]
| ['O', number, JSONValue]
| ['I', number, ModuleMetaData]
| ['P', number, string]
| ['S', number, string]
| [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,15 +151,15 @@ export function processModelChunk(
): Chunk {
// $FlowFixMe no good way to define an empty exact object
const json = convertModelToJSON(request, {}, '', model);
return ['J', id, json];
return ['O', id, json];
}

export function processReferenceChunk(
request: Request,
id: number,
reference: string,
): Chunk {
return ['J', id, reference];
return ['O', id, reference];
}

export function processModuleChunk(
Expand All @@ -168,23 +168,7 @@ export function processModuleChunk(
moduleMetaData: ModuleMetaData,
): Chunk {
// The moduleMetaData is already a JSON serializable value.
return ['M', id, moduleMetaData];
}

export function processProviderChunk(
request: Request,
id: number,
contextName: string,
): Chunk {
return ['P', id, contextName];
}

export function processSymbolChunk(
request: Request,
id: number,
name: string,
): Chunk {
return ['S', id, name];
return ['I', id, moduleMetaData];
}

export function scheduleWork(callback: () => void) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
createResponse,
resolveModel,
resolveModule,
resolveSymbol,
resolveErrorDev,
resolveErrorProd,
close,
Expand All @@ -25,15 +24,12 @@ import {
export {createResponse, close, getRoot};

export function resolveRow(response: Response, chunk: RowEncoding): void {
if (chunk[0] === 'J') {
if (chunk[0] === 'O') {
// $FlowFixMe `Chunk` doesn't flow into `JSONValue` because of the `E` row type.
resolveModel(response, chunk[1], chunk[2]);
} else if (chunk[0] === 'M') {
} else if (chunk[0] === 'I') {
// $FlowFixMe `Chunk` doesn't flow into `JSONValue` because of the `E` row type.
resolveModule(response, chunk[1], chunk[2]);
} else if (chunk[0] === 'S') {
// $FlowFixMe: Flow doesn't support disjoint unions on tuples.
resolveSymbol(response, chunk[1], chunk[2]);
} else {
if (__DEV__) {
resolveErrorDev(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export type JSONValue =
| Array<JSONValue>;

export type RowEncoding =
| ['J', number, JSONValue]
| ['M', number, ModuleMetaData]
| ['O', number, JSONValue]
| ['I', number, ModuleMetaData]
| ['P', number, string]
| ['S', number, string]
| [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,15 @@ export function processModelChunk(
): Chunk {
// $FlowFixMe no good way to define an empty exact object
const json = convertModelToJSON(request, {}, '', model);
return ['J', id, json];
return ['O', id, json];
}

export function processReferenceChunk(
request: Request,
id: number,
reference: string,
): Chunk {
return ['J', id, reference];
return ['O', id, reference];
}

export function processModuleChunk(
Expand All @@ -163,23 +163,7 @@ export function processModuleChunk(
moduleMetaData: ModuleMetaData,
): Chunk {
// The moduleMetaData is already a JSON serializable value.
return ['M', id, moduleMetaData];
}

export function processProviderChunk(
request: Request,
id: number,
contextName: string,
): Chunk {
return ['P', id, contextName];
}

export function processSymbolChunk(
request: Request,
id: number,
name: string,
): Chunk {
return ['S', id, name];
return ['I', id, moduleMetaData];
}

export function scheduleWork(callback: () => void) {
Expand Down
20 changes: 14 additions & 6 deletions packages/react-server/src/ReactFlightServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ import {
closeWithError,
processModelChunk,
processModuleChunk,
processProviderChunk,
processSymbolChunk,
processErrorChunkProd,
processErrorChunkDev,
processReferenceChunk,
Expand Down Expand Up @@ -417,7 +415,15 @@ function serializeByValueID(id: number): string {
}

function serializeByRefID(id: number): string {
return '@' + id.toString(16);
return '$L' + id.toString(16);
}

function serializeSymbolReference(name: string): string {
return '$S' + name;
}

function serializeProviderReference(name: string): string {
return '$P' + name;
}

function serializeClientReference(
Expand Down Expand Up @@ -473,7 +479,7 @@ function serializeClientReference(
}

function escapeStringValue(value: string): string {
if (value[0] === '$' || value[0] === '@') {
if (value[0] === '$') {
// We need to escape $ or @ prefixed strings since we use those to encode
// references to IDs and as special symbol values.
return '$' + value;
Expand Down Expand Up @@ -1109,7 +1115,8 @@ function emitModuleChunk(
}

function emitSymbolChunk(request: Request, id: number, name: string): void {
const processedChunk = processSymbolChunk(request, id, name);
const symbolReference = serializeSymbolReference(name);
const processedChunk = processReferenceChunk(request, id, symbolReference);
request.completedModuleChunks.push(processedChunk);
}

Expand All @@ -1118,7 +1125,8 @@ function emitProviderChunk(
id: number,
contextName: string,
): void {
const processedChunk = processProviderChunk(request, id, contextName);
const contextReference = serializeProviderReference(contextName);
const processedChunk = processReferenceChunk(request, id, contextReference);
request.completedJSONChunks.push(processedChunk);
}

Expand Down
Loading