mirror of
https://github.com/zebrajr/react.git
synced 2025-12-06 12:20:20 +01:00
Emit Activity boundaries as comments in Fizz (#32834)
Uses `&` for Activity as opposed to `$` for Suspense. This will be used to delimitate which nodes we can skip hydrating. This isn't used on the client yet. It's just a noop on the client because it's just an unknown comment. This just adds the SSR parts.
This commit is contained in:
parent
8571249eb8
commit
3fbfb9baaf
|
|
@ -4087,6 +4087,28 @@ export function writePlaceholder(
|
|||
return writeChunkAndReturn(destination, placeholder2);
|
||||
}
|
||||
|
||||
// Activity boundaries are encoded as comments.
|
||||
const startActivityBoundary = stringToPrecomputedChunk('<!--&-->');
|
||||
const endActivityBoundary = stringToPrecomputedChunk('<!--/&-->');
|
||||
|
||||
export function pushStartActivityBoundary(
|
||||
target: Array<Chunk | PrecomputedChunk>,
|
||||
renderState: RenderState,
|
||||
): void {
|
||||
target.push(startActivityBoundary);
|
||||
}
|
||||
|
||||
export function pushEndActivityBoundary(
|
||||
target: Array<Chunk | PrecomputedChunk>,
|
||||
renderState: RenderState,
|
||||
preambleState: null | PreambleState,
|
||||
): void {
|
||||
if (preambleState) {
|
||||
pushPreambleContribution(target, preambleState);
|
||||
}
|
||||
target.push(endActivityBoundary);
|
||||
}
|
||||
|
||||
// Suspense boundaries are encoded as comments.
|
||||
const startCompletedSuspenseBoundary = stringToPrecomputedChunk('<!--$-->');
|
||||
const startPendingSuspenseBoundary1 = stringToPrecomputedChunk(
|
||||
|
|
@ -4225,6 +4247,23 @@ export function writeEndClientRenderedSuspenseBoundary(
|
|||
const boundaryPreambleContributionChunkStart = stringToPrecomputedChunk('<!--');
|
||||
const boundaryPreambleContributionChunkEnd = stringToPrecomputedChunk('-->');
|
||||
|
||||
function pushPreambleContribution(
|
||||
target: Array<Chunk | PrecomputedChunk>,
|
||||
preambleState: PreambleState,
|
||||
) {
|
||||
// Same as writePreambleContribution but for the render phase.
|
||||
const contribution = preambleState.contribution;
|
||||
if (contribution !== NoContribution) {
|
||||
target.push(
|
||||
boundaryPreambleContributionChunkStart,
|
||||
// This is a number type so we can do the fast path without coercion checking
|
||||
// eslint-disable-next-line react-internal/safe-string-coercion
|
||||
stringToChunk('' + contribution),
|
||||
boundaryPreambleContributionChunkEnd,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function writePreambleContribution(
|
||||
destination: Destination,
|
||||
preambleState: PreambleState,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ import {
|
|||
createRenderState as createRenderStateImpl,
|
||||
pushTextInstance as pushTextInstanceImpl,
|
||||
pushSegmentFinale as pushSegmentFinaleImpl,
|
||||
pushStartActivityBoundary as pushStartActivityBoundaryImpl,
|
||||
pushEndActivityBoundary as pushEndActivityBoundaryImpl,
|
||||
writeStartCompletedSuspenseBoundary as writeStartCompletedSuspenseBoundaryImpl,
|
||||
writeStartClientRenderedSuspenseBoundary as writeStartClientRenderedSuspenseBoundaryImpl,
|
||||
writeEndCompletedSuspenseBoundary as writeEndCompletedSuspenseBoundaryImpl,
|
||||
|
|
@ -207,6 +209,29 @@ export function pushSegmentFinale(
|
|||
}
|
||||
}
|
||||
|
||||
export function pushStartActivityBoundary(
|
||||
target: Array<Chunk | PrecomputedChunk>,
|
||||
renderState: RenderState,
|
||||
): void {
|
||||
if (renderState.generateStaticMarkup) {
|
||||
// A completed boundary is done and doesn't need a representation in the HTML
|
||||
// if we're not going to be hydrating it.
|
||||
return;
|
||||
}
|
||||
pushStartActivityBoundaryImpl(target, renderState);
|
||||
}
|
||||
|
||||
export function pushEndActivityBoundary(
|
||||
target: Array<Chunk | PrecomputedChunk>,
|
||||
renderState: RenderState,
|
||||
preambleState: null | PreambleState,
|
||||
): void {
|
||||
if (renderState.generateStaticMarkup) {
|
||||
return;
|
||||
}
|
||||
pushEndActivityBoundaryImpl(target, renderState, preambleState);
|
||||
}
|
||||
|
||||
export function writeStartCompletedSuspenseBoundary(
|
||||
destination: Destination,
|
||||
renderState: RenderState,
|
||||
|
|
|
|||
|
|
@ -3669,7 +3669,7 @@ describe('ReactDOMServerPartialHydration', () => {
|
|||
});
|
||||
|
||||
// @gate enableActivity
|
||||
it('a visible Activity component acts like a fragment', async () => {
|
||||
it('a visible Activity component is surrounded by comment markers', async () => {
|
||||
const ref = React.createRef();
|
||||
|
||||
function App() {
|
||||
|
|
@ -3690,9 +3690,11 @@ describe('ReactDOMServerPartialHydration', () => {
|
|||
// pure indirection.
|
||||
expect(container).toMatchInlineSnapshot(`
|
||||
<div>
|
||||
<!--&-->
|
||||
<span>
|
||||
Child
|
||||
</span>
|
||||
<!--/&-->
|
||||
</div>
|
||||
`);
|
||||
|
||||
|
|
@ -3739,6 +3741,8 @@ describe('ReactDOMServerPartialHydration', () => {
|
|||
<span>
|
||||
Visible
|
||||
</span>
|
||||
<!--&-->
|
||||
<!--/&-->
|
||||
</div>
|
||||
`);
|
||||
|
||||
|
|
@ -3760,6 +3764,8 @@ describe('ReactDOMServerPartialHydration', () => {
|
|||
<span>
|
||||
Visible
|
||||
</span>
|
||||
<!--&-->
|
||||
<!--/&-->
|
||||
<span
|
||||
style="display: none;"
|
||||
>
|
||||
|
|
|
|||
|
|
@ -151,6 +151,23 @@ export function pushSegmentFinale(
|
|||
return;
|
||||
}
|
||||
|
||||
export function pushStartActivityBoundary(
|
||||
target: Array<Chunk | PrecomputedChunk>,
|
||||
renderState: RenderState,
|
||||
): void {
|
||||
// Markup doesn't have any instructions.
|
||||
return;
|
||||
}
|
||||
|
||||
export function pushEndActivityBoundary(
|
||||
target: Array<Chunk | PrecomputedChunk>,
|
||||
renderState: RenderState,
|
||||
preambleState: null | PreambleState,
|
||||
): void {
|
||||
// Markup doesn't have any instructions.
|
||||
return;
|
||||
}
|
||||
|
||||
export function writeStartCompletedSuspenseBoundary(
|
||||
destination: Destination,
|
||||
renderState: RenderState,
|
||||
|
|
@ -158,6 +175,7 @@ export function writeStartCompletedSuspenseBoundary(
|
|||
// Markup doesn't have any instructions.
|
||||
return true;
|
||||
}
|
||||
|
||||
export function writeStartClientRenderedSuspenseBoundary(
|
||||
destination: Destination,
|
||||
renderState: RenderState,
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ type TextInstance = {
|
|||
hidden: boolean,
|
||||
};
|
||||
|
||||
type ActivityInstance = {
|
||||
children: Array<Instance | TextInstance | SuspenseInstance>,
|
||||
};
|
||||
|
||||
type SuspenseInstance = {
|
||||
state: 'pending' | 'complete' | 'client-render',
|
||||
children: Array<Instance | TextInstance | SuspenseInstance>,
|
||||
|
|
@ -164,44 +168,74 @@ const ReactNoopServer = ReactFizzServer({
|
|||
});
|
||||
},
|
||||
|
||||
pushStartActivityBoundary(
|
||||
target: Array<Uint8Array>,
|
||||
renderState: RenderState,
|
||||
): void {
|
||||
const activityInstance: ActivityInstance = {
|
||||
children: [],
|
||||
};
|
||||
target.push(Buffer.from(JSON.stringify(activityInstance), 'utf8'));
|
||||
},
|
||||
|
||||
pushEndActivityBoundary(
|
||||
target: Array<Uint8Array>,
|
||||
renderState: RenderState,
|
||||
preambleState: null | PreambleState,
|
||||
): void {
|
||||
target.push(POP);
|
||||
},
|
||||
|
||||
writeStartCompletedSuspenseBoundary(
|
||||
destination: Destination,
|
||||
renderState: RenderState,
|
||||
suspenseInstance: SuspenseInstance,
|
||||
): boolean {
|
||||
suspenseInstance.state = 'complete';
|
||||
const suspenseInstance: SuspenseInstance = {
|
||||
state: 'complete',
|
||||
children: [],
|
||||
};
|
||||
const parent = destination.stack[destination.stack.length - 1];
|
||||
parent.children.push(suspenseInstance);
|
||||
destination.stack.push(suspenseInstance);
|
||||
return true;
|
||||
},
|
||||
writeStartPendingSuspenseBoundary(
|
||||
destination: Destination,
|
||||
renderState: RenderState,
|
||||
suspenseInstance: SuspenseInstance,
|
||||
): boolean {
|
||||
suspenseInstance.state = 'pending';
|
||||
const suspenseInstance: SuspenseInstance = {
|
||||
state: 'pending',
|
||||
children: [],
|
||||
};
|
||||
const parent = destination.stack[destination.stack.length - 1];
|
||||
parent.children.push(suspenseInstance);
|
||||
destination.stack.push(suspenseInstance);
|
||||
return true;
|
||||
},
|
||||
writeStartClientRenderedSuspenseBoundary(
|
||||
destination: Destination,
|
||||
renderState: RenderState,
|
||||
suspenseInstance: SuspenseInstance,
|
||||
): boolean {
|
||||
suspenseInstance.state = 'client-render';
|
||||
const suspenseInstance: SuspenseInstance = {
|
||||
state: 'client-render',
|
||||
children: [],
|
||||
};
|
||||
const parent = destination.stack[destination.stack.length - 1];
|
||||
parent.children.push(suspenseInstance);
|
||||
destination.stack.push(suspenseInstance);
|
||||
return true;
|
||||
},
|
||||
writeEndCompletedSuspenseBoundary(destination: Destination): boolean {
|
||||
destination.stack.pop();
|
||||
return true;
|
||||
},
|
||||
writeEndPendingSuspenseBoundary(destination: Destination): boolean {
|
||||
destination.stack.pop();
|
||||
return true;
|
||||
},
|
||||
writeEndClientRenderedSuspenseBoundary(destination: Destination): boolean {
|
||||
destination.stack.pop();
|
||||
return true;
|
||||
},
|
||||
|
||||
writeStartSegment(
|
||||
|
|
@ -218,9 +252,11 @@ const ReactNoopServer = ReactFizzServer({
|
|||
throw new Error('Segments are only expected at the root of the stack.');
|
||||
}
|
||||
destination.stack.push(segment);
|
||||
return true;
|
||||
},
|
||||
writeEndSegment(destination: Destination, formatContext: null): boolean {
|
||||
destination.stack.pop();
|
||||
return true;
|
||||
},
|
||||
|
||||
writeCompletedSegmentInstruction(
|
||||
|
|
@ -241,6 +277,7 @@ const ReactNoopServer = ReactFizzServer({
|
|||
0,
|
||||
...segment.children,
|
||||
);
|
||||
return true;
|
||||
},
|
||||
|
||||
writeCompletedBoundaryInstruction(
|
||||
|
|
@ -255,6 +292,7 @@ const ReactNoopServer = ReactFizzServer({
|
|||
}
|
||||
boundary.children = segment.children;
|
||||
boundary.state = 'complete';
|
||||
return true;
|
||||
},
|
||||
|
||||
writeClientRenderBoundaryInstruction(
|
||||
|
|
@ -263,6 +301,7 @@ const ReactNoopServer = ReactFizzServer({
|
|||
boundary: SuspenseInstance,
|
||||
): boolean {
|
||||
boundary.status = 'client-render';
|
||||
return true;
|
||||
},
|
||||
|
||||
writePreambleStart() {},
|
||||
|
|
|
|||
53
packages/react-server/src/ReactFizzServer.js
vendored
53
packages/react-server/src/ReactFizzServer.js
vendored
|
|
@ -51,6 +51,8 @@ import {
|
|||
import {
|
||||
writeCompletedRoot,
|
||||
writePlaceholder,
|
||||
pushStartActivityBoundary,
|
||||
pushEndActivityBoundary,
|
||||
writeStartCompletedSuspenseBoundary,
|
||||
writeStartPendingSuspenseBoundary,
|
||||
writeStartClientRenderedSuspenseBoundary,
|
||||
|
|
@ -2200,23 +2202,50 @@ function renderLazyComponent(
|
|||
renderElement(request, task, keyPath, Component, resolvedProps, ref);
|
||||
}
|
||||
|
||||
function renderOffscreen(
|
||||
function renderActivity(
|
||||
request: Request,
|
||||
task: Task,
|
||||
keyPath: KeyNode,
|
||||
props: Object,
|
||||
): void {
|
||||
const mode: ?OffscreenMode = (props.mode: any);
|
||||
if (mode === 'hidden') {
|
||||
// A hidden Offscreen boundary is not server rendered. Prerendering happens
|
||||
// on the client.
|
||||
const segment = task.blockedSegment;
|
||||
if (segment === null) {
|
||||
// Replay
|
||||
const mode: ?OffscreenMode = (props.mode: any);
|
||||
if (mode === 'hidden') {
|
||||
// A hidden Activity boundary is not server rendered. Prerendering happens
|
||||
// on the client.
|
||||
} else {
|
||||
// A visible Activity boundary has its children rendered inside the boundary.
|
||||
const prevKeyPath = task.keyPath;
|
||||
task.keyPath = keyPath;
|
||||
renderNode(request, task, props.children, -1);
|
||||
task.keyPath = prevKeyPath;
|
||||
}
|
||||
} else {
|
||||
// A visible Offscreen boundary is treated exactly like a fragment: a
|
||||
// pure indirection.
|
||||
const prevKeyPath = task.keyPath;
|
||||
task.keyPath = keyPath;
|
||||
renderNodeDestructive(request, task, props.children, -1);
|
||||
task.keyPath = prevKeyPath;
|
||||
// Render
|
||||
// An Activity boundary is delimited so that we can hydrate it separately.
|
||||
pushStartActivityBoundary(segment.chunks, request.renderState);
|
||||
segment.lastPushedText = false;
|
||||
const mode: ?OffscreenMode = (props.mode: any);
|
||||
if (mode === 'hidden') {
|
||||
// A hidden Activity boundary is not server rendered. Prerendering happens
|
||||
// on the client.
|
||||
} else {
|
||||
// A visible Activity boundary has its children rendered inside the boundary.
|
||||
const prevKeyPath = task.keyPath;
|
||||
task.keyPath = keyPath;
|
||||
// We use the non-destructive form because if something suspends, we still
|
||||
// need to pop back up and finish the end comment.
|
||||
renderNode(request, task, props.children, -1);
|
||||
task.keyPath = prevKeyPath;
|
||||
}
|
||||
pushEndActivityBoundary(
|
||||
segment.chunks,
|
||||
request.renderState,
|
||||
task.blockedPreamble,
|
||||
);
|
||||
segment.lastPushedText = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2291,7 +2320,7 @@ function renderElement(
|
|||
return;
|
||||
}
|
||||
case REACT_ACTIVITY_TYPE: {
|
||||
renderOffscreen(request, task, keyPath, props);
|
||||
renderActivity(request, task, keyPath, props);
|
||||
return;
|
||||
}
|
||||
case REACT_SUSPENSE_LIST_TYPE: {
|
||||
|
|
|
|||
|
|
@ -59,6 +59,8 @@ export const pushFormStateMarkerIsNotMatching =
|
|||
$$$config.pushFormStateMarkerIsNotMatching;
|
||||
export const writeCompletedRoot = $$$config.writeCompletedRoot;
|
||||
export const writePlaceholder = $$$config.writePlaceholder;
|
||||
export const pushStartActivityBoundary = $$$config.pushStartActivityBoundary;
|
||||
export const pushEndActivityBoundary = $$$config.pushEndActivityBoundary;
|
||||
export const writeStartCompletedSuspenseBoundary =
|
||||
$$$config.writeStartCompletedSuspenseBoundary;
|
||||
export const writeStartPendingSuspenseBoundary =
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user