Skip to content

Commit 10cc400

Browse files
authoredMar 11, 2021
Basic Fizz Architecture (#20970)
* Copy some infra structure patterns from Flight * Basic data structures * Move structural nodes and instruction commands to host config * Move instruction command to host config In the DOM this is implemented as script tags. The first time it's emitted it includes the function. Future calls invoke the same function. The side of the complete boundary function in particular is unfortunately large. * Implement Fizz Noop host configs This is implemented not as a serialized protocol but by-passing the serialization when possible and instead it's like a live tree being built. * Implement React Native host config This is not wired up. I just need something for the flow types since Flight and Fizz are both handled by the isServerSupported flag. Might as well add something though. The principle of this format is the same structure as for HTML but a simpler binary format. Each entry is a tag followed by some data and terminated by null. * Check in error codes * Comment
1 parent bd245c1 commit 10cc400

File tree

8 files changed

+1562
-69
lines changed

8 files changed

+1562
-69
lines changed
 

‎packages/react-noop-renderer/src/ReactNoopFlightServer.js

-6
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,6 @@ const ReactNoopFlightServer = ReactFlightServer({
3636
convertStringToBuffer(content: string): Uint8Array {
3737
return Buffer.from(content, 'utf8');
3838
},
39-
formatChunkAsString(type: string, props: Object): string {
40-
return JSON.stringify({type, props});
41-
},
42-
formatChunk(type: string, props: Object): Uint8Array {
43-
return Buffer.from(JSON.stringify({type, props}), 'utf8');
44-
},
4539
isModuleReference(reference: Object): boolean {
4640
return reference.$$typeof === Symbol.for('react.module.reference');
4741
},

‎packages/react-noop-renderer/src/ReactNoopServer.js

+183-9
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,206 @@
1616

1717
import ReactFizzServer from 'react-server';
1818

19-
type Destination = Array<string>;
19+
type Instance = {|
20+
type: string,
21+
children: Array<Instance | TextInstance | SuspenseInstance>,
22+
prop: any,
23+
hidden: boolean,
24+
|};
25+
26+
type TextInstance = {|
27+
text: string,
28+
hidden: boolean,
29+
|};
30+
31+
type SuspenseInstance = {|
32+
state: 'pending' | 'complete' | 'client-render',
33+
children: Array<Instance | TextInstance | SuspenseInstance>,
34+
|};
35+
36+
type Placeholder = {
37+
parent: Instance | SuspenseInstance,
38+
index: number,
39+
};
40+
41+
type Segment = {
42+
children: null | Instance | TextInstance | SuspenseInstance,
43+
};
44+
45+
type Destination = {
46+
root: null | Instance | TextInstance | SuspenseInstance,
47+
placeholders: Map<number, Placeholder>,
48+
segments: Map<number, Segment>,
49+
stack: Array<Segment | Instance | SuspenseInstance>,
50+
};
51+
52+
const POP = Buffer.from('/', 'utf8');
2053

2154
const ReactNoopServer = ReactFizzServer({
2255
scheduleWork(callback: () => void) {
2356
callback();
2457
},
2558
beginWriting(destination: Destination): void {},
2659
writeChunk(destination: Destination, buffer: Uint8Array): void {
27-
destination.push(JSON.parse(Buffer.from((buffer: any)).toString('utf8')));
60+
const stack = destination.stack;
61+
if (buffer === POP) {
62+
stack.pop();
63+
return;
64+
}
65+
// We assume one chunk is one instance.
66+
const instance = JSON.parse(Buffer.from((buffer: any)).toString('utf8'));
67+
if (stack.length === 0) {
68+
destination.root = instance;
69+
} else {
70+
const parent = stack[stack.length - 1];
71+
parent.children.push(instance);
72+
}
73+
stack.push(instance);
2874
},
2975
completeWriting(destination: Destination): void {},
3076
close(destination: Destination): void {},
3177
flushBuffered(destination: Destination): void {},
32-
convertStringToBuffer(content: string): Uint8Array {
33-
return Buffer.from(content, 'utf8');
78+
79+
createResponseState(): null {
80+
return null;
3481
},
35-
formatChunkAsString(type: string, props: Object): string {
36-
return JSON.stringify({type, props});
82+
createSuspenseBoundaryID(): SuspenseInstance {
83+
// The ID is a pointer to the boundary itself.
84+
return {state: 'pending', children: []};
85+
},
86+
87+
pushTextInstance(target: Array<Uint8Array>, text: string): void {
88+
const textInstance: TextInstance = {
89+
text,
90+
hidden: false,
91+
};
92+
target.push(Buffer.from(JSON.stringify(textInstance), 'utf8'), POP);
93+
},
94+
pushStartInstance(
95+
target: Array<Uint8Array>,
96+
type: string,
97+
props: Object,
98+
): void {
99+
const instance: Instance = {
100+
type: type,
101+
children: [],
102+
prop: props.prop,
103+
hidden: false,
104+
};
105+
target.push(Buffer.from(JSON.stringify(instance), 'utf8'));
106+
},
107+
108+
pushEndInstance(
109+
target: Array<Uint8Array>,
110+
type: string,
111+
props: Object,
112+
): void {
113+
target.push(POP);
114+
},
115+
116+
writePlaceholder(destination: Destination, id: number): boolean {
117+
const parent = destination.stack[destination.stack.length - 1];
118+
destination.placeholders.set(id, {
119+
parent: parent,
120+
index: parent.children.length,
121+
});
37122
},
38-
formatChunk(type: string, props: Object): Uint8Array {
39-
return Buffer.from(JSON.stringify({type, props}), 'utf8');
123+
124+
writeStartCompletedSuspenseBoundary(
125+
destination: Destination,
126+
suspenseInstance: SuspenseInstance,
127+
): boolean {
128+
suspenseInstance.state = 'complete';
129+
const parent = destination.stack[destination.stack.length - 1];
130+
parent.children.push(suspenseInstance);
131+
destination.stack.push(suspenseInstance);
132+
},
133+
writeStartPendingSuspenseBoundary(
134+
destination: Destination,
135+
suspenseInstance: SuspenseInstance,
136+
): boolean {
137+
suspenseInstance.state = 'pending';
138+
const parent = destination.stack[destination.stack.length - 1];
139+
parent.children.push(suspenseInstance);
140+
destination.stack.push(suspenseInstance);
141+
},
142+
writeStartClientRenderedSuspenseBoundary(
143+
destination: Destination,
144+
suspenseInstance: SuspenseInstance,
145+
): boolean {
146+
suspenseInstance.state = 'client-render';
147+
const parent = destination.stack[destination.stack.length - 1];
148+
parent.children.push(suspenseInstance);
149+
destination.stack.push(suspenseInstance);
150+
},
151+
writeEndSuspenseBoundary(destination: Destination): boolean {
152+
destination.stack.pop();
153+
},
154+
155+
writeStartSegment(destination: Destination, id: number): boolean {
156+
const segment = {
157+
children: [],
158+
};
159+
destination.segments.set(id, segment);
160+
if (destination.stack.length > 0) {
161+
throw new Error('Segments are only expected at the root of the stack.');
162+
}
163+
destination.stack.push(segment);
164+
},
165+
writeEndSegment(destination: Destination): boolean {
166+
destination.stack.pop();
167+
},
168+
169+
writeCompletedSegmentInstruction(
170+
destination: Destination,
171+
responseState: ResponseState,
172+
contentSegmentID: number,
173+
): boolean {
174+
const segment = destination.segments.get(contentSegmentID);
175+
if (!segment) {
176+
throw new Error('Missing segment.');
177+
}
178+
const placeholder = destination.placeholders.get(contentSegmentID);
179+
if (!placeholder) {
180+
throw new Error('Missing placeholder.');
181+
}
182+
placeholder.parent.children.splice(
183+
placeholder.index,
184+
0,
185+
...segment.children,
186+
);
187+
},
188+
189+
writeCompletedBoundaryInstruction(
190+
destination: Destination,
191+
responseState: ResponseState,
192+
boundary: SuspenseInstance,
193+
contentSegmentID: number,
194+
): boolean {
195+
const segment = destination.segments.get(contentSegmentID);
196+
if (!segment) {
197+
throw new Error('Missing segment.');
198+
}
199+
boundary.children = segment.children;
200+
boundary.state = 'complete';
201+
},
202+
203+
writeClientRenderBoundaryInstruction(
204+
destination: Destination,
205+
responseState: ResponseState,
206+
boundary: SuspenseInstance,
207+
): boolean {
208+
boundary.status = 'client-render';
40209
},
41210
});
42211

43212
function render(children: React$Element<any>): Destination {
44-
const destination: Destination = [];
213+
const destination: Destination = {
214+
root: null,
215+
placeholders: new Map(),
216+
segments: new Map(),
217+
stack: [],
218+
};
45219
const request = ReactNoopServer.createRequest(children, destination);
46220
ReactNoopServer.startWork(request);
47221
return destination;

0 commit comments

Comments
 (0)