Skip to content

Commit

Permalink
[Flight] Make byteLengthOfChunk Optional (facebook#30130)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
sebmarkbage authored Jun 28, 2024
1 parent 58af67a commit 2e72ea8
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.');
Expand Down
11 changes: 11 additions & 0 deletions packages/react-html/src/__tests__/ReactHTMLClient-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,17 @@ if (!__EXPERIMENTAL__) {
expect(html).toBe('<div>hello world</div>');
});

it('should be able to render a large string', async () => {
function Component() {
return <div>{'hello '.repeat(200)}world</div>;
}

const html = await ReactHTML.renderToMarkup(
React.createElement(Component),
);
expect(html).toBe('<div>' + ('hello '.repeat(200) + 'world') + '</div>');
});

it('should prefix html tags with a doctype', async () => {
const html = await ReactHTML.renderToMarkup(
<html>
Expand Down
12 changes: 12 additions & 0 deletions packages/react-html/src/__tests__/ReactHTMLServer-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@ if (!__EXPERIMENTAL__) {
expect(html).toBe('<div>hello world</div>');
});

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('<div>' + ('hello '.repeat(200) + 'world') + '</div>');
});

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.
Expand Down
10 changes: 8 additions & 2 deletions packages/react-server/src/ReactFlightServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit 2e72ea8

Please sign in to comment.