Files
react/packages/react-native-renderer/src/server/ReactNativeServerFormatConfig.js
T

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));
}