diff --git a/ReactVersions.js b/ReactVersions.js index 2ce7680f0191c..bdab35424e885 100644 --- a/ReactVersions.js +++ b/ReactVersions.js @@ -36,6 +36,7 @@ const stablePackages = { 'react-refresh': '0.11.0', 'react-test-renderer': ReactVersion, 'use-subscription': '1.6.0', + 'use-sync-external-store': '1.0.0', scheduler: '0.21.0', }; @@ -47,7 +48,6 @@ const experimentalPackages = [ 'react-fs', 'react-pg', 'react-server-dom-webpack', - 'use-sync-external-store', ]; module.exports = { diff --git a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js index 6937efd631572..087d74d628724 100644 --- a/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js +++ b/packages/react-debug-tools/src/__tests__/ReactHooksInspectionIntegration-test.js @@ -1055,9 +1055,8 @@ describe('ReactHooksInspectionIntegration', () => { ]); }); - // @gate experimental || www it('should support composite useSyncExternalStore hook', () => { - const useSyncExternalStore = React.unstable_useSyncExternalStore; + const useSyncExternalStore = React.useSyncExternalStore; function Foo() { const value = useSyncExternalStore( () => () => {}, diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js index a2ede1746d08a..abbbdc1578753 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js @@ -18,7 +18,7 @@ let ReactDOMFizzServer; let Suspense; let SuspenseList; let useSyncExternalStore; -let useSyncExternalStoreExtra; +let useSyncExternalStoreWithSelector; let PropTypes; let textCache; let window; @@ -43,11 +43,23 @@ describe('ReactDOMFizzServer', () => { Stream = require('stream'); Suspense = React.Suspense; SuspenseList = React.SuspenseList; - useSyncExternalStore = React.unstable_useSyncExternalStore; - useSyncExternalStoreExtra = require('use-sync-external-store/extra') - .useSyncExternalStoreExtra; + PropTypes = require('prop-types'); + if (gate(flags => flags.source)) { + // The `with-selector` module composes the main `use-sync-external-store` + // entrypoint. In the compiled artifacts, this is resolved to the `shim` + // implementation by our build config, but when running the tests against + // the source files, we need to tell Jest how to resolve it. Because this + // is a source module, this mock has no affect on the build tests. + jest.mock('use-sync-external-store/src/useSyncExternalStore', () => + jest.requireActual('react'), + ); + } + useSyncExternalStore = React.useSyncExternalStore; + useSyncExternalStoreWithSelector = require('use-sync-external-store/with-selector') + .useSyncExternalStoreWithSelector; + textCache = new Map(); // Test Environment @@ -1663,7 +1675,6 @@ describe('ReactDOMFizzServer', () => { ); }); - // @gate supportsNativeUseSyncExternalStore // @gate experimental it('calls getServerSnapshot instead of getSnapshot', async () => { const ref = React.createRef(); @@ -1734,7 +1745,6 @@ describe('ReactDOMFizzServer', () => { // The selector implementation uses the lazy ref initialization pattern // @gate !(enableUseRefAccessWarning && __DEV__) - // @gate supportsNativeUseSyncExternalStore // @gate experimental it('calls getServerSnapshot instead of getSnapshot (with selector and isEqual)', async () => { // Same as previous test, but with a selector that returns a complex object @@ -1767,7 +1777,7 @@ describe('ReactDOMFizzServer', () => { } function App() { - const {env} = useSyncExternalStoreExtra( + const {env} = useSyncExternalStoreWithSelector( subscribe, getClientSnapshot, getServerSnapshot, @@ -1815,7 +1825,6 @@ describe('ReactDOMFizzServer', () => { expect(ref.current).toEqual(serverRenderedDiv); }); - // @gate supportsNativeUseSyncExternalStore // @gate experimental it( 'errors during hydration force a client render at the nearest Suspense ' + @@ -1964,7 +1973,6 @@ describe('ReactDOMFizzServer', () => { }, ); - // @gate supportsNativeUseSyncExternalStore // @gate experimental it( 'errors during hydration force a client render at the nearest Suspense ' + diff --git a/packages/react-reconciler/src/__tests__/useSyncExternalStore-test.js b/packages/react-reconciler/src/__tests__/useSyncExternalStore-test.js index d939a02bee897..623ea12755907 100644 --- a/packages/react-reconciler/src/__tests__/useSyncExternalStore-test.js +++ b/packages/react-reconciler/src/__tests__/useSyncExternalStore-test.js @@ -36,7 +36,7 @@ describe('useSyncExternalStore', () => { useImperativeHandle = React.useImperativeHandle; forwardRef = React.forwardRef; useRef = React.useRef; - useSyncExternalStore = React.unstable_useSyncExternalStore; + useSyncExternalStore = React.useSyncExternalStore; startTransition = React.startTransition; act = require('jest-react').act; @@ -70,7 +70,6 @@ describe('useSyncExternalStore', () => { }; } - // @gate supportsNativeUseSyncExternalStore test( 'detects interleaved mutations during a concurrent read before ' + 'layout effects fire', diff --git a/packages/react/index.classic.fb.js b/packages/react/index.classic.fb.js index 568a8ada613d9..c5854f8f6d398 100644 --- a/packages/react/index.classic.fb.js +++ b/packages/react/index.classic.fb.js @@ -54,7 +54,6 @@ export { useMutableSource, useMutableSource as unstable_useMutableSource, useSyncExternalStore, - useSyncExternalStore as unstable_useSyncExternalStore, useReducer, useRef, useState, diff --git a/packages/react/index.experimental.js b/packages/react/index.experimental.js index 7491bbb7e832d..4b4fa89e01898 100644 --- a/packages/react/index.experimental.js +++ b/packages/react/index.experimental.js @@ -47,7 +47,7 @@ export { useLayoutEffect, useMemo, useMutableSource as unstable_useMutableSource, - useSyncExternalStore as unstable_useSyncExternalStore, + useSyncExternalStore, useReducer, useRef, useState, diff --git a/packages/react/index.js b/packages/react/index.js index 59cc05f0254e6..9a6a99ee52189 100644 --- a/packages/react/index.js +++ b/packages/react/index.js @@ -73,7 +73,6 @@ export { useMemo, useMutableSource, useSyncExternalStore, - useSyncExternalStore as unstable_useSyncExternalStore, useReducer, useRef, useState, diff --git a/packages/react/index.modern.fb.js b/packages/react/index.modern.fb.js index cd60ee426fa65..8d08a43b90946 100644 --- a/packages/react/index.modern.fb.js +++ b/packages/react/index.modern.fb.js @@ -53,7 +53,6 @@ export { useMutableSource, useMutableSource as unstable_useMutableSource, useSyncExternalStore, - useSyncExternalStore as unstable_useSyncExternalStore, useReducer, useRef, useState, diff --git a/packages/react/index.stable.js b/packages/react/index.stable.js index 4e4682b8fc29f..3a0600d11a713 100644 --- a/packages/react/index.stable.js +++ b/packages/react/index.stable.js @@ -40,6 +40,7 @@ export { useLayoutEffect, useMemo, useMutableSource as unstable_useMutableSource, + useSyncExternalStore, useReducer, useRef, useState, diff --git a/packages/use-sync-external-store/index.js b/packages/use-sync-external-store/index.js index ff57d66d841bc..55548c1126626 100644 --- a/packages/use-sync-external-store/index.js +++ b/packages/use-sync-external-store/index.js @@ -9,4 +9,4 @@ 'use strict'; -export * from './src/useSyncExternalStore'; +export {useSyncExternalStore} from './src/useSyncExternalStore'; diff --git a/packages/use-sync-external-store/npm/extra.js b/packages/use-sync-external-store/npm/extra.js deleted file mode 100644 index 468dd518ac1fc..0000000000000 --- a/packages/use-sync-external-store/npm/extra.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -if (process.env.NODE_ENV === 'production') { - module.exports = require('./cjs/use-sync-external-store-extra.production.min.js'); -} else { - module.exports = require('./cjs/use-sync-external-store-extra.development.js'); -} diff --git a/packages/use-sync-external-store/npm/index.native.js b/packages/use-sync-external-store/npm/index.native.js deleted file mode 100644 index 22546b9c0ebba..0000000000000 --- a/packages/use-sync-external-store/npm/index.native.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -if (process.env.NODE_ENV === 'production') { - module.exports = require('./cjs/use-sync-external-store.native.production.min.js'); -} else { - module.exports = require('./cjs/use-sync-external-store.native.development.js'); -} diff --git a/packages/use-sync-external-store/npm/shim/index.js b/packages/use-sync-external-store/npm/shim/index.js new file mode 100644 index 0000000000000..bde36f38efdb3 --- /dev/null +++ b/packages/use-sync-external-store/npm/shim/index.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('../cjs/use-sync-external-store-shim.production.min.js'); +} else { + module.exports = require('../cjs/use-sync-external-store-shim.development.js'); +} diff --git a/packages/use-sync-external-store/npm/shim/index.native.js b/packages/use-sync-external-store/npm/shim/index.native.js new file mode 100644 index 0000000000000..0bd5c7e1c0f85 --- /dev/null +++ b/packages/use-sync-external-store/npm/shim/index.native.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('../cjs/use-sync-external-store-shim.native.production.min.js'); +} else { + module.exports = require('../cjs/use-sync-external-store-shim.native.development.js'); +} diff --git a/packages/use-sync-external-store/npm/shim/with-selector.js b/packages/use-sync-external-store/npm/shim/with-selector.js new file mode 100644 index 0000000000000..1175186c64286 --- /dev/null +++ b/packages/use-sync-external-store/npm/shim/with-selector.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('../cjs/use-sync-external-store-shim/with-selector.production.min.js'); +} else { + module.exports = require('../cjs/use-sync-external-store-shim/with-selector.development.js'); +} diff --git a/packages/use-sync-external-store/npm/with-selector.js b/packages/use-sync-external-store/npm/with-selector.js new file mode 100644 index 0000000000000..9163b3e7e13f7 --- /dev/null +++ b/packages/use-sync-external-store/npm/with-selector.js @@ -0,0 +1,7 @@ +'use strict'; + +if (process.env.NODE_ENV === 'production') { + module.exports = require('./cjs/use-sync-external-store-with-selector.production.min.js'); +} else { + module.exports = require('./cjs/use-sync-external-store-with-selector.development.js'); +} diff --git a/packages/use-sync-external-store/package.json b/packages/use-sync-external-store/package.json index b43b3a0ec67d2..0d1d9b8d25f80 100644 --- a/packages/use-sync-external-store/package.json +++ b/packages/use-sync-external-store/package.json @@ -12,8 +12,10 @@ "README.md", "build-info.json", "index.js", - "extra.js", "index.native.js", + "with-selector.js", + "with-selector.native.js", + "shim/", "cjs/" ], "license": "MIT", diff --git a/packages/use-sync-external-store/shim/index.js b/packages/use-sync-external-store/shim/index.js new file mode 100644 index 0000000000000..b8fa72b48e775 --- /dev/null +++ b/packages/use-sync-external-store/shim/index.js @@ -0,0 +1,12 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +'use strict'; + +export {useSyncExternalStore} from 'use-sync-external-store/src/useSyncExternalStoreShim'; diff --git a/packages/use-sync-external-store/index.native.js b/packages/use-sync-external-store/shim/index.native.js similarity index 70% rename from packages/use-sync-external-store/index.native.js rename to packages/use-sync-external-store/shim/index.native.js index cac5c1c2bf710..b8fa72b48e775 100644 --- a/packages/use-sync-external-store/index.native.js +++ b/packages/use-sync-external-store/shim/index.native.js @@ -9,4 +9,4 @@ 'use strict'; -export * from './src/useSyncExternalStoreClient'; +export {useSyncExternalStore} from 'use-sync-external-store/src/useSyncExternalStoreShim'; diff --git a/packages/use-sync-external-store/shim/with-selector/index.js b/packages/use-sync-external-store/shim/with-selector/index.js new file mode 100644 index 0000000000000..e71d4dac6ac9d --- /dev/null +++ b/packages/use-sync-external-store/shim/with-selector/index.js @@ -0,0 +1,12 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +'use strict'; + +export {useSyncExternalStoreWithSelector} from 'use-sync-external-store/src/useSyncExternalStoreWithSelector'; diff --git a/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreNative-test.js b/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreNative-test.js index 0902e7554c450..caedd74f74470 100644 --- a/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreNative-test.js +++ b/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreNative-test.js @@ -15,7 +15,7 @@ let React; let ReactNoop; let Scheduler; let useSyncExternalStore; -let useSyncExternalStoreExtra; +let useSyncExternalStoreWithSelector; let act; // This tests the userspace shim of `useSyncExternalStore` in a server-rendering @@ -36,25 +36,40 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => { startTransition: _, // eslint-disable-next-line no-unused-vars useSyncExternalStore: __, - // eslint-disable-next-line no-unused-vars - unstable_useSyncExternalStore: ___, ...otherExports } = jest.requireActual('react'); return otherExports; }); - jest.mock('use-sync-external-store', () => - jest.requireActual('use-sync-external-store/index.native'), + jest.mock('use-sync-external-store/shim', () => + jest.requireActual('use-sync-external-store/shim/index.native'), ); React = require('react'); ReactNoop = require('react-noop-renderer'); Scheduler = require('scheduler'); act = require('jest-react').act; - useSyncExternalStore = require('use-sync-external-store') + + if (gate(flags => flags.source)) { + // The `shim/with-selector` module composes the main + // `use-sync-external-store` entrypoint. In the compiled artifacts, this + // is resolved to the `shim` implementation by our build config, but when + // running the tests against the source files, we need to tell Jest how to + // resolve it. Because this is a source module, this mock has no affect on + // the build tests. + jest.mock('use-sync-external-store/src/useSyncExternalStore', () => + jest.requireActual('use-sync-external-store/shim'), + ); + jest.mock('use-sync-external-store/src/isServerEnvironment', () => + jest.requireActual( + 'use-sync-external-store/src/forks/isServerEnvironment.native', + ), + ); + } + useSyncExternalStore = require('use-sync-external-store/shim') .useSyncExternalStore; - useSyncExternalStoreExtra = require('use-sync-external-store/extra') - .useSyncExternalStoreExtra; + useSyncExternalStoreWithSelector = require('use-sync-external-store/shim/with-selector') + .useSyncExternalStoreWithSelector; }); function Text({text}) { @@ -105,32 +120,12 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => { expect(root).toMatchRenderedOutput('client'); }); - test('native version', async () => { - const store = createExternalStore('client'); - - function App() { - const text = useSyncExternalStore( - store.subscribe, - store.getState, - () => 'server', - ); - return ; - } - - const root = ReactNoop.createRoot(); - await act(() => { - root.render(); - }); - expect(Scheduler).toHaveYielded(['client']); - expect(root).toMatchRenderedOutput('client'); - }); - // @gate !(enableUseRefAccessWarning && __DEV__) test('Using isEqual to bailout', async () => { const store = createExternalStore({a: 0, b: 0}); function A() { - const {a} = useSyncExternalStoreExtra( + const {a} = useSyncExternalStoreWithSelector( store.subscribe, store.getState, null, @@ -140,7 +135,7 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => { return ; } function B() { - const {b} = useSyncExternalStoreExtra( + const {b} = useSyncExternalStoreWithSelector( store.subscribe, store.getState, null, diff --git a/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShared-test.js b/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShared-test.js index ab8143f24034c..5af9e9767ab0c 100644 --- a/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShared-test.js +++ b/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShared-test.js @@ -10,7 +10,7 @@ 'use strict'; let useSyncExternalStore; -let useSyncExternalStoreExtra; +let useSyncExternalStoreWithSelector; let React; let ReactDOM; let Scheduler; @@ -25,11 +25,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => { beforeEach(() => { jest.resetModules(); - // Remove the built-in API from the React exports to force the package to - // use the shim. - if (!gate(flags => flags.supportsNativeUseSyncExternalStore)) { - // and the non-variant tests for the shim. - // + if (gate(flags => flags.enableUseSyncExternalStoreShim)) { // Remove useSyncExternalStore from the React imports so that we use the // shim instead. Also removing startTransition, since we use that to // detect outdated 18 alphas that don't yet include useSyncExternalStore. @@ -42,8 +38,6 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => { startTransition: _, // eslint-disable-next-line no-unused-vars useSyncExternalStore: __, - // eslint-disable-next-line no-unused-vars - unstable_useSyncExternalStore: ___, ...otherExports } = jest.requireActual('react'); return otherExports; @@ -64,10 +58,21 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => { // in both concurrent and legacy mode, I'm adding batching here. act = cb => internalAct(() => ReactDOM.unstable_batchedUpdates(cb)); - useSyncExternalStore = require('use-sync-external-store') + if (gate(flags => flags.source)) { + // The `shim/with-selector` module composes the main + // `use-sync-external-store` entrypoint. In the compiled artifacts, this + // is resolved to the `shim` implementation by our build config, but when + // running the tests against the source files, we need to tell Jest how to + // resolve it. Because this is a source module, this mock has no affect on + // the build tests. + jest.mock('use-sync-external-store/src/useSyncExternalStore', () => + jest.requireActual('use-sync-external-store/shim'), + ); + } + useSyncExternalStore = require('use-sync-external-store/shim') .useSyncExternalStore; - useSyncExternalStoreExtra = require('use-sync-external-store/extra') - .useSyncExternalStoreExtra; + useSyncExternalStoreWithSelector = require('use-sync-external-store/shim/with-selector') + .useSyncExternalStoreWithSelector; }); function Text({text}) { @@ -78,7 +83,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => { function createRoot(container) { // This wrapper function exists so we can test both legacy roots and // concurrent roots. - if (gate(flags => flags.supportsNativeUseSyncExternalStore)) { + if (gate(flags => !flags.enableUseSyncExternalStoreShim)) { // The native implementation only exists in 18+, so we test using // concurrent mode. To test the legacy root behavior in the native // implementation (which is supported in the sense that it needs to have @@ -265,7 +270,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => { // In React 18, you can't observe in between a sync render and its // passive effects, so this is only relevant to legacy roots - // @gate !supportsNativeUseSyncExternalStore + // @gate enableUseSyncExternalStoreShim test( "compares to current state before bailing out, even when there's a " + 'mutation in between the sync and passive effects', @@ -547,7 +552,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => { await act(() => { store.set({value: 1, throwInGetSnapshot: true, throwInIsEqual: false}); }); - if (gate(flags => flags.supportsNativeUseSyncExternalStore)) { + if (gate(flags => !flags.enableUseSyncExternalStoreShim)) { expect(Scheduler).toHaveYielded([ 'Error in getSnapshot', // In a concurrent root, React renders a second time to attempt to @@ -595,7 +600,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => { function App() { Scheduler.unstable_yieldValue('App'); - const a = useSyncExternalStoreExtra( + const a = useSyncExternalStoreWithSelector( store.subscribe, store.getState, null, @@ -632,7 +637,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => { const store = createExternalStore({a: 0, b: 0}); function A() { - const {a} = useSyncExternalStoreExtra( + const {a} = useSyncExternalStoreWithSelector( store.subscribe, store.getState, null, @@ -642,7 +647,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => { return ; } function B() { - const {b} = useSyncExternalStoreExtra( + const {b} = useSyncExternalStoreWithSelector( store.subscribe, store.getState, null, @@ -711,7 +716,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => { container.innerHTML = '
server
'; const serverRenderedDiv = container.getElementsByTagName('div')[0]; - if (gate(flags => flags.supportsNativeUseSyncExternalStore)) { + if (gate(flags => !flags.enableUseSyncExternalStoreShim)) { act(() => { ReactDOM.hydrateRoot(container, ); }); @@ -774,7 +779,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => { Scheduler.unstable_yieldValue('Inline selector'); return [...state.items, 'C']; }; - const items = useSyncExternalStoreExtra( + const items = useSyncExternalStoreWithSelector( store.subscribe, store.getState, null, @@ -842,7 +847,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => { const selector = state => state.a.toUpperCase(); function App() { - const a = useSyncExternalStoreExtra( + const a = useSyncExternalStoreWithSelector( store.subscribe, store.getState, null, @@ -877,7 +882,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => { const isEqual = (left, right) => left.a.trim() === right.a.trim(); function App() { - const a = useSyncExternalStoreExtra( + const a = useSyncExternalStoreWithSelector( store.subscribe, store.getState, null, diff --git a/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShimServer-test.js b/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShimServer-test.js index b46e028724061..016240a4d3f2f 100644 --- a/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShimServer-test.js +++ b/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShimServer-test.js @@ -35,8 +35,6 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => { startTransition: _, // eslint-disable-next-line no-unused-vars useSyncExternalStore: __, - // eslint-disable-next-line no-unused-vars - unstable_useSyncExternalStore: ___, ...otherExports } = jest.requireActual('react'); return otherExports; @@ -47,7 +45,7 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => { ReactDOMServer = require('react-dom/server'); Scheduler = require('scheduler'); - useSyncExternalStore = require('use-sync-external-store') + useSyncExternalStore = require('use-sync-external-store/shim') .useSyncExternalStore; }); diff --git a/packages/use-sync-external-store/extra.js b/packages/use-sync-external-store/src/forks/isServerEnvironment.native.js similarity index 75% rename from packages/use-sync-external-store/extra.js rename to packages/use-sync-external-store/src/forks/isServerEnvironment.native.js index 90d48eb4641b6..2bd359085541d 100644 --- a/packages/use-sync-external-store/extra.js +++ b/packages/use-sync-external-store/src/forks/isServerEnvironment.native.js @@ -7,6 +7,4 @@ * @flow */ -'use strict'; - -export * from './src/useSyncExternalStoreExtra'; +export const isServerEnvironment = false; diff --git a/packages/use-sync-external-store/src/forks/useSyncExternalStore.forward-to-built-in.js b/packages/use-sync-external-store/src/forks/useSyncExternalStore.forward-to-built-in.js new file mode 100644 index 0000000000000..049f0c887e278 --- /dev/null +++ b/packages/use-sync-external-store/src/forks/useSyncExternalStore.forward-to-built-in.js @@ -0,0 +1,16 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +'use strict'; + +// Intentionally not using named imports because Rollup uses dynamic +// dispatch for CommonJS interop named imports. +import * as React from 'react'; + +export const useSyncExternalStore = React.useSyncExternalStore; diff --git a/packages/use-sync-external-store/src/forks/useSyncExternalStore.forward-to-shim.js b/packages/use-sync-external-store/src/forks/useSyncExternalStore.forward-to-shim.js new file mode 100644 index 0000000000000..54425c3ae0e2d --- /dev/null +++ b/packages/use-sync-external-store/src/forks/useSyncExternalStore.forward-to-shim.js @@ -0,0 +1,16 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +'use strict'; + +// Intentionally not using named imports because Rollup uses dynamic +// dispatch for CommonJS interop named imports. +import * as shim from 'use-sync-external-store/shim'; + +export const useSyncExternalStore = shim.useSyncExternalStore; diff --git a/packages/use-sync-external-store/src/isServerEnvironment.js b/packages/use-sync-external-store/src/isServerEnvironment.js new file mode 100644 index 0000000000000..30adcb934731f --- /dev/null +++ b/packages/use-sync-external-store/src/isServerEnvironment.js @@ -0,0 +1,12 @@ +/** + * Copyright (c) Facebook, Inc. and its 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 {canUseDOM} from 'shared/ExecutionEnvironment'; + +export const isServerEnvironment = !canUseDOM; diff --git a/packages/use-sync-external-store/src/useSyncExternalStore.js b/packages/use-sync-external-store/src/useSyncExternalStore.js index c152287174269..f1bb95556d2bb 100644 --- a/packages/use-sync-external-store/src/useSyncExternalStore.js +++ b/packages/use-sync-external-store/src/useSyncExternalStore.js @@ -7,16 +7,24 @@ * @flow */ -import {canUseDOM} from 'shared/ExecutionEnvironment'; -import {useSyncExternalStore as client} from './useSyncExternalStoreClient'; -import {useSyncExternalStore as server} from './useSyncExternalStoreServer'; +'use strict'; + +// Intentionally not using named imports because Rollup uses dynamic +// dispatch for CommonJS interop named imports. import * as React from 'react'; -const {unstable_useSyncExternalStore: builtInAPI} = React; +export const useSyncExternalStore = React.useSyncExternalStore; -export const useSyncExternalStore = - builtInAPI !== undefined - ? ((builtInAPI: any): typeof client) - : canUseDOM - ? client - : server; +if (__DEV__) { + console.error( + "The main 'use-sync-external-store' entry point is not supported; all it " + + "does is re-export useSyncExternalStore from the 'react' package, so " + + 'it only works with React 18+.' + + '\n\n' + + 'If you wish to support React 16 and 17, import from ' + + "'use-sync-external-store/shim' instead. It will fall back to a shimmed" + + 'implementation when the native one is not available.' + + '\n\n' + + "If you only support React 18+, you can import directly from 'react'.", + ); +} diff --git a/packages/use-sync-external-store/src/useSyncExternalStoreShim.js b/packages/use-sync-external-store/src/useSyncExternalStoreShim.js new file mode 100644 index 0000000000000..82d944de1dd1d --- /dev/null +++ b/packages/use-sync-external-store/src/useSyncExternalStoreShim.js @@ -0,0 +1,18 @@ +/** + * Copyright (c) Facebook, Inc. and its 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 {useSyncExternalStore as client} from './useSyncExternalStoreShimClient'; +import {useSyncExternalStore as server} from './useSyncExternalStoreShimServer'; +import {isServerEnvironment} from './isServerEnvironment'; +import {useSyncExternalStore as builtInAPI} from 'react'; + +const shim = isServerEnvironment ? server : client; + +export const useSyncExternalStore = + builtInAPI !== undefined ? ((builtInAPI: any): typeof shim) : shim; diff --git a/packages/use-sync-external-store/src/useSyncExternalStoreClient.js b/packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js similarity index 95% rename from packages/use-sync-external-store/src/useSyncExternalStoreClient.js rename to packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js index dc42169c399d6..8578b3d3fd1c7 100644 --- a/packages/use-sync-external-store/src/useSyncExternalStoreClient.js +++ b/packages/use-sync-external-store/src/useSyncExternalStoreShimClient.js @@ -30,10 +30,10 @@ let didWarnUncachedGetSnapshot = false; export function useSyncExternalStore( subscribe: (() => void) => () => void, getSnapshot: () => T, - // Note: The client shim does not use getServerSnapshot, because pre-18 - // versions of React do not expose a way to check if we're hydrating. So - // users of the shim will need to track that themselves and return the - // correct value from `getSnapshot`. + // Note: The shim does not use getServerSnapshot, because pre-18 versions of + // React do not expose a way to check if we're hydrating. So users of the shim + // will need to track that themselves and return the correct value + // from `getSnapshot`. getServerSnapshot?: () => T, ): T { if (__DEV__) { diff --git a/packages/use-sync-external-store/src/useSyncExternalStoreServer.js b/packages/use-sync-external-store/src/useSyncExternalStoreShimServer.js similarity index 59% rename from packages/use-sync-external-store/src/useSyncExternalStoreServer.js rename to packages/use-sync-external-store/src/useSyncExternalStoreShimServer.js index 52903dd4aca89..4f2432718c365 100644 --- a/packages/use-sync-external-store/src/useSyncExternalStoreServer.js +++ b/packages/use-sync-external-store/src/useSyncExternalStoreShimServer.js @@ -12,5 +12,9 @@ export function useSyncExternalStore( getSnapshot: () => T, getServerSnapshot?: () => T, ): T { + // Note: The shim does not use getServerSnapshot, because pre-18 versions of + // React do not expose a way to check if we're hydrating. So users of the shim + // will need to track that themselves and return the correct value + // from `getSnapshot`. return getSnapshot(); } diff --git a/packages/use-sync-external-store/src/useSyncExternalStoreExtra.js b/packages/use-sync-external-store/src/useSyncExternalStoreWithSelector.js similarity index 95% rename from packages/use-sync-external-store/src/useSyncExternalStoreExtra.js rename to packages/use-sync-external-store/src/useSyncExternalStoreWithSelector.js index aa4957b534753..c7012e615ecd1 100644 --- a/packages/use-sync-external-store/src/useSyncExternalStoreExtra.js +++ b/packages/use-sync-external-store/src/useSyncExternalStoreWithSelector.js @@ -9,14 +9,14 @@ import * as React from 'react'; import is from 'shared/objectIs'; -import {useSyncExternalStore} from 'use-sync-external-store'; +import {useSyncExternalStore} from 'use-sync-external-store/src/useSyncExternalStore'; -// Intentionally not using named imports because Rollup uses dynamic -// dispatch for CommonJS interop named imports. +// Intentionally not using named imports because Rollup uses dynamic dispatch +// for CommonJS interop. const {useRef, useEffect, useMemo, useDebugValue} = React; // Same as useSyncExternalStore, but supports selector and isEqual arguments. -export function useSyncExternalStoreExtra( +export function useSyncExternalStoreWithSelector( subscribe: (() => void) => () => void, getSnapshot: () => Snapshot, getServerSnapshot: void | null | (() => Snapshot), diff --git a/packages/use-sync-external-store/with-selector.js b/packages/use-sync-external-store/with-selector.js new file mode 100644 index 0000000000000..e71d4dac6ac9d --- /dev/null +++ b/packages/use-sync-external-store/with-selector.js @@ -0,0 +1,12 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +'use strict'; + +export {useSyncExternalStoreWithSelector} from 'use-sync-external-store/src/useSyncExternalStoreWithSelector'; diff --git a/scripts/jest/TestFlags.js b/scripts/jest/TestFlags.js index c1bd03e00b1a9..af83baf0f3ec9 100644 --- a/scripts/jest/TestFlags.js +++ b/scripts/jest/TestFlags.js @@ -84,9 +84,8 @@ function getTestFlags() { source: !process.env.IS_BUILD, www, - // This isn't a flag, just a useful alias for tests. Remove once - // useSyncExternalStore lands in the `next` channel. - supportsNativeUseSyncExternalStore: __EXPERIMENTAL__ || www, + // This isn't a flag, just a useful alias for tests. + enableUseSyncExternalStoreShim: !__VARIANT__, // If there's a naming conflict between scheduler and React feature flags, the // React ones take precedence. diff --git a/scripts/jest/config.build.js b/scripts/jest/config.build.js index 69cc7d18c536c..5b04ab05df7cd 100644 --- a/scripts/jest/config.build.js +++ b/scripts/jest/config.build.js @@ -45,6 +45,13 @@ packages.forEach(name => { ] = `/build/${NODE_MODULES_DIR}/${name}/$1`; }); +moduleNameMapper[ + 'use-sync-external-store/shim/with-selector' +] = `/build/${NODE_MODULES_DIR}/use-sync-external-store/shim/with-selector`; +moduleNameMapper[ + 'use-sync-external-store/shim/index.native' +] = `/build/${NODE_MODULES_DIR}/use-sync-external-store/shim/index.native`; + module.exports = Object.assign({}, baseConfig, { // Redirect imports to the compiled bundles moduleNameMapper, diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 40ae43ae7cbc0..8b12e8550e365 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -789,7 +789,7 @@ const bundles = [ externals: ['react'], }, - /******* Shim for useSyncExternalStore *******/ + /******* useSyncExternalStore *******/ { bundleTypes: [NODE_DEV, NODE_PROD], moduleType: ISOMORPHIC, @@ -800,26 +800,48 @@ const bundles = [ externals: ['react'], }, - /******* Shim for useSyncExternalStore (+ extra user-space features) *******/ + /******* useSyncExternalStore (shim) *******/ { bundleTypes: [NODE_DEV, NODE_PROD], moduleType: ISOMORPHIC, - entry: 'use-sync-external-store/extra', - global: 'useSyncExternalStoreExtra', - minifyWithProdErrorCodes: true, + entry: 'use-sync-external-store/shim', + global: 'useSyncExternalStore', + minifyWithProdErrorCodes: false, wrapWithModuleBoundaries: true, - externals: ['react', 'use-sync-external-store'], + externals: ['react'], }, - /******* Shim for useSyncExternalStore ReactNative *******/ + /******* useSyncExternalStore (shim, native) *******/ { bundleTypes: [NODE_DEV, NODE_PROD], moduleType: ISOMORPHIC, - entry: 'use-sync-external-store/index.native', - global: 'useSyncExternalStoreNative', - minifyWithProdErrorCodes: true, + entry: 'use-sync-external-store/shim/index.native', + global: 'useSyncExternalStore', + minifyWithProdErrorCodes: false, wrapWithModuleBoundaries: true, - externals: ['react', 'ReactNativeInternalFeatureFlags'], + externals: ['react'], + }, + + /******* useSyncExternalStoreWithSelector *******/ + { + bundleTypes: [NODE_DEV, NODE_PROD], + moduleType: ISOMORPHIC, + entry: 'use-sync-external-store/with-selector', + global: 'useSyncExternalStoreWithSelector', + minifyWithProdErrorCodes: false, + wrapWithModuleBoundaries: true, + externals: ['react'], + }, + + /******* useSyncExternalStoreWithSelector (shim) *******/ + { + bundleTypes: [NODE_DEV, NODE_PROD], + moduleType: ISOMORPHIC, + entry: 'use-sync-external-store/shim/with-selector', + global: 'useSyncExternalStoreWithSelector', + minifyWithProdErrorCodes: false, + wrapWithModuleBoundaries: true, + externals: ['react', 'use-sync-external-store/shim'], }, /******* React Scheduler (experimental) *******/ diff --git a/scripts/rollup/forks.js b/scripts/rollup/forks.js index 84211da5c5125..b2234fa42a010 100644 --- a/scripts/rollup/forks.js +++ b/scripts/rollup/forks.js @@ -481,6 +481,24 @@ const forks = Object.freeze({ return null; } }, + + 'use-sync-external-store/src/useSyncExternalStore': (bundleType, entry) => { + if (entry.startsWith('use-sync-external-store/shim')) { + return 'use-sync-external-store/src/forks/useSyncExternalStore.forward-to-shim'; + } + if (entry !== 'use-sync-external-store') { + // Internal modules that aren't shims should use the native API from the + // react package. + return 'use-sync-external-store/src/forks/useSyncExternalStore.forward-to-built-in'; + } + return null; + }, + + 'use-sync-external-store/src/isServerEnvironment': (bundleType, entry) => { + if (entry.endsWith('.native')) { + return 'use-sync-external-store/src/forks/isServerEnvironment.native'; + } + }, }); module.exports = forks; diff --git a/scripts/shared/pathsByLanguageVersion.js b/scripts/shared/pathsByLanguageVersion.js index af6963f78949e..8c60a9c074a5a 100644 --- a/scripts/shared/pathsByLanguageVersion.js +++ b/scripts/shared/pathsByLanguageVersion.js @@ -11,6 +11,8 @@ const esNextPaths = [ // Internal forwarding modules 'packages/*/*.js', 'packages/*/esm/*.js', + 'packages/use-sync-external-store/shim/**/*.js', + 'packages/use-sync-external-store/with-selector/**/*.js', // Source files 'packages/*/src/**/*.js', 'packages/dom-event-testing-library/**/*.js',