@samitouri / QOS-React / commits / 977bccd24d

Refactor Flight Encoding (#26082)

This is just shifting around some encoding strategies for Flight in preparation for more types. ``` S1:"react.suspense" J2:["$", "$1", {children: "@3"}] J3:"Hello" ``` ``` 1:"$Sreact.suspense" 2:["$", "$1", {children: "$L3"}] 3:"Hello" ```

Sebastian Markbåge committed Jan 31, 2023 at 12:41 UTC 977bccd24de2b062d2c114e6cf160d2bd9ed9493
10 files changed +65 -154
packages/react-client/src/ReactFlightClient.js
+24 -47
@@ -201,14 +201,6 @@ function createErrorChunk<T>(
201 return new Chunk(ERRORED, null, error, response);
202 }
203
204 -function createInitializedChunk<T>(
205 - response: Response,
206 - value: T,
207 -): InitializedChunk<T> {
208 - // $FlowFixMe Flow doesn't support functions as constructors
209 - return new Chunk(INITIALIZED, value, null, response);
210 -}
211 -
204 function wakeChunk<T>(listeners: Array<(T) => mixed>, value: T): void {
205 for (let i = 0; i < listeners.length; i++) {
206 const listener = listeners[i];
@@ -483,14 +475,32 @@ export function parseModelString(
475 key: string,
476 value: string,
477 ): any {
486 - switch (value[0]) {
487 - case '$': {
488 - if (value === '$') {
489 - return REACT_ELEMENT_TYPE;
490 - } else if (value[1] === '$' || value[1] === '@') {
478 + if (value[0] === '$') {
479 + if (value === '$') {
480 + // A very common symbol.
481 + return REACT_ELEMENT_TYPE;
482 + }
483 + switch (value[1]) {
484 + case '$': {
485 // This was an escaped string value.
486 return value.substring(1);
493 - } else {
487 + }
488 + case 'L': {
489 + // Lazy node
490 + const id = parseInt(value.substring(2), 16);
491 + const chunk = getChunk(response, id);
492 + // We create a React.lazy wrapper around any lazy values.
493 + // When passed into React, we'll know how to suspend on this.
494 + return createLazyChunkWrapper(chunk);
495 + }
496 + case 'S': {
497 + return Symbol.for(value.substring(2));
498 + }
499 + case 'P': {
500 + return getOrCreateServerContext(value.substring(2)).Provider;
501 + }
502 + default: {
503 + // We assume that anything else is a reference ID.
504 const id = parseInt(value.substring(1), 16);
505 const chunk = getChunk(response, id);
506 switch (chunk.status) {
@@ -518,13 +528,6 @@ export function parseModelString(
528 }
529 }
530 }
521 - case '@': {
522 - const id = parseInt(value.substring(1), 16);
523 - const chunk = getChunk(response, id);
524 - // We create a React.lazy wrapper around any lazy values.
525 - // When passed into React, we'll know how to suspend on this.
526 - return createLazyChunkWrapper(chunk);
527 - }
531 }
532 return value;
533 }
@@ -566,21 +569,6 @@ export function resolveModel(
569 }
570 }
571
569 -export function resolveProvider(
570 - response: Response,
571 - id: number,
572 - contextName: string,
573 -): void {
574 - const chunks = response._chunks;
575 - chunks.set(
576 - id,
577 - createInitializedChunk(
578 - response,
579 - getOrCreateServerContext(contextName).Provider,
580 - ),
581 - );
582 -}
583 -
572 export function resolveModule(
573 response: Response,
574 id: number,
@@ -626,17 +614,6 @@ export function resolveModule(
614 }
615 }
616
629 -export function resolveSymbol(
630 - response: Response,
631 - id: number,
632 - name: string,
633 -): void {
634 - const chunks = response._chunks;
635 - // We assume that we'll always emit the symbol before anything references it
636 - // to save a few bytes.
637 - chunks.set(id, createInitializedChunk(response, Symbol.for(name)));
638 -}
639 -
617 type ErrorWithDigest = Error & {digest?: string};
618 export function resolveErrorProd(
619 response: Response,
packages/react-client/src/ReactFlightClientStream.js
+9 -24
@@ -14,8 +14,6 @@ import type {BundlerConfig} from './ReactFlightClientHostConfig';
14 import {
15 resolveModule,
16 resolveModel,
17 - resolveProvider,
18 - resolveSymbol,
17 resolveErrorProd,
18 resolveErrorDev,
19 createResponse as createResponseBase,
@@ -36,33 +34,20 @@ function processFullRow(response: Response, row: string): void {
34 if (row === '') {
35 return;
36 }
39 - const tag = row[0];
37 + const colon = row.indexOf(':', 0);
38 + const id = parseInt(row.substring(0, colon), 16);
39 + const tag = row[colon + 1];
40 // When tags that are not text are added, check them here before
41 // parsing the row as text.
42 // switch (tag) {
43 // }
44 - const colon = row.indexOf(':', 1);
45 - const id = parseInt(row.substring(1, colon), 16);
46 - const text = row.substring(colon + 1);
44 switch (tag) {
48 - case 'J': {
49 - resolveModel(response, id, text);
50 - return;
51 - }
52 - case 'M': {
53 - resolveModule(response, id, text);
54 - return;
55 - }
56 - case 'P': {
57 - resolveProvider(response, id, text);
58 - return;
59 - }
60 - case 'S': {
61 - resolveSymbol(response, id, JSON.parse(text));
45 + case 'I': {
46 + resolveModule(response, id, row.substring(colon + 2));
47 return;
48 }
49 case 'E': {
65 - const errorInfo = JSON.parse(text);
50 + const errorInfo = JSON.parse(row.substring(colon + 2));
51 if (__DEV__) {
52 resolveErrorDev(
53 response,
@@ -77,9 +62,9 @@ function processFullRow(response: Response, row: string): void {
62 return;
63 }
64 default: {
80 - throw new Error(
81 - "Error parsing the data. It's probably an error code or network corruption.",
82 - );
65 + // We assume anything else is JSON.
66 + resolveModel(response, id, row.substring(colon + 1));
67 + return;
68 }
69 }
70 }
packages/react-server-dom-relay/src/ReactFlightDOMRelayClient.js
+2 -6
@@ -15,7 +15,6 @@ import {
15 createResponse,
16 resolveModel,
17 resolveModule,
18 - resolveSymbol,
18 resolveErrorDev,
19 resolveErrorProd,
20 close,
@@ -25,15 +24,12 @@ import {
24 export {createResponse, close, getRoot};
25
26 export function resolveRow(response: Response, chunk: RowEncoding): void {
28 - if (chunk[0] === 'J') {
27 + if (chunk[0] === 'O') {
28 // $FlowFixMe unable to refine on array indices
29 resolveModel(response, chunk[1], chunk[2]);
31 - } else if (chunk[0] === 'M') {
30 + } else if (chunk[0] === 'I') {
31 // $FlowFixMe unable to refine on array indices
32 resolveModule(response, chunk[1], chunk[2]);
34 - } else if (chunk[0] === 'S') {
35 - // $FlowFixMe: Flow doesn't support disjoint unions on tuples.
36 - resolveSymbol(response, chunk[1], chunk[2]);
33 } else {
34 if (__DEV__) {
35 resolveErrorDev(
packages/react-server-dom-relay/src/ReactFlightDOMRelayProtocol.js
+2 -2
@@ -18,8 +18,8 @@ export type JSONValue =
18 | $ReadOnlyArray<JSONValue>;
19
20 export type RowEncoding =
21 - | ['J', number, JSONValue]
22 - | ['M', number, ModuleMetaData]
21 + | ['O', number, JSONValue]
22 + | ['I', number, ModuleMetaData]
23 | ['P', number, string]
24 | ['S', number, string]
25 | [
packages/react-server-dom-relay/src/ReactFlightDOMRelayServerHostConfig.js
+3 -19
@@ -151,7 +151,7 @@ export function processModelChunk(
151 ): Chunk {
152 // $FlowFixMe no good way to define an empty exact object
153 const json = convertModelToJSON(request, {}, '', model);
154 - return ['J', id, json];
154 + return ['O', id, json];
155 }
156
157 export function processReferenceChunk(
@@ -159,7 +159,7 @@ export function processReferenceChunk(
159 id: number,
160 reference: string,
161 ): Chunk {
162 - return ['J', id, reference];
162 + return ['O', id, reference];
163 }
164
165 export function processModuleChunk(
@@ -168,23 +168,7 @@ export function processModuleChunk(
168 moduleMetaData: ModuleMetaData,
169 ): Chunk {
170 // The moduleMetaData is already a JSON serializable value.
171 - return ['M', id, moduleMetaData];
172 -}
173 -
174 -export function processProviderChunk(
175 - request: Request,
176 - id: number,
177 - contextName: string,
178 -): Chunk {
179 - return ['P', id, contextName];
180 -}
181 -
182 -export function processSymbolChunk(
183 - request: Request,
184 - id: number,
185 - name: string,
186 -): Chunk {
187 - return ['S', id, name];
171 + return ['I', id, moduleMetaData];
172 }
173
174 export function scheduleWork(callback: () => void) {
packages/react-server-native-relay/src/ReactFlightNativeRelayClient.js
+2 -6
@@ -15,7 +15,6 @@ import {
15 createResponse,
16 resolveModel,
17 resolveModule,
18 - resolveSymbol,
18 resolveErrorDev,
19 resolveErrorProd,
20 close,
@@ -25,15 +24,12 @@ import {
24 export {createResponse, close, getRoot};
25
26 export function resolveRow(response: Response, chunk: RowEncoding): void {
28 - if (chunk[0] === 'J') {
27 + if (chunk[0] === 'O') {
28 // $FlowFixMe `Chunk` doesn't flow into `JSONValue` because of the `E` row type.
29 resolveModel(response, chunk[1], chunk[2]);
31 - } else if (chunk[0] === 'M') {
30 + } else if (chunk[0] === 'I') {
31 // $FlowFixMe `Chunk` doesn't flow into `JSONValue` because of the `E` row type.
32 resolveModule(response, chunk[1], chunk[2]);
34 - } else if (chunk[0] === 'S') {
35 - // $FlowFixMe: Flow doesn't support disjoint unions on tuples.
36 - resolveSymbol(response, chunk[1], chunk[2]);
33 } else {
34 if (__DEV__) {
35 resolveErrorDev(
packages/react-server-native-relay/src/ReactFlightNativeRelayProtocol.js
+2 -2
@@ -18,8 +18,8 @@ export type JSONValue =
18 | Array<JSONValue>;
19
20 export type RowEncoding =
21 - | ['J', number, JSONValue]
22 - | ['M', number, ModuleMetaData]
21 + | ['O', number, JSONValue]
22 + | ['I', number, ModuleMetaData]
23 | ['P', number, string]
24 | ['S', number, string]
25 | [
packages/react-server-native-relay/src/ReactFlightNativeRelayServerHostConfig.js
+3 -19
@@ -146,7 +146,7 @@ export function processModelChunk(
146 ): Chunk {
147 // $FlowFixMe no good way to define an empty exact object
148 const json = convertModelToJSON(request, {}, '', model);
149 - return ['J', id, json];
149 + return ['O', id, json];
150 }
151
152 export function processReferenceChunk(
@@ -154,7 +154,7 @@ export function processReferenceChunk(
154 id: number,
155 reference: string,
156 ): Chunk {
157 - return ['J', id, reference];
157 + return ['O', id, reference];
158 }
159
160 export function processModuleChunk(
@@ -163,23 +163,7 @@ export function processModuleChunk(
163 moduleMetaData: ModuleMetaData,
164 ): Chunk {
165 // The moduleMetaData is already a JSON serializable value.
166 - return ['M', id, moduleMetaData];
167 -}
168 -
169 -export function processProviderChunk(
170 - request: Request,
171 - id: number,
172 - contextName: string,
173 -): Chunk {
174 - return ['P', id, contextName];
175 -}
176 -
177 -export function processSymbolChunk(
178 - request: Request,
179 - id: number,
180 - name: string,
181 -): Chunk {
182 - return ['S', id, name];
166 + return ['I', id, moduleMetaData];
167 }
168
169 export function scheduleWork(callback: () => void) {
packages/react-server/src/ReactFlightServer.js
+14 -6
@@ -38,8 +38,6 @@ import {
38 closeWithError,
39 processModelChunk,
40 processModuleChunk,
41 - processProviderChunk,
42 - processSymbolChunk,
41 processErrorChunkProd,
42 processErrorChunkDev,
43 processReferenceChunk,
@@ -417,7 +415,15 @@ function serializeByValueID(id: number): string {
415 }
416
417 function serializeByRefID(id: number): string {
420 - return '@' + id.toString(16);
418 + return '$L' + id.toString(16);
419 +}
420 +
421 +function serializeSymbolReference(name: string): string {
422 + return '$S' + name;
423 +}
424 +
425 +function serializeProviderReference(name: string): string {
426 + return '$P' + name;
427 }
428
429 function serializeClientReference(
@@ -473,7 +479,7 @@ function serializeClientReference(
479 }
480
481 function escapeStringValue(value: string): string {
476 - if (value[0] === '$' || value[0] === '@') {
482 + if (value[0] === '$') {
483 // We need to escape $ or @ prefixed strings since we use those to encode
484 // references to IDs and as special symbol values.
485 return '$' + value;
@@ -1110,7 +1116,8 @@ function emitModuleChunk(
1116 }
1117
1118 function emitSymbolChunk(request: Request, id: number, name: string): void {
1113 - const processedChunk = processSymbolChunk(request, id, name);
1119 + const symbolReference = serializeSymbolReference(name);
1120 + const processedChunk = processReferenceChunk(request, id, symbolReference);
1121 request.completedModuleChunks.push(processedChunk);
1122 }
1123
@@ -1119,7 +1126,8 @@ function emitProviderChunk(
1126 id: number,
1127 contextName: string,
1128 ): void {
1122 - const processedChunk = processProviderChunk(request, id, contextName);
1129 + const contextReference = serializeProviderReference(contextName);
1130 + const processedChunk = processReferenceChunk(request, id, contextReference);
1131 request.completedJSONChunks.push(processedChunk);
1132 }
1133
packages/react-server/src/ReactFlightServerConfigStream.js
+4 -23
@@ -80,7 +80,7 @@ export {
80 const stringify = JSON.stringify;
81
82 function serializeRowHeader(tag: string, id: number) {
83 - return tag + id.toString(16) + ':';
83 + return id.toString(16) + ':' + tag;
84 }
85
86 export function processErrorChunkProd(
@@ -127,7 +127,7 @@ export function processModelChunk(
127 model: ReactModel,
128 ): Chunk {
129 const json: string = stringify(model, request.toJSON);
130 - const row = serializeRowHeader('J', id) + json + '\n';
130 + const row = id.toString(16) + ':' + json + '\n';
131 return stringToChunk(row);
132 }
133
@@ -137,7 +137,7 @@ export function processReferenceChunk(
137 reference: string,
138 ): Chunk {
139 const json = stringify(reference);
140 - const row = serializeRowHeader('J', id) + json + '\n';
140 + const row = id.toString(16) + ':' + json + '\n';
141 return stringToChunk(row);
142 }
143
@@ -147,26 +147,7 @@ export function processModuleChunk(
147 moduleMetaData: ReactModel,
148 ): Chunk {
149 const json: string = stringify(moduleMetaData);
150 - const row = serializeRowHeader('M', id) + json + '\n';
151 - return stringToChunk(row);
152 -}
153 -
154 -export function processProviderChunk(
155 - request: Request,
156 - id: number,
157 - contextName: string,
158 -): Chunk {
159 - const row = serializeRowHeader('P', id) + contextName + '\n';
160 - return stringToChunk(row);
161 -}
162 -
163 -export function processSymbolChunk(
164 - request: Request,
165 - id: number,
166 - name: string,
167 -): Chunk {
168 - const json = stringify(name);
169 - const row = serializeRowHeader('S', id) + json + '\n';
150 + const row = serializeRowHeader('I', id) + json + '\n';
151 return stringToChunk(row);
152 }
153