From 8c19b034abe22befd0b06891f738cd7c3d64f34e Mon Sep 17 00:00:00 2001 From: Sebastian Sebbie Silbermann Date: Thu, 23 Jan 2025 17:12:21 +0100 Subject: [PATCH 1/2] Stop exporting dev-only methods production builds --- .../src/__tests__/ReactTestUtilsAct-test.js | 8 -- .../src/__tests__/ReactIsomorphicAct-test.js | 5 ++ .../src/__tests__/ReactOwnerStacks-test.js | 9 +++ packages/react/index.fb.development.js | 79 +++++++++++++++++++ packages/react/index.fb.js | 11 --- packages/react/index.js | 2 - packages/react/index.stable.development.js | 51 ++++++++++++ packages/react/index.stable.js | 1 - scripts/jest/setupHostConfigs.js | 19 +++-- scripts/rollup/build.js | 21 +++-- 10 files changed, 166 insertions(+), 40 deletions(-) create mode 100644 packages/react/index.fb.development.js create mode 100644 packages/react/index.stable.development.js diff --git a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js index 76cfab25e0016..64ccc387d75a3 100644 --- a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js +++ b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js @@ -779,13 +779,5 @@ function runActTests(render, unmount, rerender) { }); } }); - describe('throw in prod mode', () => { - // @gate !__DEV__ - it('warns if you try to use act() in prod mode', () => { - expect(() => act(() => {})).toThrow( - 'act(...) is not supported in production builds of React', - ); - }); - }); }); } diff --git a/packages/react-reconciler/src/__tests__/ReactIsomorphicAct-test.js b/packages/react-reconciler/src/__tests__/ReactIsomorphicAct-test.js index eeec3b2b7eb3b..639ca0d5bd989 100644 --- a/packages/react-reconciler/src/__tests__/ReactIsomorphicAct-test.js +++ b/packages/react-reconciler/src/__tests__/ReactIsomorphicAct-test.js @@ -50,6 +50,11 @@ describe('isomorphic act()', () => { return text; } + // @gate !__DEV__ + it('is not exported in production builds', () => { + expect(React).not.toHaveProperty('act'); + }); + // @gate __DEV__ it('bypasses queueMicrotask', async () => { const root = ReactNoop.createRoot(); diff --git a/packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js b/packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js index 7ee176a106e15..e889aa99d85a3 100644 --- a/packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js +++ b/packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js @@ -31,6 +31,15 @@ describe('ReactOwnerStacks', () => { ); } + it('is not exported in production buids', () => { + // awkward to gate because FB builds will have it undefined if disabled + // whereas OSS builds will have it stripped if disabled because we only have + // it on in experimental which has a separate entrypoint + if (!__DEV__) { + expect(React).not.toHaveProperty('captureOwnerStack'); + } + }); + // @gate __DEV__ && enableOwnerStacks it('can get the component owner stacks during rendering in dev', async () => { let stack; diff --git a/packages/react/index.fb.development.js b/packages/react/index.fb.development.js new file mode 100644 index 0000000000000..ec819a0b31ad5 --- /dev/null +++ b/packages/react/index.fb.development.js @@ -0,0 +1,79 @@ +/** + * 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 {enableOwnerStacks} from 'shared/ReactFeatureFlags'; +import {captureOwnerStack as captureOwnerStackImpl} from './src/ReactClient'; + +export { + __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, + __COMPILER_RUNTIME, + cache, + Children, + cloneElement, + Component, + createContext, + createElement, + createRef, + experimental_useEffectEvent, + experimental_useResourceEffect, + forwardRef, + Fragment, + isValidElement, + lazy, + memo, + Profiler, + PureComponent, + startTransition, + StrictMode, + Suspense, + unstable_Activity, + unstable_getCacheForType, + unstable_LegacyHidden, + unstable_Scope, + unstable_SuspenseList, + unstable_TracingMarker, + unstable_useCacheRefresh, + use, + useActionState, + useCallback, + useContext, + useDebugValue, + useDeferredValue, + useEffect, + useId, + useImperativeHandle, + useInsertionEffect, + useLayoutEffect, + useMemo, + useOptimistic, + useReducer, + useRef, + useState, + useSyncExternalStore, + useTransition, + version, + act, // DEV-only +} from './src/ReactClient'; + +export {jsx, jsxs, jsxDEV} from './src/jsx/ReactJSX'; + +// export for backwards compatibility during upgrade +export {useMemoCache as unstable_useMemoCache} from './src/ReactHooks'; + +// export to match the name of the OSS function typically exported from +// react/compiler-runtime +export {useMemoCache as c} from './src/ReactHooks'; + +// Only export captureOwnerStack if enabled. +let captureOwnerStack: ?() => null | string; +if (enableOwnerStacks) { + captureOwnerStack = captureOwnerStackImpl; +} + +// DEV-only +export {captureOwnerStack}; diff --git a/packages/react/index.fb.js b/packages/react/index.fb.js index 29133df24f3a7..230b07834dc09 100644 --- a/packages/react/index.fb.js +++ b/packages/react/index.fb.js @@ -7,12 +7,9 @@ * @flow */ -import {enableOwnerStacks} from 'shared/ReactFeatureFlags'; -import {captureOwnerStack as captureOwnerStackImpl} from './src/ReactClient'; export { __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, __COMPILER_RUNTIME, - act, cache, Children, cloneElement, @@ -69,11 +66,3 @@ export {useMemoCache as unstable_useMemoCache} from './src/ReactHooks'; // export to match the name of the OSS function typically exported from // react/compiler-runtime export {useMemoCache as c} from './src/ReactHooks'; - -// Only export captureOwnerStack in development. -let captureOwnerStack: ?() => null | string; -if (__DEV__ && enableOwnerStacks) { - captureOwnerStack = captureOwnerStackImpl; -} - -export {captureOwnerStack}; diff --git a/packages/react/index.js b/packages/react/index.js index 711a044455257..af4cd4b0c1b17 100644 --- a/packages/react/index.js +++ b/packages/react/index.js @@ -23,8 +23,6 @@ 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, __COMPILER_RUNTIME, diff --git a/packages/react/index.stable.development.js b/packages/react/index.stable.development.js new file mode 100644 index 0000000000000..f9ac98bafbf58 --- /dev/null +++ b/packages/react/index.stable.development.js @@ -0,0 +1,51 @@ +/** + * 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, + __COMPILER_RUNTIME, + Children, + Component, + Fragment, + Profiler, + PureComponent, + StrictMode, + Suspense, + cloneElement, + createContext, + createElement, + createRef, + use, + forwardRef, + isValidElement, + lazy, + memo, + cache, + unstable_useCacheRefresh, + startTransition, + useId, + useCallback, + useContext, + useDebugValue, + useDeferredValue, + useEffect, + useImperativeHandle, + useInsertionEffect, + useLayoutEffect, + useMemo, + useReducer, + useOptimistic, + useRef, + useState, + useSyncExternalStore, + useTransition, + useActionState, + version, + act, // DEV-only +} from './src/ReactClient'; diff --git a/packages/react/index.stable.js b/packages/react/index.stable.js index 26e22c343223e..6f25c7a37d983 100644 --- a/packages/react/index.stable.js +++ b/packages/react/index.stable.js @@ -10,7 +10,6 @@ export { __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, __COMPILER_RUNTIME, - act, Children, Component, Fragment, diff --git a/scripts/jest/setupHostConfigs.js b/scripts/jest/setupHostConfigs.js index b52fac3201ebf..aacfaf116fcd2 100644 --- a/scripts/jest/setupHostConfigs.js +++ b/scripts/jest/setupHostConfigs.js @@ -43,9 +43,9 @@ function resolveEntryFork(resolvedEntry, isFBBundle) { } resolvedEntry = nodePath.join(resolvedEntry, '..', entrypoint); - const developmentEntry = resolvedEntry.replace('.js', '.development.js'); - if (fs.existsSync(developmentEntry)) { - return developmentEntry; + const devEntry = resolvedEntry.replace('.js', '.development.js'); + if (__DEV__ && fs.existsSync(devEntry)) { + return devEntry; } if (fs.existsSync(resolvedEntry)) { return resolvedEntry; @@ -60,13 +60,20 @@ function resolveEntryFork(resolvedEntry, isFBBundle) { __EXPERIMENTAL__ ? '.modern.fb.js' : '.classic.fb.js' ); const devFBEntry = resolvedFBEntry.replace('.js', '.development.js'); - if (fs.existsSync(devFBEntry)) { + if (__DEV__ && fs.existsSync(devFBEntry)) { return devFBEntry; } if (fs.existsSync(resolvedFBEntry)) { return resolvedFBEntry; } const resolvedGenericFBEntry = resolvedEntry.replace('.js', '.fb.js'); + const devGenericFBEntry = resolvedGenericFBEntry.replace( + '.js', + '.development.js' + ); + if (__DEV__ && fs.existsSync(devGenericFBEntry)) { + return devGenericFBEntry; + } if (fs.existsSync(resolvedGenericFBEntry)) { return resolvedGenericFBEntry; } @@ -77,14 +84,14 @@ function resolveEntryFork(resolvedEntry, isFBBundle) { __EXPERIMENTAL__ ? '.experimental.js' : '.stable.js' ); const devForkedEntry = resolvedForkedEntry.replace('.js', '.development.js'); - if (fs.existsSync(devForkedEntry)) { + if (__DEV__ && fs.existsSync(devForkedEntry)) { return devForkedEntry; } if (fs.existsSync(resolvedForkedEntry)) { return resolvedForkedEntry; } const plainDevEntry = resolvedEntry.replace('.js', '.development.js'); - if (fs.existsSync(plainDevEntry)) { + if (__DEV__ && fs.existsSync(plainDevEntry)) { return plainDevEntry; } // Just use the plain .js one. diff --git a/scripts/rollup/build.js b/scripts/rollup/build.js index 2a67c13e1e620..547362dae7123 100644 --- a/scripts/rollup/build.js +++ b/scripts/rollup/build.js @@ -571,7 +571,7 @@ function shouldSkipBundle(bundle, bundleType) { return false; } -function resolveEntryFork(resolvedEntry, isFBBundle) { +function resolveEntryFork(resolvedEntry, isFBBundle, isDev) { // Pick which entry point fork to use: // .modern.fb.js // .classic.fb.js @@ -586,23 +586,20 @@ function resolveEntryFork(resolvedEntry, isFBBundle) { '.js', __EXPERIMENTAL__ ? '.modern.fb.js' : '.classic.fb.js' ); - const developmentFBEntry = resolvedFBEntry.replace( - '.js', - '.development.js' - ); - if (fs.existsSync(developmentFBEntry)) { - return developmentFBEntry; + const devFBEntry = resolvedFBEntry.replace('.js', '.development.js'); + if (isDev && fs.existsSync(devFBEntry)) { + return devFBEntry; } if (fs.existsSync(resolvedFBEntry)) { return resolvedFBEntry; } const resolvedGenericFBEntry = resolvedEntry.replace('.js', '.fb.js'); - const developmentGenericFBEntry = resolvedGenericFBEntry.replace( + const devGenericFBEntry = resolvedGenericFBEntry.replace( '.js', '.development.js' ); - if (fs.existsSync(developmentGenericFBEntry)) { - return developmentGenericFBEntry; + if (isDev && fs.existsSync(devGenericFBEntry)) { + return devGenericFBEntry; } if (fs.existsSync(resolvedGenericFBEntry)) { return resolvedGenericFBEntry; @@ -614,7 +611,7 @@ function resolveEntryFork(resolvedEntry, isFBBundle) { __EXPERIMENTAL__ ? '.experimental.js' : '.stable.js' ); const devForkedEntry = resolvedForkedEntry.replace('.js', '.development.js'); - if (fs.existsSync(devForkedEntry)) { + if (isDev && fs.existsSync(devForkedEntry)) { return devForkedEntry; } if (fs.existsSync(resolvedForkedEntry)) { @@ -633,7 +630,7 @@ async function createBundle(bundle, bundleType) { const {isFBWWWBundle, isFBRNBundle} = getBundleTypeFlags(bundleType); - let resolvedEntry = resolveEntryFork( + const resolvedEntry = resolveEntryFork( require.resolve(bundle.entry), isFBWWWBundle || isFBRNBundle, !isProductionBundleType(bundleType) From fea5440a9b566131f4490e21e199072201032dac Mon Sep 17 00:00:00 2001 From: Sebastian Sebbie Silbermann Date: Tue, 11 Feb 2025 01:02:17 +0100 Subject: [PATCH 2/2] Only affect OSS for now --- .../src/__tests__/ReactIsomorphicAct-test.js | 13 ++- .../src/__tests__/ReactOwnerStacks-test.js | 11 +-- packages/react/index.fb.development.js | 79 ------------------- packages/react/index.fb.js | 11 +++ scripts/jest/TestFlags.js | 1 + 5 files changed, 28 insertions(+), 87 deletions(-) delete mode 100644 packages/react/index.fb.development.js diff --git a/packages/react-reconciler/src/__tests__/ReactIsomorphicAct-test.js b/packages/react-reconciler/src/__tests__/ReactIsomorphicAct-test.js index 639ca0d5bd989..874969b131ea5 100644 --- a/packages/react-reconciler/src/__tests__/ReactIsomorphicAct-test.js +++ b/packages/react-reconciler/src/__tests__/ReactIsomorphicAct-test.js @@ -50,9 +50,16 @@ describe('isomorphic act()', () => { return text; } - // @gate !__DEV__ - it('is not exported in production builds', () => { - expect(React).not.toHaveProperty('act'); + it('behavior in production', () => { + if (!__DEV__) { + if (gate('fb')) { + expect(() => act(() => {})).toThrow( + 'act(...) is not supported in production builds of React', + ); + } else { + expect(React).not.toHaveProperty('act'); + } + } }); // @gate __DEV__ diff --git a/packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js b/packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js index e889aa99d85a3..42294269e7b14 100644 --- a/packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js +++ b/packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js @@ -31,12 +31,13 @@ describe('ReactOwnerStacks', () => { ); } - it('is not exported in production buids', () => { - // awkward to gate because FB builds will have it undefined if disabled - // whereas OSS builds will have it stripped if disabled because we only have - // it on in experimental which has a separate entrypoint + it('behavior in production', () => { if (!__DEV__) { - expect(React).not.toHaveProperty('captureOwnerStack'); + if (gate('fb')) { + expect(React).toHaveProperty('captureOwnerStack', undefined); + } else { + expect(React).not.toHaveProperty('captureOwnerStack'); + } } }); diff --git a/packages/react/index.fb.development.js b/packages/react/index.fb.development.js deleted file mode 100644 index ec819a0b31ad5..0000000000000 --- a/packages/react/index.fb.development.js +++ /dev/null @@ -1,79 +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 {enableOwnerStacks} from 'shared/ReactFeatureFlags'; -import {captureOwnerStack as captureOwnerStackImpl} from './src/ReactClient'; - -export { - __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, - __COMPILER_RUNTIME, - cache, - Children, - cloneElement, - Component, - createContext, - createElement, - createRef, - experimental_useEffectEvent, - experimental_useResourceEffect, - forwardRef, - Fragment, - isValidElement, - lazy, - memo, - Profiler, - PureComponent, - startTransition, - StrictMode, - Suspense, - unstable_Activity, - unstable_getCacheForType, - unstable_LegacyHidden, - unstable_Scope, - unstable_SuspenseList, - unstable_TracingMarker, - unstable_useCacheRefresh, - use, - useActionState, - useCallback, - useContext, - useDebugValue, - useDeferredValue, - useEffect, - useId, - useImperativeHandle, - useInsertionEffect, - useLayoutEffect, - useMemo, - useOptimistic, - useReducer, - useRef, - useState, - useSyncExternalStore, - useTransition, - version, - act, // DEV-only -} from './src/ReactClient'; - -export {jsx, jsxs, jsxDEV} from './src/jsx/ReactJSX'; - -// export for backwards compatibility during upgrade -export {useMemoCache as unstable_useMemoCache} from './src/ReactHooks'; - -// export to match the name of the OSS function typically exported from -// react/compiler-runtime -export {useMemoCache as c} from './src/ReactHooks'; - -// Only export captureOwnerStack if enabled. -let captureOwnerStack: ?() => null | string; -if (enableOwnerStacks) { - captureOwnerStack = captureOwnerStackImpl; -} - -// DEV-only -export {captureOwnerStack}; diff --git a/packages/react/index.fb.js b/packages/react/index.fb.js index 230b07834dc09..29133df24f3a7 100644 --- a/packages/react/index.fb.js +++ b/packages/react/index.fb.js @@ -7,9 +7,12 @@ * @flow */ +import {enableOwnerStacks} from 'shared/ReactFeatureFlags'; +import {captureOwnerStack as captureOwnerStackImpl} from './src/ReactClient'; export { __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE, __COMPILER_RUNTIME, + act, cache, Children, cloneElement, @@ -66,3 +69,11 @@ export {useMemoCache as unstable_useMemoCache} from './src/ReactHooks'; // export to match the name of the OSS function typically exported from // react/compiler-runtime export {useMemoCache as c} from './src/ReactHooks'; + +// Only export captureOwnerStack in development. +let captureOwnerStack: ?() => null | string; +if (__DEV__ && enableOwnerStacks) { + captureOwnerStack = captureOwnerStackImpl; +} + +export {captureOwnerStack}; diff --git a/scripts/jest/TestFlags.js b/scripts/jest/TestFlags.js index b970955491aae..f736ca2254f64 100644 --- a/scripts/jest/TestFlags.js +++ b/scripts/jest/TestFlags.js @@ -78,6 +78,7 @@ function getTestFlags() { classic: releaseChannel === 'classic', source: !process.env.IS_BUILD, www, + fb: www || xplat, // These aren't flags, just a useful aliases for tests. enableActivity: releaseChannel === 'experimental' || www || xplat,