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..f014cb415df33 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,43 @@ export const ReactDOMClientDispatcher: HostDispatcher = { preinitStyle, preinitScript, preinitModuleScript, + flushSync, + nextDispatcher: null, }; +// We register the HostDispatcher on ReactDOMSharedInternals +// For client builds we always put the dispatcher at the end of the list +// This is because the implementations on this dispatcher will always forward +// all calls to subsequent dispatchers whereas the server dispatcher may not. +// This is a tricky construction but hopefully balances intended semantics for +// resource APIS and flushSync behavior on both server and client runtimes without +// adding overhead. One litmust test is can you preload in renderToString on the client +// without that leaking into the Document. +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 +2173,9 @@ function preconnectAs( } function prefetchDNS(href: string) { + if (ReactDOMClientDispatcher.nextDispatcher) { + ReactDOMClientDispatcher.nextDispatcher.prefetchDNS(href); + } if (!enableFloat) { return; } @@ -2141,6 +2183,9 @@ function prefetchDNS(href: string) { } function preconnect(href: string, crossOrigin?: ?CrossOriginEnum) { + if (ReactDOMClientDispatcher.nextDispatcher) { + ReactDOMClientDispatcher.nextDispatcher.preconnect(href, crossOrigin); + } if (!enableFloat) { return; } @@ -2148,6 +2193,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 +2276,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 +2342,13 @@ function preinitStyle( precedence: ?string, options?: ?PreinitStyleOptions, ) { + if (ReactDOMClientDispatcher.nextDispatcher) { + ReactDOMClientDispatcher.nextDispatcher.preinitStyle( + href, + precedence, + options, + ); + } if (!enableFloat) { return; } @@ -2367,6 +2425,9 @@ function preinitStyle( } function preinitScript(src: string, options?: ?PreinitScriptOptions) { + if (ReactDOMClientDispatcher.nextDispatcher) { + ReactDOMClientDispatcher.nextDispatcher.preinitScript(src, options); + } if (!enableFloat) { return; } @@ -2425,6 +2486,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..1d4e544a0f233 100644 --- a/packages/react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js +++ b/packages/react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js @@ -33,8 +33,18 @@ 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 (enableFloat) { if (typeof href === 'string' && href) { @@ -48,6 +58,8 @@ function prefetchDNS(href: string) { } hints.add(key); emitHint(request, 'D', href); + } else if (ReactDOMFlightServerDispatcher.nextDispatcher) { + ReactDOMFlightServerDispatcher.nextDispatcher.prefetchDNS(href); } } } @@ -71,6 +83,11 @@ function preconnect(href: string, crossOrigin?: ?CrossOriginEnum) { } else { emitHint(request, 'C', href); } + } else if (ReactDOMFlightServerDispatcher.nextDispatcher) { + ReactDOMFlightServerDispatcher.nextDispatcher.preconnect( + href, + crossOrigin, + ); } } } @@ -104,6 +121,12 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) { } else { emitHint(request, 'L', [href, as]); } + } else if (ReactDOMFlightServerDispatcher.nextDispatcher) { + ReactDOMFlightServerDispatcher.nextDispatcher.preload( + href, + as, + options, + ); } } } @@ -128,6 +151,11 @@ function preloadModule(href: string, options?: ?PreloadModuleImplOptions) { } else { return emitHint(request, 'm', href); } + } else if (ReactDOMFlightServerDispatcher.nextDispatcher) { + ReactDOMFlightServerDispatcher.nextDispatcher.preloadModule( + href, + options, + ); } } } @@ -162,6 +190,12 @@ function preinitStyle( } else { return emitHint(request, 'S', href); } + } else if (ReactDOMFlightServerDispatcher.nextDispatcher) { + ReactDOMFlightServerDispatcher.nextDispatcher.preinitStyle( + href, + precedence, + options, + ); } } } @@ -186,6 +220,11 @@ function preinitScript(href: string, options?: ?PreinitScriptOptions) { } else { return emitHint(request, 'X', href); } + } else if (ReactDOMFlightServerDispatcher.nextDispatcher) { + ReactDOMFlightServerDispatcher.nextDispatcher.preinitScript( + href, + options, + ); } } } @@ -213,6 +252,11 @@ function preinitModuleScript( } else { return emitHint(request, 'M', href); } + } else if (ReactDOMFlightServerDispatcher.nextDispatcher) { + ReactDOMFlightServerDispatcher.nextDispatcher.preinitModuleScript( + href, + options, + ); } } } diff --git a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js index b6fe8d9109a6f..9404b2009b2e5 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,10 +98,27 @@ const ReactDOMServerDispatcher = { preinitStyle, preinitScript, preinitModuleScript, + flushSync, + nextDispatcher: null, }; -export function prepareHostDispatcher() { - ReactDOMCurrentDispatcher.current = ReactDOMServerDispatcher; +// We register the HostDispatcher on ReactDOMSharedInternals +// For server builds we always put the dispatcher at the head of the list +// This is because the implementations on this dispatcher may not forward +// call calls to later dispatchers to implement correct semantics. One litmust test +// for this is can you preload inside renderToString on the client and avoid that +// leaking into the Document +if (ReactDOMCurrentDispatcher.current) { + ReactDOMServerDispatcher.nextDispatcher = ReactDOMCurrentDispatcher.current; +} +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 @@ -5323,6 +5341,9 @@ function prefetchDNS(href: string) { } const request = resolveRequest(); if (!request) { + if (ReactDOMServerDispatcher.nextDispatcher) { + ReactDOMServerDispatcher.nextDispatcher.prefetchDNS(href); + } // In async contexts we can sometimes resolve resources from AsyncLocalStorage. If we can't we can also // possibly get them from the stack if we are not in an async context. Since we were not able to resolve // the resources for this call in either case we opt to do nothing. We can consider making this a warning @@ -5378,6 +5399,9 @@ function preconnect(href: string, crossOrigin: ?CrossOriginEnum) { } const request = resolveRequest(); if (!request) { + if (ReactDOMServerDispatcher.nextDispatcher) { + ReactDOMServerDispatcher.nextDispatcher.preconnect(href, crossOrigin); + } // In async contexts we can sometimes resolve resources from AsyncLocalStorage. If we can't we can also // possibly get them from the stack if we are not in an async context. Since we were not able to resolve // the resources for this call in either case we opt to do nothing. We can consider making this a warning @@ -5441,6 +5465,9 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) { } const request = resolveRequest(); if (!request) { + if (ReactDOMServerDispatcher.nextDispatcher) { + ReactDOMServerDispatcher.nextDispatcher.preload(href, as, options); + } // In async contexts we can sometimes resolve resources from AsyncLocalStorage. If we can't we can also // possibly get them from the stack if we are not in an async context. Since we were not able to resolve // the resources for this call in either case we opt to do nothing. We can consider making this a warning @@ -5644,6 +5671,9 @@ function preloadModule( } const request = resolveRequest(); if (!request) { + if (ReactDOMServerDispatcher.nextDispatcher) { + ReactDOMServerDispatcher.nextDispatcher.preloadModule(href, options); + } // In async contexts we can sometimes resolve resources from AsyncLocalStorage. If we can't we can also // possibly get them from the stack if we are not in an async context. Since we were not able to resolve // the resources for this call in either case we opt to do nothing. We can consider making this a warning @@ -5720,6 +5750,13 @@ function preinitStyle( } const request = resolveRequest(); if (!request) { + if (ReactDOMServerDispatcher.nextDispatcher) { + ReactDOMServerDispatcher.nextDispatcher.preinitStyle( + href, + precedence, + options, + ); + } // In async contexts we can sometimes resolve resources from AsyncLocalStorage. If we can't we can also // possibly get them from the stack if we are not in an async context. Since we were not able to resolve // the resources for this call in either case we opt to do nothing. We can consider making this a warning @@ -5807,6 +5844,9 @@ function preinitScript(src: string, options?: ?PreinitScriptOptions): void { } const request = resolveRequest(); if (!request) { + if (ReactDOMServerDispatcher.nextDispatcher) { + ReactDOMServerDispatcher.nextDispatcher.preinitScript(src, options); + } // In async contexts we can sometimes resolve resources from AsyncLocalStorage. If we can't we can also // possibly get them from the stack if we are not in an async context. Since we were not able to resolve // the resources for this call in either case we opt to do nothing. We can consider making this a warning @@ -5872,6 +5912,9 @@ function preinitModuleScript( } const request = resolveRequest(); if (!request) { + if (ReactDOMServerDispatcher.nextDispatcher) { + ReactDOMServerDispatcher.nextDispatcher.preinitModuleScript(src, options); + } // In async contexts we can sometimes resolve resources from AsyncLocalStorage. If we can't we can also // possibly get them from the stack if we are not in an async context. Since we were not able to resolve // the resources for this call in either case we opt to do nothing. We can consider making this a warning 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..ce939facff16d 100644 --- a/packages/react-dom-bindings/src/server/ReactFlightServerConfigDOM.js +++ b/packages/react-dom-bindings/src/server/ReactFlightServerConfigDOM.js @@ -16,14 +16,21 @@ 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 { - ReactDOMCurrentDispatcher.current = ReactDOMFlightServerDispatcher; +// We register the HostDispatcher on ReactDOMSharedInternals +// For server builds we always put the dispatcher at the head of the list +// This is because the implementations on this dispatcher may not forward +// call calls to later dispatchers to implement correct semantics. One litmust test +// for this is can you preload inside renderToString on the client and avoid that +// leaking into the Document +if (ReactDOMCurrentDispatcher.current) { + ReactDOMFlightServerDispatcher.nextDispatcher = + ReactDOMCurrentDispatcher.current; } +ReactDOMCurrentDispatcher.current = ReactDOMFlightServerDispatcher; // Used to distinguish these contexts from ones used in other renderers. // E.g. this can be used to distinguish legacy renderers from this modern one. 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 3d46f7694c798..d8ab5222eb58e 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 a928b2651a934..ef29cd8ca28ee 100644 --- a/packages/react-server/src/ReactFizzServer.js +++ b/packages/react-server/src/ReactFizzServer.js @@ -71,7 +71,6 @@ import { writePostamble, hoistHoistables, createHoistableState, - prepareHostDispatcher, supportsRequestStorage, requestStorage, pushFormStateMarkerIsMatching, @@ -374,7 +373,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 = { @@ -487,7 +485,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 1d1ca9891135d..84c156bdd6465 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -67,7 +67,6 @@ import { isServerReference, supportsRequestStorage, requestStorage, - prepareHostDispatcher, createHints, initAsyncDebugInfo, } from './ReactFlightServerConfig'; @@ -266,7 +265,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',