Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fizz/Flight] Allow the streaming config to decide how to precompute or compute chunks #21008

Merged
merged 1 commit into from Mar 15, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
93 changes: 51 additions & 42 deletions packages/react-dom/src/server/ReactDOMServerFormatConfig.js
Expand Up @@ -7,11 +7,16 @@
* @flow
*/

import type {Destination} from 'react-server/src/ReactServerStreamConfig';
import type {
Destination,
Chunk,
PrecomputedChunk,
} from 'react-server/src/ReactServerStreamConfig';

import {
writeChunk,
convertStringToBuffer,
stringToChunk,
stringToPrecomputedChunk,
} from 'react-server/src/ReactServerStreamConfig';

import escapeTextForBrowser from './escapeTextForBrowser';
Expand Down Expand Up @@ -55,43 +60,43 @@ function encodeHTMLTextNode(text: string): string {
}

export function pushTextInstance(
target: Array<Uint8Array>,
target: Array<Chunk | PrecomputedChunk>,
text: string,
): void {
target.push(convertStringToBuffer(encodeHTMLTextNode(text)));
target.push(stringToChunk(encodeHTMLTextNode(text)));
}

const startTag1 = convertStringToBuffer('<');
const startTag2 = convertStringToBuffer('>');
const startTag1 = stringToPrecomputedChunk('<');
const startTag2 = stringToPrecomputedChunk('>');

export function pushStartInstance(
target: Array<Uint8Array>,
target: Array<Chunk | PrecomputedChunk>,
type: string,
props: Object,
): void {
// TODO: Figure out if it's self closing and everything else.
target.push(startTag1, convertStringToBuffer(type), startTag2);
target.push(startTag1, stringToChunk(type), startTag2);
}

const endTag1 = convertStringToBuffer('</');
const endTag2 = convertStringToBuffer('>');
const endTag1 = stringToPrecomputedChunk('</');
const endTag2 = stringToPrecomputedChunk('>');

export function pushEndInstance(
target: Array<Uint8Array>,
target: Array<Chunk | PrecomputedChunk>,
type: string,
props: Object,
): void {
// TODO: Figure out if it was self closing.
target.push(endTag1, convertStringToBuffer(type), endTag2);
target.push(endTag1, stringToChunk(type), endTag2);
}

// Structural Nodes

// A placeholder is a node inside a hidden partial tree that can be filled in later, but before
// display. It's never visible to users.
const placeholder1 = convertStringToBuffer('<span id="');
const placeholder2 = convertStringToBuffer('P:');
const placeholder3 = convertStringToBuffer('"></span>');
const placeholder1 = stringToPrecomputedChunk('<span id="');
const placeholder2 = stringToPrecomputedChunk('P:');
const placeholder3 = stringToPrecomputedChunk('"></span>');
export function writePlaceholder(
destination: Destination,
id: number,
Expand All @@ -101,16 +106,18 @@ export function writePlaceholder(
writeChunk(destination, placeholder1);
// TODO: Use the identifierPrefix option to make the prefix configurable.
writeChunk(destination, placeholder2);
const formattedID = convertStringToBuffer(id.toString(16));
const formattedID = stringToChunk(id.toString(16));
writeChunk(destination, formattedID);
return writeChunk(destination, placeholder3);
}

// Suspense boundaries are encoded as comments.
const startCompletedSuspenseBoundary = convertStringToBuffer('<!--$-->');
const startPendingSuspenseBoundary = convertStringToBuffer('<!--$?-->');
const startClientRenderedSuspenseBoundary = convertStringToBuffer('<!--$!-->');
const endSuspenseBoundary = convertStringToBuffer('<!--/$-->');
const startCompletedSuspenseBoundary = stringToPrecomputedChunk('<!--$-->');
const startPendingSuspenseBoundary = stringToPrecomputedChunk('<!--$?-->');
const startClientRenderedSuspenseBoundary = stringToPrecomputedChunk(
'<!--$!-->',
);
const endSuspenseBoundary = stringToPrecomputedChunk('<!--/$-->');

export function writeStartCompletedSuspenseBoundary(
destination: Destination,
Expand All @@ -134,10 +141,10 @@ export function writeEndSuspenseBoundary(destination: Destination): boolean {
return writeChunk(destination, endSuspenseBoundary);
}

const startSegment = convertStringToBuffer('<div hidden id="');
const startSegment2 = convertStringToBuffer('S:');
const startSegment3 = convertStringToBuffer('">');
const endSegment = convertStringToBuffer('"></div>');
const startSegment = stringToPrecomputedChunk('<div hidden id="');
const startSegment2 = stringToPrecomputedChunk('S:');
const startSegment3 = stringToPrecomputedChunk('">');
const endSegment = stringToPrecomputedChunk('"></div>');
export function writeStartSegment(
destination: Destination,
id: number,
Expand All @@ -146,7 +153,7 @@ export function writeStartSegment(
writeChunk(destination, startSegment);
// TODO: Use the identifierPrefix option to make the prefix configurable.
writeChunk(destination, startSegment2);
const formattedID = convertStringToBuffer(id.toString(16));
const formattedID = stringToChunk(id.toString(16));
writeChunk(destination, formattedID);
return writeChunk(destination, startSegment3);
}
Expand Down Expand Up @@ -276,12 +283,14 @@ const completeBoundaryFunction =
const clientRenderFunction =
'function $RX(b){if(b=document.getElementById(b)){do b=b.previousSibling;while(8!==b.nodeType||"$?"!==b.data);b.data="$!";b._reactRetry&&b._reactRetry()}}';

const completeSegmentScript1Full = convertStringToBuffer(
const completeSegmentScript1Full = stringToPrecomputedChunk(
'<script>' + completeSegmentFunction + ';$RS("S:',
);
const completeSegmentScript1Partial = convertStringToBuffer('<script>$RS("S:');
const completeSegmentScript2 = convertStringToBuffer('","P:');
const completeSegmentScript3 = convertStringToBuffer('")</script>');
const completeSegmentScript1Partial = stringToPrecomputedChunk(
'<script>$RS("S:',
);
const completeSegmentScript2 = stringToPrecomputedChunk('","P:');
const completeSegmentScript3 = stringToPrecomputedChunk('")</script>');

export function writeCompletedSegmentInstruction(
destination: Destination,
Expand All @@ -297,19 +306,21 @@ export function writeCompletedSegmentInstruction(
writeChunk(destination, completeSegmentScript1Partial);
}
// TODO: Use the identifierPrefix option to make the prefix configurable.
const formattedID = convertStringToBuffer(contentSegmentID.toString(16));
const formattedID = stringToChunk(contentSegmentID.toString(16));
writeChunk(destination, formattedID);
writeChunk(destination, completeSegmentScript2);
writeChunk(destination, formattedID);
return writeChunk(destination, completeSegmentScript3);
}

const completeBoundaryScript1Full = convertStringToBuffer(
const completeBoundaryScript1Full = stringToPrecomputedChunk(
'<script>' + completeBoundaryFunction + ';$RC("',
);
const completeBoundaryScript1Partial = convertStringToBuffer('<script>$RC("');
const completeBoundaryScript2 = convertStringToBuffer('","S:');
const completeBoundaryScript3 = convertStringToBuffer('")</script>');
const completeBoundaryScript1Partial = stringToPrecomputedChunk(
'<script>$RC("',
);
const completeBoundaryScript2 = stringToPrecomputedChunk('","S:');
const completeBoundaryScript3 = stringToPrecomputedChunk('")</script>');

export function writeCompletedBoundaryInstruction(
destination: Destination,
Expand All @@ -330,23 +341,21 @@ export function writeCompletedBoundaryInstruction(
boundaryID.id !== null,
'An ID must have been assigned before we can complete the boundary.',
);
const formattedBoundaryID = convertStringToBuffer(
const formattedBoundaryID = stringToChunk(
encodeHTMLIDAttribute(boundaryID.id),
);
const formattedContentID = convertStringToBuffer(
contentSegmentID.toString(16),
);
const formattedContentID = stringToChunk(contentSegmentID.toString(16));
writeChunk(destination, formattedBoundaryID);
writeChunk(destination, completeBoundaryScript2);
writeChunk(destination, formattedContentID);
return writeChunk(destination, completeBoundaryScript3);
}

const clientRenderScript1Full = convertStringToBuffer(
const clientRenderScript1Full = stringToPrecomputedChunk(
'<script>' + clientRenderFunction + ';$RX("',
);
const clientRenderScript1Partial = convertStringToBuffer('<script>$RX("');
const clientRenderScript2 = convertStringToBuffer('")</script>');
const clientRenderScript1Partial = stringToPrecomputedChunk('<script>$RX("');
const clientRenderScript2 = stringToPrecomputedChunk('")</script>');

export function writeClientRenderBoundaryInstruction(
destination: Destination,
Expand All @@ -365,7 +374,7 @@ export function writeClientRenderBoundaryInstruction(
boundaryID.id !== null,
'An ID must have been assigned before we can complete the boundary.',
);
const formattedBoundaryID = convertStringToBuffer(
const formattedBoundaryID = stringToPrecomputedChunk(
encodeHTMLIDAttribute(boundaryID.id),
);
writeChunk(destination, formattedBoundaryID);
Expand Down
Expand Up @@ -7,11 +7,16 @@
* @flow
*/

import type {Destination} from 'react-server/src/ReactServerStreamConfig';
import type {
Destination,
Chunk,
PrecomputedChunk,
} from 'react-server/src/ReactServerStreamConfig';

import {
writeChunk,
convertStringToBuffer,
stringToChunk,
stringToPrecomputedChunk,
} from 'react-server/src/ReactServerStreamConfig';

import invariant from 'shared/invariant';
Expand Down Expand Up @@ -71,10 +76,10 @@ export function createSuspenseBoundaryID(
return responseState.nextSuspenseID++;
}

const RAW_TEXT = convertStringToBuffer('RCTRawText');
const RAW_TEXT = stringToPrecomputedChunk('RCTRawText');

export function pushTextInstance(
target: Array<Uint8Array>,
target: Array<Chunk | PrecomputedChunk>,
text: string,
): void {
target.push(
Expand All @@ -87,20 +92,20 @@ export function pushTextInstance(
}

export function pushStartInstance(
target: Array<Uint8Array>,
target: Array<Chunk | PrecomputedChunk>,
type: string,
props: Object,
): void {
target.push(
INSTANCE,
convertStringToBuffer(type),
stringToChunk(type),
END, // Null terminated type string
// TODO: props
);
}

export function pushEndInstance(
target: Array<Uint8Array>,
target: Array<Chunk | PrecomputedChunk>,
type: string,
props: Object,
): void {
Expand Down
11 changes: 7 additions & 4 deletions packages/react-noop-renderer/src/ReactNoopFlightServer.js
Expand Up @@ -27,15 +27,18 @@ const ReactNoopFlightServer = ReactFlightServer({
callback();
},
beginWriting(destination: Destination): void {},
writeChunk(destination: Destination, buffer: Uint8Array): void {
destination.push(Buffer.from((buffer: any)).toString('utf8'));
writeChunk(destination: Destination, chunk: string): void {
destination.push(chunk);
},
completeWriting(destination: Destination): void {},
close(destination: Destination): void {},
closeWithError(destination: Destination, error: mixed): void {},
flushBuffered(destination: Destination): void {},
convertStringToBuffer(content: string): Uint8Array {
return Buffer.from(content, 'utf8');
stringToChunk(content: string): string {
return content;
},
stringToPrecomputedChunk(content: string): string {
return content;
},
isModuleReference(reference: Object): boolean {
return reference.$$typeof === Symbol.for('react.module.reference');
Expand Down
8 changes: 6 additions & 2 deletions packages/react-server/src/ReactFizzServer.js
Expand Up @@ -8,7 +8,11 @@
*/

import type {Dispatcher as DispatcherType} from 'react-reconciler/src/ReactInternalTypes';
import type {Destination} from './ReactServerStreamConfig';
import type {
Destination,
Chunk,
PrecomputedChunk,
} from './ReactServerStreamConfig';
import type {ReactNodeList} from 'shared/ReactTypes';
import type {
SuspenseBoundaryID,
Expand Down Expand Up @@ -78,7 +82,7 @@ type Segment = {
parentFlushed: boolean, // typically a segment will be flushed by its parent, except if its parent was already flushed
id: number, // starts as 0 and is lazily assigned if the parent flushes early
+index: number, // the index within the parent's chunks or 0 at the root
+chunks: Array<Uint8Array>,
+chunks: Array<Chunk | PrecomputedChunk>,
+children: Array<Segment>,
// If this segment represents a fallback, this is the content that will replace that fallback.
+boundary: null | SuspenseBoundary,
Expand Down
14 changes: 7 additions & 7 deletions packages/react-server/src/ReactFlightServerConfigStream.js
Expand Up @@ -66,11 +66,11 @@ ByteSize

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

import {convertStringToBuffer} from './ReactServerStreamConfig';
import {stringToChunk} from './ReactServerStreamConfig';

export type {Destination} from './ReactServerStreamConfig';
import type {Chunk} from './ReactServerStreamConfig';

export type Chunk = Uint8Array;
export type {Destination, Chunk} from './ReactServerStreamConfig';

const stringify = JSON.stringify;

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

export function processModelChunk(
Expand All @@ -96,7 +96,7 @@ export function processModelChunk(
): Chunk {
const json = stringify(model, request.toJSON);
const row = serializeRowHeader('J', id) + json + '\n';
return convertStringToBuffer(row);
return stringToChunk(row);
}

export function processModuleChunk(
Expand All @@ -106,7 +106,7 @@ export function processModuleChunk(
): Chunk {
const json = stringify(moduleMetaData);
const row = serializeRowHeader('M', id) + json + '\n';
return convertStringToBuffer(row);
return stringToChunk(row);
}

export function processSymbolChunk(
Expand All @@ -116,7 +116,7 @@ export function processSymbolChunk(
): Chunk {
const json = stringify(name);
const row = serializeRowHeader('S', id) + json + '\n';
return convertStringToBuffer(row);
return stringToChunk(row);
}

export {
Expand Down
13 changes: 10 additions & 3 deletions packages/react-server/src/ReactServerStreamConfigBrowser.js
Expand Up @@ -9,6 +9,9 @@

export type Destination = ReadableStreamController;

export type PrecomputedChunk = Uint8Array;
export type Chunk = Uint8Array;

export function scheduleWork(callback: () => void) {
callback();
}
Expand All @@ -22,9 +25,9 @@ export function beginWriting(destination: Destination) {}

export function writeChunk(
destination: Destination,
buffer: Uint8Array,
chunk: PrecomputedChunk | Chunk,
): boolean {
destination.enqueue(buffer);
destination.enqueue(chunk);
return destination.desiredSize > 0;
}

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

const textEncoder = new TextEncoder();

export function convertStringToBuffer(content: string): Uint8Array {
export function stringToChunk(content: string): Chunk {
return textEncoder.encode(content);
}

export function stringToPrecomputedChunk(content: string): PrecomputedChunk {
return textEncoder.encode(content);
}

Expand Down