From 7f343de37efd5a50111cf3d3badab5ad3893b71f Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Sun, 31 Oct 2021 13:19:51 -0400 Subject: [PATCH] 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 compataibility 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. --- .../src/__tests__/ReactDOMFizzServer-test.js | 22 +++++++--- packages/use-sync-external-store/index.js | 2 +- packages/use-sync-external-store/npm/extra.js | 7 --- .../npm/index.native.js | 7 --- .../use-sync-external-store/npm/shim/index.js | 7 +++ .../npm/shim/index.native.js | 7 +++ .../npm/shim/with-selector.js | 7 +++ .../npm/with-selector.js | 7 +++ packages/use-sync-external-store/package.json | 4 +- .../use-sync-external-store/shim/index.js | 12 +++++ .../{ => shim}/index.native.js | 2 +- .../shim/with-selector/index.js | 12 +++++ .../useSyncExternalStoreNative-test.js | 28 ++++++++---- .../useSyncExternalStoreShared-test.js | 35 +++++++++------ .../useSyncExternalStoreShimServer-test.js | 2 +- .../forks/isServerEnvironment.native.js} | 4 +- ...seSyncExternalStore.forward-to-built-in.js | 16 +++++++ .../useSyncExternalStore.forward-to-shim.js | 16 +++++++ .../src/isServerEnvironment.js | 12 +++++ .../src/useSyncExternalStore.js | 28 +++++++----- .../src/useSyncExternalStoreShim.js | 18 ++++++++ ...t.js => useSyncExternalStoreShimClient.js} | 8 ++-- ...r.js => useSyncExternalStoreShimServer.js} | 4 ++ ...js => useSyncExternalStoreWithSelector.js} | 8 ++-- .../use-sync-external-store/with-selector.js | 12 +++++ scripts/rollup/bundles.js | 44 ++++++++++++++----- scripts/rollup/forks.js | 18 ++++++++ scripts/shared/pathsByLanguageVersion.js | 2 + 28 files changed, 274 insertions(+), 77 deletions(-) delete mode 100644 packages/use-sync-external-store/npm/extra.js delete mode 100644 packages/use-sync-external-store/npm/index.native.js create mode 100644 packages/use-sync-external-store/npm/shim/index.js create mode 100644 packages/use-sync-external-store/npm/shim/index.native.js create mode 100644 packages/use-sync-external-store/npm/shim/with-selector.js create mode 100644 packages/use-sync-external-store/npm/with-selector.js create mode 100644 packages/use-sync-external-store/shim/index.js rename packages/use-sync-external-store/{ => shim}/index.native.js (70%) create mode 100644 packages/use-sync-external-store/shim/with-selector/index.js rename packages/use-sync-external-store/{extra.js => src/forks/isServerEnvironment.native.js} (75%) create mode 100644 packages/use-sync-external-store/src/forks/useSyncExternalStore.forward-to-built-in.js create mode 100644 packages/use-sync-external-store/src/forks/useSyncExternalStore.forward-to-shim.js create mode 100644 packages/use-sync-external-store/src/isServerEnvironment.js create mode 100644 packages/use-sync-external-store/src/useSyncExternalStoreShim.js rename packages/use-sync-external-store/src/{useSyncExternalStoreClient.js => useSyncExternalStoreShimClient.js} (95%) rename packages/use-sync-external-store/src/{useSyncExternalStoreServer.js => useSyncExternalStoreShimServer.js} (59%) rename packages/use-sync-external-store/src/{useSyncExternalStoreExtra.js => useSyncExternalStoreWithSelector.js} (95%) create mode 100644 packages/use-sync-external-store/with-selector.js diff --git a/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js b/packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js index a2ede1746d08a..f6a5c48166874 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.unstable_useSyncExternalStore; + useSyncExternalStoreWithSelector = require('use-sync-external-store/with-selector') + .useSyncExternalStoreWithSelector; + textCache = new Map(); // Test Environment @@ -1767,7 +1779,7 @@ describe('ReactDOMFizzServer', () => { } function App() { - const {env} = useSyncExternalStoreExtra( + const {env} = useSyncExternalStoreWithSelector( subscribe, getClientSnapshot, getServerSnapshot, 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..379b5d051c3b4 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 @@ -43,18 +43,30 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => { 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'), + ); + } + 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}) { @@ -130,7 +142,7 @@ describe('useSyncExternalStore (userspace shim, server rendering)', () => { const store = createExternalStore({a: 0, b: 0}); function A() { - const {a} = useSyncExternalStoreExtra( + const {a} = useSyncExternalStoreWithSelector( store.subscribe, store.getState, null, @@ -140,7 +152,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..817ca3781bd3d 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. - // // 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. @@ -64,10 +60,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}) { @@ -595,7 +602,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 +639,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 +649,7 @@ describe('Shared useSyncExternalStore behavior (shim and built-in)', () => { return ; } function B() { - const {b} = useSyncExternalStoreExtra( + const {b} = useSyncExternalStoreWithSelector( store.subscribe, store.getState, null, @@ -774,7 +781,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 +849,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 +884,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..9023322c9f638 100644 --- a/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShimServer-test.js +++ b/packages/use-sync-external-store/src/__tests__/useSyncExternalStoreShimServer-test.js @@ -47,7 +47,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..307146bfd9d2d --- /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.unstable_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..5ca1c93817b88 --- /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 {unstable_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/rollup/bundles.js b/scripts/rollup/bundles.js index af7b30b7feb99..857a9ce2b0431 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',