Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion fixtures/view-transition/src/components/Page.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,8 @@ export default function Page({url, navigate}) {
<Suspend />
</div>
</ViewTransition>
{show ? <Component /> : null}
</Suspense>
{show ? <Component /> : null}
</div>
</ViewTransition>
</SwipeRecognizer>
Expand Down
68 changes: 58 additions & 10 deletions packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -782,13 +782,14 @@ const HTML_COLGROUP_MODE = 9;

type InsertionMode = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;

const NO_SCOPE = /* */ 0b000000;
const NOSCRIPT_SCOPE = /* */ 0b000001;
const PICTURE_SCOPE = /* */ 0b000010;
const FALLBACK_SCOPE = /* */ 0b000100;
const EXIT_SCOPE = /* */ 0b001000; // A direct Instance below a Suspense fallback is the only thing that can "exit"
const ENTER_SCOPE = /* */ 0b010000; // A direct Instance below Suspense content is the only thing that can "enter"
const UPDATE_SCOPE = /* */ 0b100000; // Inside a scope that applies "update" ViewTransitions if anything mutates here.
const NO_SCOPE = /* */ 0b0000000;
const NOSCRIPT_SCOPE = /* */ 0b0000001;
const PICTURE_SCOPE = /* */ 0b0000010;
const FALLBACK_SCOPE = /* */ 0b0000100;
const EXIT_SCOPE = /* */ 0b0001000; // A direct Instance below a Suspense fallback is the only thing that can "exit"
const ENTER_SCOPE = /* */ 0b0010000; // A direct Instance below Suspense content is the only thing that can "enter"
const UPDATE_SCOPE = /* */ 0b0100000; // Inside a scope that applies "update" ViewTransitions if anything mutates here.
const APPEARING_SCOPE = /* */ 0b1000000; // Below Suspense content subtree which might appear in an "enter" animation or "shared" animation.

// Everything not listed here are tracked for the whole subtree as opposed to just
// until the next Instance.
Expand Down Expand Up @@ -987,11 +988,20 @@ export function getSuspenseContentFormatContext(
resumableState: ResumableState,
parentContext: FormatContext,
): FormatContext {
const viewTransition = getSuspenseViewTransition(
parentContext.viewTransition,
);
let subtreeScope = parentContext.tagScope | ENTER_SCOPE;
if (viewTransition !== null && viewTransition.share !== 'none') {
// If we have a ViewTransition wrapping Suspense then the appearing animation
// will be applied just like an "enter" below. Mark it as animating.
subtreeScope |= APPEARING_SCOPE;
}
return createFormatContext(
parentContext.insertionMode,
parentContext.selectedValue,
parentContext.tagScope | ENTER_SCOPE,
getSuspenseViewTransition(parentContext.viewTransition),
subtreeScope,
viewTransition,
);
}

Expand Down Expand Up @@ -1063,6 +1073,9 @@ export function getViewTransitionFormatContext(
} else {
subtreeScope &= ~UPDATE_SCOPE;
}
if (enter !== 'none') {
subtreeScope |= APPEARING_SCOPE;
}
return createFormatContext(
parentContext.insertionMode,
parentContext.selectedValue,
Expand Down Expand Up @@ -3289,6 +3302,7 @@ function pushImg(
props: Object,
resumableState: ResumableState,
renderState: RenderState,
hoistableState: null | HoistableState,
formatContext: FormatContext,
): null {
const pictureOrNoScriptTagInScope =
Expand Down Expand Up @@ -3321,6 +3335,19 @@ function pushImg(
) {
// We have a suspensey image and ought to preload it to optimize the loading of display blocking
// resumableState.

if (hoistableState !== null) {
// Mark this boundary's state as having suspensey images.
// Only do that if we have a ViewTransition that might trigger a parent Suspense boundary
// to animate its appearing. Since that's the only case we'd actually apply suspensey images
// for SSR reveals.
const isInSuspenseWithEnterViewTransition =
formatContext.tagScope & APPEARING_SCOPE;
if (isInSuspenseWithEnterViewTransition) {
hoistableState.suspenseyImages = true;
}
}

const sizes = typeof props.sizes === 'string' ? props.sizes : undefined;
const key = getImageResourceKey(src, srcSet, sizes);

Expand Down Expand Up @@ -4255,7 +4282,14 @@ export function pushStartInstance(
return pushStartPreformattedElement(target, props, type, formatContext);
}
case 'img': {
return pushImg(target, props, resumableState, renderState, formatContext);
return pushImg(
target,
props,
resumableState,
renderState,
hoistableState,
formatContext,
);
}
// Omitted close tags
case 'base':
Expand Down Expand Up @@ -6125,6 +6159,7 @@ type StylesheetResource = {
export type HoistableState = {
styles: Set<StyleQueue>,
stylesheets: Set<StylesheetResource>,
suspenseyImages: boolean,
};

export type StyleQueue = {
Expand All @@ -6138,6 +6173,7 @@ export function createHoistableState(): HoistableState {
return {
styles: new Set(),
stylesheets: new Set(),
suspenseyImages: false,
};
}

Expand Down Expand Up @@ -6995,6 +7031,18 @@ export function hoistHoistables(
): void {
childState.styles.forEach(hoistStyleQueueDependency, parentState);
childState.stylesheets.forEach(hoistStylesheetDependency, parentState);
if (childState.suspenseyImages) {
// If the child has suspensey images, the parent now does too if it's inlined.
// Similarly, if a SuspenseList row has a suspensey image then effectively
// the next row should be blocked on it as well since the next row can't show
// earlier. In practice, since the child will be outlined this transferring
// may never matter but is conceptually correct.
parentState.suspenseyImages = true;
}
}

export function hasSuspenseyContent(hoistableState: HoistableState): boolean {
return hoistableState.stylesheets.size > 0 || hoistableState.suspenseyImages;
}

// This function is called at various times depending on whether we are rendering
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import type {
RenderState as BaseRenderState,
ResumableState,
HoistableState,
StyleQueue,
Resource,
HeadersDescriptor,
Expand Down Expand Up @@ -325,5 +326,10 @@ export function writePreambleStart(
);
}

export function hasSuspenseyContent(hoistableState: HoistableState): boolean {
// Never outline.
return false;
}

export type TransitionStatus = FormStatus;
export const NotPendingTransition: TransitionStatus = NotPending;
5 changes: 5 additions & 0 deletions packages/react-markup/src/ReactFizzConfigMarkup.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,5 +242,10 @@ export function writeCompletedRoot(
return true;
}

export function hasSuspenseyContent(hoistableState: HoistableState): boolean {
// Never outline.
return false;
}

export type TransitionStatus = FormStatus;
export const NotPendingTransition: TransitionStatus = NotPending;
3 changes: 3 additions & 0 deletions packages/react-noop-renderer/src/ReactNoopServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,9 @@ const ReactNoopServer = ReactFizzServer({
writeHoistablesForBoundary() {},
writePostamble() {},
hoistHoistables(parent: HoistableState, child: HoistableState) {},
hasSuspenseyContent(hoistableState: HoistableState): boolean {
return false;
},
createHoistableState(): HoistableState {
return null;
},
Expand Down
15 changes: 13 additions & 2 deletions packages/react-server/src/ReactFizzServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ import {
hoistPreambleState,
isPreambleReady,
isPreambleContext,
hasSuspenseyContent,
} from './ReactFizzConfig';
import {
constructClassInstance,
Expand Down Expand Up @@ -461,7 +462,7 @@ function isEligibleForOutlining(
// The larger this limit is, the more we can save on preparing fallbacks in case we end up
// outlining.
return (
boundary.byteSize > 500 &&
(boundary.byteSize > 500 || hasSuspenseyContent(boundary.contentState)) &&
// For boundaries that can possibly contribute to the preamble we don't want to outline
// them regardless of their size since the fallbacks should only be emitted if we've
// errored the boundary.
Expand Down Expand Up @@ -5748,8 +5749,13 @@ function flushSegment(

return writeEndPendingSuspenseBoundary(destination, request.renderState);
} else if (
// We don't outline when we're emitting partially completed boundaries optimistically
// because it doesn't make sense to outline something if its parent is going to be
// blocked on something later in the stream anyway.
!flushingPartialBoundaries &&
isEligibleForOutlining(request, boundary) &&
flushedByteSize + boundary.byteSize > request.progressiveChunkSize
(flushedByteSize + boundary.byteSize > request.progressiveChunkSize ||
hasSuspenseyContent(boundary.contentState))
) {
// Inlining this boundary would make the current sequence being written too large
// and block the parent for too long. Instead, it will be emitted separately so that we
Expand Down Expand Up @@ -5980,6 +5986,8 @@ function flushPartiallyCompletedSegment(
}
}

let flushingPartialBoundaries = false;

function flushCompletedQueues(
request: Request,
destination: Destination,
Expand Down Expand Up @@ -6095,6 +6103,7 @@ function flushCompletedQueues(

// Next we emit any segments of any boundaries that are partially complete
// but not deeply complete.
flushingPartialBoundaries = true;
const partialBoundaries = request.partialBoundaries;
for (i = 0; i < partialBoundaries.length; i++) {
const boundary = partialBoundaries[i];
Expand All @@ -6106,6 +6115,7 @@ function flushCompletedQueues(
}
}
partialBoundaries.splice(0, i);
flushingPartialBoundaries = false;

// Next we check the completed boundaries again. This may have had
// boundaries added to it in case they were too larged to be inlined.
Expand All @@ -6123,6 +6133,7 @@ function flushCompletedQueues(
}
largeBoundaries.splice(0, i);
} finally {
flushingPartialBoundaries = false;
if (
request.allPendingTasks === 0 &&
request.clientRenderedBoundaries.length === 0 &&
Expand Down
1 change: 1 addition & 0 deletions packages/react-server/src/forks/ReactFizzConfig.custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,4 +104,5 @@ export const writeHoistablesForBoundary = $$$config.writeHoistablesForBoundary;
export const writePostamble = $$$config.writePostamble;
export const hoistHoistables = $$$config.hoistHoistables;
export const createHoistableState = $$$config.createHoistableState;
export const hasSuspenseyContent = $$$config.hasSuspenseyContent;
export const emitEarlyPreloads = $$$config.emitEarlyPreloads;
Loading