Skip to content

[pull] main from facebook:main #132

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 2, 2025
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
39 changes: 26 additions & 13 deletions packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -238,14 +238,15 @@ const ACTIVITY_END_DATA = '/&';
const SUSPENSE_START_DATA = '$';
const SUSPENSE_END_DATA = '/$';
const SUSPENSE_PENDING_START_DATA = '$?';
const SUSPENSE_QUEUED_START_DATA = '$~';
const SUSPENSE_FALLBACK_START_DATA = '$!';
const PREAMBLE_CONTRIBUTION_HTML = 'html';
const PREAMBLE_CONTRIBUTION_BODY = 'body';
const PREAMBLE_CONTRIBUTION_HEAD = 'head';
const FORM_STATE_IS_MATCHING = 'F!';
const FORM_STATE_IS_NOT_MATCHING = 'F';

const DOCUMENT_READY_STATE_COMPLETE = 'complete';
const DOCUMENT_READY_STATE_LOADING = 'loading';

const STYLE = 'style';

Expand Down Expand Up @@ -1084,6 +1085,7 @@ function clearHydrationBoundary(
} else if (
data === SUSPENSE_START_DATA ||
data === SUSPENSE_PENDING_START_DATA ||
data === SUSPENSE_QUEUED_START_DATA ||
data === SUSPENSE_FALLBACK_START_DATA ||
data === ACTIVITY_START_DATA
) {
Expand Down Expand Up @@ -1205,6 +1207,7 @@ function hideOrUnhideDehydratedBoundary(
} else if (
data === SUSPENSE_START_DATA ||
data === SUSPENSE_PENDING_START_DATA ||
data === SUSPENSE_QUEUED_START_DATA ||
data === SUSPENSE_FALLBACK_START_DATA
) {
depth++;
Expand Down Expand Up @@ -3140,7 +3143,10 @@ export function canHydrateSuspenseInstance(
}

export function isSuspenseInstancePending(instance: SuspenseInstance): boolean {
return instance.data === SUSPENSE_PENDING_START_DATA;
return (
instance.data === SUSPENSE_PENDING_START_DATA ||
instance.data === SUSPENSE_QUEUED_START_DATA
);
}

export function isSuspenseInstanceFallback(
Expand All @@ -3149,7 +3155,7 @@ export function isSuspenseInstanceFallback(
return (
instance.data === SUSPENSE_FALLBACK_START_DATA ||
(instance.data === SUSPENSE_PENDING_START_DATA &&
instance.ownerDocument.readyState === DOCUMENT_READY_STATE_COMPLETE)
instance.ownerDocument.readyState !== DOCUMENT_READY_STATE_LOADING)
);
}

Expand Down Expand Up @@ -3192,15 +3198,19 @@ export function registerSuspenseInstanceRetry(
callback: () => void,
) {
const ownerDocument = instance.ownerDocument;
if (
if (instance.data === SUSPENSE_QUEUED_START_DATA) {
// The Fizz runtime has already queued this boundary for reveal. We wait for it
// to be revealed and then retries.
instance._reactRetry = callback;
} else if (
// The Fizz runtime must have put this boundary into client render or complete
// state after the render finished but before it committed. We need to call the
// callback now rather than wait
instance.data !== SUSPENSE_PENDING_START_DATA ||
// The boundary is still in pending status but the document has finished loading
// before we could register the event handler that would have scheduled the retry
// on load so we call teh callback now.
ownerDocument.readyState === DOCUMENT_READY_STATE_COMPLETE
ownerDocument.readyState !== DOCUMENT_READY_STATE_LOADING
) {
callback();
} else {
Expand Down Expand Up @@ -3255,18 +3265,19 @@ function getNextHydratable(node: ?Node) {
break;
}
if (nodeType === COMMENT_NODE) {
const nodeData = (node: any).data;
const data = (node: any).data;
if (
nodeData === SUSPENSE_START_DATA ||
nodeData === SUSPENSE_FALLBACK_START_DATA ||
nodeData === SUSPENSE_PENDING_START_DATA ||
nodeData === ACTIVITY_START_DATA ||
nodeData === FORM_STATE_IS_MATCHING ||
nodeData === FORM_STATE_IS_NOT_MATCHING
data === SUSPENSE_START_DATA ||
data === SUSPENSE_FALLBACK_START_DATA ||
data === SUSPENSE_PENDING_START_DATA ||
data === SUSPENSE_QUEUED_START_DATA ||
data === ACTIVITY_START_DATA ||
data === FORM_STATE_IS_MATCHING ||
data === FORM_STATE_IS_NOT_MATCHING
) {
break;
}
if (nodeData === SUSPENSE_END_DATA || nodeData === ACTIVITY_END_DATA) {
if (data === SUSPENSE_END_DATA || data === ACTIVITY_END_DATA) {
return null;
}
}
Expand Down Expand Up @@ -3494,6 +3505,7 @@ function getNextHydratableInstanceAfterHydrationBoundary(
data === SUSPENSE_START_DATA ||
data === SUSPENSE_FALLBACK_START_DATA ||
data === SUSPENSE_PENDING_START_DATA ||
data === SUSPENSE_QUEUED_START_DATA ||
data === ACTIVITY_START_DATA
) {
depth++;
Expand Down Expand Up @@ -3535,6 +3547,7 @@ export function getParentHydrationBoundary(
data === SUSPENSE_START_DATA ||
data === SUSPENSE_FALLBACK_START_DATA ||
data === SUSPENSE_PENDING_START_DATA ||
data === SUSPENSE_QUEUED_START_DATA ||
data === ACTIVITY_START_DATA
) {
if (depth === 0) {
Expand Down
105 changes: 80 additions & 25 deletions packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import {
completeBoundaryWithStyles as styleInsertionFunction,
completeSegment as completeSegmentFunction,
formReplaying as formReplayingRuntime,
markShellTime,
} from './fizz-instruction-set/ReactDOMFizzInstructionSetInlineCodeStrings';

import {getValueDescriptorExpectingObjectForWarning} from '../shared/ReactDOMResourceValidation';
Expand Down Expand Up @@ -120,13 +121,14 @@ const ScriptStreamingFormat: StreamingFormat = 0;
const DataStreamingFormat: StreamingFormat = 1;

export type InstructionState = number;
const NothingSent /* */ = 0b000000;
const SentCompleteSegmentFunction /* */ = 0b000001;
const SentCompleteBoundaryFunction /* */ = 0b000010;
const SentClientRenderFunction /* */ = 0b000100;
const SentStyleInsertionFunction /* */ = 0b001000;
const SentFormReplayingRuntime /* */ = 0b010000;
const SentCompletedShellId /* */ = 0b100000;
const NothingSent /* */ = 0b0000000;
const SentCompleteSegmentFunction /* */ = 0b0000001;
const SentCompleteBoundaryFunction /* */ = 0b0000010;
const SentClientRenderFunction /* */ = 0b0000100;
const SentStyleInsertionFunction /* */ = 0b0001000;
const SentFormReplayingRuntime /* */ = 0b0010000;
const SentCompletedShellId /* */ = 0b0100000;
const SentMarkShellTime /* */ = 0b1000000;

// Per request, global state that is not contextual to the rendering subtree.
// This cannot be resumed and therefore should only contain things that are
Expand Down Expand Up @@ -4107,21 +4109,53 @@ function writeBootstrap(
return true;
}

const shellTimeRuntimeScript = stringToPrecomputedChunk(markShellTime);

function writeShellTimeInstruction(
destination: Destination,
resumableState: ResumableState,
renderState: RenderState,
): boolean {
if (
enableFizzExternalRuntime &&
resumableState.streamingFormat !== ScriptStreamingFormat
) {
// External runtime always tracks the shell time in the runtime.
return true;
}
if ((resumableState.instructions & SentMarkShellTime) !== NothingSent) {
// We already sent this instruction.
return true;
}
resumableState.instructions |= SentMarkShellTime;
writeChunk(destination, renderState.startInlineScript);
writeCompletedShellIdAttribute(destination, resumableState);
writeChunk(destination, endOfStartTag);
writeChunk(destination, shellTimeRuntimeScript);
return writeChunkAndReturn(destination, endInlineScript);
}

export function writeCompletedRoot(
destination: Destination,
resumableState: ResumableState,
renderState: RenderState,
isComplete: boolean,
): boolean {
if (!isComplete) {
// If we're not already fully complete, we might complete another boundary. If so,
// we need to track the paint time of the shell so we know how much to throttle the reveal.
writeShellTimeInstruction(destination, resumableState, renderState);
}
const preamble = renderState.preamble;
if (preamble.htmlChunks || preamble.headChunks) {
// If we rendered the whole document, then we emitted a rel="expect" that needs a
// matching target. Normally we use one of the bootstrap scripts for this but if
// there are none, then we need to emit a tag to complete the shell.
if ((resumableState.instructions & SentCompletedShellId) === NothingSent) {
const bootstrapChunks = renderState.bootstrapChunks;
bootstrapChunks.push(startChunkForTag('template'));
pushCompletedShellIdAttribute(bootstrapChunks, resumableState);
bootstrapChunks.push(endOfStartTag, endChunkForTag('template'));
writeChunk(destination, startChunkForTag('template'));
writeCompletedShellIdAttribute(destination, resumableState);
writeChunk(destination, endOfStartTag);
writeChunk(destination, endChunkForTag('template'));
}
}
return writeBootstrap(destination, renderState);
Expand Down Expand Up @@ -4482,14 +4516,14 @@ export function writeCompletedSegmentInstruction(
}
}

const completeBoundaryScriptFunctionOnly = stringToPrecomputedChunk(
completeBoundaryFunction,
);
const completeBoundaryScript1Full = stringToPrecomputedChunk(
completeBoundaryFunction + '$RC("',
);
const completeBoundaryScript1Partial = stringToPrecomputedChunk('$RC("');

const completeBoundaryWithStylesScript1FullBoth = stringToPrecomputedChunk(
completeBoundaryFunction + styleInsertionFunction + '$RR("',
);
const completeBoundaryWithStylesScript1FullPartial = stringToPrecomputedChunk(
styleInsertionFunction + '$RR("',
);
Expand Down Expand Up @@ -4531,19 +4565,27 @@ export function writeCompletedBoundaryInstruction(
writeChunk(destination, renderState.startInlineScript);
writeChunk(destination, endOfStartTag);
if (requiresStyleInsertion) {
if (
(resumableState.instructions & SentClientRenderFunction) ===
NothingSent
) {
// The completeBoundaryWithStyles function depends on the client render function.
resumableState.instructions |= SentClientRenderFunction;
writeChunk(destination, clientRenderScriptFunctionOnly);
}
if (
(resumableState.instructions & SentCompleteBoundaryFunction) ===
NothingSent
) {
resumableState.instructions |=
SentStyleInsertionFunction | SentCompleteBoundaryFunction;
writeChunk(destination, completeBoundaryWithStylesScript1FullBoth);
} else if (
// The completeBoundaryWithStyles function depends on the complete boundary function.
resumableState.instructions |= SentCompleteBoundaryFunction;
writeChunk(destination, completeBoundaryScriptFunctionOnly);
}
if (
(resumableState.instructions & SentStyleInsertionFunction) ===
NothingSent
) {
resumableState.instructions |= SentStyleInsertionFunction;

writeChunk(destination, completeBoundaryWithStylesScript1FullPartial);
} else {
writeChunk(destination, completeBoundaryWithStylesScript1Partial);
Expand Down Expand Up @@ -4608,6 +4650,9 @@ export function writeCompletedBoundaryInstruction(
return writeBootstrap(destination, renderState) && writeMore;
}

const clientRenderScriptFunctionOnly =
stringToPrecomputedChunk(clientRenderFunction);

const clientRenderScript1Full = stringToPrecomputedChunk(
clientRenderFunction + ';$RX("',
);
Expand Down Expand Up @@ -5004,6 +5049,21 @@ function writeBlockingRenderInstruction(

const completedShellIdAttributeStart = stringToPrecomputedChunk(' id="');

function writeCompletedShellIdAttribute(
destination: Destination,
resumableState: ResumableState,
): void {
if ((resumableState.instructions & SentCompletedShellId) !== NothingSent) {
return;
}
resumableState.instructions |= SentCompletedShellId;
const idPrefix = resumableState.idPrefix;
const shellId = '\u00AB' + idPrefix + 'R\u00BB';
writeChunk(destination, completedShellIdAttributeStart);
writeChunk(destination, stringToChunk(escapeTextForBrowser(shellId)));
writeChunk(destination, attributeEnd);
}

function pushCompletedShellIdAttribute(
target: Array<Chunk | PrecomputedChunk>,
resumableState: ResumableState,
Expand All @@ -5029,15 +5089,10 @@ export function writePreambleStart(
destination: Destination,
resumableState: ResumableState,
renderState: RenderState,
willFlushAllSegments: boolean,
skipExpect?: boolean, // Used as an override by ReactFizzConfigMarkup
): void {
// This function must be called exactly once on every request
if (
enableFizzExternalRuntime &&
!willFlushAllSegments &&
renderState.externalRuntimeScript
) {
if (enableFizzExternalRuntime && renderState.externalRuntimeScript) {
// If the root segment is incomplete due to suspended tasks
// (e.g. willFlushAllSegments = false) and we are using data
// streaming format, ensure the external runtime is sent.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ import {completeBoundary} from './ReactDOMFizzInstructionSetShared';

// This is a string so Closure's advanced compilation mode doesn't mangle it.
// eslint-disable-next-line dot-notation
window['$RB'] = [];
// eslint-disable-next-line dot-notation
window['$RC'] = completeBoundary;
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Track the paint time of the shell
requestAnimationFrame(() => {
// eslint-disable-next-line dot-notation
window['$RT'] = performance.now();
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,26 @@ import {
// This is a string so Closure's advanced compilation mode doesn't mangle it.
// These will be renamed to local references by the external-runtime-plugin.
window['$RM'] = new Map();
window['$RB'] = [];
window['$RX'] = clientRenderBoundary;
window['$RC'] = completeBoundary;
window['$RR'] = completeBoundaryWithStyles;
window['$RS'] = completeSegment;

listenToFormSubmissionsForReplaying();

// Track the paint time of the shell.
const entries = performance.getEntriesByType
? performance.getEntriesByType('paint')
: [];
if (entries.length > 0) {
// We might have already painted before this external runtime loaded. In that case we
// try to get the first paint from the performance metrics to avoid delaying further
// than necessary.
window['$RT'] = entries[0].startTime;
} else {
// Otherwise we wait for the next rAF for it.
requestAnimationFrame(() => {
window['$RT'] = performance.now();
});
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading