Skip to content

Commit dc43bbd

Browse files
committed
Split out into helper functions
This is similar to the structure of beginWork in Fiber.
1 parent a817840 commit dc43bbd

File tree

1 file changed

+161
-132
lines changed

1 file changed

+161
-132
lines changed

packages/react-server/src/ReactFizzServer.js

Lines changed: 161 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -275,49 +275,127 @@ function fatalError(request: Request, error: mixed): void {
275275
closeWithError(request.destination, error);
276276
}
277277

278-
function renderNode(request: Request, task: Task, node: ReactNodeList): void {
279-
if (typeof node === 'string') {
280-
pushTextInstance(
281-
task.blockedSegment.chunks,
282-
node,
283-
request.responseState,
284-
task.assignID,
285-
);
286-
task.assignID = null;
287-
return;
288-
}
278+
function renderSuspenseBoundary(
279+
request: Request,
280+
task: Task,
281+
props: Object,
282+
): void {
283+
const parentBoundary = task.blockedBoundary;
284+
const parentSegment = task.blockedSegment;
285+
286+
// We need to push an "empty" thing here to identify the parent suspense boundary.
287+
pushEmpty(parentSegment.chunks, request.responseState, task.assignID);
288+
task.assignID = null;
289+
// Each time we enter a suspense boundary, we split out into a new segment for
290+
// the fallback so that we can later replace that segment with the content.
291+
// This also lets us split out the main content even if it doesn't suspend,
292+
// in case it ends up generating a large subtree of content.
293+
const fallback: ReactNodeList = props.fallback;
294+
const content: ReactNodeList = props.children;
295+
296+
const fallbackAbortSet: Set<Task> = new Set();
297+
const newBoundary = createSuspenseBoundary(request, fallbackAbortSet);
298+
const insertionIndex = parentSegment.chunks.length;
299+
// The children of the boundary segment is actually the fallback.
300+
const boundarySegment = createPendingSegment(
301+
request,
302+
insertionIndex,
303+
newBoundary,
304+
parentSegment.formatContext,
305+
);
306+
parentSegment.children.push(boundarySegment);
289307

290-
if (Array.isArray(node)) {
291-
if (node.length > 0) {
292-
for (let i = 0; i < node.length; i++) {
293-
renderNode(request, task, node[i]);
294-
}
295-
} else {
296-
pushEmpty(
297-
task.blockedSegment.chunks,
298-
request.responseState,
299-
task.assignID,
300-
);
301-
task.assignID = null;
308+
// This segment is the actual child content. We can start rendering that immediately.
309+
const contentRootSegment = createPendingSegment(
310+
request,
311+
0,
312+
null,
313+
parentSegment.formatContext,
314+
);
315+
// We mark the root segment as having its parent flushed. It's not really flushed but there is
316+
// no parent segment so there's nothing to wait on.
317+
contentRootSegment.parentFlushed = true;
318+
319+
// Currently this is running synchronously. We could instead schedule this to pingedTasks.
320+
// I suspect that there might be some efficiency benefits from not creating the suspended task
321+
// and instead just using the stack if possible.
322+
// TODO: Call this directly instead of messing with saving and restoring contexts.
323+
324+
// We can reuse the current context and task to render the content immediately without
325+
// context switching. We just need to temporarily switch which boundary and which segment
326+
// we're writing to. If something suspends, it'll spawn new suspended task with that context.
327+
task.blockedBoundary = newBoundary;
328+
task.blockedSegment = contentRootSegment;
329+
try {
330+
renderNode(request, task, content);
331+
contentRootSegment.status = COMPLETED;
332+
newBoundary.completedSegments.push(contentRootSegment);
333+
if (newBoundary.pendingTasks === 0) {
334+
// This must have been the last segment we were waiting on. This boundary is now complete.
335+
// Therefore we won't need the fallback. We early return so that we don't have to create
336+
// the fallback.
337+
return;
302338
}
303-
return;
339+
} catch (error) {
340+
contentRootSegment.status = ERRORED;
341+
reportError(request, error);
342+
newBoundary.forceClientRender = true;
343+
// We don't need to decrement any task numbers because we didn't spawn any new task.
344+
// We don't need to schedule any task because we know the parent has written yet.
345+
// We do need to fallthrough to create the fallback though.
346+
} finally {
347+
task.blockedBoundary = parentBoundary;
348+
task.blockedSegment = parentSegment;
304349
}
305350

306-
if (node === null) {
307-
pushEmpty(task.blockedSegment.chunks, request.responseState, task.assignID);
308-
return;
309-
}
351+
// We create suspended task for the fallback because we don't want to actually task
352+
// on it yet in case we finish the main content, so we queue for later.
353+
const suspendedFallbackTask = createTask(
354+
request,
355+
fallback,
356+
parentBoundary,
357+
boundarySegment,
358+
fallbackAbortSet,
359+
newBoundary.id, // This is the ID we want to give this fallback so we can replace it later.
360+
);
361+
// TODO: This should be queued at a separate lower priority queue so that we only task
362+
// on preparing fallbacks if we don't have any more main content to task on.
363+
request.pingedTasks.push(suspendedFallbackTask);
364+
}
310365

311-
if (
312-
typeof node !== 'object' ||
313-
!node ||
314-
(node: any).$$typeof !== REACT_ELEMENT_TYPE
315-
) {
316-
throw new Error('Not yet implemented node type.');
317-
}
318-
const element: React$Element<any> = (node: any);
319-
const type = element.type;
320-
const props = element.props;
366+
function renderHostElement(
367+
request: Request,
368+
task: Task,
369+
type: string,
370+
props: Object,
371+
): void {
372+
const segment = task.blockedSegment;
373+
const children = pushStartInstance(
374+
segment.chunks,
375+
type,
376+
props,
377+
request.responseState,
378+
segment.formatContext,
379+
task.assignID,
380+
);
381+
// We must have assigned it already above so we don't need this anymore.
382+
task.assignID = null;
383+
const prevContext = segment.formatContext;
384+
segment.formatContext = getChildFormatContext(prevContext, type, props);
385+
renderNode(request, task, children);
386+
// We expect that errors will fatal the whole task and that we don't need
387+
// the correct context. Therefore this is not in a finally.
388+
segment.formatContext = prevContext;
389+
pushEndInstance(segment.chunks, type, props);
390+
}
391+
392+
function renderElement(
393+
request: Request,
394+
task: Task,
395+
type: any,
396+
props: Object,
397+
node: ReactNodeList,
398+
): void {
321399
if (typeof type === 'function') {
322400
try {
323401
const result = type(props);
@@ -352,109 +430,60 @@ function renderNode(request: Request, task: Task, node: ReactNodeList): void {
352430
}
353431
}
354432
} else if (typeof type === 'string') {
355-
const segment = task.blockedSegment;
356-
const children = pushStartInstance(
357-
segment.chunks,
358-
type,
359-
props,
433+
renderHostElement(request, task, type, props);
434+
} else if (type === REACT_SUSPENSE_TYPE) {
435+
renderSuspenseBoundary(request, task, props);
436+
} else {
437+
throw new Error('Not yet implemented element type.');
438+
}
439+
}
440+
441+
function renderNode(request: Request, task: Task, node: ReactNodeList): void {
442+
if (typeof node === 'string') {
443+
pushTextInstance(
444+
task.blockedSegment.chunks,
445+
node,
360446
request.responseState,
361-
segment.formatContext,
362447
task.assignID,
363448
);
364-
// We must have assigned it already above so we don't need this anymore.
365449
task.assignID = null;
366-
const prevContext = segment.formatContext;
367-
segment.formatContext = getChildFormatContext(prevContext, type, props);
368-
renderNode(request, task, children);
369-
// We expect that errors will fatal the whole task and that we don't need
370-
// the correct context. Therefore this is not in a finally.
371-
segment.formatContext = prevContext;
372-
pushEndInstance(segment.chunks, type, props);
373-
} else if (type === REACT_SUSPENSE_TYPE) {
374-
const parentBoundary = task.blockedBoundary;
375-
const parentSegment = task.blockedSegment;
450+
return;
451+
}
376452

377-
// We need to push an "empty" thing here to identify the parent suspense boundary.
378-
pushEmpty(parentSegment.chunks, request.responseState, task.assignID);
379-
task.assignID = null;
380-
// Each time we enter a suspense boundary, we split out into a new segment for
381-
// the fallback so that we can later replace that segment with the content.
382-
// This also lets us split out the main content even if it doesn't suspend,
383-
// in case it ends up generating a large subtree of content.
384-
const fallback: ReactNodeList = props.fallback;
385-
const content: ReactNodeList = props.children;
386-
387-
const fallbackAbortSet: Set<Task> = new Set();
388-
const newBoundary = createSuspenseBoundary(request, fallbackAbortSet);
389-
const insertionIndex = parentSegment.chunks.length;
390-
// The children of the boundary segment is actually the fallback.
391-
const boundarySegment = createPendingSegment(
392-
request,
393-
insertionIndex,
394-
newBoundary,
395-
parentSegment.formatContext,
396-
);
397-
parentSegment.children.push(boundarySegment);
398-
399-
// This segment is the actual child content. We can start rendering that immediately.
400-
const contentRootSegment = createPendingSegment(
401-
request,
402-
0,
403-
null,
404-
parentSegment.formatContext,
405-
);
406-
// We mark the root segment as having its parent flushed. It's not really flushed but there is
407-
// no parent segment so there's nothing to wait on.
408-
contentRootSegment.parentFlushed = true;
409-
410-
// Currently this is running synchronously. We could instead schedule this to pingedTasks.
411-
// I suspect that there might be some efficiency benefits from not creating the suspended task
412-
// and instead just using the stack if possible.
413-
// TODO: Call this directly instead of messing with saving and restoring contexts.
414-
415-
// We can reuse the current context and task to render the content immediately without
416-
// context switching. We just need to temporarily switch which boundary and which segment
417-
// we're writing to. If something suspends, it'll spawn new suspended task with that context.
418-
task.blockedBoundary = newBoundary;
419-
task.blockedSegment = contentRootSegment;
420-
try {
421-
renderNode(request, task, content);
422-
contentRootSegment.status = COMPLETED;
423-
newBoundary.completedSegments.push(contentRootSegment);
424-
if (newBoundary.pendingTasks === 0) {
425-
// This must have been the last segment we were waiting on. This boundary is now complete.
426-
// Therefore we won't need the fallback. We early return so that we don't have to create
427-
// the fallback.
428-
return;
453+
if (Array.isArray(node)) {
454+
if (node.length > 0) {
455+
for (let i = 0; i < node.length; i++) {
456+
renderNode(request, task, node[i]);
429457
}
430-
} catch (error) {
431-
contentRootSegment.status = ERRORED;
432-
reportError(request, error);
433-
newBoundary.forceClientRender = true;
434-
// We don't need to decrement any task numbers because we didn't spawn any new task.
435-
// We don't need to schedule any task because we know the parent has written yet.
436-
// We do need to fallthrough to create the fallback though.
437-
} finally {
438-
task.blockedBoundary = parentBoundary;
439-
task.blockedSegment = parentSegment;
458+
} else {
459+
pushEmpty(
460+
task.blockedSegment.chunks,
461+
request.responseState,
462+
task.assignID,
463+
);
464+
task.assignID = null;
440465
}
466+
return;
467+
}
441468

442-
// We create suspended task for the fallback because we don't want to actually task
443-
// on it yet in case we finish the main content, so we queue for later.
444-
const suspendedFallbackTask = createTask(
445-
request,
446-
fallback,
447-
parentBoundary,
448-
boundarySegment,
449-
fallbackAbortSet,
450-
newBoundary.id, // This is the ID we want to give this fallback so we can replace it later.
451-
);
452-
// TODO: This should be queued at a separate lower priority queue so that we only task
453-
// on preparing fallbacks if we don't have any more main content to task on.
454-
request.pingedTasks.push(suspendedFallbackTask);
455-
} else {
456-
throw new Error('Not yet implemented element type.');
469+
if (node === null) {
470+
pushEmpty(task.blockedSegment.chunks, request.responseState, task.assignID);
471+
return;
457472
}
473+
474+
if (
475+
typeof node === 'object' &&
476+
node &&
477+
(node: any).$$typeof === REACT_ELEMENT_TYPE
478+
) {
479+
const element: React$Element<any> = (node: any);
480+
const type = element.type;
481+
const props = element.props;
482+
renderElement(request, task, type, props, node);
483+
return;
484+
}
485+
486+
throw new Error('Not yet implemented node type.');
458487
}
459488

460489
function erroredTask(

0 commit comments

Comments
 (0)