|
7 | 7 | * @flow |
8 | 8 | */ |
9 | 9 |
|
10 | | -import {convertStringToBuffer} from 'react-server/src/ReactServerStreamConfig'; |
| 10 | +import type {Destination} from 'react-server/src/ReactServerStreamConfig'; |
11 | 11 |
|
12 | | -export function formatChunkAsString(type: string, props: Object): string { |
13 | | - let str = '<' + type + '>'; |
14 | | - if (typeof props.children === 'string') { |
15 | | - str += props.children; |
| 12 | +import { |
| 13 | + writeChunk, |
| 14 | + convertStringToBuffer, |
| 15 | +} from 'react-server/src/ReactServerStreamConfig'; |
| 16 | + |
| 17 | +import invariant from 'shared/invariant'; |
| 18 | + |
| 19 | +// Every list of children or string is null terminated. |
| 20 | +const END_TAG = 0; |
| 21 | +// Tree node tags. |
| 22 | +const INSTANCE_TAG = 1; |
| 23 | +const PLACEHOLDER_TAG = 2; |
| 24 | +const SUSPENSE_PENDING_TAG = 3; |
| 25 | +const SUSPENSE_COMPLETE_TAG = 4; |
| 26 | +const SUSPENSE_CLIENT_RENDER_TAG = 5; |
| 27 | +// Command tags. |
| 28 | +const SEGMENT_TAG = 1; |
| 29 | +const SUSPENSE_UPDATE_TO_COMPLETE_TAG = 2; |
| 30 | +const SUSPENSE_UPDATE_TO_CLIENT_RENDER_TAG = 3; |
| 31 | + |
| 32 | +const END = new Uint8Array(1); |
| 33 | +END[0] = END_TAG; |
| 34 | +const PLACEHOLDER = new Uint8Array(1); |
| 35 | +PLACEHOLDER[0] = PLACEHOLDER_TAG; |
| 36 | +const INSTANCE = new Uint8Array(1); |
| 37 | +INSTANCE[0] = INSTANCE_TAG; |
| 38 | +const SUSPENSE_PENDING = new Uint8Array(1); |
| 39 | +SUSPENSE_PENDING[0] = SUSPENSE_PENDING_TAG; |
| 40 | +const SUSPENSE_COMPLETE = new Uint8Array(1); |
| 41 | +SUSPENSE_COMPLETE[0] = SUSPENSE_COMPLETE_TAG; |
| 42 | +const SUSPENSE_CLIENT_RENDER = new Uint8Array(1); |
| 43 | +SUSPENSE_CLIENT_RENDER[0] = SUSPENSE_CLIENT_RENDER_TAG; |
| 44 | + |
| 45 | +const SEGMENT = new Uint8Array(1); |
| 46 | +SEGMENT[0] = SEGMENT_TAG; |
| 47 | +const SUSPENSE_UPDATE_TO_COMPLETE = new Uint8Array(1); |
| 48 | +SUSPENSE_UPDATE_TO_COMPLETE[0] = SUSPENSE_UPDATE_TO_COMPLETE_TAG; |
| 49 | +const SUSPENSE_UPDATE_TO_CLIENT_RENDER = new Uint8Array(1); |
| 50 | +SUSPENSE_UPDATE_TO_CLIENT_RENDER[0] = SUSPENSE_UPDATE_TO_CLIENT_RENDER_TAG; |
| 51 | + |
| 52 | +// Per response, |
| 53 | +export type ResponseState = { |
| 54 | + nextSuspenseID: number, |
| 55 | +}; |
| 56 | + |
| 57 | +// Allows us to keep track of what we've already written so we can refer back to it. |
| 58 | +export function createResponseState(): ResponseState { |
| 59 | + return { |
| 60 | + nextSuspenseID: 0, |
| 61 | + }; |
| 62 | +} |
| 63 | + |
| 64 | +// This object is used to lazily reuse the ID of the first generated node, or assign one. |
| 65 | +// This is very specific to DOM where we can't assign an ID to. |
| 66 | +export type SuspenseBoundaryID = number; |
| 67 | + |
| 68 | +export function createSuspenseBoundaryID( |
| 69 | + responseState: ResponseState, |
| 70 | +): SuspenseBoundaryID { |
| 71 | + return responseState.nextSuspenseID++; |
| 72 | +} |
| 73 | + |
| 74 | +const RAW_TEXT = convertStringToBuffer('RCTRawText'); |
| 75 | + |
| 76 | +export function pushTextInstance( |
| 77 | + target: Array<Uint8Array>, |
| 78 | + text: string, |
| 79 | +): void { |
| 80 | + target.push( |
| 81 | + INSTANCE, |
| 82 | + RAW_TEXT, // Type |
| 83 | + END, // Null terminated type string |
| 84 | + // TODO: props { text: text } |
| 85 | + END, // End of children |
| 86 | + ); |
| 87 | +} |
| 88 | + |
| 89 | +export function pushStartInstance( |
| 90 | + target: Array<Uint8Array>, |
| 91 | + type: string, |
| 92 | + props: Object, |
| 93 | +): void { |
| 94 | + target.push( |
| 95 | + INSTANCE, |
| 96 | + convertStringToBuffer(type), |
| 97 | + END, // Null terminated type string |
| 98 | + // TODO: props |
| 99 | + ); |
| 100 | +} |
| 101 | + |
| 102 | +export function pushEndInstance( |
| 103 | + target: Array<Uint8Array>, |
| 104 | + type: string, |
| 105 | + props: Object, |
| 106 | +): void { |
| 107 | + target.push(END); |
| 108 | +} |
| 109 | + |
| 110 | +// IDs are formatted as little endian Uint16 |
| 111 | +function formatID(id: number): Uint8Array { |
| 112 | + if (id > 0xffff) { |
| 113 | + invariant( |
| 114 | + false, |
| 115 | + 'More boundaries or placeholders than we expected to ever emit.', |
| 116 | + ); |
16 | 117 | } |
17 | | - str += '</' + type + '>'; |
18 | | - return str; |
| 118 | + const buffer = new Uint8Array(2); |
| 119 | + buffer[0] = (id >>> 8) & 0xff; |
| 120 | + buffer[1] = id & 0xff; |
| 121 | + return buffer; |
| 122 | +} |
| 123 | + |
| 124 | +// Structural Nodes |
| 125 | + |
| 126 | +// A placeholder is a node inside a hidden partial tree that can be filled in later, but before |
| 127 | +// display. It's never visible to users. |
| 128 | +export function writePlaceholder( |
| 129 | + destination: Destination, |
| 130 | + id: number, |
| 131 | +): boolean { |
| 132 | + writeChunk(destination, PLACEHOLDER); |
| 133 | + return writeChunk(destination, formatID(id)); |
| 134 | +} |
| 135 | + |
| 136 | +// Suspense boundaries are encoded as comments. |
| 137 | +export function writeStartCompletedSuspenseBoundary( |
| 138 | + destination: Destination, |
| 139 | + id: SuspenseBoundaryID, |
| 140 | +): boolean { |
| 141 | + writeChunk(destination, SUSPENSE_COMPLETE); |
| 142 | + return writeChunk(destination, formatID(id)); |
| 143 | +} |
| 144 | +export function writeStartPendingSuspenseBoundary( |
| 145 | + destination: Destination, |
| 146 | + id: SuspenseBoundaryID, |
| 147 | +): boolean { |
| 148 | + writeChunk(destination, SUSPENSE_PENDING); |
| 149 | + return writeChunk(destination, formatID(id)); |
| 150 | +} |
| 151 | +export function writeStartClientRenderedSuspenseBoundary( |
| 152 | + destination: Destination, |
| 153 | + id: SuspenseBoundaryID, |
| 154 | +): boolean { |
| 155 | + writeChunk(destination, SUSPENSE_CLIENT_RENDER); |
| 156 | + return writeChunk(destination, formatID(id)); |
| 157 | +} |
| 158 | +export function writeEndSuspenseBoundary(destination: Destination): boolean { |
| 159 | + return writeChunk(destination, END); |
| 160 | +} |
| 161 | + |
| 162 | +export function writeStartSegment( |
| 163 | + destination: Destination, |
| 164 | + id: number, |
| 165 | +): boolean { |
| 166 | + writeChunk(destination, SEGMENT); |
| 167 | + return writeChunk(destination, formatID(id)); |
| 168 | +} |
| 169 | +export function writeEndSegment(destination: Destination): boolean { |
| 170 | + return writeChunk(destination, END); |
| 171 | +} |
| 172 | + |
| 173 | +// Instruction Set |
| 174 | + |
| 175 | +export function writeCompletedSegmentInstruction( |
| 176 | + destination: Destination, |
| 177 | + responseState: ResponseState, |
| 178 | + contentSegmentID: number, |
| 179 | +): boolean { |
| 180 | + // We don't need to emit this. Instead the client will keep track of pending placeholders. |
| 181 | + // TODO: Returning true here is not correct. Avoid having to call this function at all. |
| 182 | + return true; |
| 183 | +} |
| 184 | + |
| 185 | +export function writeCompletedBoundaryInstruction( |
| 186 | + destination: Destination, |
| 187 | + responseState: ResponseState, |
| 188 | + boundaryID: SuspenseBoundaryID, |
| 189 | + contentSegmentID: number, |
| 190 | +): boolean { |
| 191 | + writeChunk(destination, SUSPENSE_UPDATE_TO_COMPLETE); |
| 192 | + writeChunk(destination, formatID(boundaryID)); |
| 193 | + return writeChunk(destination, formatID(contentSegmentID)); |
19 | 194 | } |
20 | 195 |
|
21 | | -export function formatChunk(type: string, props: Object): Uint8Array { |
22 | | - return convertStringToBuffer(formatChunkAsString(type, props)); |
| 196 | +export function writeClientRenderBoundaryInstruction( |
| 197 | + destination: Destination, |
| 198 | + responseState: ResponseState, |
| 199 | + boundaryID: SuspenseBoundaryID, |
| 200 | +): boolean { |
| 201 | + writeChunk(destination, SUSPENSE_UPDATE_TO_CLIENT_RENDER); |
| 202 | + return writeChunk(destination, formatID(boundaryID)); |
23 | 203 | } |
0 commit comments