From 5b4b06cf3b081f041b5110e67ac26dc0d7627763 Mon Sep 17 00:00:00 2001 From: Josh Story Date: Thu, 30 Nov 2023 13:41:14 -0800 Subject: [PATCH] Move client only exports to `react-dom/client` This change updates the entrypoints for `react-dom` to only include exports which make sense in every runtime (Flight, Fizz, and Fiber). The main benefit to doing this is we stop including the entire client build when importing anything from `react-dom`. The server-rendering-stub was added as a manual way of doing this prior to the next major and now that stub simply reexports from `react-dom`. In a future major we will remove the stub altogether. This change affects the OSS channels but does not update how the meta entrypoints are organized --- .../src/client/ReactDOMContainer.js | 42 ++ .../src/client/ReactFiberConfigDOM.js | 67 +- .../ReactDOMFlightServerHostDispatcher.js | 38 ++ .../src/server/ReactFizzConfigDOM.js | 43 +- .../src/server/ReactFizzConfigDOMLegacy.js | 1 - .../src/server/ReactFlightServerConfigDOM.js | 9 +- packages/react-dom/client.js | 48 +- packages/react-dom/index.experimental.js | 59 -- packages/react-dom/index.js | 18 +- packages/react-dom/index.stable.js | 30 - packages/react-dom/npm/client.js | 53 +- packages/react-dom/npm/client.react-server.js | 5 + packages/react-dom/npm/profiling.js | 4 +- .../react-dom/npm/profiling.react-server.js | 5 + .../react-dom/npm/server-rendering-stub.js | 7 - .../npm/unstable_testing.react-server.js | 5 + packages/react-dom/package.json | 23 +- .../focus.js => react-dom/profiling.js} | 2 +- packages/react-dom/server-rendering-stub.js | 61 -- .../ReactDOMFB.js} | 16 +- .../ReactDOMFB.modern.js} | 13 +- ...actDOMServer.js => ReactDOMReactServer.js} | 0 .../ReactDOMTestingFB.js} | 19 +- .../react-dom/src/ReactDOMTestingFB.modern.js | 23 + .../react-dom-server-rendering-stub-test.js | 100 --- .../{ReactDOM.js => ReactDOMClientBrowser.js} | 97 +-- .../react-dom/src/client/ReactDOMLegacy.js | 2 +- packages/react-dom/src/client/ReactDOMRoot.js | 45 +- .../src/server/ReactDOMServerRenderingStub.js | 48 -- packages/react-dom/src/shared/ReactDOM.js | 101 +++ .../react-dom/src/shared/ReactDOMTypes.js | 4 +- .../react-dom/unstable_testing.classic.fb.js | 46 -- .../unstable_testing.experimental.js | 20 - packages/react-dom/unstable_testing.js | 34 - packages/react-dom/unstable_testing.stable.js | 29 - packages/react-interactions/README.md | 4 - .../src/dom/create-event-handle/Focus.js | 400 ----------- .../__tests__/useFocus-test.internal.js | 335 ---------- .../__tests__/useFocusWithin-test.internal.js | 626 ------------------ .../src/dom/create-event-handle/useEvent.js | 72 -- packages/react-interactions/npm/drag.js | 7 - packages/react-interactions/npm/focus.js | 7 - packages/react-interactions/npm/hover.js | 7 - packages/react-interactions/npm/input.js | 7 - .../react-interactions/npm/press-legacy.js | 7 - packages/react-interactions/npm/press.js | 7 - packages/react-interactions/npm/scroll.js | 7 - packages/react-interactions/npm/swipe.js | 7 - packages/react-interactions/npm/tap.js | 7 - packages/react-interactions/package.json | 47 -- .../src/ReactNoopFlightServer.js | 1 - .../src/ReactNoopServer.js | 2 - packages/react-server/src/ReactFizzServer.js | 3 - .../react-server/src/ReactFlightServer.js | 2 - .../ReactFlightServerConfigBundlerCustom.js | 1 - .../src/forks/ReactFizzConfig.custom.js | 1 - .../forks/ReactFlightServerConfig.custom.js | 2 - packages/shared/ReactVersion.js | 17 +- scripts/rollup/bundles.js | 76 ++- scripts/rollup/forks.js | 3 +- scripts/rollup/modules.js | 1 - scripts/rollup/packaging.js | 24 +- scripts/shared/inlinedHostConfigs.js | 48 +- scripts/shared/pathsByLanguageVersion.js | 2 - 64 files changed, 511 insertions(+), 2336 deletions(-) create mode 100644 packages/react-dom-bindings/src/client/ReactDOMContainer.js delete mode 100644 packages/react-dom/index.experimental.js delete mode 100644 packages/react-dom/index.stable.js create mode 100644 packages/react-dom/npm/client.react-server.js create mode 100644 packages/react-dom/npm/profiling.react-server.js delete mode 100644 packages/react-dom/npm/server-rendering-stub.js create mode 100644 packages/react-dom/npm/unstable_testing.react-server.js rename packages/{react-interactions/events/focus.js => react-dom/profiling.js} (72%) delete mode 100644 packages/react-dom/server-rendering-stub.js rename packages/react-dom/{index.classic.fb.js => src/ReactDOMFB.js} (88%) rename packages/react-dom/{index.modern.fb.js => src/ReactDOMFB.modern.js} (83%) rename packages/react-dom/src/{ReactDOMServer.js => ReactDOMReactServer.js} (100%) rename packages/react-dom/{unstable_testing.modern.fb.js => src/ReactDOMTestingFB.js} (58%) create mode 100644 packages/react-dom/src/ReactDOMTestingFB.modern.js delete mode 100644 packages/react-dom/src/__tests__/react-dom-server-rendering-stub-test.js rename packages/react-dom/src/client/{ReactDOM.js => ReactDOMClientBrowser.js} (59%) delete mode 100644 packages/react-dom/src/server/ReactDOMServerRenderingStub.js create mode 100644 packages/react-dom/src/shared/ReactDOM.js delete mode 100644 packages/react-dom/unstable_testing.classic.fb.js delete mode 100644 packages/react-dom/unstable_testing.stable.js delete mode 100644 packages/react-interactions/README.md delete mode 100644 packages/react-interactions/events/src/dom/create-event-handle/Focus.js delete mode 100644 packages/react-interactions/events/src/dom/create-event-handle/__tests__/useFocus-test.internal.js delete mode 100644 packages/react-interactions/events/src/dom/create-event-handle/__tests__/useFocusWithin-test.internal.js delete mode 100644 packages/react-interactions/events/src/dom/create-event-handle/useEvent.js delete mode 100644 packages/react-interactions/npm/drag.js delete mode 100644 packages/react-interactions/npm/focus.js delete mode 100644 packages/react-interactions/npm/hover.js delete mode 100644 packages/react-interactions/npm/input.js delete mode 100644 packages/react-interactions/npm/press-legacy.js delete mode 100644 packages/react-interactions/npm/press.js delete mode 100644 packages/react-interactions/npm/scroll.js delete mode 100644 packages/react-interactions/npm/swipe.js delete mode 100644 packages/react-interactions/npm/tap.js delete mode 100644 packages/react-interactions/package.json diff --git a/packages/react-dom-bindings/src/client/ReactDOMContainer.js b/packages/react-dom-bindings/src/client/ReactDOMContainer.js new file mode 100644 index 0000000000000..0af0f13a790a8 --- /dev/null +++ b/packages/react-dom-bindings/src/client/ReactDOMContainer.js @@ -0,0 +1,42 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import {disableCommentsAsDOMContainers} from 'shared/ReactFeatureFlags'; + +import { + ELEMENT_NODE, + COMMENT_NODE, + DOCUMENT_NODE, + DOCUMENT_FRAGMENT_NODE, +} from './HTMLNodeType'; + +export function isValidContainer(node: any): boolean { + return !!( + node && + (node.nodeType === ELEMENT_NODE || + node.nodeType === DOCUMENT_NODE || + node.nodeType === DOCUMENT_FRAGMENT_NODE || + (!disableCommentsAsDOMContainers && + node.nodeType === COMMENT_NODE && + (node: any).nodeValue === ' react-mount-point-unstable ')) + ); +} + +// TODO: Remove this function which also includes comment nodes. +// We only use it in places that are currently more relaxed. +export function isValidContainerLegacy(node: any): boolean { + return !!( + node && + (node.nodeType === ELEMENT_NODE || + node.nodeType === DOCUMENT_NODE || + node.nodeType === DOCUMENT_FRAGMENT_NODE || + (node.nodeType === COMMENT_NODE && + (node: any).nodeValue === ' react-mount-point-unstable ')) + ); +} diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index 281ec1fea0582..d4c943bc4fb84 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -7,7 +7,6 @@ * @flow */ -import type {HostDispatcher} from 'react-dom/src/shared/ReactDOMTypes'; import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities'; import type {DOMEventName} from '../events/DOMEventNames'; import type {Fiber, FiberRoot} from 'react-reconciler/src/ReactInternalTypes'; @@ -20,6 +19,7 @@ import type {ReactScopeInstance} from 'shared/ReactTypes'; import type {AncestorInfoDev} from './validateDOMNesting'; import type {FormStatus} from 'react-dom-bindings/src/shared/ReactDOMFormActions'; import type { + HostDispatcher, CrossOriginEnum, PreloadImplOptions, PreloadModuleImplOptions, @@ -28,6 +28,10 @@ import type { PreinitModuleScriptOptions, } from 'react-dom/src/shared/ReactDOMTypes'; +import { + isAlreadyRendering, + flushSync as flushSyncWithoutWarningIfAlreadyRendering, +} from 'react-reconciler/src/ReactFiberReconciler'; import {NotPending} from 'react-dom-bindings/src/shared/ReactDOMFormActions'; import {getCurrentRootHostContainer} from 'react-reconciler/src/ReactFiberHostContext'; import {DefaultEventPriority} from 'react-reconciler/src/ReactEventPriorities'; @@ -106,6 +110,9 @@ import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem'; import {validateLinkPropsForStyleResource} from '../shared/ReactDOMResourceValidation'; import escapeSelectorAttributeValueInsideDoubleQuotes from './escapeSelectorAttributeValueInsideDoubleQuotes'; +import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals'; +const ReactDOMCurrentDispatcher = ReactDOMSharedInternals.Dispatcher; + export type Type = string; export type Props = { autoFocus?: boolean, @@ -2083,10 +2090,7 @@ function getDocumentFromRoot(root: HoistableRoot): Document { return root.ownerDocument || root; } -// We want this to be the default dispatcher on ReactDOMSharedInternals but we don't want to mutate -// internals in Module scope. Instead we export it and Internals will import it. There is already a cycle -// from Internals -> ReactDOM -> HostConfig -> Internals so this doesn't introduce a new one. -export const ReactDOMClientDispatcher: HostDispatcher = { +const ReactDOMClientDispatcher: HostDispatcher = { prefetchDNS, preconnect, preload, @@ -2094,8 +2098,36 @@ export const ReactDOMClientDispatcher: HostDispatcher = { preinitStyle, preinitScript, preinitModuleScript, + flushSync, + nextDispatcher: null, }; +// We register the HostDispatcher on ReactDOMSharedInternals +if (ReactDOMCurrentDispatcher.current === null) { + ReactDOMCurrentDispatcher.current = ReactDOMClientDispatcher; +} else { + ReactDOMCurrentDispatcher.current.nextDispatcher = ReactDOMClientDispatcher; +} + +function flushSync(fn: void | (() => R)): void | R { + if (__DEV__) { + if (isAlreadyRendering()) { + console.error( + 'flushSync was called from inside a lifecycle method. React cannot ' + + 'flush when React is already rendering. Consider moving this call to ' + + 'a scheduler task or micro task.', + ); + } + } + if (ReactDOMClientDispatcher.nextDispatcher) { + return ReactDOMClientDispatcher.nextDispatcher.flushSync(() => + flushSyncWithoutWarningIfAlreadyRendering(fn), + ); + } else { + return flushSyncWithoutWarningIfAlreadyRendering(fn); + } +} + // We expect this to get inlined. It is a function mostly to communicate the special nature of // how we resolve the HoistableRoot for ReactDOM.pre*() methods. Because we support calling // these methods outside of render there is no way to know which Document or ShadowRoot is 'scoped' @@ -2134,6 +2166,9 @@ function preconnectAs( } function prefetchDNS(href: string) { + if (ReactDOMClientDispatcher.nextDispatcher) { + ReactDOMClientDispatcher.nextDispatcher.prefetchDNS(href); + } if (!enableFloat) { return; } @@ -2141,6 +2176,9 @@ function prefetchDNS(href: string) { } function preconnect(href: string, crossOrigin?: ?CrossOriginEnum) { + if (ReactDOMClientDispatcher.nextDispatcher) { + ReactDOMClientDispatcher.nextDispatcher.preconnect(href, crossOrigin); + } if (!enableFloat) { return; } @@ -2148,6 +2186,9 @@ function preconnect(href: string, crossOrigin?: ?CrossOriginEnum) { } function preload(href: string, as: string, options?: ?PreloadImplOptions) { + if (ReactDOMClientDispatcher.nextDispatcher) { + ReactDOMClientDispatcher.nextDispatcher.preload(href, as, options); + } if (!enableFloat) { return; } @@ -2228,6 +2269,9 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) { } function preloadModule(href: string, options?: ?PreloadModuleImplOptions) { + if (ReactDOMClientDispatcher.nextDispatcher) { + ReactDOMClientDispatcher.nextDispatcher.preloadModule(href, options); + } if (!enableFloat) { return; } @@ -2291,6 +2335,13 @@ function preinitStyle( precedence: ?string, options?: ?PreinitStyleOptions, ) { + if (ReactDOMClientDispatcher.nextDispatcher) { + ReactDOMClientDispatcher.nextDispatcher.preinitStyle( + href, + precedence, + options, + ); + } if (!enableFloat) { return; } @@ -2367,6 +2418,9 @@ function preinitStyle( } function preinitScript(src: string, options?: ?PreinitScriptOptions) { + if (ReactDOMClientDispatcher.nextDispatcher) { + ReactDOMClientDispatcher.nextDispatcher.preinitScript(src, options); + } if (!enableFloat) { return; } @@ -2425,6 +2479,9 @@ function preinitModuleScript( src: string, options?: ?PreinitModuleScriptOptions, ) { + if (ReactDOMClientDispatcher.nextDispatcher) { + ReactDOMClientDispatcher.nextDispatcher.preinitModuleScript(src, options); + } if (!enableFloat) { return; } diff --git a/packages/react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js b/packages/react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js index 36913899bb030..9047db3da18bf 100644 --- a/packages/react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js +++ b/packages/react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js @@ -33,9 +33,22 @@ export const ReactDOMFlightServerDispatcher: HostDispatcher = { preinitStyle, preinitScript, preinitModuleScript, + flushSync, + nextDispatcher: null, }; +function flushSync(fn: void | (() => R)): void | R { + if (ReactDOMFlightServerDispatcher.nextDispatcher) { + return ReactDOMFlightServerDispatcher.nextDispatcher.flushSync(fn); + } else if (fn) { + return fn(); + } +} + function prefetchDNS(href: string) { + if (ReactDOMFlightServerDispatcher.nextDispatcher) { + ReactDOMFlightServerDispatcher.nextDispatcher.prefetchDNS(href); + } if (enableFloat) { if (typeof href === 'string' && href) { const request = resolveRequest(); @@ -54,6 +67,9 @@ function prefetchDNS(href: string) { } function preconnect(href: string, crossOrigin?: ?CrossOriginEnum) { + if (ReactDOMFlightServerDispatcher.nextDispatcher) { + ReactDOMFlightServerDispatcher.nextDispatcher.preconnect(href, crossOrigin); + } if (enableFloat) { if (typeof href === 'string') { const request = resolveRequest(); @@ -77,6 +93,9 @@ function preconnect(href: string, crossOrigin?: ?CrossOriginEnum) { } function preload(href: string, as: string, options?: ?PreloadImplOptions) { + if (ReactDOMFlightServerDispatcher.nextDispatcher) { + ReactDOMFlightServerDispatcher.nextDispatcher.preload(href, as, options); + } if (enableFloat) { if (typeof href === 'string') { const request = resolveRequest(); @@ -110,6 +129,9 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) { } function preloadModule(href: string, options?: ?PreloadModuleImplOptions) { + if (ReactDOMFlightServerDispatcher.nextDispatcher) { + ReactDOMFlightServerDispatcher.nextDispatcher.preloadModule(href, options); + } if (enableFloat) { if (typeof href === 'string') { const request = resolveRequest(); @@ -138,6 +160,13 @@ function preinitStyle( precedence: ?string, options?: ?PreinitStyleOptions, ) { + if (ReactDOMFlightServerDispatcher.nextDispatcher) { + ReactDOMFlightServerDispatcher.nextDispatcher.preinitStyle( + href, + precedence, + options, + ); + } if (enableFloat) { if (typeof href === 'string') { const request = resolveRequest(); @@ -168,6 +197,9 @@ function preinitStyle( } function preinitScript(href: string, options?: ?PreinitScriptOptions) { + if (ReactDOMFlightServerDispatcher.nextDispatcher) { + ReactDOMFlightServerDispatcher.nextDispatcher.preinitScript(href, options); + } if (enableFloat) { if (typeof href === 'string') { const request = resolveRequest(); @@ -195,6 +227,12 @@ function preinitModuleScript( href: string, options?: ?PreinitModuleScriptOptions, ) { + if (ReactDOMFlightServerDispatcher.nextDispatcher) { + ReactDOMFlightServerDispatcher.nextDispatcher.preinitModuleScript( + href, + options, + ); + } if (enableFloat) { if (typeof href === 'string') { const request = resolveRequest(); diff --git a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js index b6fe8d9109a6f..2f779a72a28cd 100644 --- a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js +++ b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js @@ -9,6 +9,7 @@ import type {ReactNodeList, ReactCustomFormAction} from 'shared/ReactTypes'; import type { + HostDispatcher, CrossOriginEnum, PreloadImplOptions, PreloadModuleImplOptions, @@ -89,7 +90,7 @@ import {NotPending} from '../shared/ReactDOMFormActions'; import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals'; const ReactDOMCurrentDispatcher = ReactDOMSharedInternals.Dispatcher; -const ReactDOMServerDispatcher = { +const ReactDOMServerDispatcher: HostDispatcher = { prefetchDNS, preconnect, preload, @@ -97,12 +98,25 @@ const ReactDOMServerDispatcher = { preinitStyle, preinitScript, preinitModuleScript, + flushSync, + nextDispatcher: null, }; -export function prepareHostDispatcher() { +// We register the HostDispatcher on ReactDOMSharedInternals +if (ReactDOMCurrentDispatcher.current) { + ReactDOMCurrentDispatcher.current.nextDispatcher = ReactDOMServerDispatcher; +} else { ReactDOMCurrentDispatcher.current = ReactDOMServerDispatcher; } +function flushSync(fn: void | (() => R)): void | R { + if (ReactDOMServerDispatcher.nextDispatcher) { + return ReactDOMServerDispatcher.nextDispatcher.flushSync(fn); + } else if (fn) { + return fn(); + } +} + // We make every property of the descriptor optional because it is not a contract that // the headers provided by onHeaders has any particular header types. export type HeadersDescriptor = { @@ -5318,6 +5332,9 @@ function getImageResourceKey( } function prefetchDNS(href: string) { + if (ReactDOMServerDispatcher.nextDispatcher) { + ReactDOMServerDispatcher.nextDispatcher.prefetchDNS(href); + } if (!enableFloat) { return; } @@ -5373,6 +5390,9 @@ function prefetchDNS(href: string) { } function preconnect(href: string, crossOrigin: ?CrossOriginEnum) { + if (ReactDOMServerDispatcher.nextDispatcher) { + ReactDOMServerDispatcher.nextDispatcher.preconnect(href, crossOrigin); + } if (!enableFloat) { return; } @@ -5436,6 +5456,9 @@ function preconnect(href: string, crossOrigin: ?CrossOriginEnum) { } function preload(href: string, as: string, options?: ?PreloadImplOptions) { + if (ReactDOMServerDispatcher.nextDispatcher) { + ReactDOMServerDispatcher.nextDispatcher.preload(href, as, options); + } if (!enableFloat) { return; } @@ -5639,6 +5662,9 @@ function preloadModule( href: string, options?: ?PreloadModuleImplOptions, ): void { + if (ReactDOMServerDispatcher.nextDispatcher) { + ReactDOMServerDispatcher.nextDispatcher.preloadModule(href, options); + } if (!enableFloat) { return; } @@ -5715,6 +5741,13 @@ function preinitStyle( precedence: ?string, options?: ?PreinitStyleOptions, ): void { + if (ReactDOMServerDispatcher.nextDispatcher) { + ReactDOMServerDispatcher.nextDispatcher.preinitStyle( + href, + precedence, + options, + ); + } if (!enableFloat) { return; } @@ -5802,6 +5835,9 @@ function preinitStyle( } function preinitScript(src: string, options?: ?PreinitScriptOptions): void { + if (ReactDOMServerDispatcher.nextDispatcher) { + ReactDOMServerDispatcher.nextDispatcher.preinitScript(src, options); + } if (!enableFloat) { return; } @@ -5867,6 +5903,9 @@ function preinitModuleScript( src: string, options?: ?PreinitModuleScriptOptions, ): void { + if (ReactDOMServerDispatcher.nextDispatcher) { + ReactDOMServerDispatcher.nextDispatcher.preinitModuleScript(src, options); + } if (!enableFloat) { return; } diff --git a/packages/react-dom-bindings/src/server/ReactFizzConfigDOMLegacy.js b/packages/react-dom-bindings/src/server/ReactFizzConfigDOMLegacy.js index a8f38e283cefc..251738aa2f0a5 100644 --- a/packages/react-dom-bindings/src/server/ReactFizzConfigDOMLegacy.js +++ b/packages/react-dom-bindings/src/server/ReactFizzConfigDOMLegacy.js @@ -163,7 +163,6 @@ export { writeHoistables, writePostamble, hoistHoistables, - prepareHostDispatcher, resetResumableState, completeResumableState, emitEarlyPreloads, diff --git a/packages/react-dom-bindings/src/server/ReactFlightServerConfigDOM.js b/packages/react-dom-bindings/src/server/ReactFlightServerConfigDOM.js index 336b1d3ef412b..805ee220c60c8 100644 --- a/packages/react-dom-bindings/src/server/ReactFlightServerConfigDOM.js +++ b/packages/react-dom-bindings/src/server/ReactFlightServerConfigDOM.js @@ -16,12 +16,13 @@ import type { PreinitModuleScriptOptions, } from 'react-dom/src/shared/ReactDOMTypes'; +import {ReactDOMFlightServerDispatcher} from './ReactDOMFlightServerHostDispatcher'; import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals'; const ReactDOMCurrentDispatcher = ReactDOMSharedInternals.Dispatcher; - -import {ReactDOMFlightServerDispatcher} from './ReactDOMFlightServerHostDispatcher'; - -export function prepareHostDispatcher(): void { +if (ReactDOMCurrentDispatcher.current) { + ReactDOMCurrentDispatcher.current.nextDispatcher = + ReactDOMFlightServerDispatcher; +} else { ReactDOMCurrentDispatcher.current = ReactDOMFlightServerDispatcher; } diff --git a/packages/react-dom/client.js b/packages/react-dom/client.js index f44c3668f7b17..db127306147db 100644 --- a/packages/react-dom/client.js +++ b/packages/react-dom/client.js @@ -7,50 +7,4 @@ * @flow */ -'use strict'; - -import type {ReactNodeList} from 'shared/ReactTypes'; -import type { - RootType, - HydrateRootOptions, - CreateRootOptions, -} from './src/client/ReactDOMRoot'; - -import { - createRoot as createRootImpl, - hydrateRoot as hydrateRootImpl, - __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED as Internals, -} from './'; - -export function createRoot( - container: Element | Document | DocumentFragment, - options?: CreateRootOptions, -): RootType { - if (__DEV__) { - Internals.usingClientEntryPoint = true; - } - try { - return createRootImpl(container, options); - } finally { - if (__DEV__) { - Internals.usingClientEntryPoint = false; - } - } -} - -export function hydrateRoot( - container: Document | Element, - children: ReactNodeList, - options?: HydrateRootOptions, -): RootType { - if (__DEV__) { - Internals.usingClientEntryPoint = true; - } - try { - return hydrateRootImpl(container, children, options); - } finally { - if (__DEV__) { - Internals.usingClientEntryPoint = false; - } - } -} +export {createRoot, hydrateRoot} from './src/client/ReactDOMClientBrowser'; diff --git a/packages/react-dom/index.experimental.js b/packages/react-dom/index.experimental.js deleted file mode 100644 index ae4320fa4ee53..0000000000000 --- a/packages/react-dom/index.experimental.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - */ - -export {default as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED} from './src/ReactDOMSharedInternals'; -export { - createPortal, - createRoot, - hydrateRoot, - flushSync, - hydrate, - render, - unmountComponentAtNode, - unstable_batchedUpdates, - unstable_renderSubtreeIntoContainer, - unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority. - useFormStatus, - useFormState, - prefetchDNS, - preconnect, - preload, - preloadModule, - preinit, - preinitModule, - version, -} from './src/client/ReactDOM'; - -import type {Awaited} from 'shared/ReactTypes'; -import type {FormStatus} from 'react-dom-bindings/src/shared/ReactDOMFormActions'; -import {useFormStatus, useFormState} from './src/client/ReactDOM'; - -export function experimental_useFormStatus(): FormStatus { - if (__DEV__) { - console.error( - 'useFormStatus is now in canary. Remove the experimental_ prefix. ' + - 'The prefixed alias will be removed in an upcoming release.', - ); - } - return useFormStatus(); -} - -export function experimental_useFormState( - action: (Awaited, P) => S, - initialState: Awaited, - permalink?: string, -): [Awaited, (P) => void] { - if (__DEV__) { - console.error( - 'useFormState is now in canary. Remove the experimental_ prefix. ' + - 'The prefixed alias will be removed in an upcoming release.', - ); - } - return useFormState(action, initialState, permalink); -} diff --git a/packages/react-dom/index.js b/packages/react-dom/index.js index d095a3a74c08d..ecfd29b3c77c0 100644 --- a/packages/react-dom/index.js +++ b/packages/react-dom/index.js @@ -7,28 +7,18 @@ * @flow */ -// Export all exports so that they're available in tests. -// We can't use export * from in Flow for some reason. export {default as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED} from './src/ReactDOMSharedInternals'; export { createPortal, - createRoot, - hydrateRoot, flushSync, - hydrate, - render, - unmountComponentAtNode, - unstable_batchedUpdates, - unstable_createEventHandle, - unstable_renderSubtreeIntoContainer, - unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority. - useFormStatus, - useFormState, prefetchDNS, preconnect, preload, preloadModule, preinit, preinitModule, + useFormState, + useFormStatus, + unstable_batchedUpdates, version, -} from './src/client/ReactDOM'; +} from './src/shared/ReactDOM'; diff --git a/packages/react-dom/index.stable.js b/packages/react-dom/index.stable.js deleted file mode 100644 index c4e58f4ff6eae..0000000000000 --- a/packages/react-dom/index.stable.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - */ - -export {default as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED} from './src/ReactDOMSharedInternals'; -export { - createPortal, - createRoot, - hydrateRoot, - flushSync, - hydrate, - render, - unmountComponentAtNode, - unstable_batchedUpdates, - unstable_renderSubtreeIntoContainer, - useFormStatus, - useFormState, - prefetchDNS, - preconnect, - preload, - preloadModule, - preinit, - preinitModule, - version, -} from './src/client/ReactDOM'; diff --git a/packages/react-dom/npm/client.js b/packages/react-dom/npm/client.js index bef4756404cfd..11d43b8bd285c 100644 --- a/packages/react-dom/npm/client.js +++ b/packages/react-dom/npm/client.js @@ -1,25 +1,38 @@ 'use strict'; -var m = require('react-dom'); +function checkDCE() { + /* global __REACT_DEVTOOLS_GLOBAL_HOOK__ */ + if ( + typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ === 'undefined' || + typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE !== 'function' + ) { + return; + } + if (process.env.NODE_ENV !== 'production') { + // This branch is unreachable because this function is only called + // in production, but the condition is true only in development. + // Therefore if the branch is still here, dead code elimination wasn't + // properly applied. + // Don't change the message. React DevTools relies on it. Also make sure + // this message doesn't occur elsewhere in this function, or it will cause + // a false positive. + throw new Error('^_^'); + } + try { + // Verify that the code above has been dead code eliminated (DCE'd). + __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(checkDCE); + } catch (err) { + // DevTools shouldn't crash React, no matter what. + // We should still report in case we break this code. + console.error(err); + } +} + if (process.env.NODE_ENV === 'production') { - exports.createRoot = m.createRoot; - exports.hydrateRoot = m.hydrateRoot; + // DCE check should happen before ReactDOM bundle executes so that + // DevTools can report bad minification during injection. + checkDCE(); + module.exports = require('./cjs/react-dom-client.production.min.js'); } else { - var i = m.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED; - exports.createRoot = function (c, o) { - i.usingClientEntryPoint = true; - try { - return m.createRoot(c, o); - } finally { - i.usingClientEntryPoint = false; - } - }; - exports.hydrateRoot = function (c, h, o) { - i.usingClientEntryPoint = true; - try { - return m.hydrateRoot(c, h, o); - } finally { - i.usingClientEntryPoint = false; - } - }; + module.exports = require('./cjs/react-dom-client.development.js'); } diff --git a/packages/react-dom/npm/client.react-server.js b/packages/react-dom/npm/client.react-server.js new file mode 100644 index 0000000000000..a00f3d9b6cf6d --- /dev/null +++ b/packages/react-dom/npm/client.react-server.js @@ -0,0 +1,5 @@ +'use strict'; + +throw new Error( + 'react-dom/client is not supported in React Server Components.' +); diff --git a/packages/react-dom/npm/profiling.js b/packages/react-dom/npm/profiling.js index 91f89f07ffe13..ad37f6ec7de04 100644 --- a/packages/react-dom/npm/profiling.js +++ b/packages/react-dom/npm/profiling.js @@ -32,7 +32,7 @@ if (process.env.NODE_ENV === 'production') { // DCE check should happen before ReactDOM bundle executes so that // DevTools can report bad minification during injection. checkDCE(); - module.exports = require('./cjs/react-dom.profiling.min.js'); + module.exports = require('./cjs/react-dom-profiling.profiling.min.js'); } else { - module.exports = require('./cjs/react-dom.development.js'); + module.exports = require('./cjs/react-dom-profiling.development.js'); } diff --git a/packages/react-dom/npm/profiling.react-server.js b/packages/react-dom/npm/profiling.react-server.js new file mode 100644 index 0000000000000..434474e421521 --- /dev/null +++ b/packages/react-dom/npm/profiling.react-server.js @@ -0,0 +1,5 @@ +'use strict'; + +throw new Error( + 'react-dom/profiling is not supported in React Server Components.' +); diff --git a/packages/react-dom/npm/server-rendering-stub.js b/packages/react-dom/npm/server-rendering-stub.js deleted file mode 100644 index fa2464a3e59fe..0000000000000 --- a/packages/react-dom/npm/server-rendering-stub.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -if (process.env.NODE_ENV === 'production') { - module.exports = require('./cjs/react-dom-server-rendering-stub.production.min.js'); -} else { - module.exports = require('./cjs/react-dom-server-rendering-stub.development.js'); -} diff --git a/packages/react-dom/npm/unstable_testing.react-server.js b/packages/react-dom/npm/unstable_testing.react-server.js new file mode 100644 index 0000000000000..e2611f10f259f --- /dev/null +++ b/packages/react-dom/npm/unstable_testing.react-server.js @@ -0,0 +1,5 @@ +'use strict'; + +throw new Error( + 'react-dom/unstable_testing is not supported in React Server Components.' +); diff --git a/packages/react-dom/package.json b/packages/react-dom/package.json index 3d71298844dc4..56d85d079c0b0 100644 --- a/packages/react-dom/package.json +++ b/packages/react-dom/package.json @@ -28,7 +28,9 @@ "README.md", "index.js", "client.js", + "client.react-server.js", "profiling.js", + "profiling.react-server.js", "server.js", "server.browser.js", "server.edge.js", @@ -38,20 +40,22 @@ "static.browser.js", "static.edge.js", "static.node.js", - "server-rendering-stub.js", "test-utils.js", "unstable_testing.js", + "unstable_testing.react-server.js", "unstable_server-external-runtime.js", "react-dom.react-server.js", - "cjs/", - "umd/" + "cjs/" ], "exports": { ".": { "react-server": "./react-dom.react-server.js", "default": "./index.js" }, - "./client": "./client.js", + "./client": { + "react-server": "./client.react-server.js", + "default": "./client.js" + }, "./server": { "workerd": "./server.edge.js", "bun": "./server.bun.js", @@ -78,10 +82,15 @@ "./static.browser": "./static.browser.js", "./static.edge": "./static.edge.js", "./static.node": "./static.node.js", - "./server-rendering-stub": "./server-rendering-stub.js", - "./profiling": "./profiling.js", + "./profiling": { + "react-server": "./profiling.react-server.js", + "default": "./profiling.js" + }, "./test-utils": "./test-utils.js", - "./unstable_testing": "./unstable_testing.js", + "./unstable_testing": { + "react-server": "./unstable_testing.react-server.js", + "default": "./unstable_testing.js" + }, "./unstable_server-external-runtime": "./unstable_server-external-runtime.js", "./src/*": "./src/*", "./package.json": "./package.json" diff --git a/packages/react-interactions/events/focus.js b/packages/react-dom/profiling.js similarity index 72% rename from packages/react-interactions/events/focus.js rename to packages/react-dom/profiling.js index c4421b2eda17c..db127306147db 100644 --- a/packages/react-interactions/events/focus.js +++ b/packages/react-dom/profiling.js @@ -7,4 +7,4 @@ * @flow */ -export * from './src/dom/create-event-handle/Focus'; +export {createRoot, hydrateRoot} from './src/client/ReactDOMClientBrowser'; diff --git a/packages/react-dom/server-rendering-stub.js b/packages/react-dom/server-rendering-stub.js deleted file mode 100644 index 71d4124ddabfa..0000000000000 --- a/packages/react-dom/server-rendering-stub.js +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - */ - -// Export all exports so that they're available in tests. -// We can't use export * from in Flow for some reason. - -import ReactVersion from 'shared/ReactVersion'; -export {ReactVersion as version}; - -export {default as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED} from './src/ReactDOMSharedInternals'; - -export { - createPortal, - flushSync, - prefetchDNS, - preconnect, - preload, - preloadModule, - preinit, - preinitModule, - useFormStatus, - useFormState, - unstable_batchedUpdates, -} from './src/server/ReactDOMServerRenderingStub'; - -import type {FormStatus} from 'react-dom-bindings/src/shared/ReactDOMFormActions'; -import { - useFormStatus, - useFormState, -} from './src/server/ReactDOMServerRenderingStub'; -import type {Awaited} from 'shared/ReactTypes'; - -export function experimental_useFormStatus(): FormStatus { - if (__DEV__) { - console.error( - 'useFormStatus is now in canary. Remove the experimental_ prefix. ' + - 'The prefixed alias will be removed in an upcoming release.', - ); - } - return useFormStatus(); -} - -export function experimental_useFormState( - action: (Awaited, P) => S, - initialState: Awaited, - permalink?: string, -): [Awaited, (P) => void] { - if (__DEV__) { - console.error( - 'useFormState is now in canary. Remove the experimental_ prefix. ' + - 'The prefixed alias will be removed in an upcoming release.', - ); - } - return useFormState(action, initialState, permalink); -} diff --git a/packages/react-dom/index.classic.fb.js b/packages/react-dom/src/ReactDOMFB.js similarity index 88% rename from packages/react-dom/index.classic.fb.js rename to packages/react-dom/src/ReactDOMFB.js index d8814243b690d..d6b1655b819d2 100644 --- a/packages/react-dom/index.classic.fb.js +++ b/packages/react-dom/src/ReactDOMFB.js @@ -9,7 +9,7 @@ import {isEnabled} from 'react-dom-bindings/src/events/ReactDOMEventListener'; -import Internals from './src/ReactDOMSharedInternals'; +import Internals from './ReactDOMSharedInternals'; // For classic WWW builds, include a few internals that are already in use. Object.assign((Internals: any), { @@ -18,19 +18,23 @@ Object.assign((Internals: any), { }, }); +export {Internals as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED}; export { - createPortal, createRoot, hydrateRoot, findDOMNode, - flushSync, hydrate, render, unmountComponentAtNode, - unstable_batchedUpdates, unstable_createEventHandle, unstable_renderSubtreeIntoContainer, unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority. +} from './client/ReactDOMClientBrowser'; + +export { + createPortal, + flushSync, + unstable_batchedUpdates, useFormStatus, useFormState, prefetchDNS, @@ -40,6 +44,4 @@ export { preinit, preinitModule, version, -} from './src/client/ReactDOM'; - -export {Internals as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED}; +} from './shared/ReactDOM'; diff --git a/packages/react-dom/index.modern.fb.js b/packages/react-dom/src/ReactDOMFB.modern.js similarity index 83% rename from packages/react-dom/index.modern.fb.js rename to packages/react-dom/src/ReactDOMFB.modern.js index 531f4d429b611..d4a0ac1ab1462 100644 --- a/packages/react-dom/index.modern.fb.js +++ b/packages/react-dom/src/ReactDOMFB.modern.js @@ -7,15 +7,18 @@ * @flow */ -export {default as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED} from './src/ReactDOMSharedInternals'; +export {default as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED} from './ReactDOMSharedInternals'; export { - createPortal, createRoot, hydrateRoot, - flushSync, - unstable_batchedUpdates, unstable_createEventHandle, unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority. +} from './client/ReactDOMClientBrowser'; + +export { + createPortal, + flushSync, + unstable_batchedUpdates, useFormStatus, useFormState, prefetchDNS, @@ -25,4 +28,4 @@ export { preinit, preinitModule, version, -} from './src/client/ReactDOM'; +} from './shared/ReactDOM'; diff --git a/packages/react-dom/src/ReactDOMServer.js b/packages/react-dom/src/ReactDOMReactServer.js similarity index 100% rename from packages/react-dom/src/ReactDOMServer.js rename to packages/react-dom/src/ReactDOMReactServer.js diff --git a/packages/react-dom/unstable_testing.modern.fb.js b/packages/react-dom/src/ReactDOMTestingFB.js similarity index 58% rename from packages/react-dom/unstable_testing.modern.fb.js rename to packages/react-dom/src/ReactDOMTestingFB.js index 17e78ae012adc..1d7a4c8fb13bc 100644 --- a/packages/react-dom/unstable_testing.modern.fb.js +++ b/packages/react-dom/src/ReactDOMTestingFB.js @@ -7,23 +7,8 @@ * @flow */ -export { - createPortal, - flushSync, - unstable_batchedUpdates, - unstable_createEventHandle, - useFormStatus, - useFormState, - prefetchDNS, - preconnect, - preload, - preloadModule, - preinit, - preinitModule, - version, - __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, -} from './index.modern.fb.js'; -export {createRoot, hydrateRoot} from './client.js'; +export * from './ReactDOMFB'; + export { createComponentSelector, createHasPseudoClassSelector, diff --git a/packages/react-dom/src/ReactDOMTestingFB.modern.js b/packages/react-dom/src/ReactDOMTestingFB.modern.js new file mode 100644 index 0000000000000..7d60bf6d4e0b8 --- /dev/null +++ b/packages/react-dom/src/ReactDOMTestingFB.modern.js @@ -0,0 +1,23 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +export * from './ReactDOMFB.modern'; + +export { + createComponentSelector, + createHasPseudoClassSelector, + createRoleSelector, + createTestNameSelector, + createTextSelector, + getFindAllNodesFailureDescription, + findAllNodes, + findBoundingRects, + focusWithin, + observeVisibleRects, +} from 'react-reconciler/src/ReactFiberReconciler'; diff --git a/packages/react-dom/src/__tests__/react-dom-server-rendering-stub-test.js b/packages/react-dom/src/__tests__/react-dom-server-rendering-stub-test.js deleted file mode 100644 index 069a1881bcc0a..0000000000000 --- a/packages/react-dom/src/__tests__/react-dom-server-rendering-stub-test.js +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @emails react-core - */ - -'use strict'; - -let React; -let ReactDOM; -let ReactDOMFizzServer; - -describe('react-dom-server-rendering-stub', () => { - beforeEach(() => { - jest.mock('react-dom', () => require('react-dom/server-rendering-stub')); - - React = require('react'); - ReactDOM = require('react-dom'); - ReactDOMFizzServer = require('react-dom/server'); - }); - - it('exports a version', () => { - expect(ReactDOM.version).toBeTruthy(); - }); - - it('exports that are expected to be client only in the future are not exported', () => { - expect(ReactDOM.createRoot).toBe(undefined); - expect(ReactDOM.hydrateRoot).toBe(undefined); - expect(ReactDOM.findDOMNode).toBe(undefined); - expect( - ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.findDOMNode, - ).toBe(null); - expect(ReactDOM.hydrate).toBe(undefined); - expect(ReactDOM.render).toBe(undefined); - expect(ReactDOM.unmountComponentAtNode).toBe(undefined); - expect(ReactDOM.unstable_createEventHandle).toBe(undefined); - expect(ReactDOM.unstable_renderSubtreeIntoContainer).toBe(undefined); - expect(ReactDOM.unstable_runWithPriority).toBe(undefined); - }); - - // @gate enableFloat - it('provides preload, preloadModule, preinit, and preinitModule exports', async () => { - function App() { - ReactDOM.preload('foo', {as: 'style'}); - ReactDOM.preloadModule('foomodule'); - ReactDOM.preinit('bar', {as: 'style'}); - ReactDOM.preinitModule('barmodule'); - return
foo
; - } - const html = ReactDOMFizzServer.renderToString(); - expect(html).toEqual( - '
foo
', - ); - }); - - it('provides preconnect and prefetchDNS exports', async () => { - function App() { - ReactDOM.preconnect('foo', {crossOrigin: 'use-credentials'}); - ReactDOM.prefetchDNS('bar'); - return
foo
; - } - const html = ReactDOMFizzServer.renderToString(); - expect(html).toEqual( - '
foo
', - ); - }); - - it('provides a stub for createPortal', async () => { - expect(() => { - ReactDOM.createPortal(); - }).toThrow( - 'createPortal was called on the server. Portals are not currently supported on the server. Update your program to conditionally call createPortal on the client only.', - ); - }); - - it('provides a stub for flushSync', async () => { - let x = false; - expect(() => { - ReactDOM.flushSync(() => (x = true)); - }).toThrow( - 'flushSync was called on the server. This is likely caused by a function being called during render or in module scope that was intended to be called from an effect or event handler. Update your to not call flushSync no the server.', - ); - expect(x).toBe(false); - }); - - // @gate enableFormActions - // @gate enableAsyncActions - it('exports useFormStatus', async () => { - function App() { - const {pending} = ReactDOM.useFormStatus(); - return 'Pending: ' + pending; - } - - const result = await ReactDOMFizzServer.renderToStaticMarkup(); - expect(result).toEqual('Pending: false'); - }); -}); diff --git a/packages/react-dom/src/client/ReactDOM.js b/packages/react-dom/src/client/ReactDOMClientBrowser.js similarity index 59% rename from packages/react-dom/src/client/ReactDOM.js rename to packages/react-dom/src/client/ReactDOMClientBrowser.js index 444577825ab76..83c76faa53f14 100644 --- a/packages/react-dom/src/client/ReactDOM.js +++ b/packages/react-dom/src/client/ReactDOMClientBrowser.js @@ -7,16 +7,10 @@ * @flow */ -import type {ReactNodeList} from 'shared/ReactTypes'; import type { Container, PublicInstance, } from 'react-dom-bindings/src/client/ReactFiberConfigDOM'; -import type { - RootType, - HydrateRootOptions, - CreateRootOptions, -} from './ReactDOMRoot'; import { findDOMNode, @@ -25,21 +19,14 @@ import { unstable_renderSubtreeIntoContainer, unmountComponentAtNode, } from './ReactDOMLegacy'; -import { - createRoot as createRootImpl, - hydrateRoot as hydrateRootImpl, - isValidContainer, -} from './ReactDOMRoot'; +import {createRoot, hydrateRoot} from './ReactDOMRoot'; import {createEventHandle} from 'react-dom-bindings/src/client/ReactDOMEventHandle'; import { batchedUpdates, - flushSync as flushSyncWithoutWarningIfAlreadyRendering, - isAlreadyRendering, injectIntoDevTools, } from 'react-reconciler/src/ReactFiberReconciler'; import {runWithPriority} from 'react-reconciler/src/ReactEventPriorities'; -import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal'; import {canUseDOM} from 'shared/ExecutionEnvironment'; import ReactVersion from 'shared/ReactVersion'; @@ -53,20 +40,7 @@ import { enqueueStateRestore, restoreStateIfNeeded, } from 'react-dom-bindings/src/events/ReactDOMControlledComponent'; -import Internals from '../ReactDOMSharedInternals'; - -export { - prefetchDNS, - preconnect, - preload, - preloadModule, - preinit, - preinitModule, -} from '../shared/ReactDOMFloat'; -export { - useFormStatus, - useFormState, -} from 'react-dom-bindings/src/shared/ReactDOMFormActions'; +import Internals from 'shared/ReactDOMSharedInternals'; if (__DEV__) { if ( @@ -87,20 +61,6 @@ if (__DEV__) { } } -function createPortal( - children: ReactNodeList, - container: Element | DocumentFragment, - key: ?string = null, -): React$Portal { - if (!isValidContainer(container)) { - throw new Error('Target container is not a DOM element.'); - } - - // TODO: pass ReactDOM portal implementation as third argument - // $FlowFixMe[incompatible-return] The Flow type is opaque but there's no way to actually create it. - return createPortalImpl(children, container, null, key); -} - function renderSubtreeIntoContainer( parentComponent: React$Component, element: React$Element, @@ -115,63 +75,10 @@ function renderSubtreeIntoContainer( ); } -function createRoot( - container: Element | Document | DocumentFragment, - options?: CreateRootOptions, -): RootType { - if (__DEV__) { - if (!Internals.usingClientEntryPoint && !__UMD__) { - console.error( - 'You are importing createRoot from "react-dom" which is not supported. ' + - 'You should instead import it from "react-dom/client".', - ); - } - } - return createRootImpl(container, options); -} - -function hydrateRoot( - container: Document | Element, - initialChildren: ReactNodeList, - options?: HydrateRootOptions, -): RootType { - if (__DEV__) { - if (!Internals.usingClientEntryPoint && !__UMD__) { - console.error( - 'You are importing hydrateRoot from "react-dom" which is not supported. ' + - 'You should instead import it from "react-dom/client".', - ); - } - } - return hydrateRootImpl(container, initialChildren, options); -} - -// Overload the definition to the two valid signatures. -// Warning, this opts-out of checking the function body. -declare function flushSync(fn: () => R): R; -// eslint-disable-next-line no-redeclare -declare function flushSync(): void; -// eslint-disable-next-line no-redeclare -function flushSync(fn: (() => R) | void): R | void { - if (__DEV__) { - if (isAlreadyRendering()) { - console.error( - 'flushSync was called from inside a lifecycle method. React cannot ' + - 'flush when React is already rendering. Consider moving this call to ' + - 'a scheduler task or micro task.', - ); - } - } - return flushSyncWithoutWarningIfAlreadyRendering(fn); -} - // Expose findDOMNode on internals Internals.findDOMNode = findDOMNode; export { - createPortal, - batchedUpdates as unstable_batchedUpdates, - flushSync, ReactVersion as version, // Disabled behind disableLegacyReactDOMAPIs findDOMNode, diff --git a/packages/react-dom/src/client/ReactDOMLegacy.js b/packages/react-dom/src/client/ReactDOMLegacy.js index 8b27bf6fb8787..366a2135476b5 100644 --- a/packages/react-dom/src/client/ReactDOMLegacy.js +++ b/packages/react-dom/src/client/ReactDOMLegacy.js @@ -22,7 +22,7 @@ import { unmarkContainerAsRoot, } from 'react-dom-bindings/src/client/ReactDOMComponentTree'; import {listenToAllSupportedEvents} from 'react-dom-bindings/src/events/DOMPluginEventSystem'; -import {isValidContainerLegacy} from './ReactDOMRoot'; +import {isValidContainerLegacy} from 'react-dom-bindings/src/client/ReactDOMContainer'; import { DOCUMENT_NODE, ELEMENT_NODE, diff --git a/packages/react-dom/src/client/ReactDOMRoot.js b/packages/react-dom/src/client/ReactDOMRoot.js index 9b93ab6354bd0..6f933702c8e0d 100644 --- a/packages/react-dom/src/client/ReactDOMRoot.js +++ b/packages/react-dom/src/client/ReactDOMRoot.js @@ -13,24 +13,15 @@ import type { TransitionTracingCallbacks, } from 'react-reconciler/src/ReactInternalTypes'; -import {ReactDOMClientDispatcher} from 'react-dom-bindings/src/client/ReactFiberConfigDOM'; +import {isValidContainer} from 'react-dom-bindings/src/client/ReactDOMContainer'; import {queueExplicitHydrationTarget} from 'react-dom-bindings/src/events/ReactDOMEventReplaying'; import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols'; import { - enableFloat, allowConcurrentByDefault, - disableCommentsAsDOMContainers, enableAsyncActions, enableFormActions, } from 'shared/ReactFeatureFlags'; -import ReactDOMSharedInternals from '../ReactDOMSharedInternals'; -const {Dispatcher} = ReactDOMSharedInternals; -if (enableFloat && typeof document !== 'undefined') { - // Set the default dispatcher to the client dispatcher - Dispatcher.current = ReactDOMClientDispatcher; -} - export type RootType = { render(children: ReactNodeList): void, unmount(): void, @@ -64,12 +55,7 @@ import { unmarkContainerAsRoot, } from 'react-dom-bindings/src/client/ReactDOMComponentTree'; import {listenToAllSupportedEvents} from 'react-dom-bindings/src/events/DOMPluginEventSystem'; -import { - ELEMENT_NODE, - COMMENT_NODE, - DOCUMENT_NODE, - DOCUMENT_FRAGMENT_NODE, -} from 'react-dom-bindings/src/client/HTMLNodeType'; +import {COMMENT_NODE} from 'react-dom-bindings/src/client/HTMLNodeType'; import { createContainer, @@ -228,7 +214,6 @@ export function createRoot( transitionCallbacks, ); markContainerAsRoot(root.current, container); - Dispatcher.current = ReactDOMClientDispatcher; const rootContainerElement: Document | Element | DocumentFragment = container.nodeType === COMMENT_NODE @@ -322,7 +307,6 @@ export function hydrateRoot( formState, ); markContainerAsRoot(root.current, container); - Dispatcher.current = ReactDOMClientDispatcher; // This can't be a comment node since hydration doesn't work on comment nodes anyway. listenToAllSupportedEvents(container); @@ -330,31 +314,6 @@ export function hydrateRoot( return new ReactDOMHydrationRoot(root); } -export function isValidContainer(node: any): boolean { - return !!( - node && - (node.nodeType === ELEMENT_NODE || - node.nodeType === DOCUMENT_NODE || - node.nodeType === DOCUMENT_FRAGMENT_NODE || - (!disableCommentsAsDOMContainers && - node.nodeType === COMMENT_NODE && - (node: any).nodeValue === ' react-mount-point-unstable ')) - ); -} - -// TODO: Remove this function which also includes comment nodes. -// We only use it in places that are currently more relaxed. -export function isValidContainerLegacy(node: any): boolean { - return !!( - node && - (node.nodeType === ELEMENT_NODE || - node.nodeType === DOCUMENT_NODE || - node.nodeType === DOCUMENT_FRAGMENT_NODE || - (node.nodeType === COMMENT_NODE && - (node: any).nodeValue === ' react-mount-point-unstable ')) - ); -} - function warnIfReactDOMContainerInDEV(container: any) { if (__DEV__) { if (isContainerMarkedAsRoot(container)) { diff --git a/packages/react-dom/src/server/ReactDOMServerRenderingStub.js b/packages/react-dom/src/server/ReactDOMServerRenderingStub.js deleted file mode 100644 index d3a7f33095fd6..0000000000000 --- a/packages/react-dom/src/server/ReactDOMServerRenderingStub.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - */ - -export { - preinit, - preinitModule, - preload, - preloadModule, - preconnect, - prefetchDNS, -} from '../shared/ReactDOMFloat'; -export { - useFormStatus, - useFormState, -} from 'react-dom-bindings/src/shared/ReactDOMFormActions'; - -export function createPortal() { - throw new Error( - 'createPortal was called on the server. Portals are not currently' + - ' supported on the server. Update your program to conditionally call' + - ' createPortal on the client only.', - ); -} - -export function flushSync() { - throw new Error( - 'flushSync was called on the server. This is likely caused by a' + - ' function being called during render or in module scope that was' + - ' intended to be called from an effect or event handler. Update your' + - ' to not call flushSync no the server.', - ); -} - -// on the server we just call the callback because there is -// not update mechanism. Really this should not be called on the -// server but since the semantics are generally clear enough we -// can provide this trivial implementation. -function batchedUpdates(fn: A => R, a: A): R { - return fn(a); -} - -export {batchedUpdates as unstable_batchedUpdates}; diff --git a/packages/react-dom/src/shared/ReactDOM.js b/packages/react-dom/src/shared/ReactDOM.js new file mode 100644 index 0000000000000..ac0510256c7e8 --- /dev/null +++ b/packages/react-dom/src/shared/ReactDOM.js @@ -0,0 +1,101 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import type {ReactNodeList} from 'shared/ReactTypes'; + +import ReactVersion from 'shared/ReactVersion'; + +import {isValidContainer} from 'react-dom-bindings/src/client/ReactDOMContainer'; +import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal'; + +import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals'; +const Dispatcher = ReactDOMSharedInternals.Dispatcher; + +import { + prefetchDNS, + preconnect, + preload, + preloadModule, + preinit, + preinitModule, +} from './ReactDOMFloat'; +import { + useFormStatus, + useFormState, +} from 'react-dom-bindings/src/shared/ReactDOMFormActions'; + +if (__DEV__) { + if ( + typeof Map !== 'function' || + // $FlowFixMe[prop-missing] Flow incorrectly thinks Map has no prototype + Map.prototype == null || + typeof Map.prototype.forEach !== 'function' || + typeof Set !== 'function' || + // $FlowFixMe[prop-missing] Flow incorrectly thinks Set has no prototype + Set.prototype == null || + typeof Set.prototype.clear !== 'function' || + typeof Set.prototype.forEach !== 'function' + ) { + console.error( + 'React depends on Map and Set built-in types. Make sure that you load a ' + + 'polyfill in older browsers. https://reactjs.org/link/react-polyfills', + ); + } +} + +// Overload the definition to the two valid signatures. +// Warning, this opts-out of checking the function body. +declare function flushSync(fn: () => R): R; +// eslint-disable-next-line no-redeclare +declare function flushSync(): void; +// eslint-disable-next-line no-redeclare +function flushSync(fn: (() => R) | void): R | void { + // We expect our HostDispatcher to provide an implementation. + const dispatcher = Dispatcher.current; + if (dispatcher) { + return dispatcher.flushSync(fn); + } else if (fn) { + // If no implementation is provided we can simply call the function + return fn(); + } +} + +function batchedUpdates(fn: (a: A) => R, a: A): R { + // batchedUpdates is now just a passthrough noop + return fn(a); +} + +function createPortal( + children: ReactNodeList, + container: Element | DocumentFragment, + key: ?string = null, +): React$Portal { + if (!isValidContainer(container)) { + throw new Error('Target container is not a DOM element.'); + } + + // TODO: pass ReactDOM portal implementation as third argument + // $FlowFixMe[incompatible-return] The Flow type is opaque but there's no way to actually create it. + return createPortalImpl(children, container, null, key); +} + +export { + ReactVersion as version, + createPortal, + flushSync, + batchedUpdates as unstable_batchedUpdates, + prefetchDNS, + preconnect, + preload, + preloadModule, + preinit, + preinitModule, + useFormStatus, + useFormState, +}; diff --git a/packages/react-dom/src/shared/ReactDOMTypes.js b/packages/react-dom/src/shared/ReactDOMTypes.js index 942515be5c81a..7bdef641d273b 100644 --- a/packages/react-dom/src/shared/ReactDOMTypes.js +++ b/packages/react-dom/src/shared/ReactDOMTypes.js @@ -90,11 +90,13 @@ export type HostDispatcher = { precedence: ?string, options?: ?PreinitStyleOptions, ) => void, - preinitScript: (src: string, options?: PreinitScriptOptions) => void, + preinitScript: (src: string, options?: ?PreinitScriptOptions) => void, preinitModuleScript: ( src: string, options?: ?PreinitModuleScriptOptions, ) => void, + flushSync: (fn: void | (() => R)) => void | R, + nextDispatcher: null | HostDispatcher, }; export type ImportMap = { diff --git a/packages/react-dom/unstable_testing.classic.fb.js b/packages/react-dom/unstable_testing.classic.fb.js deleted file mode 100644 index 6dd68f3669dc3..0000000000000 --- a/packages/react-dom/unstable_testing.classic.fb.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - */ - -export { - createPortal, - findDOMNode, - flushSync, - hydrate, - render, - unmountComponentAtNode, - unstable_batchedUpdates, - unstable_createEventHandle, - unstable_renderSubtreeIntoContainer, - unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority. - useFormStatus, - useFormState, - prefetchDNS, - preconnect, - preload, - preloadModule, - preinit, - preinitModule, - version, - __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, -} from './index.classic.fb.js'; - -export {createRoot, hydrateRoot} from './client.js'; - -export { - createComponentSelector, - createHasPseudoClassSelector, - createRoleSelector, - createTestNameSelector, - createTextSelector, - getFindAllNodesFailureDescription, - findAllNodes, - findBoundingRects, - focusWithin, - observeVisibleRects, -} from 'react-reconciler/src/ReactFiberReconciler'; diff --git a/packages/react-dom/unstable_testing.experimental.js b/packages/react-dom/unstable_testing.experimental.js index 8c2b81a7142c7..65e61a4d90f25 100644 --- a/packages/react-dom/unstable_testing.experimental.js +++ b/packages/react-dom/unstable_testing.experimental.js @@ -7,26 +7,6 @@ * @flow */ -export { - createPortal, - flushSync, - hydrate, - render, - unmountComponentAtNode, - unstable_batchedUpdates, - unstable_renderSubtreeIntoContainer, - useFormStatus, - useFormState, - prefetchDNS, - preconnect, - preload, - preloadModule, - preinit, - preinitModule, - version, - __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, -} from './index.experimental.js'; - export {createRoot, hydrateRoot} from './client.js'; export { diff --git a/packages/react-dom/unstable_testing.js b/packages/react-dom/unstable_testing.js index c5635c03d6167..73005ce373a5b 100644 --- a/packages/react-dom/unstable_testing.js +++ b/packages/react-dom/unstable_testing.js @@ -7,38 +7,4 @@ * @flow */ -export { - createPortal, - flushSync, - hydrate, - render, - unmountComponentAtNode, - unstable_batchedUpdates, - unstable_createEventHandle, - unstable_renderSubtreeIntoContainer, - unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority. - useFormStatus, - useFormState, - prefetchDNS, - preconnect, - preload, - preloadModule, - preinit, - preinitModule, - version, -} from './index.js'; - export {createRoot, hydrateRoot} from './client.js'; - -export { - createComponentSelector, - createHasPseudoClassSelector, - createRoleSelector, - createTestNameSelector, - createTextSelector, - getFindAllNodesFailureDescription, - findAllNodes, - findBoundingRects, - focusWithin, - observeVisibleRects, -} from 'react-reconciler/src/ReactFiberReconciler'; diff --git a/packages/react-dom/unstable_testing.stable.js b/packages/react-dom/unstable_testing.stable.js deleted file mode 100644 index ea7a55941728e..0000000000000 --- a/packages/react-dom/unstable_testing.stable.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - */ - -export { - createPortal, - flushSync, - hydrate, - render, - unmountComponentAtNode, - unstable_batchedUpdates, - unstable_renderSubtreeIntoContainer, - useFormStatus, - useFormState, - prefetchDNS, - preconnect, - preload, - preloadModule, - preinit, - preinitModule, - version, - __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, -} from './index.stable.js'; -export {createRoot, hydrateRoot} from './client.js'; diff --git a/packages/react-interactions/README.md b/packages/react-interactions/README.md deleted file mode 100644 index 2326ed26c5acc..0000000000000 --- a/packages/react-interactions/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# `react-interactions` - -This package is experimental. It is intended for use with the experimental React -flags for internal testing. \ No newline at end of file diff --git a/packages/react-interactions/events/src/dom/create-event-handle/Focus.js b/packages/react-interactions/events/src/dom/create-event-handle/Focus.js deleted file mode 100644 index e6cb7302e7520..0000000000000 --- a/packages/react-interactions/events/src/dom/create-event-handle/Focus.js +++ /dev/null @@ -1,400 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - */ - -import * as React from 'react'; -import useEvent from './useEvent'; - -const {useCallback, useEffect, useLayoutEffect, useRef} = React; - -type FocusEvent = SyntheticEvent; - -type UseFocusOptions = { - disabled?: boolean, - onBlur?: ?(FocusEvent) => void, - onFocus?: ?(FocusEvent) => void, - onFocusChange?: ?(boolean) => void, - onFocusVisibleChange?: ?(boolean) => void, -}; - -type UseFocusWithinOptions = { - disabled?: boolean, - onAfterBlurWithin?: FocusEvent => void, - onBeforeBlurWithin?: FocusEvent => void, - onBlurWithin?: FocusEvent => void, - onFocusWithin?: FocusEvent => void, - onFocusWithinChange?: boolean => void, - onFocusWithinVisibleChange?: boolean => void, -}; - -const isMac = - typeof window !== 'undefined' && window.navigator != null - ? /^Mac/.test(window.navigator.platform) - : false; - -const hasPointerEvents = - typeof window !== 'undefined' && window.PointerEvent != null; - -const globalFocusVisibleEvents = hasPointerEvents - ? ['keydown', 'pointermove', 'pointerdown', 'pointerup'] - : [ - 'keydown', - 'mousedown', - 'mousemove', - 'mouseup', - 'touchmove', - 'touchstart', - 'touchend', - ]; - -// Global state for tracking focus visible and emulation of mouse -let isGlobalFocusVisible = true; -let hasTrackedGlobalFocusVisible = false; - -function trackGlobalFocusVisible() { - globalFocusVisibleEvents.forEach(type => { - window.addEventListener(type, handleGlobalFocusVisibleEvent, true); - }); -} - -function isValidKey(nativeEvent: KeyboardEvent): boolean { - const {metaKey, altKey, ctrlKey} = nativeEvent; - return !(metaKey || (!isMac && altKey) || ctrlKey); -} - -function isTextInput(nativeEvent: KeyboardEvent): boolean { - const {key, target} = nativeEvent; - if (key === 'Tab' || key === 'Escape') { - return false; - } - const {isContentEditable, tagName} = (target: any); - return tagName === 'INPUT' || tagName === 'TEXTAREA' || isContentEditable; -} - -function handleGlobalFocusVisibleEvent( - nativeEvent: MouseEvent | TouchEvent | KeyboardEvent, -): void { - if (nativeEvent.type === 'keydown') { - if (isValidKey(((nativeEvent: any): KeyboardEvent))) { - isGlobalFocusVisible = true; - } - } else { - const nodeName = (nativeEvent.target: any).nodeName; - // Safari calls mousemove/pointermove events when you tab out of the active - // Safari frame. - if (nodeName === 'HTML') { - return; - } - // Handle all the other mouse/touch/pointer events - isGlobalFocusVisible = false; - } -} - -function handleFocusVisibleTargetEvents( - event: SyntheticEvent, - callback: boolean => void, -): void { - if (event.type === 'keydown') { - const {nativeEvent} = (event: any); - if (isValidKey(nativeEvent) && !isTextInput(nativeEvent)) { - callback(true); - } - } else { - callback(false); - } -} - -function isRelatedTargetWithin( - focusWithinTarget: Object, - relatedTarget: null | EventTarget, -): boolean { - if (relatedTarget == null) { - return false; - } - // As the focusWithinTarget can be a Scope Instance (experimental API), - // we need to use the containsNode() method. Otherwise, focusWithinTarget - // must be a Node, which means we can use the contains() method. - return typeof focusWithinTarget.containsNode === 'function' - ? focusWithinTarget.containsNode(relatedTarget) - : focusWithinTarget.contains(relatedTarget); -} - -function setFocusVisibleListeners( - // $FlowFixMe[missing-local-annot] - focusVisibleHandles, - focusTarget: EventTarget, - callback: boolean => void, -) { - focusVisibleHandles.forEach(focusVisibleHandle => { - focusVisibleHandle.setListener(focusTarget, event => - handleFocusVisibleTargetEvents(event, callback), - ); - }); -} - -function useFocusVisibleInputHandles() { - return [ - useEvent('mousedown'), - useEvent(hasPointerEvents ? 'pointerdown' : 'touchstart'), - useEvent('keydown'), - ]; -} - -function useFocusLifecycles() { - useEffect(() => { - if (!hasTrackedGlobalFocusVisible) { - hasTrackedGlobalFocusVisible = true; - trackGlobalFocusVisible(); - } - }, []); -} - -export function useFocus( - focusTargetRef: {current: null | Node}, - { - disabled, - onBlur, - onFocus, - onFocusChange, - onFocusVisibleChange, - }: UseFocusOptions, -): void { - // Setup controlled state for this useFocus hook - const stateRef = useRef({isFocused: false, isFocusVisible: false}); - const focusHandle = useEvent('focusin'); - const blurHandle = useEvent('focusout'); - const focusVisibleHandles = useFocusVisibleInputHandles(); - - useLayoutEffect(() => { - const focusTarget = focusTargetRef.current; - const state = stateRef.current; - - if (focusTarget !== null && state !== null && focusTarget.nodeType === 1) { - // Handle focus visible - setFocusVisibleListeners( - focusVisibleHandles, - focusTarget, - isFocusVisible => { - if (state.isFocused && state.isFocusVisible !== isFocusVisible) { - state.isFocusVisible = isFocusVisible; - if (onFocusVisibleChange) { - onFocusVisibleChange(isFocusVisible); - } - } - }, - ); - - // Handle focus - focusHandle.setListener(focusTarget, (event: FocusEvent) => { - if (disabled === true) { - return; - } - if (!state.isFocused && focusTarget === event.target) { - state.isFocused = true; - state.isFocusVisible = isGlobalFocusVisible; - if (onFocus) { - onFocus(event); - } - if (onFocusChange) { - onFocusChange(true); - } - if (state.isFocusVisible && onFocusVisibleChange) { - onFocusVisibleChange(true); - } - } - }); - - // Handle blur - blurHandle.setListener(focusTarget, (event: FocusEvent) => { - if (disabled === true) { - return; - } - if (state.isFocused) { - state.isFocused = false; - state.isFocusVisible = isGlobalFocusVisible; - if (onBlur) { - onBlur(event); - } - if (onFocusChange) { - onFocusChange(false); - } - if (state.isFocusVisible && onFocusVisibleChange) { - onFocusVisibleChange(false); - } - } - }); - } - }, [ - blurHandle, - disabled, - focusHandle, - focusTargetRef, - focusVisibleHandles, - onBlur, - onFocus, - onFocusChange, - onFocusVisibleChange, - ]); - - // Mount/Unmount logic - useFocusLifecycles(); -} - -export function useFocusWithin( - focusWithinTargetRef: - | {current: null | T} - | ((focusWithinTarget: null | T) => void), - { - disabled, - onAfterBlurWithin, - onBeforeBlurWithin, - onBlurWithin, - onFocusWithin, - onFocusWithinChange, - onFocusWithinVisibleChange, - }: UseFocusWithinOptions, -): (focusWithinTarget: null | T) => void { - // Setup controlled state for this useFocus hook - const stateRef = useRef({isFocused: false, isFocusVisible: false}); - const focusHandle = useEvent('focusin'); - const blurHandle = useEvent('focusout'); - const afterBlurHandle = useEvent('afterblur'); - const beforeBlurHandle = useEvent('beforeblur'); - const focusVisibleHandles = useFocusVisibleInputHandles(); - - const useFocusWithinRef = useCallback( - (focusWithinTarget: null | T) => { - // Handle the incoming focusTargetRef. It can be either a function ref - // or an object ref. - if (typeof focusWithinTargetRef === 'function') { - focusWithinTargetRef(focusWithinTarget); - } else { - focusWithinTargetRef.current = focusWithinTarget; - } - const state = stateRef.current; - - if (focusWithinTarget !== null && state !== null) { - // Handle focus visible - setFocusVisibleListeners( - focusVisibleHandles, - // $FlowFixMe[incompatible-call] focusWithinTarget is not null here - focusWithinTarget, - isFocusVisible => { - if (state.isFocused && state.isFocusVisible !== isFocusVisible) { - state.isFocusVisible = isFocusVisible; - if (onFocusWithinVisibleChange) { - onFocusWithinVisibleChange(isFocusVisible); - } - } - }, - ); - - // Handle focus - // $FlowFixMe[incompatible-call] focusWithinTarget is not null here - focusHandle.setListener(focusWithinTarget, (event: FocusEvent) => { - if (disabled) { - return; - } - if (!state.isFocused) { - state.isFocused = true; - state.isFocusVisible = isGlobalFocusVisible; - if (onFocusWithinChange) { - onFocusWithinChange(true); - } - if (state.isFocusVisible && onFocusWithinVisibleChange) { - onFocusWithinVisibleChange(true); - } - } - if (!state.isFocusVisible && isGlobalFocusVisible) { - state.isFocusVisible = isGlobalFocusVisible; - if (onFocusWithinVisibleChange) { - onFocusWithinVisibleChange(true); - } - } - if (onFocusWithin) { - onFocusWithin(event); - } - }); - - // Handle blur - // $FlowFixMe[incompatible-call] focusWithinTarget is not null here - blurHandle.setListener(focusWithinTarget, (event: FocusEvent) => { - if (disabled) { - return; - } - const {relatedTarget} = (event.nativeEvent: any); - - if ( - state.isFocused && - !isRelatedTargetWithin(focusWithinTarget, relatedTarget) - ) { - state.isFocused = false; - if (onFocusWithinChange) { - onFocusWithinChange(false); - } - if (state.isFocusVisible && onFocusWithinVisibleChange) { - onFocusWithinVisibleChange(false); - } - if (onBlurWithin) { - onBlurWithin(event); - } - } - }); - - // Handle before blur. This is a special - // React provided event. - // $FlowFixMe[incompatible-call] focusWithinTarget is not null here - beforeBlurHandle.setListener(focusWithinTarget, (event: FocusEvent) => { - if (disabled) { - return; - } - if (onBeforeBlurWithin) { - onBeforeBlurWithin(event); - // Add an "afterblur" listener on document. This is a special - // React provided event. - afterBlurHandle.setListener( - document, - (afterBlurEvent: FocusEvent) => { - if (onAfterBlurWithin) { - onAfterBlurWithin(afterBlurEvent); - } - // Clear listener on document - afterBlurHandle.setListener(document, null); - }, - ); - } - }); - } - }, - [ - afterBlurHandle, - beforeBlurHandle, - blurHandle, - disabled, - focusHandle, - focusWithinTargetRef, - onAfterBlurWithin, - onBeforeBlurWithin, - onBlurWithin, - onFocusWithin, - onFocusWithinChange, - onFocusWithinVisibleChange, - ], - ); - - // Mount/Unmount logic - useFocusLifecycles(); - - return useFocusWithinRef; -} diff --git a/packages/react-interactions/events/src/dom/create-event-handle/__tests__/useFocus-test.internal.js b/packages/react-interactions/events/src/dom/create-event-handle/__tests__/useFocus-test.internal.js deleted file mode 100644 index cc456434f975c..0000000000000 --- a/packages/react-interactions/events/src/dom/create-event-handle/__tests__/useFocus-test.internal.js +++ /dev/null @@ -1,335 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @emails react-core - */ - -'use strict'; - -import {createEventTarget, setPointerEvent} from 'dom-event-testing-library'; - -let React; -let ReactFeatureFlags; -let ReactDOMClient; -let useFocus; -let act; - -function initializeModules(hasPointerEvents) { - setPointerEvent(hasPointerEvents); - jest.resetModules(); - ReactFeatureFlags = require('shared/ReactFeatureFlags'); - ReactFeatureFlags.enableCreateEventHandleAPI = true; - React = require('react'); - ReactDOMClient = require('react-dom/client'); - act = require('internal-test-utils').act; - // TODO: This import throws outside of experimental mode. Figure out better - // strategy for gated imports. - if (__EXPERIMENTAL__ || global.__WWW__) { - useFocus = require('react-interactions/events/focus').useFocus; - } -} - -const forcePointerEvents = true; -const table = [[forcePointerEvents], [!forcePointerEvents]]; - -describe.each(table)(`useFocus hasPointerEvents=%s`, hasPointerEvents => { - let container; - - beforeEach(() => { - initializeModules(hasPointerEvents); - container = document.createElement('div'); - document.body.appendChild(container); - }); - - afterEach(() => { - document.body.removeChild(container); - container = null; - }); - - describe('disabled', () => { - let onBlur, onFocus, ref; - - const componentInit = async () => { - onBlur = jest.fn(); - onFocus = jest.fn(); - ref = React.createRef(); - const Component = () => { - useFocus(ref, { - disabled: true, - onBlur, - onFocus, - }); - return
; - }; - const root = ReactDOMClient.createRoot(container); - await act(() => { - root.render(); - }); - }; - - // @gate www - it('does not call callbacks', async () => { - await componentInit(); - const target = createEventTarget(ref.current); - target.focus(); - target.blur(); - expect(onFocus).not.toBeCalled(); - expect(onBlur).not.toBeCalled(); - }); - }); - - describe('onBlur', () => { - let onBlur, ref; - - const componentInit = async () => { - onBlur = jest.fn(); - ref = React.createRef(); - const Component = () => { - useFocus(ref, { - onBlur, - }); - return
; - }; - const root = ReactDOMClient.createRoot(container); - await act(() => { - root.render(); - }); - }; - - // @gate www - it('is called after "blur" event', async () => { - await componentInit(); - const target = createEventTarget(ref.current); - target.focus(); - target.blur(); - expect(onBlur).toHaveBeenCalledTimes(1); - }); - }); - - describe('onFocus', () => { - let onFocus, ref, innerRef; - - const componentInit = async () => { - onFocus = jest.fn(); - ref = React.createRef(); - innerRef = React.createRef(); - const Component = () => { - useFocus(ref, { - onFocus, - }); - return ( -
- -
- ); - }; - const root = ReactDOMClient.createRoot(container); - await act(() => { - root.render(); - }); - }; - - // @gate www - it('is called after "focus" event', async () => { - await componentInit(); - const target = createEventTarget(ref.current); - target.focus(); - expect(onFocus).toHaveBeenCalledTimes(1); - }); - - // @gate www - it('is not called if descendants of target receive focus', async () => { - await componentInit(); - const target = createEventTarget(innerRef.current); - target.focus(); - expect(onFocus).not.toBeCalled(); - }); - }); - - describe('onFocusChange', () => { - let onFocusChange, ref, innerRef; - - const componentInit = async () => { - onFocusChange = jest.fn(); - ref = React.createRef(); - innerRef = React.createRef(); - const Component = () => { - useFocus(ref, { - onFocusChange, - }); - return ( -
-
-
- ); - }; - const root = ReactDOMClient.createRoot(container); - await act(() => { - root.render(); - }); - }; - - // @gate www - it('is called after "blur" and "focus" events', async () => { - await componentInit(); - const target = createEventTarget(ref.current); - target.focus(); - expect(onFocusChange).toHaveBeenCalledTimes(1); - expect(onFocusChange).toHaveBeenCalledWith(true); - target.blur(); - expect(onFocusChange).toHaveBeenCalledTimes(2); - expect(onFocusChange).toHaveBeenCalledWith(false); - }); - - // @gate www - it('is not called after "blur" and "focus" events on descendants', async () => { - await componentInit(); - const target = createEventTarget(innerRef.current); - target.focus(); - expect(onFocusChange).toHaveBeenCalledTimes(0); - target.blur(); - expect(onFocusChange).toHaveBeenCalledTimes(0); - }); - }); - - describe('onFocusVisibleChange', () => { - let onFocusVisibleChange, ref, innerRef; - - const componentInit = async () => { - onFocusVisibleChange = jest.fn(); - ref = React.createRef(); - innerRef = React.createRef(); - const Component = () => { - useFocus(ref, { - onFocusVisibleChange, - }); - return ( -
-
-
- ); - }; - const root = ReactDOMClient.createRoot(container); - await act(() => { - root.render(); - }); - }; - - // @gate www - it('is called after "focus" and "blur" if keyboard navigation is active', async () => { - await componentInit(); - const target = createEventTarget(ref.current); - const containerTarget = createEventTarget(container); - // use keyboard first - containerTarget.keydown({key: 'Tab'}); - target.focus(); - expect(onFocusVisibleChange).toHaveBeenCalledTimes(1); - expect(onFocusVisibleChange).toHaveBeenCalledWith(true); - target.blur({relatedTarget: container}); - expect(onFocusVisibleChange).toHaveBeenCalledTimes(2); - expect(onFocusVisibleChange).toHaveBeenCalledWith(false); - }); - - // @gate www - it('is called if non-keyboard event is dispatched on target previously focused with keyboard', async () => { - await componentInit(); - const target = createEventTarget(ref.current); - const containerTarget = createEventTarget(container); - // use keyboard first - containerTarget.keydown({key: 'Tab'}); - target.focus(); - expect(onFocusVisibleChange).toHaveBeenCalledTimes(1); - expect(onFocusVisibleChange).toHaveBeenCalledWith(true); - // then use pointer on the target, focus should no longer be visible - target.pointerdown(); - expect(onFocusVisibleChange).toHaveBeenCalledTimes(2); - expect(onFocusVisibleChange).toHaveBeenCalledWith(false); - // onFocusVisibleChange should not be called again - target.blur({relatedTarget: container}); - expect(onFocusVisibleChange).toHaveBeenCalledTimes(2); - }); - - // @gate www - it('is not called after "focus" and "blur" events without keyboard', async () => { - await componentInit(); - const target = createEventTarget(ref.current); - const containerTarget = createEventTarget(container); - target.pointerdown(); - target.pointerup(); - containerTarget.pointerdown(); - target.blur({relatedTarget: container}); - expect(onFocusVisibleChange).toHaveBeenCalledTimes(0); - }); - - // @gate www - it('is not called after "blur" and "focus" events on descendants', async () => { - await componentInit(); - const innerTarget = createEventTarget(innerRef.current); - const containerTarget = createEventTarget(container); - containerTarget.keydown({key: 'Tab'}); - innerTarget.focus(); - expect(onFocusVisibleChange).toHaveBeenCalledTimes(0); - innerTarget.blur({relatedTarget: container}); - expect(onFocusVisibleChange).toHaveBeenCalledTimes(0); - }); - }); - - describe('nested Focus components', () => { - // @gate www - it('propagates events in the correct order', async () => { - const events = []; - const innerRef = React.createRef(); - const outerRef = React.createRef(); - const createEventHandler = msg => () => { - events.push(msg); - }; - - const Inner = () => { - useFocus(innerRef, { - onBlur: createEventHandler('inner: onBlur'), - onFocus: createEventHandler('inner: onFocus'), - onFocusChange: createEventHandler('inner: onFocusChange'), - }); - return
; - }; - - const Outer = () => { - useFocus(outerRef, { - onBlur: createEventHandler('outer: onBlur'), - onFocus: createEventHandler('outer: onFocus'), - onFocusChange: createEventHandler('outer: onFocusChange'), - }); - return ( -
- -
- ); - }; - - const root = ReactDOMClient.createRoot(container); - await act(() => { - root.render(); - }); - const innerTarget = createEventTarget(innerRef.current); - const outerTarget = createEventTarget(outerRef.current); - - outerTarget.focus(); - outerTarget.blur(); - innerTarget.focus(); - innerTarget.blur(); - expect(events).toEqual([ - 'outer: onFocus', - 'outer: onFocusChange', - 'outer: onBlur', - 'outer: onFocusChange', - 'inner: onFocus', - 'inner: onFocusChange', - 'inner: onBlur', - 'inner: onFocusChange', - ]); - }); - }); -}); diff --git a/packages/react-interactions/events/src/dom/create-event-handle/__tests__/useFocusWithin-test.internal.js b/packages/react-interactions/events/src/dom/create-event-handle/__tests__/useFocusWithin-test.internal.js deleted file mode 100644 index f77dd5b779355..0000000000000 --- a/packages/react-interactions/events/src/dom/create-event-handle/__tests__/useFocusWithin-test.internal.js +++ /dev/null @@ -1,626 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @emails react-core - */ - -'use strict'; - -import {createEventTarget, setPointerEvent} from 'dom-event-testing-library'; - -let React; -let ReactFeatureFlags; -let ReactDOMClient; -let useFocusWithin; -let act; - -function initializeModules(hasPointerEvents) { - setPointerEvent(hasPointerEvents); - jest.resetModules(); - ReactFeatureFlags = require('shared/ReactFeatureFlags'); - ReactFeatureFlags.enableScopeAPI = true; - ReactFeatureFlags.enableCreateEventHandleAPI = true; - React = require('react'); - ReactDOMClient = require('react-dom/client'); - act = require('internal-test-utils').act; - - // TODO: This import throws outside of experimental mode. Figure out better - // strategy for gated imports. - if (__EXPERIMENTAL__ || global.__WWW__) { - useFocusWithin = require('react-interactions/events/focus').useFocusWithin; - } -} - -const forcePointerEvents = true; -const table = [[forcePointerEvents], [!forcePointerEvents]]; - -describe.each(table)(`useFocus`, hasPointerEvents => { - let container; - let container2; - let root; - - beforeEach(() => { - initializeModules(hasPointerEvents); - container = document.createElement('div'); - document.body.appendChild(container); - container2 = document.createElement('div'); - document.body.appendChild(container2); - root = ReactDOMClient.createRoot(container); - }); - - afterEach(async () => { - await act(() => { - root.render(null); - }); - - document.body.removeChild(container); - document.body.removeChild(container2); - container = null; - container2 = null; - }); - - describe('disabled', () => { - let onFocusWithinChange, onFocusWithinVisibleChange, ref; - - const componentInit = async () => { - onFocusWithinChange = jest.fn(); - onFocusWithinVisibleChange = jest.fn(); - ref = React.createRef(); - const Component = () => { - const focusWithinRef = useFocusWithin(ref, { - disabled: true, - onFocusWithinChange, - onFocusWithinVisibleChange, - }); - return
; - }; - await act(() => { - root.render(); - }); - }; - - // @gate www - it('prevents custom events being dispatched', async () => { - await componentInit(); - const target = createEventTarget(ref.current); - target.focus(); - target.blur(); - expect(onFocusWithinChange).not.toBeCalled(); - expect(onFocusWithinVisibleChange).not.toBeCalled(); - }); - }); - - describe('onFocusWithinChange', () => { - let onFocusWithinChange, ref, innerRef, innerRef2; - - const Component = ({show}) => { - const focusWithinRef = useFocusWithin(ref, { - onFocusWithinChange, - }); - return ( -
- {show && } -
-
- ); - }; - - const componentInit = async () => { - onFocusWithinChange = jest.fn(); - ref = React.createRef(); - innerRef = React.createRef(); - innerRef2 = React.createRef(); - await act(() => { - root.render(); - }); - }; - - // @gate www - it('is called after "blur" and "focus" events on focus target', async () => { - await componentInit(); - const target = createEventTarget(ref.current); - target.focus(); - expect(onFocusWithinChange).toHaveBeenCalledTimes(1); - expect(onFocusWithinChange).toHaveBeenCalledWith(true); - target.blur({relatedTarget: container}); - expect(onFocusWithinChange).toHaveBeenCalledTimes(2); - expect(onFocusWithinChange).toHaveBeenCalledWith(false); - }); - - // @gate www - it('is called after "blur" and "focus" events on descendants', async () => { - await componentInit(); - const target = createEventTarget(innerRef.current); - target.focus(); - expect(onFocusWithinChange).toHaveBeenCalledTimes(1); - expect(onFocusWithinChange).toHaveBeenCalledWith(true); - target.blur({relatedTarget: container}); - expect(onFocusWithinChange).toHaveBeenCalledTimes(2); - expect(onFocusWithinChange).toHaveBeenCalledWith(false); - }); - - // @gate www - it('is only called once when focus moves within and outside the subtree', async () => { - await componentInit(); - const node = ref.current; - const innerNode1 = innerRef.current; - const innerNode2 = innerRef.current; - const target = createEventTarget(node); - const innerTarget1 = createEventTarget(innerNode1); - const innerTarget2 = createEventTarget(innerNode2); - - // focus shifts into subtree - innerTarget1.focus(); - expect(onFocusWithinChange).toHaveBeenCalledTimes(1); - expect(onFocusWithinChange).toHaveBeenCalledWith(true); - // focus moves around subtree - innerTarget1.blur({relatedTarget: innerNode2}); - innerTarget2.focus(); - innerTarget2.blur({relatedTarget: node}); - target.focus(); - target.blur({relatedTarget: innerNode1}); - expect(onFocusWithinChange).toHaveBeenCalledTimes(1); - // focus shifts outside subtree - innerTarget1.blur({relatedTarget: container}); - expect(onFocusWithinChange).toHaveBeenCalledTimes(2); - expect(onFocusWithinChange).toHaveBeenCalledWith(false); - }); - }); - - describe('onFocusWithinVisibleChange', () => { - let onFocusWithinVisibleChange, ref, innerRef, innerRef2; - - const Component = ({show}) => { - const focusWithinRef = useFocusWithin(ref, { - onFocusWithinVisibleChange, - }); - return ( -
- {show && } -
-
- ); - }; - - const componentInit = async () => { - onFocusWithinVisibleChange = jest.fn(); - ref = React.createRef(); - innerRef = React.createRef(); - innerRef2 = React.createRef(); - await act(() => { - root.render(); - }); - }; - - // @gate www - it('is called after "focus" and "blur" on focus target if keyboard was used', async () => { - await componentInit(); - const target = createEventTarget(ref.current); - const containerTarget = createEventTarget(container); - // use keyboard first - containerTarget.keydown({key: 'Tab'}); - target.focus(); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1); - expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true); - target.blur({relatedTarget: container}); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2); - expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false); - }); - - // @gate www - it('is called after "focus" and "blur" on descendants if keyboard was used', async () => { - await componentInit(); - const innerTarget = createEventTarget(innerRef.current); - const containerTarget = createEventTarget(container); - // use keyboard first - containerTarget.keydown({key: 'Tab'}); - innerTarget.focus(); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1); - expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true); - innerTarget.blur({relatedTarget: container}); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2); - expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false); - }); - - // @gate www - it('is called if non-keyboard event is dispatched on target previously focused with keyboard', async () => { - await componentInit(); - const node = ref.current; - const innerNode1 = innerRef.current; - const innerNode2 = innerRef2.current; - - const target = createEventTarget(node); - const innerTarget1 = createEventTarget(innerNode1); - const innerTarget2 = createEventTarget(innerNode2); - // use keyboard first - target.focus(); - target.keydown({key: 'Tab'}); - target.blur({relatedTarget: innerNode1}); - innerTarget1.focus(); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1); - expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true); - // then use pointer on the next target, focus should no longer be visible - innerTarget2.pointerdown(); - innerTarget1.blur({relatedTarget: innerNode2}); - innerTarget2.focus(); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2); - expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false); - // then use keyboard again - innerTarget2.keydown({key: 'Tab', shiftKey: true}); - innerTarget2.blur({relatedTarget: innerNode1}); - innerTarget1.focus(); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(3); - expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true); - // then use pointer on the target, focus should no longer be visible - innerTarget1.pointerdown(); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(4); - expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false); - // onFocusVisibleChange should not be called again - innerTarget1.blur({relatedTarget: container}); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(4); - }); - - // @gate www - it('is not called after "focus" and "blur" events without keyboard', async () => { - await componentInit(); - const innerTarget = createEventTarget(innerRef.current); - innerTarget.pointerdown(); - innerTarget.pointerup(); - innerTarget.blur({relatedTarget: container}); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(0); - }); - - // @gate www - it('is only called once when focus moves within and outside the subtree', async () => { - await componentInit(); - const node = ref.current; - const innerNode1 = innerRef.current; - const innerNode2 = innerRef2.current; - const target = createEventTarget(node); - const innerTarget1 = createEventTarget(innerNode1); - const innerTarget2 = createEventTarget(innerNode2); - - // focus shifts into subtree - innerTarget1.focus(); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1); - expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(true); - // focus moves around subtree - innerTarget1.blur({relatedTarget: innerNode2}); - innerTarget2.focus(); - innerTarget2.blur({relatedTarget: node}); - target.focus(); - target.blur({relatedTarget: innerNode1}); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(1); - // focus shifts outside subtree - innerTarget1.blur({relatedTarget: container}); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(2); - expect(onFocusWithinVisibleChange).toHaveBeenCalledWith(false); - }); - }); - - // @gate www - it('should correctly handle focus visibility when typing into an input', async () => { - const onFocusWithinVisibleChange = jest.fn(); - const ref = React.createRef(); - const inputRef = React.createRef(); - const Component = () => { - const focusWithinRef = useFocusWithin(ref, { - onFocusWithinVisibleChange, - }); - return ( -
- -
- ); - }; - await act(() => { - root.render(); - }); - - const target = createEventTarget(inputRef.current); - // focus the target - target.pointerdown(); - target.focus(); - target.keydown({key: 'a'}); - expect(onFocusWithinVisibleChange).toHaveBeenCalledTimes(0); - }); - - describe('onBeforeBlurWithin', () => { - let onBeforeBlurWithin, onAfterBlurWithin, ref, innerRef, innerRef2; - - beforeEach(() => { - onBeforeBlurWithin = jest.fn(); - onAfterBlurWithin = jest.fn(e => { - e.persist(); - }); - ref = React.createRef(); - innerRef = React.createRef(); - innerRef2 = React.createRef(); - }); - - // @gate www - it('is called after a focused element is unmounted', async () => { - const Component = ({show}) => { - const focusWithinRef = useFocusWithin(ref, { - onBeforeBlurWithin, - onAfterBlurWithin, - }); - return ( -
- {show && } -
-
- ); - }; - - await act(() => { - root.render(); - }); - - const inner = innerRef.current; - const target = createEventTarget(inner); - target.keydown({key: 'Tab'}); - target.focus(); - expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0); - expect(onAfterBlurWithin).toHaveBeenCalledTimes(0); - await act(() => { - root.render(); - }); - - expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1); - expect(onAfterBlurWithin).toHaveBeenCalledTimes(1); - expect(onAfterBlurWithin).toHaveBeenCalledWith( - expect.objectContaining({relatedTarget: inner}), - ); - }); - - // @gate www - it('is called after a nested focused element is unmounted', async () => { - const Component = ({show}) => { - const focusWithinRef = useFocusWithin(ref, { - onBeforeBlurWithin, - onAfterBlurWithin, - }); - return ( -
- {show && ( -
- -
- )} -
-
- ); - }; - - await act(() => { - root.render(); - }); - - const inner = innerRef.current; - const target = createEventTarget(inner); - target.keydown({key: 'Tab'}); - target.focus(); - expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0); - expect(onAfterBlurWithin).toHaveBeenCalledTimes(0); - - await act(() => { - root.render(); - }); - - expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1); - expect(onAfterBlurWithin).toHaveBeenCalledTimes(1); - expect(onAfterBlurWithin).toHaveBeenCalledWith( - expect.objectContaining({relatedTarget: inner}), - ); - }); - - // @gate www - it('is called after many elements are unmounted', async () => { - const buttonRef = React.createRef(); - const inputRef = React.createRef(); - - const Component = ({show}) => { - const focusWithinRef = useFocusWithin(ref, { - onBeforeBlurWithin, - onAfterBlurWithin, - }); - return ( -
- {show && } - {show && } - {show && } - {show && } - {!show && } - {show && } - - -
- ); - }; - - await act(() => { - root.render(); - }); - - inputRef.current.focus(); - expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0); - expect(onAfterBlurWithin).toHaveBeenCalledTimes(0); - await act(() => { - root.render(); - }); - - expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1); - expect(onAfterBlurWithin).toHaveBeenCalledTimes(1); - }); - - // @gate www - it('is called after a nested focused element is unmounted (with scope query)', async () => { - const TestScope = React.unstable_Scope; - const testScopeQuery = (type, props) => true; - let targetNodes; - let targetNode; - - const Component = ({show}) => { - const scopeRef = React.useRef(null); - const focusWithinRef = useFocusWithin(scopeRef, { - onBeforeBlurWithin(event) { - const scope = scopeRef.current; - targetNode = innerRef.current; - targetNodes = scope.DO_NOT_USE_queryAllNodes(testScopeQuery); - }, - }); - - return ( - - {show && } - - ); - }; - - await act(() => { - root.render(); - }); - - const inner = innerRef.current; - const target = createEventTarget(inner); - target.keydown({key: 'Tab'}); - target.focus(); - await act(() => { - root.render(); - }); - expect(targetNodes).toEqual([targetNode]); - }); - - // @gate www - it('is called after a focused suspended element is hidden', async () => { - const Suspense = React.Suspense; - let suspend = false; - let resolve; - const promise = new Promise(resolvePromise => (resolve = resolvePromise)); - - function Child() { - if (suspend) { - throw promise; - } else { - return ; - } - } - - const Component = ({show}) => { - const focusWithinRef = useFocusWithin(ref, { - onBeforeBlurWithin, - onAfterBlurWithin, - }); - - return ( -
- - - -
- ); - }; - - const root2 = ReactDOMClient.createRoot(container2); - - await act(() => { - root2.render(); - }); - expect(container2.innerHTML).toBe('
'); - - const inner = innerRef.current; - const target = createEventTarget(inner); - target.keydown({key: 'Tab'}); - target.focus(); - expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0); - expect(onAfterBlurWithin).toHaveBeenCalledTimes(0); - - suspend = true; - await act(() => { - root2.render(); - }); - expect(container2.innerHTML).toBe( - '
Loading...
', - ); - expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1); - expect(onAfterBlurWithin).toHaveBeenCalledTimes(1); - await act(() => { - suspend = false; - resolve(); - }); - expect(container2.innerHTML).toBe('
'); - }); - - // @gate www - it('is called after a focused suspended element is hidden then shown', async () => { - const Suspense = React.Suspense; - let suspend = false; - let resolve; - const promise = new Promise(resolvePromise => (resolve = resolvePromise)); - const buttonRef = React.createRef(); - - function Child() { - if (suspend) { - throw promise; - } else { - return ; - } - } - - const Component = ({show}) => { - const focusWithinRef = useFocusWithin(ref, { - onBeforeBlurWithin, - onAfterBlurWithin, - }); - - return ( -
- Loading...}> - - -
- ); - }; - - const root2 = ReactDOMClient.createRoot(container2); - - await act(() => { - root2.render(); - }); - - expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0); - expect(onAfterBlurWithin).toHaveBeenCalledTimes(0); - - suspend = true; - await act(() => { - root2.render(); - }); - expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0); - expect(onAfterBlurWithin).toHaveBeenCalledTimes(0); - - await act(() => { - root2.render(); - }); - expect(onBeforeBlurWithin).toHaveBeenCalledTimes(0); - expect(onAfterBlurWithin).toHaveBeenCalledTimes(0); - - buttonRef.current.focus(); - suspend = false; - await act(() => { - root2.render(); - }); - expect(onBeforeBlurWithin).toHaveBeenCalledTimes(1); - expect(onAfterBlurWithin).toHaveBeenCalledTimes(1); - - await act(() => { - suspend = false; - resolve(); - }); - expect(container2.innerHTML).toBe('
'); - }); - }); -}); diff --git a/packages/react-interactions/events/src/dom/create-event-handle/useEvent.js b/packages/react-interactions/events/src/dom/create-event-handle/useEvent.js deleted file mode 100644 index 6092e858ff7ad..0000000000000 --- a/packages/react-interactions/events/src/dom/create-event-handle/useEvent.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - * @flow - */ - -import * as React from 'react'; -import * as ReactDOM from 'react-dom'; - -const {useLayoutEffect, useRef} = React; -const {unstable_createEventHandle} = ReactDOM; - -type UseEventHandle = { - setListener: ( - target: EventTarget, - null | ((SyntheticEvent) => void), - ) => void, - clear: () => void, -}; - -export default function useEvent( - event: string, - options?: { - capture?: boolean, - }, -): UseEventHandle { - const handleRef = useRef(null); - let useEventHandle = handleRef.current; - - if (useEventHandle === null) { - const setEventHandle = unstable_createEventHandle(event, options); - const clears = new Map void>(); - useEventHandle = { - setListener( - target: EventTarget, - callback: null | ((SyntheticEvent) => void), - ): void { - let clear = clears.get(target); - if (clear !== undefined) { - clear(); - } - if (callback === null) { - clears.delete(target); - return; - } - clear = setEventHandle(target, callback); - clears.set(target, clear); - }, - clear(): void { - clears.forEach(c => { - c(); - }); - clears.clear(); - }, - }; - handleRef.current = useEventHandle; - } - - useLayoutEffect(() => { - return () => { - if (useEventHandle !== null) { - useEventHandle.clear(); - } - handleRef.current = null; - }; - }, [useEventHandle]); - - return useEventHandle; -} diff --git a/packages/react-interactions/npm/drag.js b/packages/react-interactions/npm/drag.js deleted file mode 100644 index fa844823e8ad7..0000000000000 --- a/packages/react-interactions/npm/drag.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -if (process.env.NODE_ENV === 'production') { - module.exports = require('./cjs/react-interactions-events/drag.production.min.js'); -} else { - module.exports = require('./cjs/react-interactions-events/drag.development.js'); -} diff --git a/packages/react-interactions/npm/focus.js b/packages/react-interactions/npm/focus.js deleted file mode 100644 index 9a3a56e9a26d1..0000000000000 --- a/packages/react-interactions/npm/focus.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -if (process.env.NODE_ENV === 'production') { - module.exports = require('./cjs/react-interactions-events/focus.production.min.js'); -} else { - module.exports = require('./cjs/react-interactions-events/focus.development.js'); -} diff --git a/packages/react-interactions/npm/hover.js b/packages/react-interactions/npm/hover.js deleted file mode 100644 index 6ac666f7e0c6e..0000000000000 --- a/packages/react-interactions/npm/hover.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -if (process.env.NODE_ENV === 'production') { - module.exports = require('./cjs/react-interactions-events/hover.production.min.js'); -} else { - module.exports = require('./cjs/react-interactions-events/hover.development.js'); -} diff --git a/packages/react-interactions/npm/input.js b/packages/react-interactions/npm/input.js deleted file mode 100644 index 82c5aadf6e29b..0000000000000 --- a/packages/react-interactions/npm/input.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -if (process.env.NODE_ENV === 'production') { - module.exports = require('./cjs/react-interactions-events/input.production.min.js'); -} else { - module.exports = require('./cjs/react-interactions-events/input.development.js'); -} diff --git a/packages/react-interactions/npm/press-legacy.js b/packages/react-interactions/npm/press-legacy.js deleted file mode 100644 index f98feaa1f1f31..0000000000000 --- a/packages/react-interactions/npm/press-legacy.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -if (process.env.NODE_ENV === 'production') { - module.exports = require('./cjs/react-interactions-events/press-legacy.production.min.js'); -} else { - module.exports = require('./cjs/react-interactions-events/press-legacy.development.js'); -} diff --git a/packages/react-interactions/npm/press.js b/packages/react-interactions/npm/press.js deleted file mode 100644 index 9476dfceac6e9..0000000000000 --- a/packages/react-interactions/npm/press.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -if (process.env.NODE_ENV === 'production') { - module.exports = require('./cjs/react-interactions-events/press.production.min.js'); -} else { - module.exports = require('./cjs/react-interactions-events/press.development.js'); -} diff --git a/packages/react-interactions/npm/scroll.js b/packages/react-interactions/npm/scroll.js deleted file mode 100644 index 0cb8db03587ae..0000000000000 --- a/packages/react-interactions/npm/scroll.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -if (process.env.NODE_ENV === 'production') { - module.exports = require('./cjs/react-interactions-events/scroll.production.min.js'); -} else { - module.exports = require('./cjs/react-interactions-events/scroll.development.js'); -} diff --git a/packages/react-interactions/npm/swipe.js b/packages/react-interactions/npm/swipe.js deleted file mode 100644 index acd514d10c319..0000000000000 --- a/packages/react-interactions/npm/swipe.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -if (process.env.NODE_ENV === 'production') { - module.exports = require('./cjs/react-interactions-events/swipe.production.min.js'); -} else { - module.exports = require('./cjs/react-interactions-events/swipe.development.js'); -} diff --git a/packages/react-interactions/npm/tap.js b/packages/react-interactions/npm/tap.js deleted file mode 100644 index 0a096a2fa6057..0000000000000 --- a/packages/react-interactions/npm/tap.js +++ /dev/null @@ -1,7 +0,0 @@ -'use strict'; - -if (process.env.NODE_ENV === 'production') { - module.exports = require('./cjs/react-interactions-events/tap.production.min.js'); -} else { - module.exports = require('./cjs/react-interactions-events/tap.development.js'); -} diff --git a/packages/react-interactions/package.json b/packages/react-interactions/package.json deleted file mode 100644 index e1c1ac08935cb..0000000000000 --- a/packages/react-interactions/package.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "name": "react-interactions", - "private": true, - "description": "React is a JavaScript library for building user interfaces.", - "keywords": [ - "react" - ], - "version": "0.1.0", - "homepage": "https://reactjs.org/", - "bugs": "https://github.com/facebook/react/issues", - "license": "MIT", - "files": [ - "LICENSE", - "README.md", - "events/README.md", - "events/context-menu.js", - "events/focus.js", - "events/hover.js", - "events/input.js", - "events/keyboard.js", - "events/press.js", - "events/press-legacy.js", - "events/tap.js", - "cjs/", - "umd/" - ], - "main": "index.js", - "repository": { - "type": "git", - "url": "https://github.com/facebook/react.git", - "directory": "packages/react" - }, - "engines": { - "node": ">=0.10.0" - }, - "dependencies": { - "loose-envify": "^1.1.0" - }, - "peerDependencies": { - "react": "^17.0.0" - }, - "browserify": { - "transform": [ - "loose-envify" - ] - } -} diff --git a/packages/react-noop-renderer/src/ReactNoopFlightServer.js b/packages/react-noop-renderer/src/ReactNoopFlightServer.js index 41bdcd3d6b0d4..dc7aa96d50cb1 100644 --- a/packages/react-noop-renderer/src/ReactNoopFlightServer.js +++ b/packages/react-noop-renderer/src/ReactNoopFlightServer.js @@ -64,7 +64,6 @@ const ReactNoopFlightServer = ReactFlightServer({ ) { return saveModule(reference.value); }, - prepareHostDispatcher() {}, }); type Options = { diff --git a/packages/react-noop-renderer/src/ReactNoopServer.js b/packages/react-noop-renderer/src/ReactNoopServer.js index 4252195f81c1e..7d739d3178836 100644 --- a/packages/react-noop-renderer/src/ReactNoopServer.js +++ b/packages/react-noop-renderer/src/ReactNoopServer.js @@ -261,8 +261,6 @@ const ReactNoopServer = ReactFizzServer({ boundary.status = 'client-render'; }, - prepareHostDispatcher() {}, - writePreamble() {}, writeHoistables() {}, writeHoistablesForBoundary() {}, diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index dee3fe5bfcfff..d04ae30e2c318 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -70,7 +70,6 @@ import { writePostamble, hoistHoistables, createHoistableState, - prepareHostDispatcher, supportsRequestStorage, requestStorage, pushFormStateMarkerIsMatching, @@ -372,7 +371,6 @@ export function createRequest( onPostpone: void | ((reason: string, postponeInfo: PostponeInfo) => void), formState: void | null | ReactFormState, ): Request { - prepareHostDispatcher(); const pingedTasks: Array = []; const abortSet: Set = new Set(); const request: Request = { @@ -485,7 +483,6 @@ export function resumeRequest( onFatalError: void | ((error: mixed) => void), onPostpone: void | ((reason: string, postponeInfo: PostponeInfo) => void), ): Request { - prepareHostDispatcher(); const pingedTasks: Array = []; const abortSet: Set = new Set(); const request: Request = { diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index b1b6c73e46780..10c0566aa1a66 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -64,7 +64,6 @@ import { isServerReference, supportsRequestStorage, requestStorage, - prepareHostDispatcher, createHints, initAsyncDebugInfo, } from './ReactFlightServerConfig'; @@ -260,7 +259,6 @@ export function createRequest( 'Currently React only supports one RSC renderer at a time.', ); } - prepareHostDispatcher(); ReactCurrentCache.current = DefaultCacheDispatcher; const abortSet: Set = new Set(); diff --git a/packages/react-server/src/ReactFlightServerConfigBundlerCustom.js b/packages/react-server/src/ReactFlightServerConfigBundlerCustom.js index b1fbc93ee61b1..e11c154d05f32 100644 --- a/packages/react-server/src/ReactFlightServerConfigBundlerCustom.js +++ b/packages/react-server/src/ReactFlightServerConfigBundlerCustom.js @@ -23,4 +23,3 @@ export const resolveClientReferenceMetadata = export const getServerReferenceId = $$$config.getServerReferenceId; export const getServerReferenceBoundArguments = $$$config.getServerReferenceBoundArguments; -export const prepareHostDispatcher = $$$config.prepareHostDispatcher; diff --git a/packages/react-server/src/forks/ReactFizzConfig.custom.js b/packages/react-server/src/forks/ReactFizzConfig.custom.js index eb76985c49218..f1abb1a6f2353 100644 --- a/packages/react-server/src/forks/ReactFizzConfig.custom.js +++ b/packages/react-server/src/forks/ReactFizzConfig.custom.js @@ -78,7 +78,6 @@ export const writeCompletedBoundaryInstruction = $$$config.writeCompletedBoundaryInstruction; export const writeClientRenderBoundaryInstruction = $$$config.writeClientRenderBoundaryInstruction; -export const prepareHostDispatcher = $$$config.prepareHostDispatcher; export const NotPendingTransition = $$$config.NotPendingTransition; // ------------------------- diff --git a/packages/react-server/src/forks/ReactFlightServerConfig.custom.js b/packages/react-server/src/forks/ReactFlightServerConfig.custom.js index 9c00e67bb67b1..9925a0b36964a 100644 --- a/packages/react-server/src/forks/ReactFlightServerConfig.custom.js +++ b/packages/react-server/src/forks/ReactFlightServerConfig.custom.js @@ -20,8 +20,6 @@ export type HintModel = any; export const isPrimaryRenderer = false; -export const prepareHostDispatcher = () => {}; - export const supportsRequestStorage = false; export const requestStorage: AsyncLocalStorage = (null: any); diff --git a/packages/shared/ReactVersion.js b/packages/shared/ReactVersion.js index d0a14cfff7806..9055167e29cc5 100644 --- a/packages/shared/ReactVersion.js +++ b/packages/shared/ReactVersion.js @@ -1,16 +1 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -// TODO: this is special because it gets imported during build. -// -// TODO: 18.0.0 has not been released to NPM; -// It exists as a placeholder so that DevTools can support work tag changes between releases. -// When we next publish a release, update the matching TODO in backend/renderer.js -// TODO: This module is used both by the release scripts and to expose a version -// at runtime. We should instead inject the version number as part of the build -// process, and use the ReactVersions.js module as the single source of truth. -export default '18.2.0'; +export default '18.3.0-PLACEHOLDER'; diff --git a/scripts/rollup/bundles.js b/scripts/rollup/bundles.js index 683208d1fd584..eecc2c412c104 100644 --- a/scripts/rollup/bundles.js +++ b/scripts/rollup/bundles.js @@ -169,17 +169,7 @@ const bundles = [ /******* React DOM *******/ { - bundleTypes: [ - UMD_DEV, - UMD_PROD, - UMD_PROFILING, - NODE_DEV, - NODE_PROD, - NODE_PROFILING, - FB_WWW_DEV, - FB_WWW_PROD, - FB_WWW_PROFILING, - ], + bundleTypes: [NODE_DEV, NODE_PROD], moduleType: RENDERER, entry: 'react-dom', global: 'ReactDOM', @@ -187,12 +177,43 @@ const bundles = [ wrapWithModuleBoundaries: true, externals: ['react'], }, + /******* React DOM Client *******/ + { + bundleTypes: [NODE_DEV, NODE_PROD], + moduleType: RENDERER, + entry: 'react-dom/client', + global: 'ReactDOM', + minifyWithProdErrorCodes: true, + wrapWithModuleBoundaries: true, + externals: ['react', 'react-dom'], + }, + + /******* React DOM Profiling (Client) *******/ + { + bundleTypes: [NODE_DEV, NODE_PROFILING], + moduleType: RENDERER, + entry: 'react-dom/profiling', + global: 'ReactDOM', + minifyWithProdErrorCodes: true, + wrapWithModuleBoundaries: true, + externals: ['react', 'react-dom'], + }, + /******* React DOM FB *******/ + { + bundleTypes: [FB_WWW_DEV, FB_WWW_PROD, FB_WWW_PROFILING], + moduleType: RENDERER, + entry: 'react-dom/src/ReactDOMFB.js', + global: 'ReactDOM', + minifyWithProdErrorCodes: true, + wrapWithModuleBoundaries: true, + externals: ['react'], + }, /******* React DOM React Server *******/ { bundleTypes: [NODE_DEV, NODE_PROD], moduleType: RENDERER, - entry: 'react-dom/src/ReactDOMServer.js', + entry: 'react-dom/src/ReactDOMReactServer.js', name: 'react-dom.react-server', global: 'ReactDOM', minifyWithProdErrorCodes: false, @@ -211,12 +232,10 @@ const bundles = [ externals: ['react', 'react-dom'], }, - /******* React DOM - www - Testing *******/ + /******* React DOM - Testing *******/ { moduleType: RENDERER, - bundleTypes: __EXPERIMENTAL__ - ? [FB_WWW_DEV, FB_WWW_PROD, NODE_DEV, NODE_PROD] - : [FB_WWW_DEV, FB_WWW_PROD], + bundleTypes: __EXPERIMENTAL__ ? [NODE_DEV, NODE_PROD] : [], entry: 'react-dom/unstable_testing', global: 'ReactDOMTesting', minifyWithProdErrorCodes: true, @@ -224,6 +243,17 @@ const bundles = [ externals: ['react'], }, + /******* React DOM - www - Testing *******/ + { + moduleType: RENDERER, + bundleTypes: [FB_WWW_DEV, FB_WWW_PROD], + entry: 'react-dom/src/ReactDOMTestingFB.js', + global: 'ReactDOMTesting', + minifyWithProdErrorCodes: true, + wrapWithModuleBoundaries: false, + externals: ['react'], + }, + /******* React DOM Server *******/ { bundleTypes: [ @@ -323,7 +353,7 @@ const bundles = [ /******* React DOM Fizz Server External Runtime *******/ { - bundleTypes: [BROWSER_SCRIPT], + bundleTypes: __EXPERIMENTAL__ ? [BROWSER_SCRIPT] : [], moduleType: RENDERER, entry: 'react-dom/unstable_server-external-runtime', outputPath: 'unstable_server-external-runtime.js', @@ -333,18 +363,6 @@ const bundles = [ externals: [], }, - /******* React DOM Server Render Stub *******/ - { - bundleTypes: [NODE_DEV, NODE_PROD, UMD_DEV, UMD_PROD], - moduleType: RENDERER, - entry: 'react-dom/server-rendering-stub', - name: 'react-dom-server-rendering-stub', - global: 'ReactDOMServerRenderingStub', - minifyWithProdErrorCodes: true, - wrapWithModuleBoundaries: false, - externals: ['react'], - }, - /******* React Server DOM Webpack Server *******/ { bundleTypes: [NODE_DEV, NODE_PROD, UMD_DEV, UMD_PROD], diff --git a/scripts/rollup/forks.js b/scripts/rollup/forks.js index 173ff58379251..da5211b64088b 100644 --- a/scripts/rollup/forks.js +++ b/scripts/rollup/forks.js @@ -92,7 +92,8 @@ const forks = Object.freeze({ ) => { if ( entry === 'react-dom' || - entry === 'react-dom/server-rendering-stub' || + entry === 'react-dom/src/ReactDOMFB.js' || + entry === 'react-dom/src/ReactDOMTestingFB.js' || entry === 'react-dom/src/ReactDOMServer.js' ) { return './packages/react-dom/src/ReactDOMSharedInternals.js'; diff --git a/scripts/rollup/modules.js b/scripts/rollup/modules.js index e77814a1268fc..30760bdc7b7bd 100644 --- a/scripts/rollup/modules.js +++ b/scripts/rollup/modules.js @@ -30,7 +30,6 @@ const knownGlobals = Object.freeze({ react: 'React', 'react-dom': 'ReactDOM', 'react-dom/server': 'ReactDOMServer', - 'react-interactions/events/tap': 'ReactEventsTap', scheduler: 'Scheduler', 'scheduler/unstable_mock': 'SchedulerMock', ReactNativeInternalFeatureFlags: 'ReactNativeInternalFeatureFlags', diff --git a/scripts/rollup/packaging.js b/scripts/rollup/packaging.js index 64b1cf1735b7d..2022bc2814a1d 100644 --- a/scripts/rollup/packaging.js +++ b/scripts/rollup/packaging.js @@ -191,6 +191,7 @@ for (const bundle of Bundles.bundles) { } function filterOutEntrypoints(name) { + console.log('filterOutEntrypoints', name); // Remove entry point files that are not built in this configuration. let jsonPath = `build/node_modules/${name}/package.json`; let packageJSON = JSON.parse(readFileSync(jsonPath)); @@ -201,6 +202,7 @@ function filterOutEntrypoints(name) { throw new Error('expected all package.json files to contain a files field'); } let changed = false; + console.log('entryPointsToHasBundle', entryPointsToHasBundle); for (let i = 0; i < files.length; i++) { let filename = files[i]; let entry = @@ -213,7 +215,20 @@ function filterOutEntrypoints(name) { hasBundle = entryPointsToHasBundle.get(entry + '.node') || entryPointsToHasBundle.get(entry + '.browser'); + + // The .react-server and .rsc suffixes may not have a bundle representation but + // should infer their bundle status from the non-suffixed entry point. + if (entry.endsWith('.react-server')) { + hasBundle = entryPointsToHasBundle.get( + entry.slice(0, '.react-server'.length * -1) + ); + } else if (entry.endsWith('.rsc')) { + hasBundle = entryPointsToHasBundle.get( + entry.slice(0, '.rsc'.length * -1) + ); + } } + console.log('entry', entry, hasBundle); if (hasBundle === undefined) { // This doesn't exist in the bundles. It's an extra file. } else if (hasBundle === true) { @@ -223,7 +238,14 @@ function filterOutEntrypoints(name) { // Let's remove it. files.splice(i, 1); i--; - unlinkSync(`build/node_modules/${name}/${filename}`); + try { + unlinkSync(`build/node_modules/${name}/${filename}`); + } catch (err) { + // If the file doesn't exist we can just move on. Otherwise throw the halt the build + if (err.code !== 'ENOENT') { + throw err; + } + } changed = true; // Remove it from the exports field too if it exists. if (exportsJSON) { diff --git a/scripts/shared/inlinedHostConfigs.js b/scripts/shared/inlinedHostConfigs.js index fa6916cee621b..8dbfacd738713 100644 --- a/scripts/shared/inlinedHostConfigs.js +++ b/scripts/shared/inlinedHostConfigs.js @@ -11,18 +11,19 @@ module.exports = [ shortName: 'dom-node', entryPoints: [ 'react-dom', - 'react-dom/src/ReactDOMServer.js', + 'react-dom/client', + 'react-dom/profiling', 'react-dom/unstable_testing', + 'react-dom/src/ReactDOMReactServer.js', 'react-dom/src/server/react-dom-server.node.js', 'react-dom/static.node', - 'react-dom/server-rendering-stub', 'react-dom/unstable_server-external-runtime', 'react-server-dom-webpack/server.node.unbundled', 'react-server-dom-webpack/client.node.unbundled', ], paths: [ 'react-dom', - 'react-dom/src/ReactDOMServer.js', + 'react-dom/src/ReactDOMReactServer.js', 'react-dom-bindings', 'react-dom/client', 'react-dom/server', @@ -43,7 +44,6 @@ module.exports = [ 'react-devtools-core', 'react-devtools-shell', 'react-devtools-shared', - 'react-interactions', 'shared/ReactDOMSharedInternals', 'react-server/src/ReactFlightServerConfigDebugNode.js', ], @@ -80,7 +80,6 @@ module.exports = [ 'react-devtools-core', 'react-devtools-shell', 'react-devtools-shared', - 'react-interactions', 'shared/ReactDOMSharedInternals', 'react-server/src/ReactFlightServerConfigDebugNode.js', ], @@ -117,7 +116,6 @@ module.exports = [ 'react-devtools-core', 'react-devtools-shell', 'react-devtools-shared', - 'react-interactions', 'shared/ReactDOMSharedInternals', 'react-server/src/ReactFlightServerConfigDebugNode.js', ], @@ -155,7 +153,6 @@ module.exports = [ 'react-devtools-core', 'react-devtools-shell', 'react-devtools-shared', - 'react-interactions', 'shared/ReactDOMSharedInternals', 'react-server/src/ReactFlightServerConfigDebugNode.js', ], @@ -164,7 +161,13 @@ module.exports = [ }, { shortName: 'dom-bun', - entryPoints: ['react-dom', 'react-dom/src/server/react-dom-server.bun.js'], + entryPoints: [ + 'react-dom', + 'react-dom/client', + 'react-dom/profiling', + 'react-dom/unstable_testing', + 'react-dom/src/server/react-dom-server.bun.js', + ], paths: [ 'react-dom', 'react-dom/server.bun', @@ -180,17 +183,18 @@ module.exports = [ shortName: 'dom-browser', entryPoints: [ 'react-dom', + 'react-dom/client', + 'react-dom/profiling', 'react-dom/unstable_testing', 'react-dom/src/server/react-dom-server.browser.js', 'react-dom/static.browser', - 'react-dom/server-rendering-stub', 'react-dom/unstable_server-external-runtime', 'react-server-dom-webpack/server.browser', 'react-server-dom-webpack/client.browser', ], paths: [ 'react-dom', - 'react-dom/src/ReactDOMServer.js', + 'react-dom/src/ReactDOMReactServer.js', 'react-dom-bindings', 'react-dom/client', 'react-dom/server.browser', @@ -221,7 +225,7 @@ module.exports = [ entryPoints: ['react-server-dom-esm/client.browser'], paths: [ 'react-dom', - 'react-dom/src/ReactDOMServer.js', + 'react-dom/src/ReactDOMReactServer.js', 'react-dom/client', 'react-dom/server', 'react-dom/server.node', @@ -234,7 +238,6 @@ module.exports = [ 'react-devtools-core', 'react-devtools-shell', 'react-devtools-shared', - 'react-interactions', 'shared/ReactDOMSharedInternals', ], isFlowTyped: true, @@ -248,7 +251,6 @@ module.exports = [ ], paths: [ 'react-dom', - 'react-dom/client', 'react-dom/server', 'react-dom/server.node', 'react-dom-bindings', @@ -264,7 +266,6 @@ module.exports = [ 'react-devtools-core', 'react-devtools-shell', 'react-devtools-shared', - 'react-interactions', 'shared/ReactDOMSharedInternals', ], isFlowTyped: true, @@ -280,7 +281,7 @@ module.exports = [ ], paths: [ 'react-dom', - 'react-dom/src/ReactDOMServer.js', + 'react-dom/src/ReactDOMReactServer.js', 'react-dom-bindings', 'react-dom/client', 'react-dom/server.edge', @@ -314,7 +315,7 @@ module.exports = [ ], paths: [ 'react-dom', - 'react-dom/src/ReactDOMServer.js', + 'react-dom/src/ReactDOMReactServer.js', 'react-dom-bindings', 'react-dom/client', 'react-dom/server.edge', @@ -348,7 +349,7 @@ module.exports = [ ], paths: [ 'react-dom', - 'react-dom/src/ReactDOMServer.js', + 'react-dom/src/ReactDOMReactServer.js', 'react-dom-bindings', 'react-dom/client', 'react-dom/server', @@ -368,7 +369,6 @@ module.exports = [ 'react-devtools-core', 'react-devtools-shell', 'react-devtools-shared', - 'react-interactions', 'shared/ReactDOMSharedInternals', 'react-server/src/ReactFlightServerConfigDebugNode.js', ], @@ -383,7 +383,7 @@ module.exports = [ ], paths: [ 'react-dom', - 'react-dom/src/ReactDOMServer.js', + 'react-dom/src/ReactDOMReactServer.js', 'react-dom-bindings', 'react-server-dom-webpack', 'react-dom/src/server/ReactDOMLegacyServerImpl.js', // not an entrypoint, but only usable in *Browser and *Node files @@ -398,10 +398,16 @@ module.exports = [ }, { shortName: 'dom-fb', - entryPoints: ['react-server-dom-fb/src/ReactDOMServerFB.js'], + entryPoints: [ + 'react-dom/src/ReactDOMFB.js', + 'react-dom/src/ReactDOMTestingFB.js', + 'react-server-dom-fb/src/ReactDOMServerFB.js', + ], paths: [ 'react-dom', - 'react-dom/src/ReactDOMServer.js', + 'react-dom/src/ReactDOMFB.js', + 'react-dom/src/ReactDOMTestingFB.js', + 'react-dom/src/ReactDOMReactServer.js', 'react-dom-bindings', 'react-server-dom-fb/src/ReactDOMServerFB.js', 'shared/ReactDOMSharedInternals', diff --git a/scripts/shared/pathsByLanguageVersion.js b/scripts/shared/pathsByLanguageVersion.js index 10c49821af5e9..4a754f3cd491e 100644 --- a/scripts/shared/pathsByLanguageVersion.js +++ b/scripts/shared/pathsByLanguageVersion.js @@ -16,8 +16,6 @@ const esNextPaths = [ // Source files 'packages/*/src/**/*.js', 'packages/dom-event-testing-library/**/*.js', - 'packages/react-interactions/**/*.js', - 'packages/react-interactions/**/*.js', 'packages/shared/**/*.js', // Shims and Flow environment 'scripts/flow/*.js',