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
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,17 @@ export type Hints = Set<string>;
export function createHints(): Hints {
return new Set();
}

export opaque type FormatContext = null;

export function createRootFormatContext(): FormatContext {
return null;
}

export function getChildFormatContext(
parentContext: FormatContext,
type: string,
props: Object,
): FormatContext {
return parentContext;
}
45 changes: 45 additions & 0 deletions packages/react-server/src/ReactFlightServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import type {
Hints,
HintCode,
HintModel,
FormatContext,
} from './ReactFlightServerConfig';
import type {ThenableState} from './ReactFlightThenable';
import type {
Expand Down Expand Up @@ -88,6 +89,8 @@ import {
supportsRequestStorage,
requestStorage,
createHints,
createRootFormatContext,
getChildFormatContext,
initAsyncDebugInfo,
markAsyncSequenceRootTask,
getCurrentAsyncSequence,
Expand Down Expand Up @@ -525,6 +528,7 @@ type Task = {
toJSON: (key: string, value: ReactClientValue) => ReactJSONValue,
keyPath: null | string, // parent server component keys
implicitSlot: boolean, // true if the root server component of this sequence had a null key
formatContext: FormatContext, // an approximate parent context from host components
thenableState: ThenableState | null,
timed: boolean, // Profiling-only. Whether we need to track the completion time of this task.
time: number, // Profiling-only. The last time stamp emitted for this task.
Expand Down Expand Up @@ -758,6 +762,7 @@ function RequestInstance(
model,
null,
false,
createRootFormatContext(),
abortSet,
timeOrigin,
null,
Expand Down Expand Up @@ -980,6 +985,7 @@ function serializeThenable(
(thenable: any), // will be replaced by the value before we retry. used for debug info.
task.keyPath, // the server component sequence continues through Promise-as-a-child.
task.implicitSlot,
task.formatContext,
request.abortableTasks,
enableProfilerTimer &&
(enableComponentPerformanceTrack || enableAsyncDebugInfo)
Expand Down Expand Up @@ -1102,6 +1108,7 @@ function serializeReadableStream(
task.model,
task.keyPath,
task.implicitSlot,
task.formatContext,
request.abortableTasks,
enableProfilerTimer &&
(enableComponentPerformanceTrack || enableAsyncDebugInfo)
Expand Down Expand Up @@ -1197,6 +1204,7 @@ function serializeAsyncIterable(
task.model,
task.keyPath,
task.implicitSlot,
task.formatContext,
request.abortableTasks,
enableProfilerTimer &&
(enableComponentPerformanceTrack || enableAsyncDebugInfo)
Expand Down Expand Up @@ -2028,6 +2036,7 @@ function deferTask(request: Request, task: Task): ReactJSONValue {
task.model, // the currently rendering element
task.keyPath, // unlike outlineModel this one carries along context
task.implicitSlot,
task.formatContext,
request.abortableTasks,
enableProfilerTimer &&
(enableComponentPerformanceTrack || enableAsyncDebugInfo)
Expand All @@ -2048,6 +2057,7 @@ function outlineTask(request: Request, task: Task): ReactJSONValue {
task.model, // the currently rendering element
task.keyPath, // unlike outlineModel this one carries along context
task.implicitSlot,
task.formatContext,
request.abortableTasks,
enableProfilerTimer &&
(enableComponentPerformanceTrack || enableAsyncDebugInfo)
Expand Down Expand Up @@ -2214,6 +2224,22 @@ function renderElement(
}
}
}
} else if (typeof type === 'string') {
const parentFormatContext = task.formatContext;
const newFormatContext = getChildFormatContext(
parentFormatContext,
type,
props,
);
if (parentFormatContext !== newFormatContext && props.children != null) {
// We've entered a new context. We need to create another Task which has
// the new context set up since it's not safe to push/pop in the middle of
// a tree. Additionally this means that any deduping within this tree now
// assumes the new context even if it's reused outside in a different context.
// We'll rely on this to dedupe the value later as we discover it again
// inside the returned element's tree.
outlineModelWithFormatContext(request, props.children, newFormatContext);
}
}
// For anything else, try it on the client instead.
// We don't know if the client will support it or not. This might error on the
Expand Down Expand Up @@ -2530,6 +2556,7 @@ function createTask(
model: ReactClientValue,
keyPath: null | string,
implicitSlot: boolean,
formatContext: FormatContext,
abortSet: Set<Task>,
lastTimestamp: number, // Profiling-only
debugOwner: null | ReactComponentInfo, // DEV-only
Expand All @@ -2554,6 +2581,7 @@ function createTask(
model,
keyPath,
implicitSlot,
formatContext: formatContext,
ping: () => pingTask(request, task),
toJSON: function (
this:
Expand Down Expand Up @@ -2819,11 +2847,26 @@ function serializeDebugClientReference(
}

function outlineModel(request: Request, value: ReactClientValue): number {
return outlineModelWithFormatContext(
request,
value,
// For deduped values we don't know which context it will be reused in
// so we have to assume that it's the root context.
createRootFormatContext(),
);
}

function outlineModelWithFormatContext(
request: Request,
value: ReactClientValue,
formatContext: FormatContext,
): number {
const newTask = createTask(
request,
value,
null, // The way we use outlining is for reusing an object.
false, // It makes no sense for that use case to be contextual.
formatContext, // Except for FormatContext we optimistically use it.
request.abortableTasks,
enableProfilerTimer &&
(enableComponentPerformanceTrack || enableAsyncDebugInfo)
Expand Down Expand Up @@ -3071,6 +3114,7 @@ function serializeBlob(request: Request, blob: Blob): string {
model,
null,
false,
createRootFormatContext(),
request.abortableTasks,
enableProfilerTimer &&
(enableComponentPerformanceTrack || enableAsyncDebugInfo)
Expand Down Expand Up @@ -3208,6 +3252,7 @@ function renderModel(
task.model,
task.keyPath,
task.implicitSlot,
task.formatContext,
request.abortableTasks,
enableProfilerTimer &&
(enableComponentPerformanceTrack || enableAsyncDebugInfo)
Expand Down
14 changes: 14 additions & 0 deletions packages/react-server/src/forks/ReactFlightServerConfig.custom.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,17 @@ export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> =
export function createHints(): any {
return null;
}

export type FormatContext = null;

export function createRootFormatContext(): FormatContext {
return null;
}

export function getChildFormatContext(
parentContext: FormatContext,
type: string,
props: Object,
): FormatContext {
return parentContext;
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,17 @@ export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> =
export function createHints(): any {
return null;
}

export type FormatContext = null;

export function createRootFormatContext(): FormatContext {
return null;
}

export function getChildFormatContext(
parentContext: FormatContext,
type: string,
props: Object,
): FormatContext {
return parentContext;
}
14 changes: 14 additions & 0 deletions packages/react-server/src/forks/ReactFlightServerConfig.markup.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@ export function createHints(): Hints {
return null;
}

export type FormatContext = null;

export function createRootFormatContext(): FormatContext {
return null;
}

export function getChildFormatContext(
parentContext: FormatContext,
type: string,
props: Object,
): FormatContext {
return parentContext;
}

export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request | void> = (null: any);

Expand Down
Loading