@samitouri / QOS-React-2 / commits / 6bce0355c3

Upgrade useSyncExternalStore to alpha channel (#22662)

* Move useSyncExternalStore shim to a nested entrypoint Also renames `useSyncExternalStoreExtra` to `useSyncExternalStoreWithSelector`. - 'use-sync-external-store/shim' -> A shim for `useSyncExternalStore` that works in React 16 and 17 (any release that supports hooks). The module will first check if the built-in React API exists, before falling back to the shim. - 'use-sync-external-store/with-selector' -> An extended version of `useSyncExternalStore` that also supports `selector` and `isEqual` options. It does _not_ shim `use-sync-external-store`; it composes the built-in React API. **Use this if you only support 18+.** - 'use-sync-external-store/shim/with-selector' -> Same API, but it composes `use-sync-external-store/shim` instead. **Use this for compatibility with 16 and 17.** - 'use-sync-external-store' -> Re-exports React's built-in API. Not meant to be used. It will warn and direct users to either the shim or the built-in API. * Upgrade useSyncExternalStore to alpha channel

Andrew Clark committed Oct 31, 2021 at 18:38 UTC 6bce0355c3e4bf23c16e82317094230908ee7560
38 files changed +298 -124
ReactVersions.js
+1 -1
@@ -36,6 +36,7 @@ const stablePackages = {
36 'react-refresh': '0.11.0',
37 'react-test-renderer': ReactVersion,
38 'use-subscription': '1.6.0',
39 + 'use-sync-external-store': '1.0.0',
40 scheduler: '0.21.0',
41 };
42
@@ -47,7 +48,6 @@ const experimentalPackages = [
48 'react-fs',
49 'react-pg',
50 'react-server-dom-webpack',
50 - 'use-sync-external-store',
51 ];
52
53 module.exports = {
packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js
+1 -2
@@ -1055,9 +1055,8 @@ describe('ReactHooksInspectionIntegration', () => {
1055 ]);
1056 });
1057
1058 - // @gate experimental || www
1058 it('should support composite useSyncExternalStore hook', () => {
1060 - const useSyncExternalStore = React.unstable_useSyncExternalStore;
1059 + const useSyncExternalStore = React.useSyncExternalStore;
1060 function Foo() {
1061 const value = useSyncExternalStore(
1062 () => () => {},
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+17 -9
@@ -18,7 +18,7 @@ let ReactDOMFizzServer;
18 let Suspense;
19 let SuspenseList;
20 let useSyncExternalStore;
21 -let useSyncExternalStoreExtra;
21 +let useSyncExternalStoreWithSelector;
22 let PropTypes;
23 let textCache;
24 let window;
@@ -43,11 +43,23 @@ describe('ReactDOMFizzServer', () => {
43 Stream = require('stream');
44 Suspense = React.Suspense;
45 SuspenseList = React.SuspenseList;
46 - useSyncExternalStore = React.unstable_useSyncExternalStore;
47 - useSyncExternalStoreExtra = require('use-sync-external-store/extra')
48 - .useSyncExternalStoreExtra;
46 +
47 PropTypes = require('prop-types');
48
49 + if (gate(flags => flags.source)) {
50 + // The `with-selector` module composes the main `use-sync-external-store`
51 + // entrypoint. In the compiled artifacts, this is resolved to the `shim`
52 + // implementation by our build config, but when running the tests against
53 + // the source files, we need to tell Jest how to resolve it. Because this
54 + // is a source module, this mock has no affect on the build tests.
55 + jest.mock('use-sync-external-store/src/useSyncExternalStore', () =>
56 + jest.requireActual('react'),
57 + );
58 + }
59 + useSyncExternalStore = React.useSyncExternalStore;
60 + useSyncExternalStoreWithSelector = require('use-sync-external-store/with-selector')
61 + .useSyncExternalStoreWithSelector;
62 +
63 textCache = new Map();
64
65 // Test Environment
@@ -1663,7 +1675,6 @@ describe('ReactDOMFizzServer', () => {
1675 );
1676 });
1677
1666 - // @gate supportsNativeUseSyncExternalStore
1678 // @gate experimental
1679 it('calls getServerSnapshot instead of getSnapshot', async () => {
1680 const ref = React.createRef();
@@ -1734,7 +1745,6 @@ describe('ReactDOMFizzServer', () => {
1745
1746 // The selector implementation uses the lazy ref initialization pattern
1747 // @gate !(enableUseRefAccessWarning && __DEV__)
1737 - // @gate supportsNativeUseSyncExternalStore
1748 // @gate experimental
1749 it('calls getServerSnapshot instead of getSnapshot (with selector and isEqual)', async () => {
1750 // Same as previous test, but with a selector that returns a complex object
@@ -1767,7 +1777,7 @@ describe('ReactDOMFizzServer', () => {
1777 }
1778
1779 function App() {
1770 - const {env} = useSyncExternalStoreExtra(
1780 + const {env} = useSyncExternalStoreWithSelector(
1781 subscribe,
1782 getClientSnapshot,
1783 getServerSnapshot,
@@ -1815,7 +1825,6 @@ describe('ReactDOMFizzServer', () => {
1825 expect(ref.current).toEqual(serverRenderedDiv);
1826 });
1827
1818 - // @gate supportsNativeUseSyncExternalStore
1828 // @gate experimental
1829 it(
1830 'errors during hydration force a client render at the nearest Suspense ' +
@@ -1964,7 +1973,6 @@ describe('ReactDOMFizzServer', () => {
1973 },
1974 );
1975
1967 - // @gate supportsNativeUseSyncExternalStore
1976 // @gate experimental
1977 it(
1978 'errors during hydration force a client render at the nearest Suspense ' +
packages/react-reconciler/src/__tests__/useSyncExternalStore-test.js
+1 -2
@@ -36,7 +36,7 @@ describe('useSyncExternalStore', () => {
36 useImperativeHandle = React.useImperativeHandle;
37 forwardRef = React.forwardRef;
38 useRef = React.useRef;
39 - useSyncExternalStore = React.unstable_useSyncExternalStore;
39 + useSyncExternalStore = React.useSyncExternalStore;
40 startTransition = React.startTransition;
41
42 act = require('jest-react').act;
@@ -70,7 +70,6 @@ describe('useSyncExternalStore', () => {
70 };
71 }
72
73 - // @gate supportsNativeUseSyncExternalStore
73 test(
74 'detects interleaved mutations during a concurrent read before ' +
75 'layout effects fire',
packages/react/index.classic.fb.js
-1
@@ -54,7 +54,6 @@ export {
54 useMutableSource,
55 useMutableSource as unstable_useMutableSource,
56 useSyncExternalStore,
57 - useSyncExternalStore as unstable_useSyncExternalStore,
57 useReducer,
58 useRef,
59 useState,
packages/react/index.experimental.js
+1 -1
@@ -47,7 +47,7 @@ export {
47 useLayoutEffect,
48 useMemo,
49 useMutableSource as unstable_useMutableSource,
50 - useSyncExternalStore as unstable_useSyncExternalStore,
50 + useSyncExternalStore,
51 useReducer,
52 useRef,
53 useState,
packages/react/index.js
-1
@@ -73,7 +73,6 @@ export {
73 useMemo,
74 useMutableSource,
75 useSyncExternalStore,
76 - useSyncExternalStore as unstable_useSyncExternalStore,
76 useReducer,
77 useRef,
78 useState,
packages/react/index.modern.fb.js
-1
@@ -53,7 +53,6 @@ export {
53 useMutableSource,
54 useMutableSource as unstable_useMutableSource,
55 useSyncExternalStore,
56 - useSyncExternalStore as unstable_useSyncExternalStore,
56 useReducer,
57 useRef,
58 useState,
packages/react/index.stable.js
+1
@@ -40,6 +40,7 @@ export {
40 useLayoutEffect,
41 useMemo,
42 useMutableSource as unstable_useMutableSource,
43 + useSyncExternalStore,
44 useReducer,
45 useRef,
46 useState,
packages/use-sync-external-store/index.js
+1 -1
@@ -9,4 +9,4 @@
9
10 'use strict';
11
12 -export * from './src/useSyncExternalStore';
12 +export {useSyncExternalStore} from './src/useSyncExternalStore';
packages/use-sync-external-store/npm/extra.js deleted
-7
@@ -1,7 +0,0 @@
1 -'use strict';
2 -
3 -if (process.env.NODE_ENV === 'production') {
4 - module.exports = require('./cjs/use-sync-external-store-extra.production.min.js');
5 -} else {
6 - module.exports = require('./cjs/use-sync-external-store-extra.development.js');
7 -}
packages/use-sync-external-store/npm/index.native.js deleted
-7
@@ -1,7 +0,0 @@
1 -'use strict';
2 -
3 -if (process.env.NODE_ENV === 'production') {
4 - module.exports = require('./cjs/use-sync-external-store.native.production.min.js');
5 -} else {
6 - module.exports = require('./cjs/use-sync-external-store.native.development.js');
7 -}
packages/use-sync-external-store/npm/shim/index.js new
+7
@@ -0,0 +1,7 @@
1 +'use strict';
2 +
3 +if (process.env.NODE_ENV === 'production') {
4 + module.exports = require('../cjs/use-sync-external-store-shim.production.min.js');
5 +} else {
6 + module.exports = require('../cjs/use-sync-external-store-shim.development.js');
7 +}
packages/use-sync-external-store/npm/shim/index.native.js new
+7
@@ -0,0 +1,7 @@
1 +'use strict';
2 +
3 +if (process.env.NODE_ENV === 'production') {
4 + module.exports = require('../cjs/use-sync-external-store-shim.native.production.min.js');
5 +} else {
6 + module.exports = require('../cjs/use-sync-external-store-shim.native.development.js');
7 +}
packages/use-sync-external-store/npm/shim/with-selector.js new
+7
@@ -0,0 +1,7 @@
1 +'use strict';
2 +
3 +if (process.env.NODE_ENV === 'production') {
4 + module.exports = require('../cjs/use-sync-external-store-shim/with-selector.production.min.js');
5 +} else {
6 + module.exports = require('../cjs/use-sync-external-store-shim/with-selector.development.js');
7 +}
packages/use-sync-external-store/npm/with-selector.js new
+7
@@ -0,0 +1,7 @@
1 +'use strict';
2 +
3 +if (process.env.NODE_ENV === 'production') {
4 + module.exports = require('./cjs/use-sync-external-store-with-selector.production.min.js');
5 +} else {
6 + module.exports = require('./cjs/use-sync-external-store-with-selector.development.js');
7 +}
packages/use-sync-external-store/package.json
+3 -1
@@ -12,8 +12,10 @@
12 "README.md",
13 "build-info.json",
14 "index.js",
15 - "extra.js",
15 "index.native.js",
16 + "with-selector.js",
17 + "with-selector.native.js",
18 + "shim/",
19 "cjs/"
20 ],
21 "license": "MIT",
packages/use-sync-external-store/shim/index.js new
+12
@@ -0,0 +1,12 @@
1 +/**
2 + * Copyright (c) Facebook, 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 +'use strict';
11 +
12 +export {useSyncExternalStore} from 'use-sync-external-store/src/useSyncExternalStoreShim';
packages/use-sync-external-store/shim/index.native.js renamed
+1 -1
@@ -9,4 +9,4 @@
9
10 'use strict';
11
12 -export * from './src/useSyncExternalStoreClient';
12 +export {useSyncExternalStore} from 'use-sync-external-store/src/useSyncExternalStoreShim';
packages/use-sync-external-store/shim/with-selector/index.js new
+12
@@ -0,0 +1,12 @@
1 +/**
2 + * Copyright (c) Facebook, 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 +'use strict';
11 +
12 +export {useSyncExternalStoreWithSelector} from 'use-sync-external-store/src/useSyncExternalStoreWithSelector';
packages/use-sync-external-store/src/__tests__/useSyncExternalStoreNative-test.js
+25 -30
@@ -15,7 +15,7 @@ let React;
15 let ReactNoop;
16 let Scheduler;
17 let useSyncExternalStore;
18 -let useSyncExternalStoreExtra;
18 +let useSyncExternalStoreWithSelector;
19 let act;
20
21 // This tests the userspace shim of `useSyncExternalStore` in a server-rendering
@@ -36,25 +36,40 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
36 startTransition: _,
37 // eslint-disable-next-line no-unused-vars
38 useSyncExternalStore: __,
39 - // eslint-disable-next-line no-unused-vars
40 - unstable_useSyncExternalStore: ___,
39 ...otherExports
40 } = jest.requireActual('react');
41 return otherExports;
42 });
43
46 - jest.mock('use-sync-external-store', () =>
47 - jest.requireActual('use-sync-external-store/index.native'),
44 + jest.mock('use-sync-external-store/shim', () =>
45 + jest.requireActual('use-sync-external-store/shim/index.native'),
46 );
47
48 React = require('react');
49 ReactNoop = require('react-noop-renderer');
50 Scheduler = require('scheduler');
51 act = require('jest-react').act;
54 - useSyncExternalStore = require('use-sync-external-store')
52 +
53 + if (gate(flags => flags.source)) {
54 + // The `shim/with-selector` module composes the main
55 + // `use-sync-external-store` entrypoint. In the compiled artifacts, this
56 + // is resolved to the `shim` implementation by our build config, but when
57 + // running the tests against the source files, we need to tell Jest how to
58 + // resolve it. Because this is a source module, this mock has no affect on
59 + // the build tests.
60 + jest.mock('use-sync-external-store/src/useSyncExternalStore', () =>
61 + jest.requireActual('use-sync-external-store/shim'),
62 + );
63 + jest.mock('use-sync-external-store/src/isServerEnvironment', () =>
64 + jest.requireActual(
65 + 'use-sync-external-store/src/forks/isServerEnvironment.native',
66 + ),
67 + );
68 + }
69 + useSyncExternalStore = require('use-sync-external-store/shim')
70 .useSyncExternalStore;
56 - useSyncExternalStoreExtra = require('use-sync-external-store/extra')
57 - .useSyncExternalStoreExtra;
71 + useSyncExternalStoreWithSelector = require('use-sync-external-store/shim/with-selector')
72 + .useSyncExternalStoreWithSelector;
73 });
74
75 function Text({text}) {
@@ -105,32 +120,12 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
120 expect(root).toMatchRenderedOutput('client');
121 });
122
108 - test('native version', async () => {
109 - const store = createExternalStore('client');
110 -
111 - function App() {
112 - const text = useSyncExternalStore(
113 - store.subscribe,
114 - store.getState,
115 - () => 'server',
116 - );
117 - return <Text text={text} />;
118 - }
119 -
120 - const root = ReactNoop.createRoot();
121 - await act(() => {
122 - root.render(<App />);
123 - });
124 - expect(Scheduler).toHaveYielded(['client']);
125 - expect(root).toMatchRenderedOutput('client');
126 - });
127 -
123 // @gate !(enableUseRefAccessWarning && __DEV__)
124 test('Using isEqual to bailout', async () => {
125 const store = createExternalStore({a: 0, b: 0});
126
127 function A() {
133 - const {a} = useSyncExternalStoreExtra(
128 + const {a} = useSyncExternalStoreWithSelector(
129 store.subscribe,
130 store.getState,
131 null,
@@ -140,7 +135,7 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
135 return <Text text={'A' + a} />;
136 }
137 function B() {
143 - const {b} = useSyncExternalStoreExtra(
138 + const {b} = useSyncExternalStoreWithSelector(
139 store.subscribe,
140 store.getState,
141 null,
packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShared-test.js
+26 -21
@@ -10,7 +10,7 @@
10 'use strict';
11
12 let useSyncExternalStore;
13 -let useSyncExternalStoreExtra;
13 +let useSyncExternalStoreWithSelector;
14 let React;
15 let ReactDOM;
16 let Scheduler;
@@ -25,11 +25,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
25 beforeEach(() => {
26 jest.resetModules();
27
28 - // Remove the built-in API from the React exports to force the package to
29 - // use the shim.
30 - if (!gate(flags => flags.supportsNativeUseSyncExternalStore)) {
31 - // and the non-variant tests for the shim.
32 - //
28 + if (gate(flags => flags.enableUseSyncExternalStoreShim)) {
29 // Remove useSyncExternalStore from the React imports so that we use the
30 // shim instead. Also removing startTransition, since we use that to
31 // detect outdated 18 alphas that don't yet include useSyncExternalStore.
@@ -42,8 +38,6 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
38 startTransition: _,
39 // eslint-disable-next-line no-unused-vars
40 useSyncExternalStore: __,
45 - // eslint-disable-next-line no-unused-vars
46 - unstable_useSyncExternalStore: ___,
41 ...otherExports
42 } = jest.requireActual('react');
43 return otherExports;
@@ -64,10 +58,21 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
58 // in both concurrent and legacy mode, I'm adding batching here.
59 act = cb => internalAct(() => ReactDOM.unstable_batchedUpdates(cb));
60
67 - useSyncExternalStore = require('use-sync-external-store')
61 + if (gate(flags => flags.source)) {
62 + // The `shim/with-selector` module composes the main
63 + // `use-sync-external-store` entrypoint. In the compiled artifacts, this
64 + // is resolved to the `shim` implementation by our build config, but when
65 + // running the tests against the source files, we need to tell Jest how to
66 + // resolve it. Because this is a source module, this mock has no affect on
67 + // the build tests.
68 + jest.mock('use-sync-external-store/src/useSyncExternalStore', () =>
69 + jest.requireActual('use-sync-external-store/shim'),
70 + );
71 + }
72 + useSyncExternalStore = require('use-sync-external-store/shim')
73 .useSyncExternalStore;
69 - useSyncExternalStoreExtra = require('use-sync-external-store/extra')
70 - .useSyncExternalStoreExtra;
74 + useSyncExternalStoreWithSelector = require('use-sync-external-store/shim/with-selector')
75 + .useSyncExternalStoreWithSelector;
76 });
77
78 function Text({text}) {
@@ -78,7 +83,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
83 function createRoot(container) {
84 // This wrapper function exists so we can test both legacy roots and
85 // concurrent roots.
81 - if (gate(flags => flags.supportsNativeUseSyncExternalStore)) {
86 + if (gate(flags => !flags.enableUseSyncExternalStoreShim)) {
87 // The native implementation only exists in 18+, so we test using
88 // concurrent mode. To test the legacy root behavior in the native
89 // implementation (which is supported in the sense that it needs to have
@@ -265,7 +270,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
270
271 // In React 18, you can't observe in between a sync render and its
272 // passive effects, so this is only relevant to legacy roots
268 - // @gate !supportsNativeUseSyncExternalStore
273 + // @gate enableUseSyncExternalStoreShim
274 test(
275 "compares to current state before bailing out, even when there's a " +
276 'mutation in between the sync and passive effects',
@@ -547,7 +552,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
552 await act(() => {
553 store.set({value: 1, throwInGetSnapshot: true, throwInIsEqual: false});
554 });
550 - if (gate(flags => flags.supportsNativeUseSyncExternalStore)) {
555 + if (gate(flags => !flags.enableUseSyncExternalStoreShim)) {
556 expect(Scheduler).toHaveYielded([
557 'Error in getSnapshot',
558 // In a concurrent root, React renders a second time to attempt to
@@ -595,7 +600,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
600
601 function App() {
602 Scheduler.unstable_yieldValue('App');
598 - const a = useSyncExternalStoreExtra(
603 + const a = useSyncExternalStoreWithSelector(
604 store.subscribe,
605 store.getState,
606 null,
@@ -632,7 +637,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
637 const store = createExternalStore({a: 0, b: 0});
638
639 function A() {
635 - const {a} = useSyncExternalStoreExtra(
640 + const {a} = useSyncExternalStoreWithSelector(
641 store.subscribe,
642 store.getState,
643 null,
@@ -642,7 +647,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
647 return <Text text={'A' + a} />;
648 }
649 function B() {
645 - const {b} = useSyncExternalStoreExtra(
650 + const {b} = useSyncExternalStoreWithSelector(
651 store.subscribe,
652 store.getState,
653 null,
@@ -711,7 +716,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
716 container.innerHTML = '<div>server</div>';
717 const serverRenderedDiv = container.getElementsByTagName('div')[0];
718
714 - if (gate(flags => flags.supportsNativeUseSyncExternalStore)) {
719 + if (gate(flags => !flags.enableUseSyncExternalStoreShim)) {
720 act(() => {
721 ReactDOM.hydrateRoot(container, <App />);
722 });
@@ -774,7 +779,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
779 Scheduler.unstable_yieldValue('Inline selector');
780 return [...state.items, 'C'];
781 };
777 - const items = useSyncExternalStoreExtra(
782 + const items = useSyncExternalStoreWithSelector(
783 store.subscribe,
784 store.getState,
785 null,
@@ -842,7 +847,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
847 const selector = state => state.a.toUpperCase();
848
849 function App() {
845 - const a = useSyncExternalStoreExtra(
850 + const a = useSyncExternalStoreWithSelector(
851 store.subscribe,
852 store.getState,
853 null,
@@ -877,7 +882,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => {
882 const isEqual = (left, right) => left.a.trim() === right.a.trim();
883
884 function App() {
880 - const a = useSyncExternalStoreExtra(
885 + const a = useSyncExternalStoreWithSelector(
886 store.subscribe,
887 store.getState,
888 null,
packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShimServer-test.js
+1 -3
@@ -35,8 +35,6 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
35 startTransition: _,
36 // eslint-disable-next-line no-unused-vars
37 useSyncExternalStore: __,
38 - // eslint-disable-next-line no-unused-vars
39 - unstable_useSyncExternalStore: ___,
38 ...otherExports
39 } = jest.requireActual('react');
40 return otherExports;
@@ -47,7 +45,7 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => {
45 ReactDOMServer = require('react-dom/server');
46 Scheduler = require('scheduler');
47
50 - useSyncExternalStore = require('use-sync-external-store')
48 + useSyncExternalStore = require('use-sync-external-store/shim')
49 .useSyncExternalStore;
50 });
51
packages/use-sync-external-store/src/forks/isServerEnvironment.native.js renamed
+1 -3
@@ -7,6 +7,4 @@
7 * @flow
8 */
9
10 -'use strict';
11 -
12 -export * from './src/useSyncExternalStoreExtra';
10 +export const isServerEnvironment = false;
packages/use-sync-external-store/src/forks/useSyncExternalStore.forward-to-built-in.js new
+16
@@ -0,0 +1,16 @@
1 +/**
2 + * Copyright (c) Facebook, 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 +'use strict';
11 +
12 +// Intentionally not using named imports because Rollup uses dynamic
13 +// dispatch for CommonJS interop named imports.
14 +import * as React from 'react';
15 +
16 +export const useSyncExternalStore = React.useSyncExternalStore;
packages/use-sync-external-store/src/forks/useSyncExternalStore.forward-to-shim.js new
+16
@@ -0,0 +1,16 @@
1 +/**
2 + * Copyright (c) Facebook, 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 +'use strict';
11 +
12 +// Intentionally not using named imports because Rollup uses dynamic
13 +// dispatch for CommonJS interop named imports.
14 +import * as shim from 'use-sync-external-store/shim';
15 +
16 +export const useSyncExternalStore = shim.useSyncExternalStore;
packages/use-sync-external-store/src/isServerEnvironment.js new
+12
@@ -0,0 +1,12 @@
1 +/**
2 + * Copyright (c) Facebook, 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 {canUseDOM} from 'shared/ExecutionEnvironment';
11 +
12 +export const isServerEnvironment = !canUseDOM;
packages/use-sync-external-store/src/useSyncExternalStore.js
+18 -10
@@ -7,16 +7,24 @@
7 * @flow
8 */
9
10 -import {canUseDOM} from 'shared/ExecutionEnvironment';
11 -import {useSyncExternalStore as client} from './useSyncExternalStoreClient';
12 -import {useSyncExternalStore as server} from './useSyncExternalStoreServer';
10 +'use strict';
11 +
12 +// Intentionally not using named imports because Rollup uses dynamic
13 +// dispatch for CommonJS interop named imports.
14 import * as React from 'react';
15
15 -const {unstable_useSyncExternalStore: builtInAPI} = React;
16 +export const useSyncExternalStore = React.useSyncExternalStore;
17
17 -export const useSyncExternalStore =
18 - builtInAPI !== undefined
19 - ? ((builtInAPI: any): typeof client)
20 - : canUseDOM
21 - ? client
22 - : server;
18 +if (__DEV__) {
19 + console.error(
20 + "The main 'use-sync-external-store' entry point is not supported; all it " +
21 + "does is re-export useSyncExternalStore from the 'react' package, so " +
22 + 'it only works with React 18+.' +
23 + '\n\n' +
24 + 'If you wish to support React 16 and 17, import from ' +
25 + "'use-sync-external-store/shim' instead. It will fall back to a shimmed" +
26 + 'implementation when the native one is not available.' +
27 + '\n\n' +
28 + "If you only support React 18+, you can import directly from 'react'.",
29 + );
30 +}
packages/use-sync-external-store/src/useSyncExternalStoreShim.js new
+18
@@ -0,0 +1,18 @@
1 +/**
2 + * Copyright (c) Facebook, 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 {useSyncExternalStore as client} from './useSyncExternalStoreShimClient';
11 +import {useSyncExternalStore as server} from './useSyncExternalStoreShimServer';
12 +import {isServerEnvironment} from './isServerEnvironment';
13 +import {useSyncExternalStore as builtInAPI} from 'react';
14 +
15 +const shim = isServerEnvironment ? server : client;
16 +
17 +export const useSyncExternalStore =
18 + builtInAPI !== undefined ? ((builtInAPI: any): typeof shim) : shim;
packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js renamed
+4 -4
@@ -30,10 +30,10 @@ let didWarnUncachedGetSnapshot = false;
30 export function useSyncExternalStore<T>(
31 subscribe: (() => void) => () => void,
32 getSnapshot: () => T,
33 - // Note: The client shim does not use getServerSnapshot, because pre-18
34 - // versions of React do not expose a way to check if we're hydrating. So
35 - // users of the shim will need to track that themselves and return the
36 - // correct value from `getSnapshot`.
33 + // Note: The shim does not use getServerSnapshot, because pre-18 versions of
34 + // React do not expose a way to check if we're hydrating. So users of the shim
35 + // will need to track that themselves and return the correct value
36 + // from `getSnapshot`.
37 getServerSnapshot?: () => T,
38 ): T {
39 if (__DEV__) {
packages/use-sync-external-store/src/useSyncExternalStoreShimServer.js renamed
+4
@@ -12,5 +12,9 @@ export function useSyncExternalStore<T>(
12 getSnapshot: () => T,
13 getServerSnapshot?: () => T,
14 ): T {
15 + // Note: The shim does not use getServerSnapshot, because pre-18 versions of
16 + // React do not expose a way to check if we're hydrating. So users of the shim
17 + // will need to track that themselves and return the correct value
18 + // from `getSnapshot`.
19 return getSnapshot();
20 }
packages/use-sync-external-store/src/useSyncExternalStoreWithSelector.js renamed
+4 -4
@@ -9,14 +9,14 @@
9
10 import * as React from 'react';
11 import is from 'shared/objectIs';
12 -import {useSyncExternalStore} from 'use-sync-external-store';
12 +import {useSyncExternalStore} from 'use-sync-external-store/src/useSyncExternalStore';
13
14 -// Intentionally not using named imports because Rollup uses dynamic
15 -// dispatch for CommonJS interop named imports.
14 +// Intentionally not using named imports because Rollup uses dynamic dispatch
15 +// for CommonJS interop.
16 const {useRef, useEffect, useMemo, useDebugValue} = React;
17
18 // Same as useSyncExternalStore, but supports selector and isEqual arguments.
19 -export function useSyncExternalStoreExtra<Snapshot, Selection>(
19 +export function useSyncExternalStoreWithSelector<Snapshot, Selection>(
20 subscribe: (() => void) => () => void,
21 getSnapshot: () => Snapshot,
22 getServerSnapshot: void | null | (() => Snapshot),
packages/use-sync-external-store/with-selector.js new
+12
@@ -0,0 +1,12 @@
1 +/**
2 + * Copyright (c) Facebook, 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 +'use strict';
11 +
12 +export {useSyncExternalStoreWithSelector} from 'use-sync-external-store/src/useSyncExternalStoreWithSelector';
scripts/jest/TestFlags.js
+2 -3
@@ -84,9 +84,8 @@ function getTestFlags() {
84 source: !process.env.IS_BUILD,
85 www,
86
87 - // This isn't a flag, just a useful alias for tests. Remove once
88 - // useSyncExternalStore lands in the `next` channel.
89 - supportsNativeUseSyncExternalStore: __EXPERIMENTAL__ || www,
87 + // This isn't a flag, just a useful alias for tests.
88 + enableUseSyncExternalStoreShim: !__VARIANT__,
89
90 // If there's a naming conflict between scheduler and React feature flags, the
91 // React ones take precedence.
scripts/jest/config.build.js
+7
@@ -45,6 +45,13 @@ packages.forEach(name => {
45 ] = `<rootDir>/build/${NODE_MODULES_DIR}/${name}/$1`;
46 });
47
48 +moduleNameMapper[
49 + 'use-sync-external-store/shim/with-selector'
50 +] = `<rootDir>/build/${NODE_MODULES_DIR}/use-sync-external-store/shim/with-selector`;
51 +moduleNameMapper[
52 + 'use-sync-external-store/shim/index.native'
53 +] = `<rootDir>/build/${NODE_MODULES_DIR}/use-sync-external-store/shim/index.native`;
54 +
55 module.exports = Object.assign({}, baseConfig, {
56 // Redirect imports to the compiled bundles
57 moduleNameMapper,
scripts/rollup/bundles.js
+33 -11
@@ -789,7 +789,7 @@ const bundles = [
789 externals: ['react'],
790 },
791
792 - /******* Shim for useSyncExternalStore *******/
792 + /******* useSyncExternalStore *******/
793 {
794 bundleTypes: [NODE_DEV, NODE_PROD],
795 moduleType: ISOMORPHIC,
@@ -800,26 +800,48 @@ const bundles = [
800 externals: ['react'],
801 },
802
803 - /******* Shim for useSyncExternalStore (+ extra user-space features) *******/
803 + /******* useSyncExternalStore (shim) *******/
804 {
805 bundleTypes: [NODE_DEV, NODE_PROD],
806 moduleType: ISOMORPHIC,
807 - entry: 'use-sync-external-store/extra',
808 - global: 'useSyncExternalStoreExtra',
809 - minifyWithProdErrorCodes: true,
807 + entry: 'use-sync-external-store/shim',
808 + global: 'useSyncExternalStore',
809 + minifyWithProdErrorCodes: false,
810 wrapWithModuleBoundaries: true,
811 - externals: ['react', 'use-sync-external-store'],
811 + externals: ['react'],
812 },
813
814 - /******* Shim for useSyncExternalStore ReactNative *******/
814 + /******* useSyncExternalStore (shim, native) *******/
815 {
816 bundleTypes: [NODE_DEV, NODE_PROD],
817 moduleType: ISOMORPHIC,
818 - entry: 'use-sync-external-store/index.native',
819 - global: 'useSyncExternalStoreNative',
820 - minifyWithProdErrorCodes: true,
818 + entry: 'use-sync-external-store/shim/index.native',
819 + global: 'useSyncExternalStore',
820 + minifyWithProdErrorCodes: false,
821 wrapWithModuleBoundaries: true,
822 - externals: ['react', 'ReactNativeInternalFeatureFlags'],
822 + externals: ['react'],
823 + },
824 +
825 + /******* useSyncExternalStoreWithSelector *******/
826 + {
827 + bundleTypes: [NODE_DEV, NODE_PROD],
828 + moduleType: ISOMORPHIC,
829 + entry: 'use-sync-external-store/with-selector',
830 + global: 'useSyncExternalStoreWithSelector',
831 + minifyWithProdErrorCodes: false,
832 + wrapWithModuleBoundaries: true,
833 + externals: ['react'],
834 + },
835 +
836 + /******* useSyncExternalStoreWithSelector (shim) *******/
837 + {
838 + bundleTypes: [NODE_DEV, NODE_PROD],
839 + moduleType: ISOMORPHIC,
840 + entry: 'use-sync-external-store/shim/with-selector',
841 + global: 'useSyncExternalStoreWithSelector',
842 + minifyWithProdErrorCodes: false,
843 + wrapWithModuleBoundaries: true,
844 + externals: ['react', 'use-sync-external-store/shim'],
845 },
846
847 /******* React Scheduler (experimental) *******/
scripts/rollup/forks.js
+18
@@ -481,6 +481,24 @@ const forks = Object.freeze({
481 return null;
482 }
483 },
484 +
485 + 'use-sync-external-store/src/useSyncExternalStore': (bundleType, entry) => {
486 + if (entry.startsWith('use-sync-external-store/shim')) {
487 + return 'use-sync-external-store/src/forks/useSyncExternalStore.forward-to-shim';
488 + }
489 + if (entry !== 'use-sync-external-store') {
490 + // Internal modules that aren't shims should use the native API from the
491 + // react package.
492 + return 'use-sync-external-store/src/forks/useSyncExternalStore.forward-to-built-in';
493 + }
494 + return null;
495 + },
496 +
497 + 'use-sync-external-store/src/isServerEnvironment': (bundleType, entry) => {
498 + if (entry.endsWith('.native')) {
499 + return 'use-sync-external-store/src/forks/isServerEnvironment.native';
500 + }
501 + },
502 });
503
504 module.exports = forks;
scripts/shared/pathsByLanguageVersion.js
+2
@@ -11,6 +11,8 @@ const esNextPaths = [
11 // Internal forwarding modules
12 'packages/*/*.js',
13 'packages/*/esm/*.js',
14 + 'packages/use-sync-external-store/shim/**/*.js',
15 + 'packages/use-sync-external-store/with-selector/**/*.js',
16 // Source files
17 'packages/*/src/**/*.js',
18 'packages/dom-event-testing-library/**/*.js',