From 67bab16b464f111634fb20590feaee63aeaedcf1 Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Fri, 28 Jun 2024 11:17:40 -0400 Subject: [PATCH 1/2] react-html should be able to render large strings Currently they are outlined with byte length. --- .../react-html/src/__tests__/ReactHTMLClient-test.js | 11 +++++++++++ .../react-html/src/__tests__/ReactHTMLServer-test.js | 12 ++++++++++++ 2 files changed, 23 insertions(+) diff --git a/packages/react-html/src/__tests__/ReactHTMLClient-test.js b/packages/react-html/src/__tests__/ReactHTMLClient-test.js index 92fb762c8ca1b..02cef97c2d82f 100644 --- a/packages/react-html/src/__tests__/ReactHTMLClient-test.js +++ b/packages/react-html/src/__tests__/ReactHTMLClient-test.js @@ -38,6 +38,17 @@ if (!__EXPERIMENTAL__) { expect(html).toBe('
hello world
'); }); + it('should be able to render a large string', async () => { + function Component() { + return
{'hello '.repeat(200)}world
; + } + + const html = await ReactHTML.renderToMarkup( + React.createElement(Component), + ); + expect(html).toBe('
' + ('hello '.repeat(200) + 'world') + '
'); + }); + it('should prefix html tags with a doctype', async () => { const html = await ReactHTML.renderToMarkup( diff --git a/packages/react-html/src/__tests__/ReactHTMLServer-test.js b/packages/react-html/src/__tests__/ReactHTMLServer-test.js index f97b5aa92d60e..236c2b0f4021c 100644 --- a/packages/react-html/src/__tests__/ReactHTMLServer-test.js +++ b/packages/react-html/src/__tests__/ReactHTMLServer-test.js @@ -61,6 +61,18 @@ if (!__EXPERIMENTAL__) { expect(html).toBe('
hello world
'); }); + it('should be able to render a large string', async () => { + function Component() { + // We can't use JSX because that's client-JSX in our tests. + return React.createElement('div', null, 'hello '.repeat(200) + 'world'); + } + + const html = await ReactHTML.renderToMarkup( + React.createElement(Component), + ); + expect(html).toBe('
' + ('hello '.repeat(200) + 'world') + '
'); + }); + it('should prefix html tags with a doctype', async () => { const html = await ReactHTML.renderToMarkup( // We can't use JSX because that's client-JSX in our tests. From 1285d5cfd7b9edbb061e5229de77d2d6cc106def Mon Sep 17 00:00:00 2001 From: Sebastian Markbage Date: Fri, 28 Jun 2024 11:23:42 -0400 Subject: [PATCH 2/2] Make byteLengthOfChunk Optional 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. --- .../src/server/ReactDOMLegacyServerStreamConfig.js | 6 +++--- packages/react-server/src/ReactFlightServer.js | 10 ++++++++-- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/react-dom-bindings/src/server/ReactDOMLegacyServerStreamConfig.js b/packages/react-dom-bindings/src/server/ReactDOMLegacyServerStreamConfig.js index 4b940731b99b0..b9b5d741d796a 100644 --- a/packages/react-dom-bindings/src/server/ReactDOMLegacyServerStreamConfig.js +++ b/packages/react-dom-bindings/src/server/ReactDOMLegacyServerStreamConfig.js @@ -66,9 +66,9 @@ export function typedArrayToBinaryChunk( throw new Error('Not implemented.'); } -export function byteLengthOfChunk(chunk: Chunk | PrecomputedChunk): number { - throw new Error('Not implemented.'); -} +export const byteLengthOfChunk: + | null + | ((chunk: Chunk | PrecomputedChunk) => number) = null; export function byteLengthOfBinaryChunk(chunk: BinaryChunk): number { throw new Error('Not implemented.'); diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index 9987c689f2aa8..7ff1e47897299 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -2541,7 +2541,7 @@ function renderModelDestructive( return serializeDateFromDateJSON(value); } } - if (value.length >= 1024) { + if (value.length >= 1024 && byteLengthOfChunk !== null) { // For large strings, we encode them outside the JSON payload so that we // don't have to double encode and double parse the strings. This can also // be more compact in case the string has a lot of escaped characters. @@ -2892,6 +2892,12 @@ function emitTypedArrayChunk( } function emitTextChunk(request: Request, id: number, text: string): void { + if (byteLengthOfChunk === null) { + // eslint-disable-next-line react-internal/prod-error-codes + throw new Error( + 'Existence of byteLengthOfChunk should have already been checked. This is a bug in React.', + ); + } request.pendingChunks++; // Extra chunk for the header. const textChunk = stringToChunk(text); const binaryLength = byteLengthOfChunk(textChunk); @@ -3289,7 +3295,7 @@ function emitChunk( const id = task.id; // For certain types we have special types, we typically outlined them but // we can emit them directly for this row instead of through an indirection. - if (typeof value === 'string') { + if (typeof value === 'string' && byteLengthOfChunk !== null) { if (enableTaint) { const tainted = TaintRegistryValues.get(value); if (tainted !== undefined) {