Compare commits

...

13 Commits

Author SHA1 Message Date
Sebastian Markbage
e471f106ab Track owner on componentStorage 2024-04-22 12:06:40 -04:00
Sebastian Markbage
7611cd82ef Split out current owner for parity with Fiber 2024-04-22 12:06:07 -04:00
Sebastian Markbage
a282ee0950 Add component storage async context
This will store owner on a per component basis.
2024-04-22 12:06:05 -04:00
Sebastian Markbage
88e47069c1 Resolve cycle 2024-04-22 12:05:55 -04:00
Sebastian Markbage
1af924d03c Remove old field from internals 2024-04-22 12:05:53 -04:00
Sebastian Markbage
ae602c3dcb Update tests of the internal states 2024-04-22 12:05:45 -04:00
Sebastian Markbage
b5150a3417 Get owner from the dispatcher in JSX
We need to be careful about only getting it in branches that has the dispatcher
activated and if the dispatcher is activate.
2024-04-22 12:05:45 -04:00
Sebastian Markbage
afaf8a1f67 Same thing for Flight
This will later use AsyncLocalStorage to track this just like the cache.
2024-04-22 12:05:45 -04:00
Sebastian Markbage
b217f5adf1 Track currentOwner for Fiber locally inside the reconciler
This means that usage internal to the reconciler can refer to it directly
without going through the dispatcher.
2024-04-22 12:05:45 -04:00
Sebastian Markbage
84d0305f89 Push the async dispatcher if either feature is used for this flag
Basically always but we currently don't actually need it for prod on the
client.
2024-04-22 12:05:45 -04:00
Sebastian Markbage
76cc7fe481 Rename Cache files to AsyncDispatcher 2024-04-22 12:05:45 -04:00
Sebastian Markbage
d87e6e4e07 Add getOwner on the AsyncDispatcher 2024-04-22 12:05:45 -04:00
Sebastian Markbage
d5d6023a01 Rename CacheDispatcher to AsyncDispatcher 2024-04-22 12:05:42 -04:00
36 changed files with 377 additions and 160 deletions

View File

@@ -537,16 +537,24 @@ describe('ReactCompositeComponent', () => {
});
it('should cleanup even if render() fatals', async () => {
const dispatcherEnabled =
__DEV__ ||
!gate(flags => flags.disableStringRefs) ||
gate(flags => flags.enableCache);
const ownerEnabled = __DEV__ || !gate(flags => flags.disableStringRefs);
let stashedDispatcher;
class BadComponent extends React.Component {
render() {
// Stash the dispatcher that was available in render so we can check
// that its internals also reset.
stashedDispatcher = ReactSharedInternals.A;
throw new Error();
}
}
const instance = <BadComponent />;
expect(ReactSharedInternals.owner).toBe(
__DEV__ || !gate(flags => flags.disableStringRefs) ? null : undefined,
);
expect(ReactSharedInternals.A).toBe(dispatcherEnabled ? null : undefined);
const root = ReactDOMClient.createRoot(document.createElement('div'));
await expect(async () => {
@@ -555,9 +563,16 @@ describe('ReactCompositeComponent', () => {
});
}).rejects.toThrow();
expect(ReactSharedInternals.owner).toBe(
__DEV__ || !gate(flags => flags.disableStringRefs) ? null : undefined,
);
expect(ReactSharedInternals.A).toBe(dispatcherEnabled ? null : undefined);
if (dispatcherEnabled) {
if (ownerEnabled) {
expect(stashedDispatcher.getOwner()).toBe(null);
} else {
expect(stashedDispatcher.getOwner).toBe(undefined);
}
} else {
expect(stashedDispatcher).toBe(undefined);
}
});
it('should call componentWillUnmount before unmounting', async () => {

View File

@@ -61,7 +61,7 @@ import {LegacyRoot} from 'react-reconciler/src/ReactRootTags';
import getComponentNameFromType from 'shared/getComponentNameFromType';
import {has as hasInstance} from 'shared/ReactInstanceMap';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {currentOwner} from 'react-reconciler/src/ReactFiberCurrentOwner';
import assign from 'shared/assign';
@@ -342,7 +342,7 @@ export function findDOMNode(
componentOrElement: Element | ?React$Component<any, any>,
): null | Element | Text {
if (__DEV__) {
const owner = (ReactSharedInternals.owner: any);
const owner = currentOwner;
if (owner !== null && owner.stateNode !== null) {
const warnedAboutRefsInRender = owner.stateNode._warnedAboutRefsInRender;
if (!warnedAboutRefsInRender) {

View File

@@ -24,14 +24,14 @@ import {
findHostInstanceWithWarning,
} from 'react-reconciler/src/ReactFiberReconciler';
import {doesFiberContain} from 'react-reconciler/src/ReactFiberTreeReflection';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import getComponentNameFromType from 'shared/getComponentNameFromType';
import {currentOwner} from 'react-reconciler/src/ReactFiberCurrentOwner';
export function findHostInstance_DEPRECATED<TElementType: ElementType>(
componentOrHandle: ?(ElementRef<TElementType> | number),
): ?ElementRef<HostComponent<mixed>> {
if (__DEV__) {
const owner = ReactSharedInternals.owner;
const owner = currentOwner;
if (owner !== null && owner.stateNode !== null) {
if (!owner.stateNode._warnedAboutRefsInRender) {
console.error(
@@ -86,7 +86,7 @@ export function findHostInstance_DEPRECATED<TElementType: ElementType>(
export function findNodeHandle(componentOrHandle: any): ?number {
if (__DEV__) {
const owner = ReactSharedInternals.owner;
const owner = currentOwner;
if (owner !== null && owner.stateNode !== null) {
if (!owner.stateNode._warnedAboutRefsInRender) {
console.error(

View File

@@ -7,13 +7,17 @@
* @flow
*/
import type {CacheDispatcher} from './ReactInternalTypes';
import type {AsyncDispatcher, Fiber} from './ReactInternalTypes';
import type {Cache} from './ReactFiberCacheComponent';
import {enableCache} from 'shared/ReactFeatureFlags';
import {readContext} from './ReactFiberNewContext';
import {CacheContext} from './ReactFiberCacheComponent';
import {disableStringRefs} from 'shared/ReactFeatureFlags';
import {currentOwner} from './ReactFiberCurrentOwner';
function getCacheForType<T>(resourceType: () => T): T {
if (!enableCache) {
throw new Error('Not implemented.');
@@ -27,6 +31,12 @@ function getCacheForType<T>(resourceType: () => T): T {
return cacheForType;
}
export const DefaultCacheDispatcher: CacheDispatcher = {
export const DefaultAsyncDispatcher: AsyncDispatcher = ({
getCacheForType,
};
}: any);
if (__DEV__ || !disableStringRefs) {
DefaultAsyncDispatcher.getOwner = (): null | Fiber => {
return currentOwner;
};
}

View File

@@ -91,7 +91,6 @@ import {
Passive,
DidDefer,
} from './ReactFiberFlags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {
debugRenderPhaseSideEffectsForStrictMode,
disableLegacyContext,
@@ -297,6 +296,7 @@ import {
pushRootMarkerInstance,
TransitionTracingMarker,
} from './ReactFiberTracingMarkerComponent';
import {setCurrentOwner} from './ReactFiberCurrentOwner';
// A special exception that's used to unwind the stack when an update flows
// into a dehydrated boundary.
@@ -432,7 +432,7 @@ function updateForwardRef(
markComponentRenderStarted(workInProgress);
}
if (__DEV__) {
ReactSharedInternals.owner = workInProgress;
setCurrentOwner(workInProgress);
setIsRendering(true);
nextChildren = renderWithHooks(
current,
@@ -1150,7 +1150,7 @@ function updateFunctionComponent(
markComponentRenderStarted(workInProgress);
}
if (__DEV__) {
ReactSharedInternals.owner = workInProgress;
setCurrentOwner(workInProgress);
setIsRendering(true);
nextChildren = renderWithHooks(
current,
@@ -1373,7 +1373,7 @@ function finishClassComponent(
// Rerender
if (__DEV__ || !disableStringRefs) {
ReactSharedInternals.owner = workInProgress;
setCurrentOwner(workInProgress);
}
let nextChildren;
if (
@@ -3419,7 +3419,7 @@ function updateContextConsumer(
}
let newChildren;
if (__DEV__) {
ReactSharedInternals.owner = workInProgress;
setCurrentOwner(workInProgress);
setIsRendering(true);
newChildren = render(newValue);
setIsRendering(false);

View File

@@ -0,0 +1,16 @@
/**
* 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 {Fiber} from './ReactInternalTypes';
export let currentOwner: Fiber | null = null;
export function setCurrentOwner(fiber: null | Fiber) {
currentOwner = fiber;
}

View File

@@ -12,7 +12,6 @@ import type {Container, SuspenseInstance} from './ReactFiberConfig';
import type {SuspenseState} from './ReactFiberSuspenseComponent';
import {get as getInstance} from 'shared/ReactInstanceMap';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFromFiber';
import {
ClassComponent,
@@ -25,6 +24,7 @@ import {
SuspenseComponent,
} from './ReactWorkTags';
import {NoFlags, Placement, Hydrating} from './ReactFiberFlags';
import {currentOwner} from './ReactFiberCurrentOwner';
export function getNearestMountedFiber(fiber: Fiber): null | Fiber {
let node = fiber;
@@ -89,7 +89,7 @@ export function isFiberMounted(fiber: Fiber): boolean {
export function isMounted(component: React$Component<any, any>): boolean {
if (__DEV__) {
const owner = (ReactSharedInternals.owner: any);
const owner = currentOwner;
if (owner !== null && owner.tag === ClassComponent) {
const ownerFiber: Fiber = owner;
const instance = ownerFiber.stateNode;

View File

@@ -203,7 +203,8 @@ import {
resetHooksOnUnwind,
ContextOnlyDispatcher,
} from './ReactFiberHooks';
import {DefaultCacheDispatcher} from './ReactFiberCache';
import {DefaultAsyncDispatcher} from './ReactFiberAsyncDispatcher';
import {setCurrentOwner} from './ReactFiberCurrentOwner';
import {
createCapturedValueAtFiber,
type CapturedValue,
@@ -1684,7 +1685,7 @@ function handleThrow(root: FiberRoot, thrownValue: any): void {
resetHooksAfterThrow();
resetCurrentDebugFiberInDEV();
if (__DEV__ || !disableStringRefs) {
ReactSharedInternals.owner = null;
setCurrentOwner(null);
}
if (thrownValue === SuspenseException) {
@@ -1874,19 +1875,19 @@ function popDispatcher(prevDispatcher: any) {
ReactSharedInternals.H = prevDispatcher;
}
function pushCacheDispatcher() {
if (enableCache) {
const prevCacheDispatcher = ReactSharedInternals.C;
ReactSharedInternals.C = DefaultCacheDispatcher;
return prevCacheDispatcher;
function pushAsyncDispatcher() {
if (enableCache || __DEV__ || !disableStringRefs) {
const prevAsyncDispatcher = ReactSharedInternals.A;
ReactSharedInternals.A = DefaultAsyncDispatcher;
return prevAsyncDispatcher;
} else {
return null;
}
}
function popCacheDispatcher(prevCacheDispatcher: any) {
if (enableCache) {
ReactSharedInternals.C = prevCacheDispatcher;
function popAsyncDispatcher(prevAsyncDispatcher: any) {
if (enableCache || __DEV__ || !disableStringRefs) {
ReactSharedInternals.A = prevAsyncDispatcher;
}
}
@@ -1963,7 +1964,7 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
const prevExecutionContext = executionContext;
executionContext |= RenderContext;
const prevDispatcher = pushDispatcher(root.containerInfo);
const prevCacheDispatcher = pushCacheDispatcher();
const prevAsyncDispatcher = pushAsyncDispatcher();
// If the root or lanes have changed, throw out the existing stack
// and prepare a fresh one. Otherwise we'll continue where we left off.
@@ -2061,7 +2062,7 @@ function renderRootSync(root: FiberRoot, lanes: Lanes) {
executionContext = prevExecutionContext;
popDispatcher(prevDispatcher);
popCacheDispatcher(prevCacheDispatcher);
popAsyncDispatcher(prevAsyncDispatcher);
if (workInProgress !== null) {
// This is a sync render, so we should have finished the whole tree.
@@ -2104,7 +2105,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
const prevExecutionContext = executionContext;
executionContext |= RenderContext;
const prevDispatcher = pushDispatcher(root.containerInfo);
const prevCacheDispatcher = pushCacheDispatcher();
const prevAsyncDispatcher = pushAsyncDispatcher();
// If the root or lanes have changed, throw out the existing stack
// and prepare a fresh one. Otherwise we'll continue where we left off.
@@ -2317,7 +2318,7 @@ function renderRootConcurrent(root: FiberRoot, lanes: Lanes) {
resetContextDependencies();
popDispatcher(prevDispatcher);
popCacheDispatcher(prevCacheDispatcher);
popAsyncDispatcher(prevAsyncDispatcher);
executionContext = prevExecutionContext;
if (__DEV__) {
@@ -2386,7 +2387,7 @@ function performUnitOfWork(unitOfWork: Fiber): void {
}
if (__DEV__ || !disableStringRefs) {
ReactSharedInternals.owner = null;
setCurrentOwner(null);
}
}
@@ -2501,7 +2502,7 @@ function replaySuspendedUnitOfWork(unitOfWork: Fiber): void {
}
if (__DEV__ || !disableStringRefs) {
ReactSharedInternals.owner = null;
setCurrentOwner(null);
}
}
@@ -2894,7 +2895,7 @@ function commitRootImpl(
// Reset this to null before calling lifecycles
if (__DEV__ || !disableStringRefs) {
ReactSharedInternals.owner = null;
setCurrentOwner(null);
}
// The commit phase is broken into several sub-phases. We do a separate pass

View File

@@ -434,6 +434,8 @@ export type Dispatcher = {
) => [Awaited<S>, (P) => void, boolean],
};
export type CacheDispatcher = {
export type AsyncDispatcher = {
getCacheForType: <T>(resourceType: () => T) => T,
// DEV-only (or !disableStringRefs)
getOwner: () => null | Fiber | ReactComponentInfo,
};

View File

@@ -21,6 +21,8 @@ if (typeof Blob === 'undefined') {
if (typeof File === 'undefined') {
global.File = require('buffer').File;
}
// Patch for Edge environments for global scope
global.AsyncLocalStorage = require('async_hooks').AsyncLocalStorage;
// Don't wait before processing work on the server.
// TODO: we can replace this with FlightServer.act().
@@ -32,6 +34,7 @@ let webpackMap;
let webpackModules;
let webpackModuleLoading;
let React;
let ReactServer;
let ReactDOMServer;
let ReactServerDOMServer;
let ReactServerDOMClient;
@@ -55,6 +58,7 @@ describe('ReactFlightDOMEdge', () => {
webpackModules = WebpackMock.webpackModules;
webpackModuleLoading = WebpackMock.moduleLoading;
ReactServer = require('react');
ReactServerDOMServer = require('react-server-dom-webpack/server');
jest.resetModules();
@@ -572,6 +576,73 @@ describe('ReactFlightDOMEdge', () => {
);
});
it('supports async server component debug info as the element owner in DEV', async () => {
function Container({children}) {
return children;
}
const promise = Promise.resolve(true);
async function Greeting({firstName}) {
// We can't use JSX here because it'll use the Client React.
const child = ReactServer.createElement(
'span',
null,
'Hello, ' + firstName,
);
// Yield the synchronous pass
await promise;
// We should still be able to track owner using AsyncLocalStorage.
return ReactServer.createElement(Container, null, child);
}
const model = {
greeting: ReactServer.createElement(Greeting, {firstName: 'Seb'}),
};
const stream = ReactServerDOMServer.renderToReadableStream(
model,
webpackMap,
);
const rootModel = await ReactServerDOMClient.createFromReadableStream(
stream,
{
ssrManifest: {
moduleMap: null,
moduleLoading: null,
},
},
);
const ssrStream = await ReactDOMServer.renderToReadableStream(
rootModel.greeting,
);
const result = await readResult(ssrStream);
expect(result).toEqual('<span>Hello, Seb</span>');
// Resolve the React Lazy wrapper which must have resolved by now.
const lazyWrapper = rootModel.greeting;
const greeting = lazyWrapper._init(lazyWrapper._payload);
// We've rendered down to the span.
expect(greeting.type).toBe('span');
if (__DEV__) {
const greetInfo = {name: 'Greeting', env: 'Server', owner: null};
expect(lazyWrapper._debugInfo).toEqual([
greetInfo,
{name: 'Container', env: 'Server', owner: greetInfo},
]);
// The owner that created the span was the outer server component.
// We expect the debug info to be referentially equal to the owner.
expect(greeting._owner).toBe(lazyWrapper._debugInfo[0]);
} else {
expect(lazyWrapper._debugInfo).toBe(undefined);
expect(greeting._owner).toBe(
gate(flags => flags.disableStringRefs) ? undefined : null,
);
}
});
// @gate enableFlightReadableStream && enableBinaryFlight
it('should supports ReadableStreams with typed arrays', async () => {
const buffer = new Uint8Array([

View File

@@ -0,0 +1,27 @@
/**
* 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 {AsyncDispatcher} from 'react-reconciler/src/ReactInternalTypes';
import {disableStringRefs} from 'shared/ReactFeatureFlags';
function getCacheForType<T>(resourceType: () => T): T {
throw new Error('Not implemented.');
}
export const DefaultAsyncDispatcher: AsyncDispatcher = ({
getCacheForType,
}: any);
if (__DEV__ || !disableStringRefs) {
// Fizz never tracks owner but the JSX runtime looks for this.
DefaultAsyncDispatcher.getOwner = (): null => {
return null;
};
}

View File

@@ -1,18 +0,0 @@
/**
* 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 {CacheDispatcher} from 'react-reconciler/src/ReactInternalTypes';
function getCacheForType<T>(resourceType: () => T): T {
throw new Error('Not implemented.');
}
export const DefaultCacheDispatcher: CacheDispatcher = {
getCacheForType,
};

View File

@@ -109,7 +109,7 @@ import {
getActionStateCount,
getActionStateMatchingIndex,
} from './ReactFizzHooks';
import {DefaultCacheDispatcher} from './ReactFizzCache';
import {DefaultAsyncDispatcher} from './ReactFizzAsyncDispatcher';
import {getStackByComponentStackNode} from './ReactFizzComponentStack';
import {emptyTreeContext, pushTreeContext} from './ReactFizzTreeContext';
@@ -144,6 +144,7 @@ import {
enableRenderableContext,
enableRefAsProp,
disableDefaultPropsExceptForClasses,
disableStringRefs,
} from 'shared/ReactFeatureFlags';
import assign from 'shared/assign';
@@ -3663,10 +3664,10 @@ export function performWork(request: Request): void {
const prevContext = getActiveContext();
const prevDispatcher = ReactSharedInternals.H;
ReactSharedInternals.H = HooksDispatcher;
let prevCacheDispatcher = null;
if (enableCache) {
prevCacheDispatcher = ReactSharedInternals.C;
ReactSharedInternals.C = DefaultCacheDispatcher;
let prevAsyncDispatcher = null;
if (enableCache || __DEV__ || !disableStringRefs) {
prevAsyncDispatcher = ReactSharedInternals.A;
ReactSharedInternals.A = DefaultAsyncDispatcher;
}
const prevRequest = currentRequest;
@@ -3698,7 +3699,7 @@ export function performWork(request: Request): void {
setCurrentResumableState(prevResumableState);
ReactSharedInternals.H = prevDispatcher;
if (enableCache) {
ReactSharedInternals.C = prevCacheDispatcher;
ReactSharedInternals.A = prevAsyncDispatcher;
}
if (__DEV__) {

View File

@@ -73,6 +73,8 @@ import {
isServerReference,
supportsRequestStorage,
requestStorage,
supportsComponentStorage,
componentStorage,
createHints,
initAsyncDebugInfo,
} from './ReactFlightServerConfig';
@@ -89,7 +91,9 @@ import {
getThenableStateAfterSuspending,
resetHooksForRequest,
} from './ReactFlightHooks';
import {DefaultCacheDispatcher} from './flight/ReactFlightServerCache';
import {DefaultAsyncDispatcher} from './flight/ReactFlightAsyncDispatcher';
import {resolveOwner, setCurrentOwner} from './flight/ReactFlightCurrentOwner';
import {
getIteratorFn,
@@ -158,7 +162,7 @@ function patchConsole(consoleInst: typeof console, methodName: string) {
// We don't currently use this id for anything but we emit it so that we can later
// refer to previous logs in debug info to associate them with a component.
const id = request.nextChunkId++;
const owner: null | ReactComponentInfo = ReactSharedInternals.owner;
const owner: null | ReactComponentInfo = resolveOwner();
emitConsoleChunk(request, id, methodName, owner, stack, arguments);
}
// $FlowFixMe[prop-missing]
@@ -358,14 +362,14 @@ export function createRequest(
environmentName: void | string,
): Request {
if (
ReactSharedInternals.C !== null &&
ReactSharedInternals.C !== DefaultCacheDispatcher
ReactSharedInternals.A !== null &&
ReactSharedInternals.A !== DefaultAsyncDispatcher
) {
throw new Error(
'Currently React only supports one RSC renderer at a time.',
);
}
ReactSharedInternals.C = DefaultCacheDispatcher;
ReactSharedInternals.A = DefaultAsyncDispatcher;
const abortSet: Set<Task> = new Set();
const pingedTasks: Array<Task> = [];
@@ -818,7 +822,11 @@ function renderFunctionComponent<Props>(
const prevThenableState = task.thenableState;
task.thenableState = null;
let componentDebugInfo: null | ReactComponentInfo = null;
// The secondArg is always undefined in Server Components since refs error early.
const secondArg = undefined;
let result;
let componentDebugInfo: ReactComponentInfo;
if (__DEV__) {
if (debugID === null) {
// We don't have a chunk to assign debug info. We need to outline this
@@ -847,22 +855,28 @@ function renderFunctionComponent<Props>(
outlineModel(request, componentDebugInfo);
emitDebugChunk(request, componentDebugID, componentDebugInfo);
}
}
prepareToUseHooksForComponent(prevThenableState, componentDebugInfo);
// The secondArg is always undefined in Server Components since refs error early.
const secondArg = undefined;
let result;
if (__DEV__) {
ReactSharedInternals.owner = componentDebugInfo;
prepareToUseHooksForComponent(prevThenableState, componentDebugInfo);
setCurrentOwner(componentDebugInfo);
try {
result = Component(props, secondArg);
if (supportsComponentStorage) {
// Run the component in an Async Context that tracks the current owner.
result = componentStorage.run(
componentDebugInfo,
Component,
props,
secondArg,
);
} else {
result = Component(props, secondArg);
}
} finally {
ReactSharedInternals.owner = null;
setCurrentOwner(null);
}
} else {
prepareToUseHooksForComponent(prevThenableState, null);
result = Component(props, secondArg);
}
if (
typeof result === 'object' &&
result !== null &&

View File

@@ -7,10 +7,16 @@
* @flow
*/
import type {CacheDispatcher} from 'react-reconciler/src/ReactInternalTypes';
import type {ReactComponentInfo} from 'shared/ReactTypes';
import type {AsyncDispatcher} from 'react-reconciler/src/ReactInternalTypes';
import {resolveRequest, getCache} from '../ReactFlightServer';
import {disableStringRefs} from 'shared/ReactFeatureFlags';
import {resolveOwner} from './ReactFlightCurrentOwner';
function resolveCache(): Map<Function, mixed> {
const request = resolveRequest();
if (request) {
@@ -19,7 +25,7 @@ function resolveCache(): Map<Function, mixed> {
return new Map();
}
export const DefaultCacheDispatcher: CacheDispatcher = {
export const DefaultAsyncDispatcher: AsyncDispatcher = ({
getCacheForType<T>(resourceType: () => T): T {
const cache = resolveCache();
let entry: T | void = (cache.get(resourceType): any);
@@ -30,4 +36,13 @@ export const DefaultCacheDispatcher: CacheDispatcher = {
}
return entry;
},
};
}: any);
if (__DEV__) {
DefaultAsyncDispatcher.getOwner = resolveOwner;
} else if (!disableStringRefs) {
// Server Components never use string refs but the JSX runtime looks for it.
DefaultAsyncDispatcher.getOwner = (): null | ReactComponentInfo => {
return null;
};
}

View File

@@ -0,0 +1,30 @@
/**
* 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 {ReactComponentInfo} from 'shared/ReactTypes';
import {
supportsComponentStorage,
componentStorage,
} from '../ReactFlightServerConfig';
let currentOwner: ReactComponentInfo | null = null;
export function setCurrentOwner(componentInfo: null | ReactComponentInfo) {
currentOwner = componentInfo;
}
export function resolveOwner(): null | ReactComponentInfo {
if (currentOwner) return currentOwner;
if (supportsComponentStorage) {
const owner = componentStorage.getStore();
if (owner) return owner;
}
return null;
}

View File

@@ -8,6 +8,7 @@
*/
import type {Request} from 'react-server/src/ReactFlightServer';
import type {ReactComponentInfo} from 'shared/ReactTypes';
export * from '../ReactFlightServerConfigBundlerCustom';
@@ -23,6 +24,10 @@ export const isPrimaryRenderer = false;
export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request | void> = (null: any);
export const supportsComponentStorage = false;
export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> =
(null: any);
export function createHints(): any {
return null;
}

View File

@@ -6,15 +6,18 @@
*
* @flow
*/
import {AsyncLocalStorage} from 'async_hooks';
import type {Request} from 'react-server/src/ReactFlightServer';
import type {ReactComponentInfo} from 'shared/ReactTypes';
export * from 'react-server-dom-esm/src/ReactFlightServerConfigESMBundler';
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
export const supportsRequestStorage = true;
export const requestStorage: AsyncLocalStorage<Request | void> =
new AsyncLocalStorage();
export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request | void> = (null: any);
export const supportsComponentStorage = false;
export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> =
(null: any);
export * from '../ReactFlightServerConfigDebugNoop';

View File

@@ -8,6 +8,7 @@
*/
import type {Request} from 'react-server/src/ReactFlightServer';
import type {ReactComponentInfo} from 'shared/ReactTypes';
export * from 'react-server-dom-turbopack/src/ReactFlightServerConfigTurbopackBundler';
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
@@ -15,4 +16,8 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request | void> = (null: any);
export const supportsComponentStorage = false;
export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> =
(null: any);
export * from '../ReactFlightServerConfigDebugNoop';

View File

@@ -8,6 +8,7 @@
*/
import type {Request} from 'react-server/src/ReactFlightServer';
import type {ReactComponentInfo} from 'shared/ReactTypes';
export * from 'react-server-dom-webpack/src/ReactFlightServerConfigWebpackBundler';
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
@@ -15,4 +16,8 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request | void> = (null: any);
export const supportsComponentStorage = false;
export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> =
(null: any);
export * from '../ReactFlightServerConfigDebugNoop';

View File

@@ -8,6 +8,7 @@
*/
import type {Request} from 'react-server/src/ReactFlightServer';
import type {ReactComponentInfo} from 'shared/ReactTypes';
export * from '../ReactFlightServerConfigBundlerCustom';
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
@@ -15,4 +16,8 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request | void> = (null: any);
export const supportsComponentStorage = false;
export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> =
(null: any);
export * from '../ReactFlightServerConfigDebugNoop';

View File

@@ -7,6 +7,7 @@
* @flow
*/
import type {Request} from 'react-server/src/ReactFlightServer';
import type {ReactComponentInfo} from 'shared/ReactTypes';
export * from 'react-server-dom-turbopack/src/ReactFlightServerConfigTurbopackBundler';
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
@@ -16,6 +17,11 @@ export const supportsRequestStorage = typeof AsyncLocalStorage === 'function';
export const requestStorage: AsyncLocalStorage<Request | void> =
supportsRequestStorage ? new AsyncLocalStorage() : (null: any);
export const supportsComponentStorage: boolean =
__DEV__ && supportsRequestStorage;
export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> =
supportsComponentStorage ? new AsyncLocalStorage() : (null: any);
// We use the Node version but get access to async_hooks from a global.
import type {HookCallbacks, AsyncHook} from 'async_hooks';
export const createAsyncHook: HookCallbacks => AsyncHook =

View File

@@ -6,7 +6,9 @@
*
* @flow
*/
import type {Request} from 'react-server/src/ReactFlightServer';
import type {ReactComponentInfo} from 'shared/ReactTypes';
export * from 'react-server-dom-webpack/src/ReactFlightServerConfigWebpackBundler';
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
@@ -16,6 +18,11 @@ export const supportsRequestStorage = typeof AsyncLocalStorage === 'function';
export const requestStorage: AsyncLocalStorage<Request | void> =
supportsRequestStorage ? new AsyncLocalStorage() : (null: any);
export const supportsComponentStorage: boolean =
__DEV__ && supportsRequestStorage;
export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> =
supportsComponentStorage ? new AsyncLocalStorage() : (null: any);
// We use the Node version but get access to async_hooks from a global.
import type {HookCallbacks, AsyncHook} from 'async_hooks';
export const createAsyncHook: HookCallbacks => AsyncHook =

View File

@@ -8,6 +8,7 @@
*/
import type {Request} from 'react-server/src/ReactFlightServer';
import type {ReactComponentInfo} from 'shared/ReactTypes';
export * from '../ReactFlightServerConfigBundlerCustom';
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
@@ -15,4 +16,8 @@ export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
export const supportsRequestStorage = false;
export const requestStorage: AsyncLocalStorage<Request | void> = (null: any);
export const supportsComponentStorage = false;
export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> =
(null: any);
export * from '../ReactFlightServerConfigDebugNoop';

View File

@@ -6,9 +6,11 @@
*
* @flow
*/
import {AsyncLocalStorage} from 'async_hooks';
import type {Request} from 'react-server/src/ReactFlightServer';
import type {ReactComponentInfo} from 'shared/ReactTypes';
export * from 'react-server-dom-esm/src/ReactFlightServerConfigESMBundler';
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
@@ -17,5 +19,9 @@ export const supportsRequestStorage = true;
export const requestStorage: AsyncLocalStorage<Request | void> =
new AsyncLocalStorage();
export const supportsComponentStorage = __DEV__;
export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> =
supportsComponentStorage ? new AsyncLocalStorage() : (null: any);
export {createHook as createAsyncHook, executionAsyncId} from 'async_hooks';
export * from '../ReactFlightServerConfigDebugNode';

View File

@@ -10,6 +10,7 @@
import {AsyncLocalStorage} from 'async_hooks';
import type {Request} from 'react-server/src/ReactFlightServer';
import type {ReactComponentInfo} from 'shared/ReactTypes';
export * from 'react-server-dom-turbopack/src/ReactFlightServerConfigTurbopackBundler';
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
@@ -18,5 +19,9 @@ export const supportsRequestStorage = true;
export const requestStorage: AsyncLocalStorage<Request | void> =
new AsyncLocalStorage();
export const supportsComponentStorage = __DEV__;
export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> =
supportsComponentStorage ? new AsyncLocalStorage() : (null: any);
export {createHook as createAsyncHook, executionAsyncId} from 'async_hooks';
export * from '../ReactFlightServerConfigDebugNode';

View File

@@ -10,6 +10,7 @@
import {AsyncLocalStorage} from 'async_hooks';
import type {Request} from 'react-server/src/ReactFlightServer';
import type {ReactComponentInfo} from 'shared/ReactTypes';
export * from 'react-server-dom-webpack/src/ReactFlightServerConfigWebpackBundler';
export * from 'react-dom-bindings/src/server/ReactFlightServerConfigDOM';
@@ -18,5 +19,9 @@ export const supportsRequestStorage = true;
export const requestStorage: AsyncLocalStorage<Request | void> =
new AsyncLocalStorage();
export const supportsComponentStorage = __DEV__;
export const componentStorage: AsyncLocalStorage<ReactComponentInfo | void> =
supportsComponentStorage ? new AsyncLocalStorage() : (null: any);
export {createHook as createAsyncHook, executionAsyncId} from 'async_hooks';
export * from '../ReactFlightServerConfigDebugNode';

View File

@@ -7,12 +7,12 @@
* @flow
*/
import type {CacheDispatcher} from 'react-reconciler/src/ReactInternalTypes';
import type {AsyncDispatcher} from 'react-reconciler/src/ReactInternalTypes';
import ReactSharedInternals from 'shared/ReactSharedInternals';
export function waitForSuspense<T>(fn: () => T): Promise<T> {
const cache: Map<Function, mixed> = new Map();
const testDispatcher: CacheDispatcher = {
const testDispatcher: AsyncDispatcher = {
getCacheForType<R>(resourceType: () => R): R {
let entry: R | void = (cache.get(resourceType): any);
if (entry === undefined) {
@@ -22,12 +22,15 @@ export function waitForSuspense<T>(fn: () => T): Promise<T> {
}
return entry;
},
getOwner(): null {
return null;
},
};
// Not using async/await because we don't compile it.
return new Promise((resolve, reject) => {
function retry() {
const prevDispatcher = ReactSharedInternals.C;
ReactSharedInternals.C = testDispatcher;
const prevDispatcher = ReactSharedInternals.A;
ReactSharedInternals.A = testDispatcher;
try {
const result = fn();
resolve(result);
@@ -38,7 +41,7 @@ export function waitForSuspense<T>(fn: () => T): Promise<T> {
reject(thrownValue);
}
} finally {
ReactSharedInternals.C = prevDispatcher;
ReactSharedInternals.A = prevDispatcher;
}
}
retry();

View File

@@ -54,7 +54,7 @@ function createCacheNode<T>(): CacheNode<T> {
export function cache<A: Iterable<mixed>, T>(fn: (...A) => T): (...A) => T {
return function () {
const dispatcher = ReactSharedInternals.C;
const dispatcher = ReactSharedInternals.A;
if (!dispatcher) {
// If there is no dispatcher, then we treat this as not being cached.
// $FlowFixMe[incompatible-call]: We don't want to use rest arguments since we transpile the code.

View File

@@ -46,7 +46,7 @@ if (enableCache && enableFetchInstrumentation) {
resource: URL | RequestInfo,
options?: RequestOptions,
) {
const dispatcher = ReactSharedInternals.C;
const dispatcher = ReactSharedInternals.A;
if (!dispatcher) {
// We're outside a cached scope.
return originalFetch(resource, options);

View File

@@ -44,7 +44,7 @@ function resolveDispatcher() {
}
export function getCacheForType<T>(resourceType: () => T): T {
const dispatcher = ReactSharedInternals.C;
const dispatcher = ReactSharedInternals.A;
if (!dispatcher) {
// If there is no dispatcher, then we treat this as not being cached.
return resourceType();

View File

@@ -8,19 +8,15 @@
*/
import type {Dispatcher} from 'react-reconciler/src/ReactInternalTypes';
import type {CacheDispatcher} from 'react-reconciler/src/ReactInternalTypes';
import type {AsyncDispatcher} from 'react-reconciler/src/ReactInternalTypes';
import type {BatchConfigTransition} from 'react-reconciler/src/ReactFiberTracingMarkerComponent';
import type {Fiber} from 'react-reconciler/src/ReactInternalTypes';
import {disableStringRefs} from 'shared/ReactFeatureFlags';
export type SharedStateClient = {
H: null | Dispatcher, // ReactCurrentDispatcher for Hooks
C: null | CacheDispatcher, // ReactCurrentCache for Cache
A: null | AsyncDispatcher, // ReactCurrentCache for Cache
T: null | BatchConfigTransition, // ReactCurrentBatchConfig for Transitions
// DEV-only-ish
owner: null | Fiber, // ReactCurrentOwner is Fiber on the Client, null in Fizz. Flight uses SharedStateServer.
// DEV-only
// ReactCurrentActQueue
actQueue: null | Array<RendererTask>,
@@ -47,14 +43,10 @@ export type RendererTask = boolean => RendererTask | null;
const ReactSharedInternals: SharedStateClient = ({
H: null,
C: null,
A: null,
T: null,
}: any);
if (__DEV__ || !disableStringRefs) {
ReactSharedInternals.owner = null;
}
if (__DEV__) {
ReactSharedInternals.actQueue = null;
ReactSharedInternals.isBatchingLegacy = false;

View File

@@ -8,8 +8,7 @@
*/
import type {Dispatcher} from 'react-reconciler/src/ReactInternalTypes';
import type {CacheDispatcher} from 'react-reconciler/src/ReactInternalTypes';
import type {ReactComponentInfo} from 'shared/ReactTypes';
import type {AsyncDispatcher} from 'react-reconciler/src/ReactInternalTypes';
import type {
Reference,
@@ -24,11 +23,11 @@ import {
TaintRegistryPendingRequests,
} from './ReactTaintRegistry';
import {disableStringRefs, enableTaint} from 'shared/ReactFeatureFlags';
import {enableTaint} from 'shared/ReactFeatureFlags';
export type SharedStateServer = {
H: null | Dispatcher, // ReactCurrentDispatcher for Hooks
C: null | CacheDispatcher, // ReactCurrentCache for Cache
A: null | AsyncDispatcher, // ReactCurrentCache for Cache
// enableTaint
TaintRegistryObjects: WeakMap<Reference, string>,
@@ -36,8 +35,7 @@ export type SharedStateServer = {
TaintRegistryByteLengths: Set<number>,
TaintRegistryPendingRequests: Set<RequestCleanupQueue>,
// DEV-only-ish
owner: null | ReactComponentInfo, // ReactCurrentOwner is ReactComponentInfo in Flight, null in Fizz. Fiber/Fizz uses SharedStateClient.
// DEV-only
// ReactDebugCurrentFrame
setExtraStackFrame: (stack: null | string) => void,
@@ -49,7 +47,7 @@ export type RendererTask = boolean => RendererTask | null;
const ReactSharedInternals: SharedStateServer = ({
H: null,
C: null,
A: null,
}: any);
if (enableTaint) {
@@ -60,10 +58,6 @@ if (enableTaint) {
TaintRegistryPendingRequests;
}
if (__DEV__ || !disableStringRefs) {
ReactSharedInternals.owner = null;
}
if (__DEV__) {
let currentExtraStackFrame = (null: null | string);
ReactSharedInternals.setExtraStackFrame = function (stack: null | string) {

View File

@@ -29,6 +29,17 @@ import getComponentNameFromFiber from 'react-reconciler/src/getComponentNameFrom
const REACT_CLIENT_REFERENCE = Symbol.for('react.client.reference');
function getOwner() {
if (__DEV__ || !disableStringRefs) {
const dispatcher = ReactSharedInternals.A;
if (dispatcher === null) {
return null;
}
return dispatcher.getOwner();
}
return null;
}
let specialPropKeyWarningShown;
let specialPropRefWarningShown;
let didWarnAboutStringRefs;
@@ -66,16 +77,15 @@ function hasValidKey(config) {
function warnIfStringRefCannotBeAutoConverted(config, self) {
if (__DEV__) {
let owner;
if (
!disableStringRefs &&
typeof config.ref === 'string' &&
ReactSharedInternals.owner &&
(owner = getOwner()) &&
self &&
ReactSharedInternals.owner.stateNode !== self
owner.stateNode !== self
) {
const componentName = getComponentNameFromType(
ReactSharedInternals.owner.type,
);
const componentName = getComponentNameFromType(owner.type);
if (!didWarnAboutStringRefs[componentName]) {
console.error(
@@ -85,7 +95,7 @@ function warnIfStringRefCannotBeAutoConverted(config, self) {
'We ask you to manually fix this case by using useRef() or createRef() instead. ' +
'Learn more about using refs safely here: ' +
'https://react.dev/link/strict-mode-string-ref',
getComponentNameFromType(ReactSharedInternals.owner.type),
getComponentNameFromType(owner.type),
config.ref,
);
didWarnAboutStringRefs[componentName] = true;
@@ -339,7 +349,7 @@ export function jsxProd(type, config, maybeKey) {
if (!enableRefAsProp) {
ref = config.ref;
if (!disableStringRefs) {
ref = coerceStringRef(ref, ReactSharedInternals.owner, type);
ref = coerceStringRef(ref, getOwner(), type);
}
}
}
@@ -365,11 +375,7 @@ export function jsxProd(type, config, maybeKey) {
// Skip over reserved prop names
if (propName !== 'key' && (enableRefAsProp || propName !== 'ref')) {
if (enableRefAsProp && !disableStringRefs && propName === 'ref') {
props.ref = coerceStringRef(
config[propName],
ReactSharedInternals.owner,
type,
);
props.ref = coerceStringRef(config[propName], getOwner(), type);
} else {
props[propName] = config[propName];
}
@@ -389,15 +395,7 @@ export function jsxProd(type, config, maybeKey) {
}
}
return ReactElement(
type,
key,
ref,
undefined,
undefined,
ReactSharedInternals.owner,
props,
);
return ReactElement(type, key, ref, undefined, undefined, getOwner(), props);
}
// While `jsxDEV` should never be called when running in production, we do
@@ -571,7 +569,7 @@ export function jsxDEV(type, config, maybeKey, isStaticChildren, source, self) {
if (!enableRefAsProp) {
ref = config.ref;
if (!disableStringRefs) {
ref = coerceStringRef(ref, ReactSharedInternals.owner, type);
ref = coerceStringRef(ref, getOwner(), type);
}
}
if (!disableStringRefs) {
@@ -600,11 +598,7 @@ export function jsxDEV(type, config, maybeKey, isStaticChildren, source, self) {
// Skip over reserved prop names
if (propName !== 'key' && (enableRefAsProp || propName !== 'ref')) {
if (enableRefAsProp && !disableStringRefs && propName === 'ref') {
props.ref = coerceStringRef(
config[propName],
ReactSharedInternals.owner,
type,
);
props.ref = coerceStringRef(config[propName], getOwner(), type);
} else {
props[propName] = config[propName];
}
@@ -643,7 +637,7 @@ export function jsxDEV(type, config, maybeKey, isStaticChildren, source, self) {
ref,
self,
source,
ReactSharedInternals.owner,
getOwner(),
props,
);
@@ -749,7 +743,7 @@ export function createElement(type, config, children) {
if (!enableRefAsProp) {
ref = config.ref;
if (!disableStringRefs) {
ref = coerceStringRef(ref, ReactSharedInternals.owner, type);
ref = coerceStringRef(ref, getOwner(), type);
}
}
@@ -779,11 +773,7 @@ export function createElement(type, config, children) {
propName !== '__source'
) {
if (enableRefAsProp && !disableStringRefs && propName === 'ref') {
props.ref = coerceStringRef(
config[propName],
ReactSharedInternals.owner,
type,
);
props.ref = coerceStringRef(config[propName], getOwner(), type);
} else {
props[propName] = config[propName];
}
@@ -839,7 +829,7 @@ export function createElement(type, config, children) {
ref,
undefined,
undefined,
ReactSharedInternals.owner,
getOwner(),
props,
);
@@ -889,7 +879,7 @@ export function cloneElement(element, config, children) {
if (config != null) {
if (hasValidRef(config)) {
owner = ReactSharedInternals.owner;
owner = __DEV__ || !disableStringRefs ? getOwner() : undefined;
if (!enableRefAsProp) {
// Silently steal the ref from the parent.
ref = config.ref;
@@ -983,8 +973,9 @@ export function cloneElement(element, config, children) {
function getDeclarationErrorAddendum() {
if (__DEV__) {
if (ReactSharedInternals.owner) {
const name = getComponentNameFromType(ReactSharedInternals.owner.type);
const owner = getOwner();
if (owner) {
const name = getComponentNameFromType(owner.type);
if (name) {
return '\n\nCheck the render method of `' + name + '`.';
}
@@ -1085,11 +1076,7 @@ function validateExplicitKey(element, parentType) {
// property, it may be the creator of the child that's responsible for
// assigning it a key.
let childOwner = '';
if (
element &&
element._owner != null &&
element._owner !== ReactSharedInternals.owner
) {
if (element && element._owner != null && element._owner !== getOwner()) {
let ownerName = null;
if (typeof element._owner.tag === 'number') {
ownerName = getComponentNameFromType(element._owner.type);