[Fizz] Split createRequest into createRequest, createPrerenderRequest and resumeRequest (#27342)
Just moving some internal code around again. I originally encoded what type of work using startRender vs startPrerender. I had intended to do more forking of the work loop but we've decided not to go with that strategy. It also turns out that forking when we start working is actually too late because of a subtle thing where you can call abort before work begins. Therefore it's important that starting the work comes later.
Sebastian Markbåge committed
Sep 7, 2023 at 00:18 UTC
953cb02f6de2a9f3eb52456b263e11f839584bc6
18 files changed
+150
-59
packages/react-dom/src/server/ReactDOMFizzServerBrowser.js
+6
-7
@@ -16,7 +16,8 @@ import ReactVersion from 'shared/ReactVersion';
16
17
import {
18
createRequest,
19
- startRender,
19
+ resumeRequest,
20
+ startWork,
21
startFlowing,
22
abort,
23
} from 'react-server/src/ReactFizzServer';
@@ -129,7 +130,7 @@ function renderToReadableStream(
130
signal.addEventListener('abort', listener);
131
}
132
}
132
- startRender(request);
133
+ startWork(request);
134
});
135
}
136
@@ -171,16 +172,14 @@ function resume(
172
allReady.catch(() => {});
173
reject(error);
174
}
174
- const request = createRequest(
175
+ const request = resumeRequest(
176
children,
176
- postponedState.resumableState,
177
+ postponedState,
178
createRenderState(
179
postponedState.resumableState,
180
options ? options.nonce : undefined,
181
undefined, // importMap
182
),
182
- postponedState.rootFormatContext,
183
- postponedState.progressiveChunkSize,
183
options ? options.onError : undefined,
184
onAllReady,
185
onShellReady,
@@ -200,7 +199,7 @@ function resume(
199
signal.addEventListener('abort', listener);
200
}
201
}
203
- startRender(request);
202
+ startWork(request);
203
});
204
}
205
packages/react-dom/src/server/ReactDOMFizzServerBun.js
+2
-2
@@ -15,7 +15,7 @@ import ReactVersion from 'shared/ReactVersion';
15
16
import {
17
createRequest,
18
- startRender,
18
+ startWork,
19
startFlowing,
20
abort,
21
} from 'react-server/src/ReactFizzServer';
@@ -121,7 +121,7 @@ function renderToReadableStream(
121
signal.addEventListener('abort', listener);
122
}
123
}
124
- startRender(request);
124
+ startWork(request);
125
});
126
}
127
packages/react-dom/src/server/ReactDOMFizzServerEdge.js
+6
-7
@@ -16,7 +16,8 @@ import ReactVersion from 'shared/ReactVersion';
16
17
import {
18
createRequest,
19
- startRender,
19
+ resumeRequest,
20
+ startWork,
21
startFlowing,
22
abort,
23
} from 'react-server/src/ReactFizzServer';
@@ -129,7 +130,7 @@ function renderToReadableStream(
130
signal.addEventListener('abort', listener);
131
}
132
}
132
- startRender(request);
133
+ startWork(request);
134
});
135
}
136
@@ -171,16 +172,14 @@ function resume(
172
allReady.catch(() => {});
173
reject(error);
174
}
174
- const request = createRequest(
175
+ const request = resumeRequest(
176
children,
176
- postponedState.resumableState,
177
+ postponedState,
178
createRenderState(
179
postponedState.resumableState,
180
options ? options.nonce : undefined,
181
undefined, // importMap
182
),
182
- postponedState.rootFormatContext,
183
- postponedState.progressiveChunkSize,
183
options ? options.onError : undefined,
184
onAllReady,
185
onShellReady,
@@ -200,7 +199,7 @@ function resume(
199
signal.addEventListener('abort', listener);
200
}
201
}
203
- startRender(request);
202
+ startWork(request);
203
});
204
}
205
packages/react-dom/src/server/ReactDOMFizzServerNode.js
+6
-7
@@ -18,7 +18,8 @@ import ReactVersion from 'shared/ReactVersion';
18
19
import {
20
createRequest,
21
- startRender,
21
+ resumeRequest,
22
+ startWork,
23
startFlowing,
24
abort,
25
} from 'react-server/src/ReactFizzServer';
@@ -105,7 +106,7 @@ function renderToPipeableStream(
106
): PipeableStream {
107
const request = createRequestImpl(children, options);
108
let hasStartedFlowing = false;
108
- startRender(request);
109
+ startWork(request);
110
return {
111
pipe<T: Writable>(destination: T): T {
112
if (hasStartedFlowing) {
@@ -140,16 +141,14 @@ function resumeRequestImpl(
141
postponedState: PostponedState,
142
options: void | ResumeOptions,
143
) {
143
- return createRequest(
144
+ return resumeRequest(
145
children,
145
- postponedState.resumableState,
146
+ postponedState,
147
createRenderState(
148
postponedState.resumableState,
149
options ? options.nonce : undefined,
150
undefined, // importMap
151
),
151
- postponedState.rootFormatContext,
152
- postponedState.progressiveChunkSize,
152
options ? options.onError : undefined,
153
options ? options.onAllReady : undefined,
154
options ? options.onShellReady : undefined,
@@ -166,7 +165,7 @@ function resumeToPipeableStream(
165
): PipeableStream {
166
const request = resumeRequestImpl(children, postponedState, options);
167
let hasStartedFlowing = false;
169
- startRender(request);
168
+ startWork(request);
169
return {
170
pipe<T: Writable>(destination: T): T {
171
if (hasStartedFlowing) {
packages/react-dom/src/server/ReactDOMFizzStaticBrowser.js
+4
-4
@@ -15,8 +15,8 @@ import type {ImportMap} from '../shared/ReactDOMTypes';
15
import ReactVersion from 'shared/ReactVersion';
16
17
import {
18
- createRequest,
19
- startPrerender,
18
+ createPrerenderRequest,
19
+ startWork,
20
startFlowing,
21
abort,
22
getPostponedState,
@@ -80,7 +80,7 @@ function prerender(
80
options ? options.bootstrapModules : undefined,
81
options ? options.unstable_externalRuntimeSrc : undefined,
82
);
83
- const request = createRequest(
83
+ const request = createPrerenderRequest(
84
children,
85
resources,
86
createRenderState(
@@ -109,7 +109,7 @@ function prerender(
109
signal.addEventListener('abort', listener);
110
}
111
}
112
- startPrerender(request);
112
+ startWork(request);
113
});
114
}
115
packages/react-dom/src/server/ReactDOMFizzStaticEdge.js
+4
-4
@@ -15,8 +15,8 @@ import type {ImportMap} from '../shared/ReactDOMTypes';
15
import ReactVersion from 'shared/ReactVersion';
16
17
import {
18
- createRequest,
19
- startPrerender,
18
+ createPrerenderRequest,
19
+ startWork,
20
startFlowing,
21
abort,
22
getPostponedState,
@@ -80,7 +80,7 @@ function prerender(
80
options ? options.bootstrapModules : undefined,
81
options ? options.unstable_externalRuntimeSrc : undefined,
82
);
83
- const request = createRequest(
83
+ const request = createPrerenderRequest(
84
children,
85
resources,
86
createRenderState(
@@ -109,7 +109,7 @@ function prerender(
109
signal.addEventListener('abort', listener);
110
}
111
}
112
- startPrerender(request);
112
+ startWork(request);
113
});
114
}
115
packages/react-dom/src/server/ReactDOMFizzStaticNode.js
+4
-4
@@ -17,8 +17,8 @@ import {Writable, Readable} from 'stream';
17
import ReactVersion from 'shared/ReactVersion';
18
19
import {
20
- createRequest,
21
- startPrerender,
20
+ createPrerenderRequest,
21
+ startWork,
22
startFlowing,
23
abort,
24
getPostponedState,
@@ -94,7 +94,7 @@ function prerenderToNodeStream(
94
options ? options.bootstrapModules : undefined,
95
options ? options.unstable_externalRuntimeSrc : undefined,
96
);
97
- const request = createRequest(
97
+ const request = createPrerenderRequest(
98
children,
99
resumableState,
100
createRenderState(
@@ -123,7 +123,7 @@ function prerenderToNodeStream(
123
signal.addEventListener('abort', listener);
124
}
125
}
126
- startPrerender(request);
126
+ startWork(request);
127
});
128
}
129
packages/react-dom/src/server/ReactDOMLegacyServerImpl.js
+2
-2
@@ -13,7 +13,7 @@ import type {ReactNodeList} from 'shared/ReactTypes';
13
14
import {
15
createRequest,
16
- startRender,
16
+ startWork,
17
startFlowing,
18
abort,
19
} from 'react-server/src/ReactFizzServer';
@@ -81,7 +81,7 @@ function renderToStringImpl(
81
undefined,
82
undefined,
83
);
84
- startRender(request);
84
+ startWork(request);
85
// If anything suspended and is still pending, we'll abort it before writing.
86
// That way we write only client-rendered boundaries from the start.
87
abort(request, abortReason);
packages/react-dom/src/server/ReactDOMLegacyServerNodeStream.js
+2
-2
@@ -13,7 +13,7 @@ import type {Request} from 'react-server/src/ReactFizzServer';
13
14
import {
15
createRequest,
16
- startRender,
16
+ startWork,
17
startFlowing,
18
abort,
19
} from 'react-server/src/ReactFizzServer';
@@ -92,7 +92,7 @@ function renderToNodeStreamImpl(
92
undefined,
93
);
94
destination.request = request;
95
- startRender(request);
95
+ startWork(request);
96
return destination;
97
}
98
packages/react-noop-renderer/src/ReactNoopFlightServer.js
+1
-1
@@ -84,7 +84,7 @@ function render(model: ReactClientValue, options?: Options): Destination {
84
options ? options.context : undefined,
85
options ? options.identifierPrefix : undefined,
86
);
87
- ReactNoopFlightServer.startRender(request);
87
+ ReactNoopFlightServer.startWork(request);
88
ReactNoopFlightServer.startFlowing(request, destination);
89
return destination;
90
}
packages/react-noop-renderer/src/ReactNoopServer.js
+1
-1
@@ -304,7 +304,7 @@ function render(children: React$Element<any>, options?: Options): Destination {
304
options ? options.onAllReady : undefined,
305
options ? options.onShellReady : undefined,
306
);
307
- ReactNoopServer.startRender(request);
307
+ ReactNoopServer.startWork(request);
308
ReactNoopServer.startFlowing(request, destination);
309
return destination;
310
}
packages/react-server-dom-esm/src/ReactFlightDOMServerNode.js
+2
-2
@@ -20,7 +20,7 @@ import type {ServerContextJSONValue, Thenable} from 'shared/ReactTypes';
20
21
import {
22
createRequest,
23
- startRender,
23
+ startWork,
24
startFlowing,
25
abort,
26
} from 'react-server/src/ReactFlightServer';
@@ -73,7 +73,7 @@ function renderToPipeableStream(
73
options ? options.onPostpone : undefined,
74
);
75
let hasStartedFlowing = false;
76
- startRender(request);
76
+ startWork(request);
77
return {
78
pipe<T: Writable>(destination: T): T {
79
if (hasStartedFlowing) {
packages/react-server-dom-fb/src/ReactDOMServerFB.js
+2
-2
@@ -16,7 +16,7 @@ import type {BootstrapScriptDescriptor} from 'react-dom-bindings/src/server/Reac
16
17
import {
18
createRequest,
19
- startRender,
19
+ startWork,
20
performWork,
21
startFlowing,
22
abort,
@@ -68,7 +68,7 @@ function renderToStream(children: ReactNodeList, options: Options): Stream {
68
undefined,
69
undefined,
70
);
71
- startRender(request);
71
+ startWork(request);
72
if (destination.fatal) {
73
throw destination.error;
74
}
packages/react-server-dom-webpack/src/ReactFlightDOMServerBrowser.js
+2
-2
@@ -14,7 +14,7 @@ import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig';
14
15
import {
16
createRequest,
17
- startRender,
17
+ startWork,
18
startFlowing,
19
abort,
20
} from 'react-server/src/ReactFlightServer';
@@ -70,7 +70,7 @@ function renderToReadableStream(
70
{
71
type: 'bytes',
72
start: (controller): ?Promise<void> => {
73
- startRender(request);
73
+ startWork(request);
74
},
75
pull: (controller): ?Promise<void> => {
76
startFlowing(request, controller);
packages/react-server-dom-webpack/src/ReactFlightDOMServerEdge.js
+2
-2
@@ -14,7 +14,7 @@ import type {ServerManifest} from 'react-client/src/ReactFlightClientConfig';
14
15
import {
16
createRequest,
17
- startRender,
17
+ startWork,
18
startFlowing,
19
abort,
20
} from 'react-server/src/ReactFlightServer';
@@ -70,7 +70,7 @@ function renderToReadableStream(
70
{
71
type: 'bytes',
72
start: (controller): ?Promise<void> => {
73
- startRender(request);
73
+ startWork(request);
74
},
75
pull: (controller): ?Promise<void> => {
76
startFlowing(request, controller);
packages/react-server-dom-webpack/src/ReactFlightDOMServerNode.js
+2
-2
@@ -20,7 +20,7 @@ import type {ServerContextJSONValue, Thenable} from 'shared/ReactTypes';
20
21
import {
22
createRequest,
23
- startRender,
23
+ startWork,
24
startFlowing,
25
abort,
26
} from 'react-server/src/ReactFlightServer';
@@ -74,7 +74,7 @@ function renderToPipeableStream(
74
options ? options.onPostpone : undefined,
75
);
76
let hasStartedFlowing = false;
77
- startRender(request);
77
+ startWork(request);
78
return {
79
pipe<T: Writable>(destination: T): T {
80
if (hasStartedFlowing) {
packages/react-server/src/ReactFizzServer.js
+101
-7
@@ -396,6 +396,106 @@ export function createRequest(
396
return request;
397
}
398
399
+export function createPrerenderRequest(
400
+ children: ReactNodeList,
401
+ resumableState: ResumableState,
402
+ renderState: RenderState,
403
+ rootFormatContext: FormatContext,
404
+ progressiveChunkSize: void | number,
405
+ onError: void | ((error: mixed) => ?string),
406
+ onAllReady: void | (() => void),
407
+ onShellReady: void | (() => void),
408
+ onShellError: void | ((error: mixed) => void),
409
+ onFatalError: void | ((error: mixed) => void),
410
+ onPostpone: void | ((reason: string) => void),
411
+): Request {
412
+ const request = createRequest(
413
+ children,
414
+ resumableState,
415
+ renderState,
416
+ rootFormatContext,
417
+ progressiveChunkSize,
418
+ onError,
419
+ onAllReady,
420
+ onShellReady,
421
+ onShellError,
422
+ onFatalError,
423
+ onPostpone,
424
+ );
425
+ // Start tracking postponed holes during this render.
426
+ request.trackedPostpones = {workingMap: new Map(), root: []};
427
+ return request;
428
+}
429
+
430
+export function resumeRequest(
431
+ children: ReactNodeList,
432
+ postponedState: PostponedState,
433
+ renderState: RenderState,
434
+ onError: void | ((error: mixed) => ?string),
435
+ onAllReady: void | (() => void),
436
+ onShellReady: void | (() => void),
437
+ onShellError: void | ((error: mixed) => void),
438
+ onFatalError: void | ((error: mixed) => void),
439
+ onPostpone: void | ((reason: string) => void),
440
+): Request {
441
+ prepareHostDispatcher();
442
+ const pingedTasks: Array<Task> = [];
443
+ const abortSet: Set<Task> = new Set();
444
+ const request: Request = {
445
+ destination: null,
446
+ flushScheduled: false,
447
+ resumableState: postponedState.resumableState,
448
+ renderState,
449
+ rootFormatContext: postponedState.rootFormatContext,
450
+ progressiveChunkSize: postponedState.progressiveChunkSize,
451
+ status: OPEN,
452
+ fatalError: null,
453
+ nextSegmentId: 0,
454
+ allPendingTasks: 0,
455
+ pendingRootTasks: 0,
456
+ completedRootSegment: null,
457
+ abortableTasks: abortSet,
458
+ pingedTasks: pingedTasks,
459
+ clientRenderedBoundaries: ([]: Array<SuspenseBoundary>),
460
+ completedBoundaries: ([]: Array<SuspenseBoundary>),
461
+ partialBoundaries: ([]: Array<SuspenseBoundary>),
462
+ trackedPostpones: null,
463
+ onError: onError === undefined ? defaultErrorHandler : onError,
464
+ onPostpone: onPostpone === undefined ? noop : onPostpone,
465
+ onAllReady: onAllReady === undefined ? noop : onAllReady,
466
+ onShellReady: onShellReady === undefined ? noop : onShellReady,
467
+ onShellError: onShellError === undefined ? noop : onShellError,
468
+ onFatalError: onFatalError === undefined ? noop : onFatalError,
469
+ };
470
+ // This segment represents the root fallback.
471
+ const rootSegment = createPendingSegment(
472
+ request,
473
+ 0,
474
+ null,
475
+ postponedState.rootFormatContext,
476
+ // Root segments are never embedded in Text on either edge
477
+ false,
478
+ false,
479
+ );
480
+ // There is no parent so conceptually, we're unblocked to flush this segment.
481
+ rootSegment.parentFlushed = true;
482
+ const rootTask = createTask(
483
+ request,
484
+ null,
485
+ children,
486
+ null,
487
+ rootSegment,
488
+ abortSet,
489
+ null,
490
+ postponedState.rootFormatContext,
491
+ emptyContextObject,
492
+ rootContextSnapshot,
493
+ emptyTreeContext,
494
+ );
495
+ pingedTasks.push(rootTask);
496
+ return request;
497
+}
498
+
499
let currentRequest: null | Request = null;
500
501
export function resolveRequest(): null | Request {
@@ -2751,7 +2851,7 @@ function flushCompletedQueues(
2851
}
2852
}
2853
2754
-export function startRender(request: Request): void {
2854
+export function startWork(request: Request): void {
2855
request.flushScheduled = request.destination !== null;
2856
if (supportsRequestStorage) {
2857
scheduleWork(() => requestStorage.run(request, performWork, request));
@@ -2760,12 +2860,6 @@ export function startRender(request: Request): void {
2860
}
2861
}
2862
2763
-export function startPrerender(request: Request): void {
2764
- // Start tracking postponed holes during this render.
2765
- request.trackedPostpones = {workingMap: new Map(), root: []};
2766
- startRender(request);
2767
-}
2768
-
2863
function enqueueFlush(request: Request): void {
2864
if (
2865
request.flushScheduled === false &&
packages/react-server/src/ReactFlightServer.js
+1
-1
@@ -1519,7 +1519,7 @@ function flushCompletedChunks(
1519
}
1520
}
1521
1522
-export function startRender(request: Request): void {
1522
+export function startWork(request: Request): void {
1523
request.flushScheduled = request.destination !== null;
1524
if (supportsRequestStorage) {
1525
scheduleWork(() => requestStorage.run(request, performWork, request));