mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
312 lines
8.2 KiB
JavaScript
312 lines
8.2 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
import type {ReactNodeList} from 'shared/ReactTypes';
|
|
|
|
import type {
|
|
Destination,
|
|
Chunk,
|
|
PrecomputedChunk,
|
|
} from 'react-server/src/ReactServerStreamConfig';
|
|
|
|
import {
|
|
writeChunk,
|
|
writeChunkAndReturn,
|
|
stringToChunk,
|
|
stringToPrecomputedChunk,
|
|
} from 'react-server/src/ReactServerStreamConfig';
|
|
|
|
export const isPrimaryRenderer = true;
|
|
|
|
// Every list of children or string is null terminated.
|
|
const END_TAG = 0;
|
|
// Tree node tags.
|
|
const INSTANCE_TAG = 1;
|
|
const PLACEHOLDER_TAG = 2;
|
|
const SUSPENSE_PENDING_TAG = 3;
|
|
const SUSPENSE_COMPLETE_TAG = 4;
|
|
const SUSPENSE_CLIENT_RENDER_TAG = 5;
|
|
// Command tags.
|
|
const SEGMENT_TAG = 1;
|
|
const SUSPENSE_UPDATE_TO_COMPLETE_TAG = 2;
|
|
const SUSPENSE_UPDATE_TO_CLIENT_RENDER_TAG = 3;
|
|
|
|
const END = new Uint8Array(1);
|
|
END[0] = END_TAG;
|
|
const PLACEHOLDER = new Uint8Array(1);
|
|
PLACEHOLDER[0] = PLACEHOLDER_TAG;
|
|
const INSTANCE = new Uint8Array(1);
|
|
INSTANCE[0] = INSTANCE_TAG;
|
|
const SUSPENSE_PENDING = new Uint8Array(1);
|
|
SUSPENSE_PENDING[0] = SUSPENSE_PENDING_TAG;
|
|
const SUSPENSE_COMPLETE = new Uint8Array(1);
|
|
SUSPENSE_COMPLETE[0] = SUSPENSE_COMPLETE_TAG;
|
|
const SUSPENSE_CLIENT_RENDER = new Uint8Array(1);
|
|
SUSPENSE_CLIENT_RENDER[0] = SUSPENSE_CLIENT_RENDER_TAG;
|
|
|
|
const SEGMENT = new Uint8Array(1);
|
|
SEGMENT[0] = SEGMENT_TAG;
|
|
const SUSPENSE_UPDATE_TO_COMPLETE = new Uint8Array(1);
|
|
SUSPENSE_UPDATE_TO_COMPLETE[0] = SUSPENSE_UPDATE_TO_COMPLETE_TAG;
|
|
const SUSPENSE_UPDATE_TO_CLIENT_RENDER = new Uint8Array(1);
|
|
SUSPENSE_UPDATE_TO_CLIENT_RENDER[0] = SUSPENSE_UPDATE_TO_CLIENT_RENDER_TAG;
|
|
|
|
// Per response,
|
|
export type ResponseState = {
|
|
nextSuspenseID: number,
|
|
};
|
|
|
|
// Allows us to keep track of what we've already written so we can refer back to it.
|
|
export function createResponseState(): ResponseState {
|
|
return {
|
|
nextSuspenseID: 0,
|
|
};
|
|
}
|
|
|
|
// isInAParentText
|
|
export type FormatContext = boolean;
|
|
|
|
export function createRootFormatContext(): FormatContext {
|
|
return false;
|
|
}
|
|
|
|
export function getChildFormatContext(
|
|
parentContext: FormatContext,
|
|
type: string,
|
|
props: Object,
|
|
): FormatContext {
|
|
const prevIsInAParentText = parentContext;
|
|
const isInAParentText =
|
|
type === 'AndroidTextInput' || // Android
|
|
type === 'RCTMultilineTextInputView' || // iOS
|
|
type === 'RCTSinglelineTextInputView' || // iOS
|
|
type === 'RCTText' ||
|
|
type === 'RCTVirtualText';
|
|
|
|
if (prevIsInAParentText !== isInAParentText) {
|
|
return isInAParentText;
|
|
} else {
|
|
return parentContext;
|
|
}
|
|
}
|
|
|
|
// This object is used to lazily reuse the ID of the first generated node, or assign one.
|
|
// This is very specific to DOM where we can't assign an ID to.
|
|
export type SuspenseBoundaryID = number;
|
|
|
|
export const UNINITIALIZED_SUSPENSE_BOUNDARY_ID = -1;
|
|
|
|
export function assignSuspenseBoundaryID(
|
|
responseState: ResponseState,
|
|
): SuspenseBoundaryID {
|
|
return responseState.nextSuspenseID++;
|
|
}
|
|
|
|
export function makeId(
|
|
responseState: ResponseState,
|
|
treeId: string,
|
|
localId: number,
|
|
): string {
|
|
throw new Error('Not implemented');
|
|
}
|
|
|
|
const RAW_TEXT = stringToPrecomputedChunk('RCTRawText');
|
|
|
|
export function pushTextInstance(
|
|
target: Array<Chunk | PrecomputedChunk>,
|
|
text: string,
|
|
responseState: ResponseState,
|
|
// This Renderer does not use this argument
|
|
textEmbedded: boolean,
|
|
): boolean {
|
|
target.push(
|
|
INSTANCE,
|
|
RAW_TEXT, // Type
|
|
END, // Null terminated type string
|
|
// TODO: props { text: text }
|
|
END, // End of children
|
|
);
|
|
return false;
|
|
}
|
|
|
|
export function pushStartInstance(
|
|
target: Array<Chunk | PrecomputedChunk>,
|
|
preamble: Array<Chunk | PrecomputedChunk>,
|
|
type: string,
|
|
props: Object,
|
|
responseState: ResponseState,
|
|
formatContext: FormatContext,
|
|
): ReactNodeList {
|
|
target.push(
|
|
INSTANCE,
|
|
stringToChunk(type),
|
|
END, // Null terminated type string
|
|
// TODO: props
|
|
);
|
|
return props.children;
|
|
}
|
|
|
|
export function pushEndInstance(
|
|
target: Array<Chunk | PrecomputedChunk>,
|
|
postamble: Array<Chunk | PrecomputedChunk>,
|
|
type: string,
|
|
props: Object,
|
|
): void {
|
|
target.push(END);
|
|
}
|
|
|
|
// In this Renderer this is a noop
|
|
export function pushSegmentFinale(
|
|
target: Array<Chunk | PrecomputedChunk>,
|
|
responseState: ResponseState,
|
|
lastPushedText: boolean,
|
|
textEmbedded: boolean,
|
|
): void {}
|
|
|
|
export function writeCompletedRoot(
|
|
destination: Destination,
|
|
responseState: ResponseState,
|
|
): boolean {
|
|
return true;
|
|
}
|
|
|
|
// IDs are formatted as little endian Uint16
|
|
function formatID(id: number): Uint8Array {
|
|
if (id > 0xffff) {
|
|
throw new Error(
|
|
'More boundaries or placeholders than we expected to ever emit.',
|
|
);
|
|
}
|
|
const buffer = new Uint8Array(2);
|
|
buffer[0] = (id >>> 8) & 0xff;
|
|
buffer[1] = id & 0xff;
|
|
return buffer;
|
|
}
|
|
|
|
// 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.
|
|
export function writePlaceholder(
|
|
destination: Destination,
|
|
responseState: ResponseState,
|
|
id: number,
|
|
): boolean {
|
|
writeChunk(destination, PLACEHOLDER);
|
|
return writeChunkAndReturn(destination, formatID(id));
|
|
}
|
|
|
|
// Suspense boundaries are encoded as comments.
|
|
export function writeStartCompletedSuspenseBoundary(
|
|
destination: Destination,
|
|
responseState: ResponseState,
|
|
): boolean {
|
|
return writeChunkAndReturn(destination, SUSPENSE_COMPLETE);
|
|
}
|
|
|
|
export function pushStartCompletedSuspenseBoundary(
|
|
target: Array<Chunk | PrecomputedChunk>,
|
|
): void {
|
|
target.push(SUSPENSE_COMPLETE);
|
|
}
|
|
|
|
export function writeStartPendingSuspenseBoundary(
|
|
destination: Destination,
|
|
responseState: ResponseState,
|
|
id: SuspenseBoundaryID,
|
|
): boolean {
|
|
writeChunk(destination, SUSPENSE_PENDING);
|
|
return writeChunkAndReturn(destination, formatID(id));
|
|
}
|
|
export function writeStartClientRenderedSuspenseBoundary(
|
|
destination: Destination,
|
|
responseState: ResponseState,
|
|
// TODO: encode error for native
|
|
errorDigest: ?string,
|
|
errorMessage: ?string,
|
|
errorComponentStack: ?string,
|
|
): boolean {
|
|
return writeChunkAndReturn(destination, SUSPENSE_CLIENT_RENDER);
|
|
}
|
|
export function writeEndCompletedSuspenseBoundary(
|
|
destination: Destination,
|
|
responseState: ResponseState,
|
|
): boolean {
|
|
return writeChunkAndReturn(destination, END);
|
|
}
|
|
export function pushEndCompletedSuspenseBoundary(
|
|
target: Array<Chunk | PrecomputedChunk>,
|
|
): void {
|
|
target.push(END);
|
|
}
|
|
export function writeEndPendingSuspenseBoundary(
|
|
destination: Destination,
|
|
responseState: ResponseState,
|
|
): boolean {
|
|
return writeChunkAndReturn(destination, END);
|
|
}
|
|
export function writeEndClientRenderedSuspenseBoundary(
|
|
destination: Destination,
|
|
responseState: ResponseState,
|
|
): boolean {
|
|
return writeChunkAndReturn(destination, END);
|
|
}
|
|
|
|
export function writeStartSegment(
|
|
destination: Destination,
|
|
responseState: ResponseState,
|
|
formatContext: FormatContext,
|
|
id: number,
|
|
): boolean {
|
|
writeChunk(destination, SEGMENT);
|
|
return writeChunkAndReturn(destination, formatID(id));
|
|
}
|
|
export function writeEndSegment(
|
|
destination: Destination,
|
|
formatContext: FormatContext,
|
|
): boolean {
|
|
return writeChunkAndReturn(destination, END);
|
|
}
|
|
|
|
// Instruction Set
|
|
|
|
export function writeCompletedSegmentInstruction(
|
|
destination: Destination,
|
|
responseState: ResponseState,
|
|
contentSegmentID: number,
|
|
): boolean {
|
|
// We don't need to emit this. Instead the client will keep track of pending placeholders.
|
|
// TODO: Returning true here is not correct. Avoid having to call this function at all.
|
|
return true;
|
|
}
|
|
|
|
export function writeCompletedBoundaryInstruction(
|
|
destination: Destination,
|
|
responseState: ResponseState,
|
|
boundaryID: SuspenseBoundaryID,
|
|
contentSegmentID: number,
|
|
): boolean {
|
|
writeChunk(destination, SUSPENSE_UPDATE_TO_COMPLETE);
|
|
writeChunk(destination, formatID(boundaryID));
|
|
return writeChunkAndReturn(destination, formatID(contentSegmentID));
|
|
}
|
|
|
|
export function writeClientRenderBoundaryInstruction(
|
|
destination: Destination,
|
|
responseState: ResponseState,
|
|
boundaryID: SuspenseBoundaryID,
|
|
// TODO: encode error for native
|
|
errorDigest: ?string,
|
|
errorMessage: ?string,
|
|
errorComponentStack: ?string,
|
|
): boolean {
|
|
writeChunk(destination, SUSPENSE_UPDATE_TO_CLIENT_RENDER);
|
|
return writeChunkAndReturn(destination, formatID(boundaryID));
|
|
}
|