Files
react/packages/react-server/src/ReactFlightNewContext.js
T
salazarm d5f1b067c8 [ServerContext] Flight support for ServerContext (#23244)
* Flight side of server context

* 1 more test

* rm unused function

* flow+prettier

* flow again =)

* duplicate ReactServerContext across packages

* store default value when lazily initializing server context

* .

* better comment

* derp... missing import

* rm optional chaining

* missed feature flag

* React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED ??

* add warning if non ServerContext passed into useServerContext

* pass context in as array of arrays

* make importServerContext nott pollute the global context state

* merge main

* remove useServerContext

* dont rely on object getters in ReactServerContext and disallow JSX

* add symbols to devtools + rename globalServerContextRegistry to just ContextRegistry

* gate test case as experimental

* feedback

* remove unions

* Lint

* fix oopsies (tests/lint/mismatching arguments/signatures

* lint again

* replace-fork

* remove extraneous change

* rebase

* 1 more test

* rm unused function

* flow+prettier

* flow again =)

* duplicate ReactServerContext across packages

* store default value when lazily initializing server context

* .

* better comment

* derp... missing import

* rm optional chaining

* missed feature flag

* React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED ??

* add warning if non ServerContext passed into useServerContext

* pass context in as array of arrays

* make importServerContext nott pollute the global context state

* merge main

* remove useServerContext

* dont rely on object getters in ReactServerContext and disallow JSX

* add symbols to devtools + rename globalServerContextRegistry to just ContextRegistry

* gate test case as experimental

* feedback

* remove unions

* Lint

* fix oopsies (tests/lint/mismatching arguments/signatures

* lint again

* replace-fork

* remove extraneous change

* rebase

* reinline

* rebase

* add back changes lost due to rebase being hard

* emit chunk for provider

* remove case for React provider type

* update type for SomeChunk

* enable flag with experimental

* add missing types

* fix flow type

* missing type

* t: any

* revert extraneous type change

* better type

* better type

* feedback

* change import to type import

* test?

* test?

* remove react-dom

* remove react-native-renderer from react-server-native-relay/package.json

* gate change in FiberNewContext, getComponentNameFromType, use switch statement in FlightServer

* getComponentNameFromTpe: server context type gated and use displayName if available

* fallthrough

* lint....

* POP

* lint
2022-03-08 07:55:32 -05:00

270 lines
8.2 KiB
JavaScript

/**
* Copyright (c) Facebook, Inc. and its 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 {
ReactServerContext,
ServerContextJSONValue,
} from 'shared/ReactTypes';
import {REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED} from 'shared/ReactSymbols';
import {isPrimaryRenderer} from './ReactServerFormatConfig';
let rendererSigil;
if (__DEV__) {
// Use this to detect multiple renderers using the same context
rendererSigil = {};
}
// Used to store the parent path of all context overrides in a shared linked list.
// Forming a reverse tree.
type ContextNode<T: ServerContextJSONValue> = {
parent: null | ContextNode<any>,
depth: number, // Short hand to compute the depth of the tree at this node.
context: ReactServerContext<T>,
parentValue: T,
value: T,
};
// The structure of a context snapshot is an implementation of this file.
// Currently, it's implemented as tracking the current active node.
export opaque type ContextSnapshot = null | ContextNode<any>;
export const rootContextSnapshot: ContextSnapshot = null;
// We assume that this runtime owns the "current" field on all ReactContext instances.
// This global (actually thread local) state represents what state all those "current",
// fields are currently in.
let currentActiveSnapshot: ContextSnapshot = null;
function popNode(prev: ContextNode<any>): void {
if (isPrimaryRenderer) {
prev.context._currentValue = prev.parentValue;
} else {
prev.context._currentValue2 = prev.parentValue;
}
}
function pushNode(next: ContextNode<any>): void {
if (isPrimaryRenderer) {
next.context._currentValue = next.value;
} else {
next.context._currentValue2 = next.value;
}
}
function popToNearestCommonAncestor(
prev: ContextNode<any>,
next: ContextNode<any>,
): void {
if (prev === next) {
// We've found a shared ancestor. We don't need to pop nor reapply this one or anything above.
} else {
popNode(prev);
const parentPrev = prev.parent;
const parentNext = next.parent;
if (parentPrev === null) {
if (parentNext !== null) {
throw new Error(
'The stacks must reach the root at the same time. This is a bug in React.',
);
}
} else {
if (parentNext === null) {
throw new Error(
'The stacks must reach the root at the same time. This is a bug in React.',
);
}
popToNearestCommonAncestor(parentPrev, parentNext);
// On the way back, we push the new ones that weren't common.
pushNode(next);
}
}
}
function popAllPrevious(prev: ContextNode<any>): void {
popNode(prev);
const parentPrev = prev.parent;
if (parentPrev !== null) {
popAllPrevious(parentPrev);
}
}
function pushAllNext(next: ContextNode<any>): void {
const parentNext = next.parent;
if (parentNext !== null) {
pushAllNext(parentNext);
}
pushNode(next);
}
function popPreviousToCommonLevel(
prev: ContextNode<any>,
next: ContextNode<any>,
): void {
popNode(prev);
const parentPrev = prev.parent;
if (parentPrev === null) {
throw new Error(
'The depth must equal at least at zero before reaching the root. This is a bug in React.',
);
}
if (parentPrev.depth === next.depth) {
// We found the same level. Now we just need to find a shared ancestor.
popToNearestCommonAncestor(parentPrev, next);
} else {
// We must still be deeper.
popPreviousToCommonLevel(parentPrev, next);
}
}
function popNextToCommonLevel(
prev: ContextNode<any>,
next: ContextNode<any>,
): void {
const parentNext = next.parent;
if (parentNext === null) {
throw new Error(
'The depth must equal at least at zero before reaching the root. This is a bug in React.',
);
}
if (prev.depth === parentNext.depth) {
// We found the same level. Now we just need to find a shared ancestor.
popToNearestCommonAncestor(prev, parentNext);
} else {
// We must still be deeper.
popNextToCommonLevel(prev, parentNext);
}
pushNode(next);
}
// Perform context switching to the new snapshot.
// To make it cheap to read many contexts, while not suspending, we make the switch eagerly by
// updating all the context's current values. That way reads, always just read the current value.
// At the cost of updating contexts even if they're never read by this subtree.
export function switchContext(newSnapshot: ContextSnapshot): void {
// The basic algorithm we need to do is to pop back any contexts that are no longer on the stack.
// We also need to update any new contexts that are now on the stack with the deepest value.
// The easiest way to update new contexts is to just reapply them in reverse order from the
// perspective of the backpointers. To avoid allocating a lot when switching, we use the stack
// for that. Therefore this algorithm is recursive.
// 1) First we pop which ever snapshot tree was deepest. Popping old contexts as we go.
// 2) Then we find the nearest common ancestor from there. Popping old contexts as we go.
// 3) Then we reapply new contexts on the way back up the stack.
const prev = currentActiveSnapshot;
const next = newSnapshot;
if (prev !== next) {
if (prev === null) {
// $FlowFixMe: This has to be non-null since it's not equal to prev.
pushAllNext(next);
} else if (next === null) {
popAllPrevious(prev);
} else if (prev.depth === next.depth) {
popToNearestCommonAncestor(prev, next);
} else if (prev.depth > next.depth) {
popPreviousToCommonLevel(prev, next);
} else {
popNextToCommonLevel(prev, next);
}
currentActiveSnapshot = next;
}
}
export function pushProvider<T: ServerContextJSONValue>(
context: ReactServerContext<T>,
nextValue: T,
): ContextSnapshot {
let prevValue;
if (isPrimaryRenderer) {
prevValue = context._currentValue;
context._currentValue = nextValue;
if (__DEV__) {
if (
context._currentRenderer !== undefined &&
context._currentRenderer !== null &&
context._currentRenderer !== rendererSigil
) {
console.error(
'Detected multiple renderers concurrently rendering the ' +
'same context provider. This is currently unsupported.',
);
}
context._currentRenderer = rendererSigil;
}
} else {
prevValue = context._currentValue2;
context._currentValue2 = nextValue;
if (__DEV__) {
if (
context._currentRenderer2 !== undefined &&
context._currentRenderer2 !== null &&
context._currentRenderer2 !== rendererSigil
) {
console.error(
'Detected multiple renderers concurrently rendering the ' +
'same context provider. This is currently unsupported.',
);
}
context._currentRenderer2 = rendererSigil;
}
}
const prevNode = currentActiveSnapshot;
const newNode: ContextNode<T> = {
parent: prevNode,
depth: prevNode === null ? 0 : prevNode.depth + 1,
context: context,
parentValue: prevValue,
value: nextValue,
};
currentActiveSnapshot = newNode;
return newNode;
}
export function popProvider(): ContextSnapshot {
const prevSnapshot = currentActiveSnapshot;
if (prevSnapshot === null) {
throw new Error(
'Tried to pop a Context at the root of the app. This is a bug in React.',
);
}
if (isPrimaryRenderer) {
const value = prevSnapshot.parentValue;
if (value === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED) {
prevSnapshot.context._currentValue = prevSnapshot.context._defaultValue;
} else {
prevSnapshot.context._currentValue = value;
}
} else {
const value = prevSnapshot.parentValue;
if (value === REACT_SERVER_CONTEXT_DEFAULT_VALUE_NOT_LOADED) {
prevSnapshot.context._currentValue2 = prevSnapshot.context._defaultValue;
} else {
prevSnapshot.context._currentValue2 = value;
}
}
return (currentActiveSnapshot = prevSnapshot.parent);
}
export function getActiveContext(): ContextSnapshot {
return currentActiveSnapshot;
}
export function readContext<T>(context: ReactServerContext<T>): T {
const value = isPrimaryRenderer
? context._currentValue
: context._currentValue2;
return value;
}