From ed8b68dd178af17a2dd36c8678f81f8b454559a9 Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Thu, 13 Feb 2025 18:26:36 +0100 Subject: [PATCH] Stop exporting dev-only methods in OSS production builds (#32200) --- .../src/__tests__/ReactTestUtilsAct-test.js | 8 --- .../src/__tests__/ReactIsomorphicAct-test.js | 12 +++++ .../src/__tests__/ReactOwnerStacks-test.js | 10 ++++ packages/react/index.js | 2 - packages/react/index.stable.development.js | 51 +++++++++++++++++++ packages/react/index.stable.js | 1 - scripts/jest/TestFlags.js | 1 + scripts/jest/setupHostConfigs.js | 19 ++++--- scripts/rollup/build.js | 21 ++++---- 9 files changed, 96 insertions(+), 29 deletions(-) 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 76cfab25e0..64ccc387d7 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 eeec3b2b7e..874969b131 100644 --- a/packages/react-reconciler/src/__tests__/ReactIsomorphicAct-test.js +++ b/packages/react-reconciler/src/__tests__/ReactIsomorphicAct-test.js @@ -50,6 +50,18 @@ describe('isomorphic act()', () => { return text; } + 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__ 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 7ee176a106..42294269e7 100644 --- a/packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js +++ b/packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js @@ -31,6 +31,16 @@ describe('ReactOwnerStacks', () => { ); } + it('behavior in production', () => { + if (!__DEV__) { + if (gate('fb')) { + expect(React).toHaveProperty('captureOwnerStack', undefined); + } else { + 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.js b/packages/react/index.js index 711a044455..af4cd4b0c1 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 0000000000..f9ac98bafb --- /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 26e22c3432..6f25c7a37d 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/TestFlags.js b/scripts/jest/TestFlags.js index b970955491..f736ca2254 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, diff --git a/scripts/jest/setupHostConfigs.js b/scripts/jest/setupHostConfigs.js index b52fac3201..aacfaf116f 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 2a67c13e1e..547362dae7 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)