Skip to content

Commit 6b4646c

Browse files
authored
Export captureOwnerStacks() only in DEV "react" builds (facebook#29923)
Only with the enableOwnerStacks flag (which is not on in www). This is a new DEV-only API to be able to implement what we do for console.error in user space. This API does not actually include the current stack that you'd get from `new Error().stack`. That you'd have to add yourself. This adds the ability to have conditional development exports because we plan on eventually having separate ESM builds that use the "development" or "production" export conditions. NOTE: This removes the export of `act` from `react` in prod (as opposed to a function that throws) - inline with what we do with other conditional exports.
1 parent e684ca6 commit 6b4646c

14 files changed

+380
-12
lines changed

packages/react-reconciler/src/ReactCurrentFiber.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export function getCurrentParentStackInDev(): string {
4444
return '';
4545
}
4646

47-
function getCurrentFiberStackInDev(stack: Error): string {
47+
function getCurrentFiberStackInDev(stack: null | Error): string {
4848
if (__DEV__) {
4949
if (current === null) {
5050
return '';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @emails react-core
8+
* @jest-environment node
9+
*/
10+
'use strict';
11+
12+
let React;
13+
let ReactNoop;
14+
let act;
15+
16+
describe('ReactOwnerStacks', () => {
17+
beforeEach(function () {
18+
jest.resetModules();
19+
20+
React = require('react');
21+
ReactNoop = require('react-noop-renderer');
22+
act = require('internal-test-utils').act;
23+
});
24+
25+
function normalizeCodeLocInfo(str) {
26+
return (
27+
str &&
28+
str.replace(/\n +(?:at|in) ([\S]+)[^\n]*/g, function (m, name) {
29+
return '\n in ' + name + ' (at **)';
30+
})
31+
);
32+
}
33+
34+
// @gate __DEV__ && enableOwnerStacks
35+
it('can get the component owner stacks during rendering in dev', async () => {
36+
let stack;
37+
38+
function Foo() {
39+
return <Bar />;
40+
}
41+
function Bar() {
42+
return (
43+
<div>
44+
<Baz />
45+
</div>
46+
);
47+
}
48+
function Baz() {
49+
stack = React.captureOwnerStack();
50+
return <span>hi</span>;
51+
}
52+
53+
await act(() => {
54+
ReactNoop.render(
55+
<div>
56+
<Foo />
57+
</div>,
58+
);
59+
});
60+
61+
expect(normalizeCodeLocInfo(stack)).toBe(
62+
'\n in Bar (at **)' + '\n in Foo (at **)',
63+
);
64+
});
65+
});

packages/react-test-renderer/src/ReactTestRenderer.js

+1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import {
6161
disableLegacyMode,
6262
} from 'shared/ReactFeatureFlags';
6363

64+
// $FlowFixMe[prop-missing]: This is only in the development export.
6465
const act = React.act;
6566

6667
// TODO: Remove from public bundle

packages/react/index.development.js

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
// Keep in sync with https://github.com/facebook/flow/blob/main/lib/react.js
11+
export type ComponentType<-P> = React$ComponentType<P>;
12+
export type AbstractComponent<
13+
-Config,
14+
+Instance = mixed,
15+
> = React$AbstractComponent<Config, Instance>;
16+
export type ElementType = React$ElementType;
17+
export type Element<+C> = React$Element<C>;
18+
export type Key = React$Key;
19+
export type Ref<C> = React$Ref<C>;
20+
export type Node = React$Node;
21+
export type Context<T> = React$Context<T>;
22+
export type Portal = React$Portal;
23+
export type ElementProps<C> = React$ElementProps<C>;
24+
export type ElementConfig<C> = React$ElementConfig<C>;
25+
export type ElementRef<C> = React$ElementRef<C>;
26+
export type Config<Props, DefaultProps> = React$Config<Props, DefaultProps>;
27+
export type ChildrenArray<+T> = $ReadOnlyArray<ChildrenArray<T>> | T;
28+
29+
// Export all exports so that they're available in tests.
30+
// We can't use export * from in Flow for some reason.
31+
export {
32+
__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
33+
Children,
34+
Component,
35+
Fragment,
36+
Profiler,
37+
PureComponent,
38+
StrictMode,
39+
Suspense,
40+
cloneElement,
41+
createContext,
42+
createElement,
43+
createRef,
44+
use,
45+
forwardRef,
46+
isValidElement,
47+
lazy,
48+
memo,
49+
cache,
50+
startTransition,
51+
unstable_DebugTracingMode,
52+
unstable_LegacyHidden,
53+
unstable_Activity,
54+
unstable_Scope,
55+
unstable_SuspenseList,
56+
unstable_TracingMarker,
57+
unstable_getCacheForType,
58+
unstable_useCacheRefresh,
59+
useId,
60+
useCallback,
61+
useContext,
62+
useDebugValue,
63+
useDeferredValue,
64+
useEffect,
65+
experimental_useEffectEvent,
66+
useImperativeHandle,
67+
useInsertionEffect,
68+
useLayoutEffect,
69+
useMemo,
70+
useOptimistic,
71+
useSyncExternalStore,
72+
useReducer,
73+
useRef,
74+
useState,
75+
useTransition,
76+
useActionState,
77+
version,
78+
act, // DEV-only
79+
captureOwnerStack, // DEV-only
80+
} from './src/ReactClient';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
export {
11+
__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
12+
Children,
13+
Component,
14+
Fragment,
15+
Profiler,
16+
PureComponent,
17+
StrictMode,
18+
Suspense,
19+
cloneElement,
20+
createContext,
21+
createElement,
22+
createRef,
23+
use,
24+
forwardRef,
25+
isValidElement,
26+
lazy,
27+
memo,
28+
cache,
29+
startTransition,
30+
unstable_DebugTracingMode,
31+
unstable_Activity,
32+
unstable_postpone,
33+
unstable_getCacheForType,
34+
unstable_SuspenseList,
35+
unstable_useCacheRefresh,
36+
useId,
37+
useCallback,
38+
useContext,
39+
useDebugValue,
40+
useDeferredValue,
41+
useEffect,
42+
experimental_useEffectEvent,
43+
useImperativeHandle,
44+
useInsertionEffect,
45+
useLayoutEffect,
46+
useMemo,
47+
useOptimistic,
48+
useReducer,
49+
useRef,
50+
useState,
51+
useSyncExternalStore,
52+
useTransition,
53+
useActionState,
54+
version,
55+
act, // DEV-only
56+
captureOwnerStack, // DEV-only
57+
} from './src/ReactClient';
58+
59+
import {useOptimistic} from './src/ReactClient';
60+
61+
export function experimental_useOptimistic<S, A>(
62+
passthrough: S,
63+
reducer: ?(S, A) => S,
64+
): [S, (A) => void] {
65+
if (__DEV__) {
66+
console.error(
67+
'useOptimistic is now in canary. Remove the experimental_ prefix. ' +
68+
'The prefixed alias will be removed in an upcoming release.',
69+
);
70+
}
71+
return useOptimistic(passthrough, reducer);
72+
}

packages/react/index.experimental.js

-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
export {
1111
__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
12-
act,
1312
Children,
1413
Component,
1514
Fragment,

packages/react/index.js

-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ export type ChildrenArray<+T> = $ReadOnlyArray<ChildrenArray<T>> | T;
3030
// We can't use export * from in Flow for some reason.
3131
export {
3232
__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
33-
act,
3433
Children,
3534
Component,
3635
Fragment,

packages/react/src/ReactClient.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ import {
6161
import ReactSharedInternals from './ReactSharedInternalsClient';
6262
import {startTransition} from './ReactStartTransition';
6363
import {act} from './ReactAct';
64+
import {captureOwnerStack} from './ReactOwnerStack';
6465

6566
const Children = {
6667
map,
@@ -121,5 +122,6 @@ export {
121122
// enableTransitionTracing
122123
REACT_TRACING_MARKER_TYPE as unstable_TracingMarker,
123124
useId,
124-
act,
125+
act, // DEV-only
126+
captureOwnerStack, // DEV-only
125127
};

packages/react/src/ReactOwnerStack.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import {enableOwnerStacks} from 'shared/ReactFeatureFlags';
11+
import ReactSharedInternals from 'shared/ReactSharedInternals';
12+
13+
export function captureOwnerStack(): null | string {
14+
if (!enableOwnerStacks || !__DEV__) {
15+
return null;
16+
}
17+
const getCurrentStack = ReactSharedInternals.getCurrentStack;
18+
if (getCurrentStack === null) {
19+
return null;
20+
}
21+
// The current stack will be the owner stack if enableOwnerStacks is true
22+
// which it is always here. Otherwise it's the parent stack.
23+
return getCurrentStack(null);
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
export {default as __SERVER_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE} from './ReactSharedInternalsServer';
11+
12+
import {forEach, map, count, toArray, only} from './ReactChildren';
13+
import {
14+
REACT_FRAGMENT_TYPE,
15+
REACT_PROFILER_TYPE,
16+
REACT_STRICT_MODE_TYPE,
17+
REACT_SUSPENSE_TYPE,
18+
REACT_DEBUG_TRACING_MODE_TYPE,
19+
} from 'shared/ReactSymbols';
20+
import {
21+
cloneElement,
22+
createElement,
23+
isValidElement,
24+
} from './jsx/ReactJSXElement';
25+
import {createRef} from './ReactCreateRef';
26+
import {
27+
use,
28+
useId,
29+
useCallback,
30+
useDebugValue,
31+
useMemo,
32+
useActionState,
33+
getCacheForType,
34+
} from './ReactHooks';
35+
import {forwardRef} from './ReactForwardRef';
36+
import {lazy} from './ReactLazy';
37+
import {memo} from './ReactMemo';
38+
import {cache} from './ReactCacheServer';
39+
import {startTransition} from './ReactStartTransition';
40+
import {postpone} from './ReactPostpone';
41+
import {captureOwnerStack} from './ReactOwnerStack';
42+
import version from 'shared/ReactVersion';
43+
44+
const Children = {
45+
map,
46+
forEach,
47+
count,
48+
toArray,
49+
only,
50+
};
51+
52+
// These are server-only
53+
export {
54+
taintUniqueValue as experimental_taintUniqueValue,
55+
taintObjectReference as experimental_taintObjectReference,
56+
} from './ReactTaint';
57+
58+
export {
59+
Children,
60+
REACT_FRAGMENT_TYPE as Fragment,
61+
REACT_PROFILER_TYPE as Profiler,
62+
REACT_STRICT_MODE_TYPE as StrictMode,
63+
REACT_SUSPENSE_TYPE as Suspense,
64+
cloneElement,
65+
createElement,
66+
createRef,
67+
use,
68+
forwardRef,
69+
isValidElement,
70+
lazy,
71+
memo,
72+
cache,
73+
startTransition,
74+
REACT_DEBUG_TRACING_MODE_TYPE as unstable_DebugTracingMode,
75+
REACT_SUSPENSE_TYPE as unstable_SuspenseList,
76+
getCacheForType as unstable_getCacheForType,
77+
postpone as unstable_postpone,
78+
useId,
79+
useCallback,
80+
useDebugValue,
81+
useMemo,
82+
useActionState,
83+
version,
84+
captureOwnerStack, // DEV-only
85+
};

packages/react/src/ReactSharedInternalsClient.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export type SharedStateClient = {
3535
thrownErrors: Array<mixed>,
3636

3737
// ReactDebugCurrentFrame
38-
getCurrentStack: null | ((stack: Error) => string),
38+
getCurrentStack: null | ((stack: null | Error) => string),
3939
};
4040

4141
export type RendererTask = boolean => RendererTask | null;
@@ -56,7 +56,7 @@ if (__DEV__) {
5656
// Stack implementation injected by the current renderer.
5757
ReactSharedInternals.getCurrentStack = (null:
5858
| null
59-
| ((stack: Error) => string));
59+
| ((stack: null | Error) => string));
6060
}
6161

6262
export default ReactSharedInternals;

0 commit comments

Comments
 (0)