Skip to content

Commit 194d544

Browse files
committed
Parse chunks with a running parser
1 parent ce6842d commit 194d544

File tree

1 file changed

+117
-31
lines changed

1 file changed

+117
-31
lines changed

packages/react-client/src/ReactFlightClient.js

Lines changed: 117 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,14 @@ export type JSONValue =
5252
| {+[key: string]: JSONValue}
5353
| $ReadOnlyArray<JSONValue>;
5454

55+
const ROW_ID = 0;
56+
const ROW_TAG = 1;
57+
const ROW_LENGTH = 2;
58+
const ROW_CHUNK_BY_NEWLINE = 3;
59+
const ROW_CHUNK_BY_LENGTH = 4;
60+
61+
type RowParserState = 0 | 1 | 2 | 3 | 4;
62+
5563
const PENDING = 'pending';
5664
const BLOCKED = 'blocked';
5765
const RESOLVED_MODEL = 'resolved_model';
@@ -165,9 +173,13 @@ export type Response = {
165173
_bundlerConfig: SSRManifest,
166174
_callServer: CallServerCallback,
167175
_chunks: Map<number, SomeChunk<any>>,
168-
_partialRow: string,
169176
_fromJSON: (key: string, value: JSONValue) => any,
170177
_stringDecoder: StringDecoder,
178+
_rowState: RowParserState,
179+
_rowID: number, // parts of a row ID parsed so far
180+
_rowTag: number, // 0 indicates that we're currently parsing the row ID
181+
_rowLength: number, // remaining bytes in the row. 0 indicates that we're looking for a newline.
182+
_buffer: Array<string | Uint8Array>, // chunks received so far as part of this row
171183
};
172184

173185
function readChunk<T>(chunk: SomeChunk<T>): T {
@@ -665,9 +677,13 @@ export function createResponse(
665677
_bundlerConfig: bundlerConfig,
666678
_callServer: callServer !== undefined ? callServer : missingCall,
667679
_chunks: chunks,
668-
_partialRow: '',
669680
_stringDecoder: createStringDecoder(),
670681
_fromJSON: (null: any),
682+
_rowState: 0,
683+
_rowID: 0,
684+
_rowTag: 0,
685+
_rowLength: 0,
686+
_buffer: [],
671687
};
672688
// Don't inline this call because it causes closure to outline the call above.
673689
response._fromJSON = createFromJSONCallback(response);
@@ -806,29 +822,40 @@ function resolveHint(
806822
dispatchHint(code, hintModel);
807823
}
808824

809-
function processFullRow(response: Response, row: string): void {
810-
if (row === '') {
811-
return;
825+
function processFullRow(
826+
response: Response,
827+
id: number,
828+
tag: number,
829+
buffer: Array<string | Uint8Array>,
830+
lastChunk: string | Uint8Array,
831+
): void {
832+
let row = '';
833+
const stringDecoder = response._stringDecoder;
834+
for (let i = 0; i < buffer.length; i++) {
835+
const chunk = buffer[i];
836+
if (typeof chunk === 'string') {
837+
row += chunk;
838+
} else {
839+
row += readPartialStringChunk(stringDecoder, chunk);
840+
}
841+
}
842+
if (typeof lastChunk === 'string') {
843+
row += lastChunk;
844+
} else {
845+
row += readFinalStringChunk(stringDecoder, lastChunk);
812846
}
813-
const colon = row.indexOf(':', 0);
814-
const id = parseInt(row.slice(0, colon), 16);
815-
const tag = row[colon + 1];
816-
// When tags that are not text are added, check them here before
817-
// parsing the row as text.
818-
// switch (tag) {
819-
// }
820847
switch (tag) {
821-
case 'I': {
822-
resolveModule(response, id, row.slice(colon + 2));
848+
case 73 /* "I" */: {
849+
resolveModule(response, id, row);
823850
return;
824851
}
825-
case 'H': {
826-
const code = row[colon + 2];
827-
resolveHint(response, code, row.slice(colon + 3));
852+
case 72 /* "H" */: {
853+
const code = row[0];
854+
resolveHint(response, code, row.slice(1));
828855
return;
829856
}
830-
case 'E': {
831-
const errorInfo = JSON.parse(row.slice(colon + 2));
857+
case 69 /* "E" */: {
858+
const errorInfo = JSON.parse(row);
832859
if (__DEV__) {
833860
resolveErrorDev(
834861
response,
@@ -844,7 +871,7 @@ function processFullRow(response: Response, row: string): void {
844871
}
845872
default: {
846873
// We assume anything else is JSON.
847-
resolveModel(response, id, row.slice(colon + 1));
874+
resolveModel(response, id, row);
848875
return;
849876
}
850877
}
@@ -854,18 +881,77 @@ export function processBinaryChunk(
854881
response: Response,
855882
chunk: Uint8Array,
856883
): void {
857-
const stringDecoder = response._stringDecoder;
858-
let linebreak = chunk.indexOf(10); // newline
859-
while (linebreak > -1) {
860-
const fullrow =
861-
response._partialRow +
862-
readFinalStringChunk(stringDecoder, chunk.subarray(0, linebreak));
863-
processFullRow(response, fullrow);
864-
response._partialRow = '';
865-
chunk = chunk.subarray(linebreak + 1);
866-
linebreak = chunk.indexOf(10); // newline
884+
let i = 0;
885+
while (i < chunk.length) {
886+
let lastIdx = -1;
887+
switch (response._rowState) {
888+
case ROW_ID: {
889+
const byte = chunk[i++];
890+
if (byte === 58 /* ":" */) {
891+
// Finished the rowID, next we'll parse the tag.
892+
response._rowState = ROW_TAG;
893+
} else {
894+
response._rowID =
895+
(response._rowID << 4) | (byte > 96 ? byte - 87 : byte - 48);
896+
}
897+
continue;
898+
}
899+
case ROW_TAG: {
900+
const resolvedRowTag = chunk[i];
901+
if (resolvedRowTag > 64 && resolvedRowTag < 91) {
902+
response._rowTag = resolvedRowTag;
903+
i++;
904+
} else {
905+
// This was an unknown tag so it was probably part of the data.
906+
}
907+
response._rowState = ROW_CHUNK_BY_NEWLINE;
908+
continue;
909+
}
910+
case ROW_LENGTH: {
911+
// TODO
912+
continue;
913+
}
914+
case ROW_CHUNK_BY_NEWLINE: {
915+
// We're looking for a newline
916+
lastIdx = chunk.indexOf(10 /* "\n" */, i);
917+
break;
918+
}
919+
case ROW_CHUNK_BY_LENGTH: {
920+
// We're looking for the remaining byte length
921+
const rowLength = response._rowLength;
922+
if (i + rowLength <= chunk.length) {
923+
lastIdx = i + rowLength;
924+
}
925+
break;
926+
}
927+
}
928+
if (lastIdx > -1) {
929+
// We found the last chunk of the row
930+
const lastChunk = chunk.slice(i, lastIdx);
931+
processFullRow(
932+
response,
933+
response._rowID,
934+
response._rowTag,
935+
response._buffer,
936+
lastChunk,
937+
);
938+
// Reset state machine for a new row
939+
response._rowState = ROW_ID;
940+
response._rowTag = 0;
941+
response._rowID = 0;
942+
response._rowLength = 0;
943+
response._buffer.length = 0;
944+
i = lastIdx + 1;
945+
} else {
946+
// The rest of this row is in a future chunk. We stash the rest of the
947+
// current chunk until we can process the full row.
948+
const remainingSlice = chunk.slice(i);
949+
response._buffer.push(remainingSlice);
950+
// Update how many bytes we're still waiting for.
951+
response._rowLength -= remainingSlice.length;
952+
break;
953+
}
867954
}
868-
response._partialRow += readPartialStringChunk(stringDecoder, chunk);
869955
}
870956

871957
function parseModel<T>(response: Response, json: UninitializedModel): T {

0 commit comments

Comments
 (0)