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);
|
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.
|
// Suspense boundaries are encoded as comments.
|
||||||
const startCompletedSuspenseBoundary = stringToPrecomputedChunk('<!--$-->');
|
const startCompletedSuspenseBoundary = stringToPrecomputedChunk('<!--$-->');
|
||||||
const startPendingSuspenseBoundary1 = stringToPrecomputedChunk(
|
const startPendingSuspenseBoundary1 = stringToPrecomputedChunk(
|
||||||
|
|
@ -4225,6 +4247,23 @@ export function writeEndClientRenderedSuspenseBoundary(
|
||||||
const boundaryPreambleContributionChunkStart = stringToPrecomputedChunk('<!--');
|
const boundaryPreambleContributionChunkStart = stringToPrecomputedChunk('<!--');
|
||||||
const boundaryPreambleContributionChunkEnd = 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(
|
function writePreambleContribution(
|
||||||
destination: Destination,
|
destination: Destination,
|
||||||
preambleState: PreambleState,
|
preambleState: PreambleState,
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,8 @@ import {
|
||||||
createRenderState as createRenderStateImpl,
|
createRenderState as createRenderStateImpl,
|
||||||
pushTextInstance as pushTextInstanceImpl,
|
pushTextInstance as pushTextInstanceImpl,
|
||||||
pushSegmentFinale as pushSegmentFinaleImpl,
|
pushSegmentFinale as pushSegmentFinaleImpl,
|
||||||
|
pushStartActivityBoundary as pushStartActivityBoundaryImpl,
|
||||||
|
pushEndActivityBoundary as pushEndActivityBoundaryImpl,
|
||||||
writeStartCompletedSuspenseBoundary as writeStartCompletedSuspenseBoundaryImpl,
|
writeStartCompletedSuspenseBoundary as writeStartCompletedSuspenseBoundaryImpl,
|
||||||
writeStartClientRenderedSuspenseBoundary as writeStartClientRenderedSuspenseBoundaryImpl,
|
writeStartClientRenderedSuspenseBoundary as writeStartClientRenderedSuspenseBoundaryImpl,
|
||||||
writeEndCompletedSuspenseBoundary as writeEndCompletedSuspenseBoundaryImpl,
|
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(
|
export function writeStartCompletedSuspenseBoundary(
|
||||||
destination: Destination,
|
destination: Destination,
|
||||||
renderState: RenderState,
|
renderState: RenderState,
|
||||||
|
|
|
||||||
|
|
@ -3669,7 +3669,7 @@ describe('ReactDOMServerPartialHydration', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// @gate enableActivity
|
// @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();
|
const ref = React.createRef();
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
|
@ -3690,9 +3690,11 @@ describe('ReactDOMServerPartialHydration', () => {
|
||||||
// pure indirection.
|
// pure indirection.
|
||||||
expect(container).toMatchInlineSnapshot(`
|
expect(container).toMatchInlineSnapshot(`
|
||||||
<div>
|
<div>
|
||||||
|
<!--&-->
|
||||||
<span>
|
<span>
|
||||||
Child
|
Child
|
||||||
</span>
|
</span>
|
||||||
|
<!--/&-->
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
|
@ -3739,6 +3741,8 @@ describe('ReactDOMServerPartialHydration', () => {
|
||||||
<span>
|
<span>
|
||||||
Visible
|
Visible
|
||||||
</span>
|
</span>
|
||||||
|
<!--&-->
|
||||||
|
<!--/&-->
|
||||||
</div>
|
</div>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
|
@ -3760,6 +3764,8 @@ describe('ReactDOMServerPartialHydration', () => {
|
||||||
<span>
|
<span>
|
||||||
Visible
|
Visible
|
||||||
</span>
|
</span>
|
||||||
|
<!--&-->
|
||||||
|
<!--/&-->
|
||||||
<span
|
<span
|
||||||
style="display: none;"
|
style="display: none;"
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,23 @@ export function pushSegmentFinale(
|
||||||
return;
|
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(
|
export function writeStartCompletedSuspenseBoundary(
|
||||||
destination: Destination,
|
destination: Destination,
|
||||||
renderState: RenderState,
|
renderState: RenderState,
|
||||||
|
|
@ -158,6 +175,7 @@ export function writeStartCompletedSuspenseBoundary(
|
||||||
// Markup doesn't have any instructions.
|
// Markup doesn't have any instructions.
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function writeStartClientRenderedSuspenseBoundary(
|
export function writeStartClientRenderedSuspenseBoundary(
|
||||||
destination: Destination,
|
destination: Destination,
|
||||||
renderState: RenderState,
|
renderState: RenderState,
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,10 @@ type TextInstance = {
|
||||||
hidden: boolean,
|
hidden: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ActivityInstance = {
|
||||||
|
children: Array<Instance | TextInstance | SuspenseInstance>,
|
||||||
|
};
|
||||||
|
|
||||||
type SuspenseInstance = {
|
type SuspenseInstance = {
|
||||||
state: 'pending' | 'complete' | 'client-render',
|
state: 'pending' | 'complete' | 'client-render',
|
||||||
children: Array<Instance | TextInstance | SuspenseInstance>,
|
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(
|
writeStartCompletedSuspenseBoundary(
|
||||||
destination: Destination,
|
destination: Destination,
|
||||||
renderState: RenderState,
|
renderState: RenderState,
|
||||||
suspenseInstance: SuspenseInstance,
|
|
||||||
): boolean {
|
): boolean {
|
||||||
suspenseInstance.state = 'complete';
|
const suspenseInstance: SuspenseInstance = {
|
||||||
|
state: 'complete',
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
const parent = destination.stack[destination.stack.length - 1];
|
const parent = destination.stack[destination.stack.length - 1];
|
||||||
parent.children.push(suspenseInstance);
|
parent.children.push(suspenseInstance);
|
||||||
destination.stack.push(suspenseInstance);
|
destination.stack.push(suspenseInstance);
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
writeStartPendingSuspenseBoundary(
|
writeStartPendingSuspenseBoundary(
|
||||||
destination: Destination,
|
destination: Destination,
|
||||||
renderState: RenderState,
|
renderState: RenderState,
|
||||||
suspenseInstance: SuspenseInstance,
|
|
||||||
): boolean {
|
): boolean {
|
||||||
suspenseInstance.state = 'pending';
|
const suspenseInstance: SuspenseInstance = {
|
||||||
|
state: 'pending',
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
const parent = destination.stack[destination.stack.length - 1];
|
const parent = destination.stack[destination.stack.length - 1];
|
||||||
parent.children.push(suspenseInstance);
|
parent.children.push(suspenseInstance);
|
||||||
destination.stack.push(suspenseInstance);
|
destination.stack.push(suspenseInstance);
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
writeStartClientRenderedSuspenseBoundary(
|
writeStartClientRenderedSuspenseBoundary(
|
||||||
destination: Destination,
|
destination: Destination,
|
||||||
renderState: RenderState,
|
renderState: RenderState,
|
||||||
suspenseInstance: SuspenseInstance,
|
|
||||||
): boolean {
|
): boolean {
|
||||||
suspenseInstance.state = 'client-render';
|
const suspenseInstance: SuspenseInstance = {
|
||||||
|
state: 'client-render',
|
||||||
|
children: [],
|
||||||
|
};
|
||||||
const parent = destination.stack[destination.stack.length - 1];
|
const parent = destination.stack[destination.stack.length - 1];
|
||||||
parent.children.push(suspenseInstance);
|
parent.children.push(suspenseInstance);
|
||||||
destination.stack.push(suspenseInstance);
|
destination.stack.push(suspenseInstance);
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
writeEndCompletedSuspenseBoundary(destination: Destination): boolean {
|
writeEndCompletedSuspenseBoundary(destination: Destination): boolean {
|
||||||
destination.stack.pop();
|
destination.stack.pop();
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
writeEndPendingSuspenseBoundary(destination: Destination): boolean {
|
writeEndPendingSuspenseBoundary(destination: Destination): boolean {
|
||||||
destination.stack.pop();
|
destination.stack.pop();
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
writeEndClientRenderedSuspenseBoundary(destination: Destination): boolean {
|
writeEndClientRenderedSuspenseBoundary(destination: Destination): boolean {
|
||||||
destination.stack.pop();
|
destination.stack.pop();
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
writeStartSegment(
|
writeStartSegment(
|
||||||
|
|
@ -218,9 +252,11 @@ const ReactNoopServer = ReactFizzServer({
|
||||||
throw new Error('Segments are only expected at the root of the stack.');
|
throw new Error('Segments are only expected at the root of the stack.');
|
||||||
}
|
}
|
||||||
destination.stack.push(segment);
|
destination.stack.push(segment);
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
writeEndSegment(destination: Destination, formatContext: null): boolean {
|
writeEndSegment(destination: Destination, formatContext: null): boolean {
|
||||||
destination.stack.pop();
|
destination.stack.pop();
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
writeCompletedSegmentInstruction(
|
writeCompletedSegmentInstruction(
|
||||||
|
|
@ -241,6 +277,7 @@ const ReactNoopServer = ReactFizzServer({
|
||||||
0,
|
0,
|
||||||
...segment.children,
|
...segment.children,
|
||||||
);
|
);
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
writeCompletedBoundaryInstruction(
|
writeCompletedBoundaryInstruction(
|
||||||
|
|
@ -255,6 +292,7 @@ const ReactNoopServer = ReactFizzServer({
|
||||||
}
|
}
|
||||||
boundary.children = segment.children;
|
boundary.children = segment.children;
|
||||||
boundary.state = 'complete';
|
boundary.state = 'complete';
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
writeClientRenderBoundaryInstruction(
|
writeClientRenderBoundaryInstruction(
|
||||||
|
|
@ -263,6 +301,7 @@ const ReactNoopServer = ReactFizzServer({
|
||||||
boundary: SuspenseInstance,
|
boundary: SuspenseInstance,
|
||||||
): boolean {
|
): boolean {
|
||||||
boundary.status = 'client-render';
|
boundary.status = 'client-render';
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
writePreambleStart() {},
|
writePreambleStart() {},
|
||||||
|
|
|
||||||
41
packages/react-server/src/ReactFizzServer.js
vendored
41
packages/react-server/src/ReactFizzServer.js
vendored
|
|
@ -51,6 +51,8 @@ import {
|
||||||
import {
|
import {
|
||||||
writeCompletedRoot,
|
writeCompletedRoot,
|
||||||
writePlaceholder,
|
writePlaceholder,
|
||||||
|
pushStartActivityBoundary,
|
||||||
|
pushEndActivityBoundary,
|
||||||
writeStartCompletedSuspenseBoundary,
|
writeStartCompletedSuspenseBoundary,
|
||||||
writeStartPendingSuspenseBoundary,
|
writeStartPendingSuspenseBoundary,
|
||||||
writeStartClientRenderedSuspenseBoundary,
|
writeStartClientRenderedSuspenseBoundary,
|
||||||
|
|
@ -2200,24 +2202,51 @@ function renderLazyComponent(
|
||||||
renderElement(request, task, keyPath, Component, resolvedProps, ref);
|
renderElement(request, task, keyPath, Component, resolvedProps, ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderOffscreen(
|
function renderActivity(
|
||||||
request: Request,
|
request: Request,
|
||||||
task: Task,
|
task: Task,
|
||||||
keyPath: KeyNode,
|
keyPath: KeyNode,
|
||||||
props: Object,
|
props: Object,
|
||||||
): void {
|
): void {
|
||||||
|
const segment = task.blockedSegment;
|
||||||
|
if (segment === null) {
|
||||||
|
// Replay
|
||||||
const mode: ?OffscreenMode = (props.mode: any);
|
const mode: ?OffscreenMode = (props.mode: any);
|
||||||
if (mode === 'hidden') {
|
if (mode === 'hidden') {
|
||||||
// A hidden Offscreen boundary is not server rendered. Prerendering happens
|
// A hidden Activity boundary is not server rendered. Prerendering happens
|
||||||
// on the client.
|
// on the client.
|
||||||
} else {
|
} else {
|
||||||
// A visible Offscreen boundary is treated exactly like a fragment: a
|
// A visible Activity boundary has its children rendered inside the boundary.
|
||||||
// pure indirection.
|
|
||||||
const prevKeyPath = task.keyPath;
|
const prevKeyPath = task.keyPath;
|
||||||
task.keyPath = keyPath;
|
task.keyPath = keyPath;
|
||||||
renderNodeDestructive(request, task, props.children, -1);
|
renderNode(request, task, props.children, -1);
|
||||||
task.keyPath = prevKeyPath;
|
task.keyPath = prevKeyPath;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderViewTransition(
|
function renderViewTransition(
|
||||||
|
|
@ -2291,7 +2320,7 @@ function renderElement(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case REACT_ACTIVITY_TYPE: {
|
case REACT_ACTIVITY_TYPE: {
|
||||||
renderOffscreen(request, task, keyPath, props);
|
renderActivity(request, task, keyPath, props);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
case REACT_SUSPENSE_LIST_TYPE: {
|
case REACT_SUSPENSE_LIST_TYPE: {
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,8 @@ export const pushFormStateMarkerIsNotMatching =
|
||||||
$$$config.pushFormStateMarkerIsNotMatching;
|
$$$config.pushFormStateMarkerIsNotMatching;
|
||||||
export const writeCompletedRoot = $$$config.writeCompletedRoot;
|
export const writeCompletedRoot = $$$config.writeCompletedRoot;
|
||||||
export const writePlaceholder = $$$config.writePlaceholder;
|
export const writePlaceholder = $$$config.writePlaceholder;
|
||||||
|
export const pushStartActivityBoundary = $$$config.pushStartActivityBoundary;
|
||||||
|
export const pushEndActivityBoundary = $$$config.pushEndActivityBoundary;
|
||||||
export const writeStartCompletedSuspenseBoundary =
|
export const writeStartCompletedSuspenseBoundary =
|
||||||
$$$config.writeStartCompletedSuspenseBoundary;
|
$$$config.writeStartCompletedSuspenseBoundary;
|
||||||
export const writeStartPendingSuspenseBoundary =
|
export const writeStartPendingSuspenseBoundary =
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user