mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
8bda71558c
follow up to https://github.com/facebook/react/pull/32163 This continues the work of making Suspense workable anywhere in a react-dom tree. See the prior PRs for how we handle server rendering and client rendering. In this change we update the hydration implementation to be able to locate expected nodes. In particular this means hydration understands now that the default hydration context is the document body when the container is above the body. One case that is unique to hydration is clearing Suspense boundaries. When hydration fails or when the server instructs the client to recover an errored boundary it's possible that the html, head, and body tags in the initial document were written from a fallback or a different primary content on the server and need to be replaced by the client render. However these tags (and in the case of head, their content) won't be inside the comment nodes that identify the bounds of the Suspense boundary. And when client rendering you may not even render the same singletons that were server rendered. So when server rendering a boudnary which contributes to the preamble (the html, head, and body tag openings plus the head contents) we emit a special marker comment just before closing the boundary out. This marker encodes which parts of the preamble this boundary owned. If we need to clear the suspense boundary on the client we read this marker and use it to reset the appropriate singleton state.
193 lines
4.8 KiB
JavaScript
193 lines
4.8 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
import type {ReactNodeList} from 'shared/ReactTypes';
|
|
|
|
import type {
|
|
RenderState,
|
|
ResumableState,
|
|
PreambleState,
|
|
HoistableState,
|
|
FormatContext,
|
|
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
|
|
|
|
import {pushStartInstance as pushStartInstanceImpl} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
|
|
|
|
import type {
|
|
Destination,
|
|
Chunk,
|
|
PrecomputedChunk,
|
|
} from 'react-server/src/ReactServerStreamConfig';
|
|
|
|
import type {FormStatus} from 'react-dom-bindings/src/shared/ReactDOMFormActions';
|
|
|
|
import {NotPending} from 'react-dom-bindings/src/shared/ReactDOMFormActions';
|
|
|
|
import hasOwnProperty from 'shared/hasOwnProperty';
|
|
|
|
// Allow embedding inside another Fizz render.
|
|
export const isPrimaryRenderer = false;
|
|
|
|
// Disable Client Hooks
|
|
export const supportsClientAPIs = false;
|
|
|
|
import {stringToChunk} from 'react-server/src/ReactServerStreamConfig';
|
|
|
|
export type {
|
|
RenderState,
|
|
ResumableState,
|
|
HoistableState,
|
|
PreambleState,
|
|
FormatContext,
|
|
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
|
|
|
|
export {
|
|
getChildFormatContext,
|
|
makeId,
|
|
pushEndInstance,
|
|
pushFormStateMarkerIsMatching,
|
|
pushFormStateMarkerIsNotMatching,
|
|
writeStartSegment,
|
|
writeEndSegment,
|
|
writeCompletedSegmentInstruction,
|
|
writeCompletedBoundaryInstruction,
|
|
writeClientRenderBoundaryInstruction,
|
|
writeStartPendingSuspenseBoundary,
|
|
writeEndPendingSuspenseBoundary,
|
|
writeHoistablesForBoundary,
|
|
writePlaceholder,
|
|
writeCompletedRoot,
|
|
createRootFormatContext,
|
|
createRenderState,
|
|
createResumableState,
|
|
createPreambleState,
|
|
createHoistableState,
|
|
writePreambleStart,
|
|
writePreambleEnd,
|
|
writeHoistables,
|
|
writePostamble,
|
|
hoistHoistables,
|
|
resetResumableState,
|
|
completeResumableState,
|
|
emitEarlyPreloads,
|
|
doctypeChunk,
|
|
canHavePreamble,
|
|
hoistPreambleState,
|
|
isPreambleReady,
|
|
isPreambleContext,
|
|
} from 'react-dom-bindings/src/server/ReactFizzConfigDOM';
|
|
|
|
import escapeTextForBrowser from 'react-dom-bindings/src/server/escapeTextForBrowser';
|
|
|
|
export function pushStartInstance(
|
|
target: Array<Chunk | PrecomputedChunk>,
|
|
type: string,
|
|
props: Object,
|
|
resumableState: ResumableState,
|
|
renderState: RenderState,
|
|
preambleState: null | PreambleState,
|
|
hoistableState: null | HoistableState,
|
|
formatContext: FormatContext,
|
|
textEmbedded: boolean,
|
|
isFallback: boolean,
|
|
): ReactNodeList {
|
|
for (const propKey in props) {
|
|
if (hasOwnProperty.call(props, propKey)) {
|
|
const propValue = props[propKey];
|
|
if (propKey === 'ref' && propValue != null) {
|
|
throw new Error(
|
|
'Cannot pass ref in renderToHTML because they will never be hydrated.',
|
|
);
|
|
}
|
|
if (typeof propValue === 'function') {
|
|
throw new Error(
|
|
'Cannot pass event handlers (' +
|
|
propKey +
|
|
') in renderToHTML because ' +
|
|
'the HTML will never be hydrated so they can never get called.',
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
return pushStartInstanceImpl(
|
|
target,
|
|
type,
|
|
props,
|
|
resumableState,
|
|
renderState,
|
|
preambleState,
|
|
hoistableState,
|
|
formatContext,
|
|
textEmbedded,
|
|
isFallback,
|
|
);
|
|
}
|
|
|
|
export function pushTextInstance(
|
|
target: Array<Chunk | PrecomputedChunk>,
|
|
text: string,
|
|
renderState: RenderState,
|
|
textEmbedded: boolean,
|
|
): boolean {
|
|
// Markup doesn't need any termination.
|
|
target.push(stringToChunk(escapeTextForBrowser(text)));
|
|
return false;
|
|
}
|
|
|
|
export function pushSegmentFinale(
|
|
target: Array<Chunk | PrecomputedChunk>,
|
|
renderState: RenderState,
|
|
lastPushedText: boolean,
|
|
textEmbedded: boolean,
|
|
): void {
|
|
// Markup doesn't need any termination.
|
|
return;
|
|
}
|
|
|
|
export function writeStartCompletedSuspenseBoundary(
|
|
destination: Destination,
|
|
renderState: RenderState,
|
|
): boolean {
|
|
// Markup doesn't have any instructions.
|
|
return true;
|
|
}
|
|
export function writeStartClientRenderedSuspenseBoundary(
|
|
destination: Destination,
|
|
renderState: RenderState,
|
|
// flushing these error arguments are not currently supported in this legacy streaming format.
|
|
errorDigest: ?string,
|
|
errorMessage: ?string,
|
|
errorStack: ?string,
|
|
errorComponentStack: ?string,
|
|
): boolean {
|
|
// Markup doesn't have any instructions.
|
|
return true;
|
|
}
|
|
|
|
export function writeEndCompletedSuspenseBoundary(
|
|
destination: Destination,
|
|
renderState: RenderState,
|
|
preambleState: null | PreambleState,
|
|
): boolean {
|
|
// Markup doesn't have any instructions.
|
|
return true;
|
|
}
|
|
export function writeEndClientRenderedSuspenseBoundary(
|
|
destination: Destination,
|
|
renderState: RenderState,
|
|
preambleState: null | PreambleState,
|
|
): boolean {
|
|
// Markup doesn't have any instructions.
|
|
return true;
|
|
}
|
|
|
|
export type TransitionStatus = FormStatus;
|
|
export const NotPendingTransition: TransitionStatus = NotPending;
|