52
| {+[key: string]: JSONValue}
53
| $ReadOnlyArray<JSONValue>;
54
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
+
63
const PENDING = 'pending';
64
const BLOCKED = 'blocked';
65
const RESOLVED_MODEL = 'resolved_model';
173
_bundlerConfig: SSRManifest,
174
_callServer: CallServerCallback,
175
_chunks: Map<number, SomeChunk<any>>,
168
- _partialRow: string,
176
_fromJSON: (key: string, value: JSONValue) => any,
177
_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<Uint8Array>, // chunks received so far as part of this row
183
};
184
185
function readChunk<T>(chunk: SomeChunk<T>): T {
288
return new Chunk(RESOLVED_MODULE, value, null, response);
289
}
290
291
+function createInitializedTextChunk(
292
+ response: Response,
293
+ value: string,
294
+): InitializedChunk<string> {
295
+ // $FlowFixMe[invalid-constructor] Flow doesn't support functions as constructors
296
+ return new Chunk(INITIALIZED, value, null, response);
297
+}
298
+
299
function resolveModelChunk<T>(
300
chunk: SomeChunk<T>,
301
value: UninitializedModel,
685
_bundlerConfig: bundlerConfig,
686
_callServer: callServer !== undefined ? callServer : missingCall,
687
_chunks: chunks,
668
- _partialRow: '',
688
_stringDecoder: createStringDecoder(),
689
_fromJSON: (null: any),
690
+ _rowState: 0,
691
+ _rowID: 0,
692
+ _rowTag: 0,
693
+ _rowLength: 0,
694
+ _buffer: [],
695
};
696
// Don't inline this call because it causes closure to outline the call above.
697
response._fromJSON = createFromJSONCallback(response);
712
}
713
}
714
715
+function resolveText(response: Response, id: number, text: string): void {
716
+ const chunks = response._chunks;
717
+ // We assume that we always reference large strings after they've been
718
+ // emitted.
719
+ chunks.set(id, createInitializedTextChunk(response, text));
720
+}
721
+
722
function resolveModule(
723
response: Response,
724
id: number,
833
code: string,
834
model: UninitializedModel,
835
): void {
805
- const hintModel = parseModel<HintModel>(response, model);
836
+ const hintModel: HintModel = parseModel(response, model);
837
dispatchHint(code, hintModel);
838
}
839
809
-function processFullRow(response: Response, row: string): void {
810
- if (row === '') {
811
- return;
840
+function processFullRow(
841
+ response: Response,
842
+ id: number,
843
+ tag: number,
844
+ buffer: Array<Uint8Array>,
845
+ lastChunk: string | Uint8Array,
846
+): void {
847
+ let row = '';
848
+ const stringDecoder = response._stringDecoder;
849
+ for (let i = 0; i < buffer.length; i++) {
850
+ const chunk = buffer[i];
851
+ row += readPartialStringChunk(stringDecoder, chunk);
852
+ }
853
+ if (typeof lastChunk === 'string') {
854
+ row += lastChunk;
855
+ } else {
856
+ row += readFinalStringChunk(stringDecoder, lastChunk);
857
}
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
- // }
858
switch (tag) {
821
- case 'I': {
822
- resolveModule(response, id, row.slice(colon + 2));
859
+ case 73 /* "I" */: {
860
+ resolveModule(response, id, row);
861
return;
862
}
825
- case 'H': {
826
- const code = row[colon + 2];
827
- resolveHint(response, code, row.slice(colon + 3));
863
+ case 72 /* "H" */: {
864
+ const code = row[0];
865
+ resolveHint(response, code, row.slice(1));
866
return;
867
}
830
- case 'E': {
831
- const errorInfo = JSON.parse(row.slice(colon + 2));
868
+ case 69 /* "E" */: {
869
+ const errorInfo = JSON.parse(row);
870
if (__DEV__) {
871
resolveErrorDev(
872
response,
880
}
881
return;
882
}
883
+ case 84 /* "T" */: {
884
+ resolveText(response, id, row);
885
+ return;
886
+ }
887
default: {
888
// We assume anything else is JSON.
847
- resolveModel(response, id, row.slice(colon + 1));
889
+ resolveModel(response, id, row);
890
return;
891
}
892
}
896
response: Response,
897
chunk: Uint8Array,
898
): 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
899
+ let i = 0;
900
+ let rowState = response._rowState;
901
+ let rowID = response._rowID;
902
+ let rowTag = response._rowTag;
903
+ let rowLength = response._rowLength;
904
+ const buffer = response._buffer;
905
+ const chunkLength = chunk.length;
906
+ while (i < chunkLength) {
907
+ let lastIdx = -1;
908
+ switch (rowState) {
909
+ case ROW_ID: {
910
+ const byte = chunk[i++];
911
+ if (byte === 58 /* ":" */) {
912
+ // Finished the rowID, next we'll parse the tag.
913
+ rowState = ROW_TAG;
914
+ } else {
915
+ rowID = (rowID << 4) | (byte > 96 ? byte - 87 : byte - 48);
916
+ }
917
+ continue;
918
+ }
919
+ case ROW_TAG: {
920
+ const resolvedRowTag = chunk[i];
921
+ if (resolvedRowTag === 84 /* "T" */) {
922
+ rowTag = resolvedRowTag;
923
+ rowState = ROW_LENGTH;
924
+ i++;
925
+ } else if (resolvedRowTag > 64 && resolvedRowTag < 91 /* "A"-"Z" */) {
926
+ rowTag = resolvedRowTag;
927
+ rowState = ROW_CHUNK_BY_NEWLINE;
928
+ i++;
929
+ } else {
930
+ rowTag = 0;
931
+ rowState = ROW_CHUNK_BY_NEWLINE;
932
+ // This was an unknown tag so it was probably part of the data.
933
+ }
934
+ continue;
935
+ }
936
+ case ROW_LENGTH: {
937
+ const byte = chunk[i++];
938
+ if (byte === 44 /* "," */) {
939
+ // Finished the rowLength, next we'll buffer up to that length.
940
+ rowState = ROW_CHUNK_BY_LENGTH;
941
+ } else {
942
+ rowLength = (rowLength << 4) | (byte > 96 ? byte - 87 : byte - 48);
943
+ }
944
+ continue;
945
+ }
946
+ case ROW_CHUNK_BY_NEWLINE: {
947
+ // We're looking for a newline
948
+ lastIdx = chunk.indexOf(10 /* "\n" */, i);
949
+ break;
950
+ }
951
+ case ROW_CHUNK_BY_LENGTH: {
952
+ // We're looking for the remaining byte length
953
+ if (i + rowLength <= chunk.length) {
954
+ lastIdx = i + rowLength;
955
+ }
956
+ break;
957
+ }
958
+ }
959
+ if (lastIdx > -1) {
960
+ // We found the last chunk of the row
961
+ const offset = chunk.byteOffset + i;
962
+ const length = lastIdx - i;
963
+ const lastChunk = new Uint8Array(chunk.buffer, offset, length);
964
+ processFullRow(response, rowID, rowTag, buffer, lastChunk);
965
+ // Reset state machine for a new row
966
+ rowState = ROW_ID;
967
+ rowTag = 0;
968
+ rowID = 0;
969
+ rowLength = 0;
970
+ buffer.length = 0;
971
+ i = lastIdx + 1;
972
+ } else {
973
+ // The rest of this row is in a future chunk. We stash the rest of the
974
+ // current chunk until we can process the full row.
975
+ const offset = chunk.byteOffset + i;
976
+ const length = chunk.byteLength - i;
977
+ const remainingSlice = new Uint8Array(chunk.buffer, offset, length);
978
+ buffer.push(remainingSlice);
979
+ // Update how many bytes we're still waiting for. If we're looking for
980
+ // a newline, this doesn't hurt since we'll just ignore it.
981
+ rowLength -= remainingSlice.byteLength;
982
+ break;
983
+ }
984
}
868
- response._partialRow += readPartialStringChunk(stringDecoder, chunk);
985
+ response._rowState = rowState;
986
+ response._rowID = rowID;
987
+ response._rowTag = rowTag;
988
+ response._rowLength = rowLength;
989
}
990
991
function parseModel<T>(response: Response, json: UninitializedModel): T {