[Fizz] Expose maxBoundarySize as an option (#21029)
* Expose maxBoundarySize as an option * Adjust the heuristic * Rename to progressiveChunkSize
Sebastian Markbåge committed
Mar 19, 2021 at 12:41 UTC
f6bc9c8243a02fc7a3a399845dabac3dc15dd7e7
4 files changed
+47
-7
packages/react-dom/src/server/ReactDOMFizzServerBrowser.js
+6
-1
@@ -18,6 +18,7 @@ import {
18
19
type Options = {
20
signal?: AbortSignal,
21
+ progressiveChunkSize?: number,
22
};
23
24
function renderToReadableStream(
@@ -35,7 +36,11 @@ function renderToReadableStream(
36
}
37
return new ReadableStream({
38
start(controller) {
38
- request = createRequest(children, controller);
39
+ request = createRequest(
40
+ children,
41
+ controller,
42
+ options ? options.progressiveChunkSize : undefined,
43
+ );
44
startWork(request);
45
},
46
pull(controller) {
packages/react-dom/src/server/ReactDOMFizzServerNode.js
+10
-1
@@ -21,6 +21,10 @@ function createDrainHandler(destination, request) {
21
return () => startFlowing(request);
22
}
23
24
+type Options = {
25
+ progressiveChunkSize?: number,
26
+};
27
+
28
type Controls = {
29
// Cancel any pending I/O and put anything remaining into
30
// client rendered mode.
@@ -30,8 +34,13 @@ type Controls = {
34
function pipeToNodeWritable(
35
children: ReactNodeList,
36
destination: Writable,
37
+ options?: Options,
38
): Controls {
34
- const request = createRequest(children, destination);
39
+ const request = createRequest(
40
+ children,
41
+ destination,
42
+ options ? options.progressiveChunkSize : undefined,
43
+ );
44
let hasStartedFlowing = false;
45
startWork(request);
46
return {
packages/react-noop-renderer/src/ReactNoopServer.js
+10
-2
@@ -210,7 +210,11 @@ const ReactNoopServer = ReactFizzServer({
210
},
211
});
212
213
-function render(children: React$Element<any>): Destination {
213
+type Options = {
214
+ progressiveChunkSize?: number,
215
+};
216
+
217
+function render(children: React$Element<any>, options?: Options): Destination {
218
const destination: Destination = {
219
root: null,
220
placeholders: new Map(),
@@ -220,7 +224,11 @@ function render(children: React$Element<any>): Destination {
224
ReactNoopServer.abort(request);
225
},
226
};
223
- const request = ReactNoopServer.createRequest(children, destination);
227
+ const request = ReactNoopServer.createRequest(
228
+ children,
229
+ destination,
230
+ options ? options.progressiveChunkSize : undefined,
231
+ );
232
ReactNoopServer.startWork(request);
233
ReactNoopServer.startFlowing(request);
234
return destination;
packages/react-server/src/ReactFizzServer.js
+21
-3
@@ -99,7 +99,7 @@ const CLOSED = 2;
99
type Request = {
100
+destination: Destination,
101
+responseState: ResponseState,
102
- +maxBoundarySize: number,
102
+ +progressiveChunkSize: number,
103
status: 0 | 1 | 2,
104
nextSegmentId: number,
105
allPendingWork: number, // when it reaches zero, we can close the connection.
@@ -113,16 +113,34 @@ type Request = {
113
partialBoundaries: Array<SuspenseBoundary>, // Partially completed boundaries that can flush its segments early.
114
};
115
116
+// This is a default heuristic for how to split up the HTML content into progressive
117
+// loading. Our goal is to be able to display additional new content about every 500ms.
118
+// Faster than that is unnecessary and should be throttled on the client. It also
119
+// adds unnecessary overhead to do more splits. We don't know if it's a higher or lower
120
+// end device but higher end suffer less from the overhead than lower end does from
121
+// not getting small enough pieces. We error on the side of low end.
122
+// We base this on low end 3G speeds which is about 500kbits per second. We assume
123
+// that there can be a reasonable drop off from max bandwidth which leaves you with
124
+// as little as 80%. We can receive half of that each 500ms - at best. In practice,
125
+// a little bandwidth is lost to processing and contention - e.g. CSS and images that
126
+// are downloaded along with the main content. So we estimate about half of that to be
127
+// the lower end throughput. In other words, we expect that you can at least show
128
+// about 12.5kb of content per 500ms. Not counting starting latency for the first
129
+// paint.
130
+// 500 * 1024 / 8 * .8 * 0.5 / 2
131
+const DEFAULT_PROGRESSIVE_CHUNK_SIZE = 12800;
132
+
133
export function createRequest(
134
children: ReactNodeList,
135
destination: Destination,
136
+ progressiveChunkSize: number = DEFAULT_PROGRESSIVE_CHUNK_SIZE,
137
): Request {
138
const pingedWork = [];
139
const abortSet: Set<SuspendedWork> = new Set();
140
const request = {
141
destination,
142
responseState: createResponseState(),
125
- maxBoundarySize: 1024,
143
+ progressiveChunkSize,
144
status: BUFFERING,
145
nextSegmentId: 0,
146
allPendingWork: 0,
@@ -642,7 +660,7 @@ function flushSegment(
660
flushSubtree(request, destination, segment);
661
662
return writeEndSuspenseBoundary(destination);
645
- } else if (boundary.byteSize > request.maxBoundarySize) {
663
+ } else if (boundary.byteSize > request.progressiveChunkSize) {
664
// This boundary is large and will be emitted separately so that we can progressively show
665
// other content. We add it to the queue during the flush because we have to ensure that
666
// the parent flushes first so that there's something to inject it into.