Skip to content

Commit b9c4a01

Browse files
authored
Allow the streaming config to decide how to precompute or compute chunks (#21008)
Some legacy environments can not encode non-strings. Those would specify both as strings. They'll throw for binary data. Some environments have to encode strings (like web streams). Those would encode both as uint8array. Some environments (like Node) can do either. It can be beneficial to leave things as strings in case the native stream can do something smart with it.
1 parent 00d4f95 commit b9c4a01

9 files changed

+115
-70
lines changed

packages/react-dom/src/server/ReactDOMServerFormatConfig.js

+51-42
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,16 @@
77
* @flow
88
*/
99

10-
import type {Destination} from 'react-server/src/ReactServerStreamConfig';
10+
import type {
11+
Destination,
12+
Chunk,
13+
PrecomputedChunk,
14+
} from 'react-server/src/ReactServerStreamConfig';
1115

1216
import {
1317
writeChunk,
14-
convertStringToBuffer,
18+
stringToChunk,
19+
stringToPrecomputedChunk,
1520
} from 'react-server/src/ReactServerStreamConfig';
1621

1722
import escapeTextForBrowser from './escapeTextForBrowser';
@@ -55,43 +60,43 @@ function encodeHTMLTextNode(text: string): string {
5560
}
5661

5762
export function pushTextInstance(
58-
target: Array<Uint8Array>,
63+
target: Array<Chunk | PrecomputedChunk>,
5964
text: string,
6065
): void {
61-
target.push(convertStringToBuffer(encodeHTMLTextNode(text)));
66+
target.push(stringToChunk(encodeHTMLTextNode(text)));
6267
}
6368

64-
const startTag1 = convertStringToBuffer('<');
65-
const startTag2 = convertStringToBuffer('>');
69+
const startTag1 = stringToPrecomputedChunk('<');
70+
const startTag2 = stringToPrecomputedChunk('>');
6671

6772
export function pushStartInstance(
68-
target: Array<Uint8Array>,
73+
target: Array<Chunk | PrecomputedChunk>,
6974
type: string,
7075
props: Object,
7176
): void {
7277
// TODO: Figure out if it's self closing and everything else.
73-
target.push(startTag1, convertStringToBuffer(type), startTag2);
78+
target.push(startTag1, stringToChunk(type), startTag2);
7479
}
7580

76-
const endTag1 = convertStringToBuffer('</');
77-
const endTag2 = convertStringToBuffer('>');
81+
const endTag1 = stringToPrecomputedChunk('</');
82+
const endTag2 = stringToPrecomputedChunk('>');
7883

7984
export function pushEndInstance(
80-
target: Array<Uint8Array>,
85+
target: Array<Chunk | PrecomputedChunk>,
8186
type: string,
8287
props: Object,
8388
): void {
8489
// TODO: Figure out if it was self closing.
85-
target.push(endTag1, convertStringToBuffer(type), endTag2);
90+
target.push(endTag1, stringToChunk(type), endTag2);
8691
}
8792

8893
// Structural Nodes
8994

9095
// A placeholder is a node inside a hidden partial tree that can be filled in later, but before
9196
// display. It's never visible to users.
92-
const placeholder1 = convertStringToBuffer('<span id="');
93-
const placeholder2 = convertStringToBuffer('P:');
94-
const placeholder3 = convertStringToBuffer('"></span>');
97+
const placeholder1 = stringToPrecomputedChunk('<span id="');
98+
const placeholder2 = stringToPrecomputedChunk('P:');
99+
const placeholder3 = stringToPrecomputedChunk('"></span>');
95100
export function writePlaceholder(
96101
destination: Destination,
97102
id: number,
@@ -101,16 +106,18 @@ export function writePlaceholder(
101106
writeChunk(destination, placeholder1);
102107
// TODO: Use the identifierPrefix option to make the prefix configurable.
103108
writeChunk(destination, placeholder2);
104-
const formattedID = convertStringToBuffer(id.toString(16));
109+
const formattedID = stringToChunk(id.toString(16));
105110
writeChunk(destination, formattedID);
106111
return writeChunk(destination, placeholder3);
107112
}
108113

109114
// Suspense boundaries are encoded as comments.
110-
const startCompletedSuspenseBoundary = convertStringToBuffer('<!--$-->');
111-
const startPendingSuspenseBoundary = convertStringToBuffer('<!--$?-->');
112-
const startClientRenderedSuspenseBoundary = convertStringToBuffer('<!--$!-->');
113-
const endSuspenseBoundary = convertStringToBuffer('<!--/$-->');
115+
const startCompletedSuspenseBoundary = stringToPrecomputedChunk('<!--$-->');
116+
const startPendingSuspenseBoundary = stringToPrecomputedChunk('<!--$?-->');
117+
const startClientRenderedSuspenseBoundary = stringToPrecomputedChunk(
118+
'<!--$!-->',
119+
);
120+
const endSuspenseBoundary = stringToPrecomputedChunk('<!--/$-->');
114121

115122
export function writeStartCompletedSuspenseBoundary(
116123
destination: Destination,
@@ -134,10 +141,10 @@ export function writeEndSuspenseBoundary(destination: Destination): boolean {
134141
return writeChunk(destination, endSuspenseBoundary);
135142
}
136143

137-
const startSegment = convertStringToBuffer('<div hidden id="');
138-
const startSegment2 = convertStringToBuffer('S:');
139-
const startSegment3 = convertStringToBuffer('">');
140-
const endSegment = convertStringToBuffer('"></div>');
144+
const startSegment = stringToPrecomputedChunk('<div hidden id="');
145+
const startSegment2 = stringToPrecomputedChunk('S:');
146+
const startSegment3 = stringToPrecomputedChunk('">');
147+
const endSegment = stringToPrecomputedChunk('"></div>');
141148
export function writeStartSegment(
142149
destination: Destination,
143150
id: number,
@@ -146,7 +153,7 @@ export function writeStartSegment(
146153
writeChunk(destination, startSegment);
147154
// TODO: Use the identifierPrefix option to make the prefix configurable.
148155
writeChunk(destination, startSegment2);
149-
const formattedID = convertStringToBuffer(id.toString(16));
156+
const formattedID = stringToChunk(id.toString(16));
150157
writeChunk(destination, formattedID);
151158
return writeChunk(destination, startSegment3);
152159
}
@@ -276,12 +283,14 @@ const completeBoundaryFunction =
276283
const clientRenderFunction =
277284
'function $RX(b){if(b=document.getElementById(b)){do b=b.previousSibling;while(8!==b.nodeType||"$?"!==b.data);b.data="$!";b._reactRetry&&b._reactRetry()}}';
278285

279-
const completeSegmentScript1Full = convertStringToBuffer(
286+
const completeSegmentScript1Full = stringToPrecomputedChunk(
280287
'<script>' + completeSegmentFunction + ';$RS("S:',
281288
);
282-
const completeSegmentScript1Partial = convertStringToBuffer('<script>$RS("S:');
283-
const completeSegmentScript2 = convertStringToBuffer('","P:');
284-
const completeSegmentScript3 = convertStringToBuffer('")</script>');
289+
const completeSegmentScript1Partial = stringToPrecomputedChunk(
290+
'<script>$RS("S:',
291+
);
292+
const completeSegmentScript2 = stringToPrecomputedChunk('","P:');
293+
const completeSegmentScript3 = stringToPrecomputedChunk('")</script>');
285294

286295
export function writeCompletedSegmentInstruction(
287296
destination: Destination,
@@ -297,19 +306,21 @@ export function writeCompletedSegmentInstruction(
297306
writeChunk(destination, completeSegmentScript1Partial);
298307
}
299308
// TODO: Use the identifierPrefix option to make the prefix configurable.
300-
const formattedID = convertStringToBuffer(contentSegmentID.toString(16));
309+
const formattedID = stringToChunk(contentSegmentID.toString(16));
301310
writeChunk(destination, formattedID);
302311
writeChunk(destination, completeSegmentScript2);
303312
writeChunk(destination, formattedID);
304313
return writeChunk(destination, completeSegmentScript3);
305314
}
306315

307-
const completeBoundaryScript1Full = convertStringToBuffer(
316+
const completeBoundaryScript1Full = stringToPrecomputedChunk(
308317
'<script>' + completeBoundaryFunction + ';$RC("',
309318
);
310-
const completeBoundaryScript1Partial = convertStringToBuffer('<script>$RC("');
311-
const completeBoundaryScript2 = convertStringToBuffer('","S:');
312-
const completeBoundaryScript3 = convertStringToBuffer('")</script>');
319+
const completeBoundaryScript1Partial = stringToPrecomputedChunk(
320+
'<script>$RC("',
321+
);
322+
const completeBoundaryScript2 = stringToPrecomputedChunk('","S:');
323+
const completeBoundaryScript3 = stringToPrecomputedChunk('")</script>');
313324

314325
export function writeCompletedBoundaryInstruction(
315326
destination: Destination,
@@ -330,23 +341,21 @@ export function writeCompletedBoundaryInstruction(
330341
boundaryID.id !== null,
331342
'An ID must have been assigned before we can complete the boundary.',
332343
);
333-
const formattedBoundaryID = convertStringToBuffer(
344+
const formattedBoundaryID = stringToChunk(
334345
encodeHTMLIDAttribute(boundaryID.id),
335346
);
336-
const formattedContentID = convertStringToBuffer(
337-
contentSegmentID.toString(16),
338-
);
347+
const formattedContentID = stringToChunk(contentSegmentID.toString(16));
339348
writeChunk(destination, formattedBoundaryID);
340349
writeChunk(destination, completeBoundaryScript2);
341350
writeChunk(destination, formattedContentID);
342351
return writeChunk(destination, completeBoundaryScript3);
343352
}
344353

345-
const clientRenderScript1Full = convertStringToBuffer(
354+
const clientRenderScript1Full = stringToPrecomputedChunk(
346355
'<script>' + clientRenderFunction + ';$RX("',
347356
);
348-
const clientRenderScript1Partial = convertStringToBuffer('<script>$RX("');
349-
const clientRenderScript2 = convertStringToBuffer('")</script>');
357+
const clientRenderScript1Partial = stringToPrecomputedChunk('<script>$RX("');
358+
const clientRenderScript2 = stringToPrecomputedChunk('")</script>');
350359

351360
export function writeClientRenderBoundaryInstruction(
352361
destination: Destination,
@@ -365,7 +374,7 @@ export function writeClientRenderBoundaryInstruction(
365374
boundaryID.id !== null,
366375
'An ID must have been assigned before we can complete the boundary.',
367376
);
368-
const formattedBoundaryID = convertStringToBuffer(
377+
const formattedBoundaryID = stringToPrecomputedChunk(
369378
encodeHTMLIDAttribute(boundaryID.id),
370379
);
371380
writeChunk(destination, formattedBoundaryID);

packages/react-native-renderer/src/server/ReactNativeServerFormatConfig.js

+12-7
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,16 @@
77
* @flow
88
*/
99

10-
import type {Destination} from 'react-server/src/ReactServerStreamConfig';
10+
import type {
11+
Destination,
12+
Chunk,
13+
PrecomputedChunk,
14+
} from 'react-server/src/ReactServerStreamConfig';
1115

1216
import {
1317
writeChunk,
14-
convertStringToBuffer,
18+
stringToChunk,
19+
stringToPrecomputedChunk,
1520
} from 'react-server/src/ReactServerStreamConfig';
1621

1722
import invariant from 'shared/invariant';
@@ -71,10 +76,10 @@ export function createSuspenseBoundaryID(
7176
return responseState.nextSuspenseID++;
7277
}
7378

74-
const RAW_TEXT = convertStringToBuffer('RCTRawText');
79+
const RAW_TEXT = stringToPrecomputedChunk('RCTRawText');
7580

7681
export function pushTextInstance(
77-
target: Array<Uint8Array>,
82+
target: Array<Chunk | PrecomputedChunk>,
7883
text: string,
7984
): void {
8085
target.push(
@@ -87,20 +92,20 @@ export function pushTextInstance(
8792
}
8893

8994
export function pushStartInstance(
90-
target: Array<Uint8Array>,
95+
target: Array<Chunk | PrecomputedChunk>,
9196
type: string,
9297
props: Object,
9398
): void {
9499
target.push(
95100
INSTANCE,
96-
convertStringToBuffer(type),
101+
stringToChunk(type),
97102
END, // Null terminated type string
98103
// TODO: props
99104
);
100105
}
101106

102107
export function pushEndInstance(
103-
target: Array<Uint8Array>,
108+
target: Array<Chunk | PrecomputedChunk>,
104109
type: string,
105110
props: Object,
106111
): void {

packages/react-noop-renderer/src/ReactNoopFlightServer.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,18 @@ const ReactNoopFlightServer = ReactFlightServer({
2727
callback();
2828
},
2929
beginWriting(destination: Destination): void {},
30-
writeChunk(destination: Destination, buffer: Uint8Array): void {
31-
destination.push(Buffer.from((buffer: any)).toString('utf8'));
30+
writeChunk(destination: Destination, chunk: string): void {
31+
destination.push(chunk);
3232
},
3333
completeWriting(destination: Destination): void {},
3434
close(destination: Destination): void {},
3535
closeWithError(destination: Destination, error: mixed): void {},
3636
flushBuffered(destination: Destination): void {},
37-
convertStringToBuffer(content: string): Uint8Array {
38-
return Buffer.from(content, 'utf8');
37+
stringToChunk(content: string): string {
38+
return content;
39+
},
40+
stringToPrecomputedChunk(content: string): string {
41+
return content;
3942
},
4043
isModuleReference(reference: Object): boolean {
4144
return reference.$$typeof === Symbol.for('react.module.reference');

packages/react-server/src/ReactFizzServer.js

+6-2
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88
*/
99

1010
import type {Dispatcher as DispatcherType} from 'react-reconciler/src/ReactInternalTypes';
11-
import type {Destination} from './ReactServerStreamConfig';
11+
import type {
12+
Destination,
13+
Chunk,
14+
PrecomputedChunk,
15+
} from './ReactServerStreamConfig';
1216
import type {ReactNodeList} from 'shared/ReactTypes';
1317
import type {
1418
SuspenseBoundaryID,
@@ -78,7 +82,7 @@ type Segment = {
7882
parentFlushed: boolean, // typically a segment will be flushed by its parent, except if its parent was already flushed
7983
id: number, // starts as 0 and is lazily assigned if the parent flushes early
8084
+index: number, // the index within the parent's chunks or 0 at the root
81-
+chunks: Array<Uint8Array>,
85+
+chunks: Array<Chunk | PrecomputedChunk>,
8286
+children: Array<Segment>,
8387
// If this segment represents a fallback, this is the content that will replace that fallback.
8488
+boundary: null | SuspenseBoundary,

packages/react-server/src/ReactFlightServerConfigStream.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,11 @@ ByteSize
6666

6767
import type {Request, ReactModel} from 'react-server/src/ReactFlightServer';
6868

69-
import {convertStringToBuffer} from './ReactServerStreamConfig';
69+
import {stringToChunk} from './ReactServerStreamConfig';
7070

71-
export type {Destination} from './ReactServerStreamConfig';
71+
import type {Chunk} from './ReactServerStreamConfig';
7272

73-
export type Chunk = Uint8Array;
73+
export type {Destination, Chunk} from './ReactServerStreamConfig';
7474

7575
const stringify = JSON.stringify;
7676

@@ -86,7 +86,7 @@ export function processErrorChunk(
8686
): Chunk {
8787
const errorInfo = {message, stack};
8888
const row = serializeRowHeader('E', id) + stringify(errorInfo) + '\n';
89-
return convertStringToBuffer(row);
89+
return stringToChunk(row);
9090
}
9191

9292
export function processModelChunk(
@@ -96,7 +96,7 @@ export function processModelChunk(
9696
): Chunk {
9797
const json = stringify(model, request.toJSON);
9898
const row = serializeRowHeader('J', id) + json + '\n';
99-
return convertStringToBuffer(row);
99+
return stringToChunk(row);
100100
}
101101

102102
export function processModuleChunk(
@@ -106,7 +106,7 @@ export function processModuleChunk(
106106
): Chunk {
107107
const json = stringify(moduleMetaData);
108108
const row = serializeRowHeader('M', id) + json + '\n';
109-
return convertStringToBuffer(row);
109+
return stringToChunk(row);
110110
}
111111

112112
export function processSymbolChunk(
@@ -116,7 +116,7 @@ export function processSymbolChunk(
116116
): Chunk {
117117
const json = stringify(name);
118118
const row = serializeRowHeader('S', id) + json + '\n';
119-
return convertStringToBuffer(row);
119+
return stringToChunk(row);
120120
}
121121

122122
export {

packages/react-server/src/ReactServerStreamConfigBrowser.js

+10-3
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99

1010
export type Destination = ReadableStreamController;
1111

12+
export type PrecomputedChunk = Uint8Array;
13+
export type Chunk = Uint8Array;
14+
1215
export function scheduleWork(callback: () => void) {
1316
callback();
1417
}
@@ -22,9 +25,9 @@ export function beginWriting(destination: Destination) {}
2225

2326
export function writeChunk(
2427
destination: Destination,
25-
buffer: Uint8Array,
28+
chunk: PrecomputedChunk | Chunk,
2629
): boolean {
27-
destination.enqueue(buffer);
30+
destination.enqueue(chunk);
2831
return destination.desiredSize > 0;
2932
}
3033

@@ -36,7 +39,11 @@ export function close(destination: Destination) {
3639

3740
const textEncoder = new TextEncoder();
3841

39-
export function convertStringToBuffer(content: string): Uint8Array {
42+
export function stringToChunk(content: string): Chunk {
43+
return textEncoder.encode(content);
44+
}
45+
46+
export function stringToPrecomputedChunk(content: string): PrecomputedChunk {
4047
return textEncoder.encode(content);
4148
}
4249

0 commit comments

Comments
 (0)