diff --git a/packages/react-reconciler/src/ReactCurrentFiber.js b/packages/react-reconciler/src/ReactCurrentFiber.js
index fd2b4e7d80291..c73a997d48cff 100644
--- a/packages/react-reconciler/src/ReactCurrentFiber.js
+++ b/packages/react-reconciler/src/ReactCurrentFiber.js
@@ -44,7 +44,7 @@ export function getCurrentParentStackInDev(): string {
return '';
}
-function getCurrentFiberStackInDev(stack: Error): string {
+function getCurrentFiberStackInDev(stack: null | Error): string {
if (__DEV__) {
if (current === null) {
return '';
diff --git a/packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js b/packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js
new file mode 100644
index 0000000000000..ca0e9fd12cc11
--- /dev/null
+++ b/packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js
@@ -0,0 +1,65 @@
+/**
+ * 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.
+ *
+ * @emails react-core
+ * @jest-environment node
+ */
+'use strict';
+
+let React;
+let ReactNoop;
+let act;
+
+describe('ReactOwnerStacks', () => {
+ beforeEach(function () {
+ jest.resetModules();
+
+ React = require('react');
+ ReactNoop = require('react-noop-renderer');
+ act = require('internal-test-utils').act;
+ });
+
+ function normalizeCodeLocInfo(str) {
+ return (
+ str &&
+ str.replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function (m, name) {
+ return '\n in ' + name + ' (at **)';
+ })
+ );
+ }
+
+ // @gate __DEV__ && enableOwnerStacks
+ it('can get the component owner stacks during rendering in dev', async () => {
+ let stack;
+
+ function Foo() {
+ return ;
+ }
+ function Bar() {
+ return (
+
+
+
+ );
+ }
+ function Baz() {
+ stack = React.captureOwnerStack();
+ return hi;
+ }
+
+ await act(() => {
+ ReactNoop.render(
+
+
+
,
+ );
+ });
+
+ expect(normalizeCodeLocInfo(stack)).toBe(
+ '\n in Bar (at **)' + '\n in Foo (at **)',
+ );
+ });
+});
diff --git a/packages/react-test-renderer/src/ReactTestRenderer.js b/packages/react-test-renderer/src/ReactTestRenderer.js
index e23bc1f1663c2..e642fef49d1cb 100644
--- a/packages/react-test-renderer/src/ReactTestRenderer.js
+++ b/packages/react-test-renderer/src/ReactTestRenderer.js
@@ -61,6 +61,7 @@ import {
disableLegacyMode,
} from 'shared/ReactFeatureFlags';
+// $FlowFixMe[prop-missing]: This is only in the development export.
const act = React.act;
// TODO: Remove from public bundle
diff --git a/packages/react/index.development.js b/packages/react/index.development.js
new file mode 100644
index 0000000000000..c94c460b97416
--- /dev/null
+++ b/packages/react/index.development.js
@@ -0,0 +1,80 @@
+/**
+ * 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
+ */
+
+// Keep in sync with https://github.com/facebook/flow/blob/main/lib/react.js
+export type ComponentType<-P> = React$ComponentType;
+export type AbstractComponent<
+ -Config,
+ +Instance = mixed,
+> = React$AbstractComponent;
+export type ElementType = React$ElementType;
+export type Element<+C> = React$Element;
+export type Key = React$Key;
+export type Ref = React$Ref;
+export type Node = React$Node;
+export type Context = React$Context;
+export type Portal = React$Portal;
+export type ElementProps = React$ElementProps;
+export type ElementConfig = React$ElementConfig;
+export type ElementRef = React$ElementRef;
+export type Config = React$Config;
+export type ChildrenArray<+T> = $ReadOnlyArray> | T;
+
+// Export all exports so that they're available in tests.
+// We can't use export * from in Flow for some reason.
+export {
+ __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
+ Children,
+ Component,
+ Fragment,
+ Profiler,
+ PureComponent,
+ StrictMode,
+ Suspense,
+ cloneElement,
+ createContext,
+ createElement,
+ createRef,
+ use,
+ forwardRef,
+ isValidElement,
+ lazy,
+ memo,
+ cache,
+ startTransition,
+ unstable_DebugTracingMode,
+ unstable_LegacyHidden,
+ unstable_Activity,
+ unstable_Scope,
+ unstable_SuspenseList,
+ unstable_TracingMarker,
+ unstable_getCacheForType,
+ unstable_useCacheRefresh,
+ useId,
+ useCallback,
+ useContext,
+ useDebugValue,
+ useDeferredValue,
+ useEffect,
+ experimental_useEffectEvent,
+ useImperativeHandle,
+ useInsertionEffect,
+ useLayoutEffect,
+ useMemo,
+ useOptimistic,
+ useSyncExternalStore,
+ useReducer,
+ useRef,
+ useState,
+ useTransition,
+ useActionState,
+ version,
+ act, // DEV-only
+ captureOwnerStack, // DEV-only
+} from './src/ReactClient';
diff --git a/packages/react/index.experimental.development.js b/packages/react/index.experimental.development.js
new file mode 100644
index 0000000000000..4ffcdea0d242e
--- /dev/null
+++ b/packages/react/index.experimental.development.js
@@ -0,0 +1,72 @@
+/**
+ * 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
+ */
+
+export {
+ __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
+ Children,
+ Component,
+ Fragment,
+ Profiler,
+ PureComponent,
+ StrictMode,
+ Suspense,
+ cloneElement,
+ createContext,
+ createElement,
+ createRef,
+ use,
+ forwardRef,
+ isValidElement,
+ lazy,
+ memo,
+ cache,
+ startTransition,
+ unstable_DebugTracingMode,
+ unstable_Activity,
+ unstable_postpone,
+ unstable_getCacheForType,
+ unstable_SuspenseList,
+ unstable_useCacheRefresh,
+ useId,
+ useCallback,
+ useContext,
+ useDebugValue,
+ useDeferredValue,
+ useEffect,
+ experimental_useEffectEvent,
+ useImperativeHandle,
+ useInsertionEffect,
+ useLayoutEffect,
+ useMemo,
+ useOptimistic,
+ useReducer,
+ useRef,
+ useState,
+ useSyncExternalStore,
+ useTransition,
+ useActionState,
+ version,
+ act, // DEV-only
+ captureOwnerStack, // DEV-only
+} from './src/ReactClient';
+
+import {useOptimistic} from './src/ReactClient';
+
+export function experimental_useOptimistic(
+ passthrough: S,
+ reducer: ?(S, A) => S,
+): [S, (A) => void] {
+ if (__DEV__) {
+ console.error(
+ 'useOptimistic is now in canary. Remove the experimental_ prefix. ' +
+ 'The prefixed alias will be removed in an upcoming release.',
+ );
+ }
+ return useOptimistic(passthrough, reducer);
+}
diff --git a/packages/react/index.experimental.js b/packages/react/index.experimental.js
index efac1257849be..6f2ab835135c0 100644
--- a/packages/react/index.experimental.js
+++ b/packages/react/index.experimental.js
@@ -9,7 +9,6 @@
export {
__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
- act,
Children,
Component,
Fragment,
diff --git a/packages/react/index.js b/packages/react/index.js
index 8808d259583ee..bad2c40e6c3dd 100644
--- a/packages/react/index.js
+++ b/packages/react/index.js
@@ -30,7 +30,6 @@ export type ChildrenArray<+T> = $ReadOnlyArray> | T;
// We can't use export * from in Flow for some reason.
export {
__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
- act,
Children,
Component,
Fragment,
diff --git a/packages/react/src/ReactClient.js b/packages/react/src/ReactClient.js
index 4d5ec0d3ea5f8..fc668246a988b 100644
--- a/packages/react/src/ReactClient.js
+++ b/packages/react/src/ReactClient.js
@@ -61,6 +61,7 @@ import {
import ReactSharedInternals from './ReactSharedInternalsClient';
import {startTransition} from './ReactStartTransition';
import {act} from './ReactAct';
+import {captureOwnerStack} from './ReactOwnerStack';
const Children = {
map,
@@ -121,5 +122,6 @@ export {
// enableTransitionTracing
REACT_TRACING_MARKER_TYPE as unstable_TracingMarker,
useId,
- act,
+ act, // DEV-only
+ captureOwnerStack, // DEV-only
};
diff --git a/packages/react/src/ReactOwnerStack.js b/packages/react/src/ReactOwnerStack.js
new file mode 100644
index 0000000000000..f6944b08cde32
--- /dev/null
+++ b/packages/react/src/ReactOwnerStack.js
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) Meta Platforms, 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 {enableOwnerStacks} from 'shared/ReactFeatureFlags';
+import ReactSharedInternals from 'shared/ReactSharedInternals';
+
+export function captureOwnerStack(): null | string {
+ if (!enableOwnerStacks || !__DEV__) {
+ return null;
+ }
+ const getCurrentStack = ReactSharedInternals.getCurrentStack;
+ if (getCurrentStack === null) {
+ return null;
+ }
+ // The current stack will be the owner stack if enableOwnerStacks is true
+ // which it is always here. Otherwise it's the parent stack.
+ return getCurrentStack(null);
+}
diff --git a/packages/react/src/ReactServer.experimental.development.js b/packages/react/src/ReactServer.experimental.development.js
new file mode 100644
index 0000000000000..ccf423d79ecc7
--- /dev/null
+++ b/packages/react/src/ReactServer.experimental.development.js
@@ -0,0 +1,85 @@
+/**
+ * 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
+ */
+
+export {default as __SERVER_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE} from './ReactSharedInternalsServer';
+
+import {forEach, map, count, toArray, only} from './ReactChildren';
+import {
+ REACT_FRAGMENT_TYPE,
+ REACT_PROFILER_TYPE,
+ REACT_STRICT_MODE_TYPE,
+ REACT_SUSPENSE_TYPE,
+ REACT_DEBUG_TRACING_MODE_TYPE,
+} from 'shared/ReactSymbols';
+import {
+ cloneElement,
+ createElement,
+ isValidElement,
+} from './jsx/ReactJSXElement';
+import {createRef} from './ReactCreateRef';
+import {
+ use,
+ useId,
+ useCallback,
+ useDebugValue,
+ useMemo,
+ useActionState,
+ getCacheForType,
+} from './ReactHooks';
+import {forwardRef} from './ReactForwardRef';
+import {lazy} from './ReactLazy';
+import {memo} from './ReactMemo';
+import {cache} from './ReactCacheServer';
+import {startTransition} from './ReactStartTransition';
+import {postpone} from './ReactPostpone';
+import {captureOwnerStack} from './ReactOwnerStack';
+import version from 'shared/ReactVersion';
+
+const Children = {
+ map,
+ forEach,
+ count,
+ toArray,
+ only,
+};
+
+// These are server-only
+export {
+ taintUniqueValue as experimental_taintUniqueValue,
+ taintObjectReference as experimental_taintObjectReference,
+} from './ReactTaint';
+
+export {
+ Children,
+ REACT_FRAGMENT_TYPE as Fragment,
+ REACT_PROFILER_TYPE as Profiler,
+ REACT_STRICT_MODE_TYPE as StrictMode,
+ REACT_SUSPENSE_TYPE as Suspense,
+ cloneElement,
+ createElement,
+ createRef,
+ use,
+ forwardRef,
+ isValidElement,
+ lazy,
+ memo,
+ cache,
+ startTransition,
+ REACT_DEBUG_TRACING_MODE_TYPE as unstable_DebugTracingMode,
+ REACT_SUSPENSE_TYPE as unstable_SuspenseList,
+ getCacheForType as unstable_getCacheForType,
+ postpone as unstable_postpone,
+ useId,
+ useCallback,
+ useDebugValue,
+ useMemo,
+ useActionState,
+ version,
+ captureOwnerStack, // DEV-only
+};
diff --git a/packages/react/src/ReactSharedInternalsClient.js b/packages/react/src/ReactSharedInternalsClient.js
index 6a54c73be30f1..8b00f98a51bf3 100644
--- a/packages/react/src/ReactSharedInternalsClient.js
+++ b/packages/react/src/ReactSharedInternalsClient.js
@@ -35,7 +35,7 @@ export type SharedStateClient = {
thrownErrors: Array,
// ReactDebugCurrentFrame
- getCurrentStack: null | ((stack: Error) => string),
+ getCurrentStack: null | ((stack: null | Error) => string),
};
export type RendererTask = boolean => RendererTask | null;
@@ -56,7 +56,7 @@ if (__DEV__) {
// Stack implementation injected by the current renderer.
ReactSharedInternals.getCurrentStack = (null:
| null
- | ((stack: Error) => string));
+ | ((stack: null | Error) => string));
}
export default ReactSharedInternals;
diff --git a/packages/react/src/ReactSharedInternalsServer.js b/packages/react/src/ReactSharedInternalsServer.js
index 749ce5c3ad44a..0bfeb26fb8007 100644
--- a/packages/react/src/ReactSharedInternalsServer.js
+++ b/packages/react/src/ReactSharedInternalsServer.js
@@ -38,7 +38,7 @@ export type SharedStateServer = {
// DEV-only
// ReactDebugCurrentFrame
- getCurrentStack: null | ((stack: Error) => string),
+ getCurrentStack: null | ((stack: null | Error) => string),
};
export type RendererTask = boolean => RendererTask | null;
@@ -60,7 +60,7 @@ if (__DEV__) {
// Stack implementation injected by the current renderer.
ReactSharedInternals.getCurrentStack = (null:
| null
- | ((stack: Error) => string));
+ | ((stack: null | Error) => string));
}
export default ReactSharedInternals;
diff --git a/scripts/jest/setupHostConfigs.js b/scripts/jest/setupHostConfigs.js
index fcd1ed213030e..b52fac3201ebf 100644
--- a/scripts/jest/setupHostConfigs.js
+++ b/scripts/jest/setupHostConfigs.js
@@ -12,6 +12,8 @@ function resolveEntryFork(resolvedEntry, isFBBundle) {
// .stable.js
// .experimental.js
// .js
+ // or any of those plus .development.js
+
if (isFBBundle) {
// FB builds for react-dom need to alias both react-dom and react-dom/client to the same
// entrypoint since there is only a single build for them.
@@ -41,6 +43,10 @@ function resolveEntryFork(resolvedEntry, isFBBundle) {
}
resolvedEntry = nodePath.join(resolvedEntry, '..', entrypoint);
+ const developmentEntry = resolvedEntry.replace('.js', '.development.js');
+ if (fs.existsSync(developmentEntry)) {
+ return developmentEntry;
+ }
if (fs.existsSync(resolvedEntry)) {
return resolvedEntry;
}
@@ -53,6 +59,10 @@ function resolveEntryFork(resolvedEntry, isFBBundle) {
'.js',
__EXPERIMENTAL__ ? '.modern.fb.js' : '.classic.fb.js'
);
+ const devFBEntry = resolvedFBEntry.replace('.js', '.development.js');
+ if (fs.existsSync(devFBEntry)) {
+ return devFBEntry;
+ }
if (fs.existsSync(resolvedFBEntry)) {
return resolvedFBEntry;
}
@@ -66,9 +76,17 @@ function resolveEntryFork(resolvedEntry, isFBBundle) {
'.js',
__EXPERIMENTAL__ ? '.experimental.js' : '.stable.js'
);
+ const devForkedEntry = resolvedForkedEntry.replace('.js', '.development.js');
+ if (fs.existsSync(devForkedEntry)) {
+ return devForkedEntry;
+ }
if (fs.existsSync(resolvedForkedEntry)) {
return resolvedForkedEntry;
}
+ const plainDevEntry = resolvedEntry.replace('.js', '.development.js');
+ if (fs.existsSync(plainDevEntry)) {
+ return plainDevEntry;
+ }
// Just use the plain .js one.
return resolvedEntry;
}
@@ -77,7 +95,8 @@ function mockReact() {
jest.mock('react', () => {
const resolvedEntryPoint = resolveEntryFork(
require.resolve('react'),
- global.__WWW__ || global.__XPLAT__
+ global.__WWW__ || global.__XPLAT__,
+ global.__DEV__
);
return jest.requireActual(resolvedEntryPoint);
});
@@ -100,7 +119,8 @@ jest.mock('react/react.react-server', () => {
});
const resolvedEntryPoint = resolveEntryFork(
require.resolve('react/src/ReactServer'),
- global.__WWW__ || global.__XPLAT__
+ global.__WWW__ || global.__XPLAT__,
+ global.__DEV__
);
return jest.requireActual(resolvedEntryPoint);
});
@@ -198,7 +218,8 @@ inlinedHostConfigs.forEach(rendererInfo => {
mockAllConfigs(rendererInfo);
const resolvedEntryPoint = resolveEntryFork(
require.resolve(entryPoint),
- global.__WWW__ || global.__XPLAT__
+ global.__WWW__ || global.__XPLAT__,
+ global.__DEV__
);
return jest.requireActual(resolvedEntryPoint);
});
diff --git a/scripts/rollup/build.js b/scripts/rollup/build.js
index df9fb00b09424..ce87ba57d4481 100644
--- a/scripts/rollup/build.js
+++ b/scripts/rollup/build.js
@@ -567,16 +567,31 @@ function resolveEntryFork(resolvedEntry, isFBBundle) {
// .stable.js
// .experimental.js
// .js
+ // or any of those plus .development.js
if (isFBBundle) {
const resolvedFBEntry = resolvedEntry.replace(
'.js',
__EXPERIMENTAL__ ? '.modern.fb.js' : '.classic.fb.js'
);
+ const developmentFBEntry = resolvedFBEntry.replace(
+ '.js',
+ '.development.js'
+ );
+ if (fs.existsSync(developmentFBEntry)) {
+ return developmentFBEntry;
+ }
if (fs.existsSync(resolvedFBEntry)) {
return resolvedFBEntry;
}
const resolvedGenericFBEntry = resolvedEntry.replace('.js', '.fb.js');
+ const developmentGenericFBEntry = resolvedGenericFBEntry.replace(
+ '.js',
+ '.development.js'
+ );
+ if (fs.existsSync(developmentGenericFBEntry)) {
+ return developmentGenericFBEntry;
+ }
if (fs.existsSync(resolvedGenericFBEntry)) {
return resolvedGenericFBEntry;
}
@@ -586,6 +601,10 @@ function resolveEntryFork(resolvedEntry, isFBBundle) {
'.js',
__EXPERIMENTAL__ ? '.experimental.js' : '.stable.js'
);
+ const devForkedEntry = resolvedForkedEntry.replace('.js', '.development.js');
+ if (fs.existsSync(devForkedEntry)) {
+ return devForkedEntry;
+ }
if (fs.existsSync(resolvedForkedEntry)) {
return resolvedForkedEntry;
}
@@ -604,7 +623,8 @@ async function createBundle(bundle, bundleType) {
let resolvedEntry = resolveEntryFork(
require.resolve(bundle.entry),
- isFBWWWBundle || isFBRNBundle
+ isFBWWWBundle || isFBRNBundle,
+ !isProductionBundleType(bundleType)
);
const peerGlobals = Modules.getPeerGlobals(bundle.externals, bundleType);