@samitouri / QOS-React / commits / ed8b68dd17

Stop exporting dev-only methods in OSS production builds (#32200)

Sebastian "Sebbie" Silbermann committed Feb 13, 2025 at 18:26 UTC ed8b68dd178af17a2dd36c8678f81f8b454559a9
9 files changed +96 -29
packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js
-8
@@ -779,13 +779,5 @@ function runActTests(render, unmount, rerender) {
779 });
780 }
781 });
782 - describe('throw in prod mode', () => {
783 - // @gate !__DEV__
784 - it('warns if you try to use act() in prod mode', () => {
785 - expect(() => act(() => {})).toThrow(
786 - 'act(...) is not supported in production builds of React',
787 - );
788 - });
789 - });
782 });
783 }
packages/react-reconciler/src/__tests__/ReactIsomorphicAct-test.js
+12
@@ -50,6 +50,18 @@ describe('isomorphic act()', () => {
50 return text;
51 }
52
53 + it('behavior in production', () => {
54 + if (!__DEV__) {
55 + if (gate('fb')) {
56 + expect(() => act(() => {})).toThrow(
57 + 'act(...) is not supported in production builds of React',
58 + );
59 + } else {
60 + expect(React).not.toHaveProperty('act');
61 + }
62 + }
63 + });
64 +
65 // @gate __DEV__
66 it('bypasses queueMicrotask', async () => {
67 const root = ReactNoop.createRoot();
packages/react-reconciler/src/__tests__/ReactOwnerStacks-test.js
+10
@@ -31,6 +31,16 @@ describe('ReactOwnerStacks', () => {
31 );
32 }
33
34 + it('behavior in production', () => {
35 + if (!__DEV__) {
36 + if (gate('fb')) {
37 + expect(React).toHaveProperty('captureOwnerStack', undefined);
38 + } else {
39 + expect(React).not.toHaveProperty('captureOwnerStack');
40 + }
41 + }
42 + });
43 +
44 // @gate __DEV__ && enableOwnerStacks
45 it('can get the component owner stacks during rendering in dev', async () => {
46 let stack;
packages/react/index.js
-2
@@ -23,8 +23,6 @@ export type ElementRef<C> = React$ElementRef<C>;
23 export type Config<Props, DefaultProps> = React$Config<Props, DefaultProps>;
24 export type ChildrenArray<+T> = $ReadOnlyArray<ChildrenArray<T>> | T;
25
26 -// Export all exports so that they're available in tests.
27 -// We can't use export * from in Flow for some reason.
26 export {
27 __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
28 __COMPILER_RUNTIME,
packages/react/index.stable.development.js new
+51
@@ -0,0 +1,51 @@
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 + __COMPILER_RUNTIME,
13 + Children,
14 + Component,
15 + Fragment,
16 + Profiler,
17 + PureComponent,
18 + StrictMode,
19 + Suspense,
20 + cloneElement,
21 + createContext,
22 + createElement,
23 + createRef,
24 + use,
25 + forwardRef,
26 + isValidElement,
27 + lazy,
28 + memo,
29 + cache,
30 + unstable_useCacheRefresh,
31 + startTransition,
32 + useId,
33 + useCallback,
34 + useContext,
35 + useDebugValue,
36 + useDeferredValue,
37 + useEffect,
38 + useImperativeHandle,
39 + useInsertionEffect,
40 + useLayoutEffect,
41 + useMemo,
42 + useReducer,
43 + useOptimistic,
44 + useRef,
45 + useState,
46 + useSyncExternalStore,
47 + useTransition,
48 + useActionState,
49 + version,
50 + act, // DEV-only
51 +} from './src/ReactClient';
packages/react/index.stable.js
-1
@@ -10,7 +10,6 @@
10 export {
11 __CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE,
12 __COMPILER_RUNTIME,
13 - act,
13 Children,
14 Component,
15 Fragment,
scripts/jest/TestFlags.js
+1
@@ -78,6 +78,7 @@ function getTestFlags() {
78 classic: releaseChannel === 'classic',
79 source: !process.env.IS_BUILD,
80 www,
81 + fb: www || xplat,
82
83 // These aren't flags, just a useful aliases for tests.
84 enableActivity: releaseChannel === 'experimental' || www || xplat,
scripts/jest/setupHostConfigs.js
+13 -6
@@ -43,9 +43,9 @@ function resolveEntryFork(resolvedEntry, isFBBundle) {
43 }
44
45 resolvedEntry = nodePath.join(resolvedEntry, '..', entrypoint);
46 - const developmentEntry = resolvedEntry.replace('.js', '.development.js');
47 - if (fs.existsSync(developmentEntry)) {
48 - return developmentEntry;
46 + const devEntry = resolvedEntry.replace('.js', '.development.js');
47 + if (__DEV__ && fs.existsSync(devEntry)) {
48 + return devEntry;
49 }
50 if (fs.existsSync(resolvedEntry)) {
51 return resolvedEntry;
@@ -60,13 +60,20 @@ function resolveEntryFork(resolvedEntry, isFBBundle) {
60 __EXPERIMENTAL__ ? '.modern.fb.js' : '.classic.fb.js'
61 );
62 const devFBEntry = resolvedFBEntry.replace('.js', '.development.js');
63 - if (fs.existsSync(devFBEntry)) {
63 + if (__DEV__ && fs.existsSync(devFBEntry)) {
64 return devFBEntry;
65 }
66 if (fs.existsSync(resolvedFBEntry)) {
67 return resolvedFBEntry;
68 }
69 const resolvedGenericFBEntry = resolvedEntry.replace('.js', '.fb.js');
70 + const devGenericFBEntry = resolvedGenericFBEntry.replace(
71 + '.js',
72 + '.development.js'
73 + );
74 + if (__DEV__ && fs.existsSync(devGenericFBEntry)) {
75 + return devGenericFBEntry;
76 + }
77 if (fs.existsSync(resolvedGenericFBEntry)) {
78 return resolvedGenericFBEntry;
79 }
@@ -77,14 +84,14 @@ function resolveEntryFork(resolvedEntry, isFBBundle) {
84 __EXPERIMENTAL__ ? '.experimental.js' : '.stable.js'
85 );
86 const devForkedEntry = resolvedForkedEntry.replace('.js', '.development.js');
80 - if (fs.existsSync(devForkedEntry)) {
87 + if (__DEV__ && fs.existsSync(devForkedEntry)) {
88 return devForkedEntry;
89 }
90 if (fs.existsSync(resolvedForkedEntry)) {
91 return resolvedForkedEntry;
92 }
93 const plainDevEntry = resolvedEntry.replace('.js', '.development.js');
87 - if (fs.existsSync(plainDevEntry)) {
94 + if (__DEV__ && fs.existsSync(plainDevEntry)) {
95 return plainDevEntry;
96 }
97 // Just use the plain .js one.
scripts/rollup/build.js
+9 -12
@@ -571,7 +571,7 @@ function shouldSkipBundle(bundle, bundleType) {
571 return false;
572 }
573
574 -function resolveEntryFork(resolvedEntry, isFBBundle) {
574 +function resolveEntryFork(resolvedEntry, isFBBundle, isDev) {
575 // Pick which entry point fork to use:
576 // .modern.fb.js
577 // .classic.fb.js
@@ -586,23 +586,20 @@ function resolveEntryFork(resolvedEntry, isFBBundle) {
586 '.js',
587 __EXPERIMENTAL__ ? '.modern.fb.js' : '.classic.fb.js'
588 );
589 - const developmentFBEntry = resolvedFBEntry.replace(
590 - '.js',
591 - '.development.js'
592 - );
593 - if (fs.existsSync(developmentFBEntry)) {
594 - return developmentFBEntry;
589 + const devFBEntry = resolvedFBEntry.replace('.js', '.development.js');
590 + if (isDev && fs.existsSync(devFBEntry)) {
591 + return devFBEntry;
592 }
593 if (fs.existsSync(resolvedFBEntry)) {
594 return resolvedFBEntry;
595 }
596 const resolvedGenericFBEntry = resolvedEntry.replace('.js', '.fb.js');
600 - const developmentGenericFBEntry = resolvedGenericFBEntry.replace(
597 + const devGenericFBEntry = resolvedGenericFBEntry.replace(
598 '.js',
599 '.development.js'
600 );
604 - if (fs.existsSync(developmentGenericFBEntry)) {
605 - return developmentGenericFBEntry;
601 + if (isDev && fs.existsSync(devGenericFBEntry)) {
602 + return devGenericFBEntry;
603 }
604 if (fs.existsSync(resolvedGenericFBEntry)) {
605 return resolvedGenericFBEntry;
@@ -614,7 +611,7 @@ function resolveEntryFork(resolvedEntry, isFBBundle) {
611 __EXPERIMENTAL__ ? '.experimental.js' : '.stable.js'
612 );
613 const devForkedEntry = resolvedForkedEntry.replace('.js', '.development.js');
617 - if (fs.existsSync(devForkedEntry)) {
614 + if (isDev && fs.existsSync(devForkedEntry)) {
615 return devForkedEntry;
616 }
617 if (fs.existsSync(resolvedForkedEntry)) {
@@ -633,7 +630,7 @@ async function createBundle(bundle, bundleType) {
630
631 const {isFBWWWBundle, isFBRNBundle} = getBundleTypeFlags(bundleType);
632
636 - let resolvedEntry = resolveEntryFork(
633 + const resolvedEntry = resolveEntryFork(
634 require.resolve(bundle.entry),
635 isFBWWWBundle || isFBRNBundle,
636 !isProductionBundleType(bundleType)