Skip to content

Commit ca596c6

Browse files
committed
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.
1 parent 2e4791e commit ca596c6

File tree

4 files changed

+214
-18
lines changed

4 files changed

+214
-18
lines changed

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

Lines changed: 0 additions & 6 deletions
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

Lines changed: 183 additions & 9 deletions
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;

packages/react-server/src/__tests__/ReactServer-test.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,15 @@ describe('ReactServer', () => {
2121
ReactNoopServer = require('react-noop-renderer/server');
2222
});
2323

24+
function div(...children) {
25+
children = children.map(c =>
26+
typeof c === 'string' ? {text: c, hidden: false} : c,
27+
);
28+
return {type: 'div', children, prop: undefined, hidden: false};
29+
}
30+
2431
it('can call render', () => {
2532
const result = ReactNoopServer.render(<div>hello world</div>);
26-
expect(result).toEqual([{type: 'div', props: {children: 'hello world'}}]);
33+
expect(result.root).toEqual(div('hello world'));
2734
});
2835
});

packages/react-server/src/forks/ReactServerFormatConfig.custom.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,27 @@
2525

2626
declare var $$$hostConfig: any;
2727
export opaque type Destination = mixed; // eslint-disable-line no-undef
28+
export opaque type ResponseState = mixed;
29+
export opaque type SuspenseBoundaryID = mixed;
2830

29-
export const formatChunkAsString = $$$hostConfig.formatChunkAsString;
30-
export const formatChunk = $$$hostConfig.formatChunk;
31+
export const createResponseState = $$$hostConfig.createResponseState;
32+
export const createSuspenseBoundaryID = $$$hostConfig.createSuspenseBoundaryID;
33+
export const pushTextInstance = $$$hostConfig.pushTextInstance;
34+
export const pushStartInstance = $$$hostConfig.pushStartInstance;
35+
export const pushEndInstance = $$$hostConfig.pushEndInstance;
36+
export const writePlaceholder = $$$hostConfig.writePlaceholder;
37+
export const writeStartCompletedSuspenseBoundary =
38+
$$$hostConfig.writeStartCompletedSuspenseBoundary;
39+
export const writeStartPendingSuspenseBoundary =
40+
$$$hostConfig.writeStartPendingSuspenseBoundary;
41+
export const writeStartClientRenderedSuspenseBoundary =
42+
$$$hostConfig.writeStartClientRenderedSuspenseBoundary;
43+
export const writeEndSuspenseBoundary = $$$hostConfig.writeEndSuspenseBoundary;
44+
export const writeStartSegment = $$$hostConfig.writeStartSegment;
45+
export const writeEndSegment = $$$hostConfig.writeEndSegment;
46+
export const writeCompletedSegmentInstruction =
47+
$$$hostConfig.writeCompletedSegmentInstruction;
48+
export const writeCompletedBoundaryInstruction =
49+
$$$hostConfig.writeCompletedBoundaryInstruction;
50+
export const writeClientRenderBoundaryInstruction =
51+
$$$hostConfig.writeClientRenderBoundaryInstruction;

0 commit comments

Comments
 (0)