@samitouri / QOS-React-2 / commits / 2e72ea8401

[Flight] Make byteLengthOfChunk Optional (#30130)

We use this to encode the binary length of a large string without escaping it. This is really kind of optional though. This lets a Server that can't encode strings but just pass them along able to emit RSC - albeit a less optimal format. The only build we have that does that today is react-html but the FB version of Flight had a similar constraint. It's still possible to support binary data as long as byteLengthOfBinaryChunk is implemented which doesn't require a text encoder. Many streams (including Node streams) support binary OR string chunks.

Sebastian Markbåge committed Jun 28, 2024 at 19:42 UTC 2e72ea8401df491c1c6aa7af80419af8e21d8cc6
4 files changed +34 -5
packages/react-dom-bindings/src/server/ReactDOMLegacyServerStreamConfig.js
+3 -3
@@ -66,9 +66,9 @@ export function typedArrayToBinaryChunk(
66 throw new Error('Not implemented.');
67 }
68
69 -export function byteLengthOfChunk(chunk: Chunk | PrecomputedChunk): number {
70 - throw new Error('Not implemented.');
71 -}
69 +export const byteLengthOfChunk:
70 + | null
71 + | ((chunk: Chunk | PrecomputedChunk) => number) = null;
72
73 export function byteLengthOfBinaryChunk(chunk: BinaryChunk): number {
74 throw new Error('Not implemented.');
packages/react-html/src/__tests__/ReactHTMLClient-test.js
+11
@@ -38,6 +38,17 @@ if (!__EXPERIMENTAL__) {
38 expect(html).toBe('<div>hello world</div>');
39 });
40
41 + it('should be able to render a large string', async () => {
42 + function Component() {
43 + return <div>{'hello '.repeat(200)}world</div>;
44 + }
45 +
46 + const html = await ReactHTML.renderToMarkup(
47 + React.createElement(Component),
48 + );
49 + expect(html).toBe('<div>' + ('hello '.repeat(200) + 'world') + '</div>');
50 + });
51 +
52 it('should prefix html tags with a doctype', async () => {
53 const html = await ReactHTML.renderToMarkup(
54 <html>
packages/react-html/src/__tests__/ReactHTMLServer-test.js
+12
@@ -61,6 +61,18 @@ if (!__EXPERIMENTAL__) {
61 expect(html).toBe('<div>hello world</div>');
62 });
63
64 + it('should be able to render a large string', async () => {
65 + function Component() {
66 + // We can't use JSX because that's client-JSX in our tests.
67 + return React.createElement('div', null, 'hello '.repeat(200) + 'world');
68 + }
69 +
70 + const html = await ReactHTML.renderToMarkup(
71 + React.createElement(Component),
72 + );
73 + expect(html).toBe('<div>' + ('hello '.repeat(200) + 'world') + '</div>');
74 + });
75 +
76 it('should prefix html tags with a doctype', async () => {
77 const html = await ReactHTML.renderToMarkup(
78 // We can't use JSX because that's client-JSX in our tests.
packages/react-server/src/ReactFlightServer.js
+8 -2
@@ -2541,7 +2541,7 @@ function renderModelDestructive(
2541 return serializeDateFromDateJSON(value);
2542 }
2543 }
2544 - if (value.length >= 1024) {
2544 + if (value.length >= 1024 && byteLengthOfChunk !== null) {
2545 // For large strings, we encode them outside the JSON payload so that we
2546 // don't have to double encode and double parse the strings. This can also
2547 // be more compact in case the string has a lot of escaped characters.
@@ -2892,6 +2892,12 @@ function emitTypedArrayChunk(
2892 }
2893
2894 function emitTextChunk(request: Request, id: number, text: string): void {
2895 + if (byteLengthOfChunk === null) {
2896 + // eslint-disable-next-line react-internal/prod-error-codes
2897 + throw new Error(
2898 + 'Existence of byteLengthOfChunk should have already been checked. This is a bug in React.',
2899 + );
2900 + }
2901 request.pendingChunks++; // Extra chunk for the header.
2902 const textChunk = stringToChunk(text);
2903 const binaryLength = byteLengthOfChunk(textChunk);
@@ -3289,7 +3295,7 @@ function emitChunk(
3295 const id = task.id;
3296 // For certain types we have special types, we typically outlined them but
3297 // we can emit them directly for this row instead of through an indirection.
3292 - if (typeof value === 'string') {
3298 + if (typeof value === 'string' && byteLengthOfChunk !== null) {
3299 if (enableTaint) {
3300 const tainted = TaintRegistryValues.get(value);
3301 if (tainted !== undefined) {