From 155bae42b80f9dd04e9e99b7eac2d33d7223ef4b Mon Sep 17 00:00:00 2001 From: Josh Story Date: Mon, 4 Mar 2024 10:01:01 -0800 Subject: [PATCH] chains host dispatchers. The idea here is that host dispatchers are not bound to renders so we need to be able to dispatch to them at any time. This updates the implementation to chain these dispatchers so that each renderer can respond to the dispatch. Semantically we don't always want every renderer to do this for instance if Fizz handles a float method we don't want Fiber to as well so each dispatcher implementation can decide if it makes sense to forward the call or not. For float methods server disaptchers will handle the call if they can resolve a Request otherwise they will forward. For client dispatchers they will handle the call and always forward. The choice needs to be made for each dispatcher method and may have implications on correct renderer import order. For now we just live with the restriction that if you want to use server and client together (such as renderToString in the browser) you need to import the server renderer after the client renderer. --- .../src/client/ReactFiberConfigDOM.js | 47 +++--- .../ReactDOMFlightServerHostDispatcher.js | 42 +++-- .../src/server/ReactFizzConfigDOM.js | 19 ++- .../src/server/ReactFizzConfigDOMLegacy.js | 1 - .../src/server/ReactFlightServerConfigDOM.js | 11 +- .../src/shared/ReactFlightClientConfigDOM.js | 147 +++++++++--------- .../react-dom/src/ReactDOMSharedInternals.js | 20 ++- packages/react-dom/src/client/ReactDOMRoot.js | 11 -- .../react-dom/src/shared/ReactDOMFloat.js | 43 ++--- .../react-dom/src/shared/ReactDOMTypes.js | 2 +- .../src/ReactNoopFlightServer.js | 1 - .../src/ReactNoopServer.js | 2 - .../src/__tests__/ReactFlightDOM-test.js | 8 +- .../__tests__/ReactFlightDOMBrowser-test.js | 22 ++- packages/react-server/src/ReactFizzServer.js | 3 - .../react-server/src/ReactFlightServer.js | 2 - .../ReactFlightServerConfigBundlerCustom.js | 1 - .../src/forks/ReactFizzConfig.custom.js | 1 - .../forks/ReactFlightServerConfig.custom.js | 2 - scripts/rollup/forks.js | 3 +- 20 files changed, 202 insertions(+), 186 deletions(-) diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index ebfe403f2315d..b3293ea83bd8a 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'; @@ -107,6 +106,10 @@ import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem'; import {validateLinkPropsForStyleResource} from '../shared/ReactDOMResourceValidation'; import escapeSelectorAttributeValueInsideDoubleQuotes from './escapeSelectorAttributeValueInsideDoubleQuotes'; +import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals'; +const ReactDOMCurrentDispatcher = + ReactDOMSharedInternals.ReactDOMCurrentDispatcher; + export type Type = string; export type Props = { autoFocus?: boolean, @@ -2108,10 +2111,8 @@ 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 previousDispatcher = ReactDOMCurrentDispatcher.current; +ReactDOMCurrentDispatcher.current = { prefetchDNS, preconnect, preload, @@ -2127,8 +2128,9 @@ export const ReactDOMClientDispatcher: HostDispatcher = { // and so we have to fall back to something universal. Currently we just refer to the global document. // This is notable because nowhere else in ReactDOM do we actually reference the global document or window // because we may be rendering inside an iframe. -function getDocumentForImperativeFloatMethods(): Document { - return document; +const globalDocument = typeof document === 'undefined' ? null : document; +function getGlobalDocument(): ?Document { + return globalDocument; } function preconnectAs( @@ -2136,8 +2138,8 @@ function preconnectAs( href: string, crossOrigin: ?CrossOriginEnum, ) { - const ownerDocument = getDocumentForImperativeFloatMethods(); - if (typeof href === 'string' && href) { + const ownerDocument = getGlobalDocument(); + if (ownerDocument && typeof href === 'string' && href) { const limitedEscapedHref = escapeSelectorAttributeValueInsideDoubleQuotes(href); let key = `link[rel="${rel}"][href="${limitedEscapedHref}"]`; @@ -2162,6 +2164,7 @@ function prefetchDNS(href: string) { if (!enableFloat) { return; } + previousDispatcher.prefetchDNS(href); preconnectAs('dns-prefetch', href, null); } @@ -2169,6 +2172,7 @@ function preconnect(href: string, crossOrigin?: ?CrossOriginEnum) { if (!enableFloat) { return; } + previousDispatcher.preconnect(href, crossOrigin); preconnectAs('preconnect', href, crossOrigin); } @@ -2176,8 +2180,9 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) { if (!enableFloat) { return; } - const ownerDocument = getDocumentForImperativeFloatMethods(); - if (href && as && ownerDocument) { + previousDispatcher.preload(href, as, options); + const ownerDocument = getGlobalDocument(); + if (ownerDocument && href && as) { let preloadSelector = `link[rel="preload"][as="${escapeSelectorAttributeValueInsideDoubleQuotes( as, )}"]`; @@ -2256,8 +2261,9 @@ function preloadModule(href: string, options?: ?PreloadModuleImplOptions) { if (!enableFloat) { return; } - const ownerDocument = getDocumentForImperativeFloatMethods(); - if (href) { + previousDispatcher.preloadModule(href, options); + const ownerDocument = getGlobalDocument(); + if (ownerDocument && href) { const as = options && typeof options.as === 'string' ? options.as : 'script'; const preloadSelector = `link[rel="modulepreload"][as="${escapeSelectorAttributeValueInsideDoubleQuotes( @@ -2319,9 +2325,10 @@ function preinitStyle( if (!enableFloat) { return; } - const ownerDocument = getDocumentForImperativeFloatMethods(); + previousDispatcher.preinitStyle(href, precedence, options); - if (href) { + const ownerDocument = getGlobalDocument(); + if (ownerDocument && href) { const styles = getResourcesFromRoot(ownerDocument).hoistableStyles; const key = getStyleKey(href); @@ -2395,9 +2402,10 @@ function preinitScript(src: string, options?: ?PreinitScriptOptions) { if (!enableFloat) { return; } - const ownerDocument = getDocumentForImperativeFloatMethods(); + previousDispatcher.preinitScript(src, options); - if (src) { + const ownerDocument = getGlobalDocument(); + if (ownerDocument && src) { const scripts = getResourcesFromRoot(ownerDocument).hoistableScripts; const key = getScriptKey(src); @@ -2453,9 +2461,10 @@ function preinitModuleScript( if (!enableFloat) { return; } - const ownerDocument = getDocumentForImperativeFloatMethods(); + previousDispatcher.preinitModuleScript(src, options); - if (src) { + const ownerDocument = getGlobalDocument(); + if (ownerDocument && src) { const scripts = getResourcesFromRoot(ownerDocument).hoistableScripts; const key = getScriptKey(src); diff --git a/packages/react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js b/packages/react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js index 36913899bb030..5cd5dbfb6dbbd 100644 --- a/packages/react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js +++ b/packages/react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js @@ -8,7 +8,6 @@ */ import type { - HostDispatcher, CrossOriginEnum, PreloadImplOptions, PreloadModuleImplOptions, @@ -25,7 +24,12 @@ import { resolveRequest, } from 'react-server/src/ReactFlightServer'; -export const ReactDOMFlightServerDispatcher: HostDispatcher = { +import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals'; +const ReactDOMCurrentDispatcher = + ReactDOMSharedInternals.ReactDOMCurrentDispatcher; + +const previousDispatcher = ReactDOMCurrentDispatcher.current; +ReactDOMCurrentDispatcher.current = { prefetchDNS, preconnect, preload, @@ -48,6 +52,8 @@ function prefetchDNS(href: string) { } hints.add(key); emitHint(request, 'D', href); + } else { + previousDispatcher.prefetchDNS(href); } } } @@ -71,6 +77,8 @@ function preconnect(href: string, crossOrigin?: ?CrossOriginEnum) { } else { emitHint(request, 'C', href); } + } else { + previousDispatcher.preconnect(href, crossOrigin); } } } @@ -104,6 +112,8 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) { } else { emitHint(request, 'L', [href, as]); } + } else { + previousDispatcher.preload(href, as, options); } } } @@ -128,6 +138,8 @@ function preloadModule(href: string, options?: ?PreloadModuleImplOptions) { } else { return emitHint(request, 'm', href); } + } else { + previousDispatcher.preloadModule(href, options); } } } @@ -162,18 +174,20 @@ function preinitStyle( } else { return emitHint(request, 'S', href); } + } else { + previousDispatcher.preinitStyle(href, precedence, options); } } } } -function preinitScript(href: string, options?: ?PreinitScriptOptions) { +function preinitScript(src: string, options?: ?PreinitScriptOptions) { if (enableFloat) { - if (typeof href === 'string') { + if (typeof src === 'string') { const request = resolveRequest(); if (request) { const hints = getHints(request); - const key = 'X|' + href; + const key = 'X|' + src; if (hints.has(key)) { // duplicate hint return; @@ -182,25 +196,27 @@ function preinitScript(href: string, options?: ?PreinitScriptOptions) { const trimmed = trimOptions(options); if (trimmed) { - return emitHint(request, 'X', [href, trimmed]); + return emitHint(request, 'X', [src, trimmed]); } else { - return emitHint(request, 'X', href); + return emitHint(request, 'X', src); } + } else { + previousDispatcher.preinitScript(src, options); } } } } function preinitModuleScript( - href: string, + src: string, options?: ?PreinitModuleScriptOptions, ) { if (enableFloat) { - if (typeof href === 'string') { + if (typeof src === 'string') { const request = resolveRequest(); if (request) { const hints = getHints(request); - const key = 'M|' + href; + const key = 'M|' + src; if (hints.has(key)) { // duplicate hint return; @@ -209,10 +225,12 @@ function preinitModuleScript( const trimmed = trimOptions(options); if (trimmed) { - return emitHint(request, 'M', [href, trimmed]); + return emitHint(request, 'M', [src, trimmed]); } else { - return emitHint(request, 'M', href); + return emitHint(request, 'M', src); } + } else { + previousDispatcher.preinitModuleScript(src, options); } } } diff --git a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js index 0410f9f042211..5902ece4d718e 100644 --- a/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js +++ b/packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js @@ -88,22 +88,20 @@ import {getValueDescriptorExpectingObjectForWarning} from '../shared/ReactDOMRes import {NotPending} from '../shared/ReactDOMFormActions'; import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals'; -const ReactDOMCurrentDispatcher = ReactDOMSharedInternals.Dispatcher; +const ReactDOMCurrentDispatcher = + ReactDOMSharedInternals.ReactDOMCurrentDispatcher; -const ReactDOMServerDispatcher = { +const previousDispatcher = ReactDOMCurrentDispatcher.current; +ReactDOMCurrentDispatcher.current = { prefetchDNS, preconnect, preload, preloadModule, - preinitStyle, preinitScript, + preinitStyle, preinitModuleScript, }; -export function prepareHostDispatcher() { - ReactDOMCurrentDispatcher.current = ReactDOMServerDispatcher; -} - // We make every property of the descriptor optional because it is not a contract that // the headers provided by onHeaders has any particular header types. export type HeadersDescriptor = { @@ -5342,6 +5340,7 @@ function prefetchDNS(href: string) { // the resources for this call in either case we opt to do nothing. We can consider making this a warning // but there may be times where calling a function outside of render is intentional (i.e. to warm up data // fetching) and we don't want to warn in those cases. + previousDispatcher.prefetchDNS(href); return; } const resumableState = getResumableState(request); @@ -5397,6 +5396,7 @@ function preconnect(href: string, crossOrigin: ?CrossOriginEnum) { // the resources for this call in either case we opt to do nothing. We can consider making this a warning // but there may be times where calling a function outside of render is intentional (i.e. to warm up data // fetching) and we don't want to warn in those cases. + previousDispatcher.preconnect(href, crossOrigin); return; } const resumableState = getResumableState(request); @@ -5460,6 +5460,7 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) { // the resources for this call in either case we opt to do nothing. We can consider making this a warning // but there may be times where calling a function outside of render is intentional (i.e. to warm up data // fetching) and we don't want to warn in those cases. + previousDispatcher.preload(href, as, options); return; } const resumableState = getResumableState(request); @@ -5663,6 +5664,7 @@ function preloadModule( // the resources for this call in either case we opt to do nothing. We can consider making this a warning // but there may be times where calling a function outside of render is intentional (i.e. to warm up data // fetching) and we don't want to warn in those cases. + previousDispatcher.preloadModule(href, options); return; } const resumableState = getResumableState(request); @@ -5739,6 +5741,7 @@ function preinitStyle( // the resources for this call in either case we opt to do nothing. We can consider making this a warning // but there may be times where calling a function outside of render is intentional (i.e. to warm up data // fetching) and we don't want to warn in those cases. + previousDispatcher.preinitStyle(href, precedence, options); return; } const resumableState = getResumableState(request); @@ -5826,6 +5829,7 @@ function preinitScript(src: string, options?: ?PreinitScriptOptions): void { // the resources for this call in either case we opt to do nothing. We can consider making this a warning // but there may be times where calling a function outside of render is intentional (i.e. to warm up data // fetching) and we don't want to warn in those cases. + previousDispatcher.preinitScript(src, options); return; } const resumableState = getResumableState(request); @@ -5891,6 +5895,7 @@ function preinitModuleScript( // the resources for this call in either case we opt to do nothing. We can consider making this a warning // but there may be times where calling a function outside of render is intentional (i.e. to warm up data // fetching) and we don't want to warn in those cases. + previousDispatcher.preinitModuleScript(src, options); return; } const resumableState = getResumableState(request); 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..32905aa45b1fb 100644 --- a/packages/react-dom-bindings/src/server/ReactFlightServerConfigDOM.js +++ b/packages/react-dom-bindings/src/server/ReactFlightServerConfigDOM.js @@ -16,14 +16,9 @@ import type { PreinitModuleScriptOptions, } from 'react-dom/src/shared/ReactDOMTypes'; -import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals'; -const ReactDOMCurrentDispatcher = ReactDOMSharedInternals.Dispatcher; - -import {ReactDOMFlightServerDispatcher} from './ReactDOMFlightServerHostDispatcher'; - -export function prepareHostDispatcher(): void { - ReactDOMCurrentDispatcher.current = ReactDOMFlightServerDispatcher; -} +// This module registers the host dispatcher so it needs to be imported +// but it does not have any exports +import './ReactDOMFlightServerHostDispatcher'; // 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-bindings/src/shared/ReactFlightClientConfigDOM.js b/packages/react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js index 18ef25b06842e..61356ce060a53 100644 --- a/packages/react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js +++ b/packages/react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js @@ -13,7 +13,8 @@ import type {HintCode, HintModel} from '../server/ReactFlightServerConfigDOM'; import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals'; -const ReactDOMCurrentDispatcher = ReactDOMSharedInternals.Dispatcher; +const ReactDOMCurrentDispatcher = + ReactDOMSharedInternals.ReactDOMCurrentDispatcher; import {getCrossOriginString} from './crossOriginStrings'; @@ -22,87 +23,85 @@ export function dispatchHint( model: HintModel, ): void { const dispatcher = ReactDOMCurrentDispatcher.current; - if (dispatcher) { - switch (code) { - case 'D': { - const refined = refineModel(code, model); + switch (code) { + case 'D': { + const refined = refineModel(code, model); + const href = refined; + dispatcher.prefetchDNS(href); + return; + } + case 'C': { + const refined = refineModel(code, model); + if (typeof refined === 'string') { const href = refined; - dispatcher.prefetchDNS(href); - return; + dispatcher.preconnect(href); + } else { + const href = refined[0]; + const crossOrigin = refined[1]; + dispatcher.preconnect(href, crossOrigin); } - case 'C': { - const refined = refineModel(code, model); - if (typeof refined === 'string') { - const href = refined; - dispatcher.preconnect(href); - } else { - const href = refined[0]; - const crossOrigin = refined[1]; - dispatcher.preconnect(href, crossOrigin); - } - return; + return; + } + case 'L': { + const refined = refineModel(code, model); + const href = refined[0]; + const as = refined[1]; + if (refined.length === 3) { + const options = refined[2]; + dispatcher.preload(href, as, options); + } else { + dispatcher.preload(href, as); } - case 'L': { - const refined = refineModel(code, model); + return; + } + case 'm': { + const refined = refineModel(code, model); + if (typeof refined === 'string') { + const href = refined; + dispatcher.preloadModule(href); + } else { const href = refined[0]; - const as = refined[1]; - if (refined.length === 3) { - const options = refined[2]; - dispatcher.preload(href, as, options); - } else { - dispatcher.preload(href, as); - } - return; + const options = refined[1]; + dispatcher.preloadModule(href, options); } - case 'm': { - const refined = refineModel(code, model); - if (typeof refined === 'string') { - const href = refined; - dispatcher.preloadModule(href); - } else { - const href = refined[0]; - const options = refined[1]; - dispatcher.preloadModule(href, options); - } - return; - } - case 'S': { - const refined = refineModel(code, model); - if (typeof refined === 'string') { - const href = refined; - dispatcher.preinitStyle(href); - } else { - const href = refined[0]; - const precedence = refined[1] === 0 ? undefined : refined[1]; - const options = refined.length === 3 ? refined[2] : undefined; - dispatcher.preinitStyle(href, precedence, options); - } - return; + return; + } + case 'S': { + const refined = refineModel(code, model); + if (typeof refined === 'string') { + const href = refined; + dispatcher.preinitStyle(href); + } else { + const href = refined[0]; + const precedence = refined[1] === 0 ? undefined : refined[1]; + const options = refined.length === 3 ? refined[2] : undefined; + dispatcher.preinitStyle(href, precedence, options); } - case 'X': { - const refined = refineModel(code, model); - if (typeof refined === 'string') { - const href = refined; - dispatcher.preinitScript(href); - } else { - const href = refined[0]; - const options = refined[1]; - dispatcher.preinitScript(href, options); - } - return; + return; + } + case 'X': { + const refined = refineModel(code, model); + if (typeof refined === 'string') { + const href = refined; + dispatcher.preinitScript(href); + } else { + const href = refined[0]; + const options = refined[1]; + dispatcher.preinitScript(href, options); } - case 'M': { - const refined = refineModel(code, model); - if (typeof refined === 'string') { - const href = refined; - dispatcher.preinitModuleScript(href); - } else { - const href = refined[0]; - const options = refined[1]; - dispatcher.preinitModuleScript(href, options); - } - return; + return; + } + case 'M': { + const refined = refineModel(code, model); + if (typeof refined === 'string') { + const href = refined; + dispatcher.preinitModuleScript(href); + } else { + const href = refined[0]; + const options = refined[1]; + dispatcher.preinitModuleScript(href, options); } + return; } } } diff --git a/packages/react-dom/src/ReactDOMSharedInternals.js b/packages/react-dom/src/ReactDOMSharedInternals.js index b082b4a19cc27..9f721b718b819 100644 --- a/packages/react-dom/src/ReactDOMSharedInternals.js +++ b/packages/react-dom/src/ReactDOMSharedInternals.js @@ -12,16 +12,28 @@ import type {HostDispatcher} from './shared/ReactDOMTypes'; type InternalsType = { usingClientEntryPoint: boolean, Events: [any, any, any, any, any, any], - Dispatcher: { - current: null | HostDispatcher, + ReactDOMCurrentDispatcher: { + current: HostDispatcher, }, }; +function noop() {} + +const DefaultDispatcher: HostDispatcher = { + prefetchDNS: noop, + preconnect: noop, + preload: noop, + preloadModule: noop, + preinitScript: noop, + preinitStyle: noop, + preinitModuleScript: noop, +}; + const Internals: InternalsType = ({ usingClientEntryPoint: false, Events: null, - Dispatcher: { - current: null, + ReactDOMCurrentDispatcher: { + current: DefaultDispatcher, }, }: any); diff --git a/packages/react-dom/src/client/ReactDOMRoot.js b/packages/react-dom/src/client/ReactDOMRoot.js index bbbde79e05fad..563fd6be1fc0c 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 {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, @@ -228,7 +219,6 @@ export function createRoot( transitionCallbacks, ); markContainerAsRoot(root.current, container); - Dispatcher.current = ReactDOMClientDispatcher; const rootContainerElement: Document | Element | DocumentFragment = container.nodeType === COMMENT_NODE @@ -322,7 +312,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); diff --git a/packages/react-dom/src/shared/ReactDOMFloat.js b/packages/react-dom/src/shared/ReactDOMFloat.js index 74701e761179d..2effe174416be 100644 --- a/packages/react-dom/src/shared/ReactDOMFloat.js +++ b/packages/react-dom/src/shared/ReactDOMFloat.js @@ -15,7 +15,8 @@ import type { } from './ReactDOMTypes'; import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals'; -const Dispatcher = ReactDOMSharedInternals.Dispatcher; +const ReactDOMCurrentDispatcher = + ReactDOMSharedInternals.ReactDOMCurrentDispatcher; import { getCrossOriginString, @@ -47,9 +48,8 @@ export function prefetchDNS(href: string) { } } } - const dispatcher = Dispatcher.current; - if (dispatcher && typeof href === 'string') { - dispatcher.prefetchDNS(href); + if (typeof href === 'string') { + ReactDOMCurrentDispatcher.current.prefetchDNS(href); } // We don't error because preconnect needs to be resilient to being called in a variety of scopes // and the runtime may not be capable of responding. The function is optimistic and not critical @@ -75,12 +75,11 @@ export function preconnect(href: string, options?: ?PreconnectOptions) { ); } } - const dispatcher = Dispatcher.current; - if (dispatcher && typeof href === 'string') { + if (typeof href === 'string') { const crossOrigin = options ? getCrossOriginString(options.crossOrigin) : null; - dispatcher.preconnect(href, crossOrigin); + ReactDOMCurrentDispatcher.current.preconnect(href, crossOrigin); } // We don't error because preconnect needs to be resilient to being called in a variety of scopes // and the runtime may not be capable of responding. The function is optimistic and not critical @@ -111,9 +110,7 @@ export function preload(href: string, options: PreloadOptions) { ); } } - const dispatcher = Dispatcher.current; if ( - dispatcher && typeof href === 'string' && // We check existence because we cannot enforce this function is actually called with the stated type typeof options === 'object' && @@ -122,7 +119,7 @@ export function preload(href: string, options: PreloadOptions) { ) { const as = options.as; const crossOrigin = getCrossOriginStringAs(as, options.crossOrigin); - dispatcher.preload(href, as, { + ReactDOMCurrentDispatcher.current.preload(href, as, { crossOrigin, integrity: typeof options.integrity === 'string' ? options.integrity : undefined, @@ -173,14 +170,13 @@ export function preloadModule(href: string, options?: ?PreloadModuleOptions) { ); } } - const dispatcher = Dispatcher.current; - if (dispatcher && typeof href === 'string') { + if (typeof href === 'string') { if (options) { const crossOrigin = getCrossOriginStringAs( options.as, options.crossOrigin, ); - dispatcher.preloadModule(href, { + ReactDOMCurrentDispatcher.current.preloadModule(href, { as: typeof options.as === 'string' && options.as !== 'script' ? options.as @@ -190,7 +186,7 @@ export function preloadModule(href: string, options?: ?PreloadModuleOptions) { typeof options.integrity === 'string' ? options.integrity : undefined, }); } else { - dispatcher.preloadModule(href); + ReactDOMCurrentDispatcher.current.preloadModule(href); } } // We don't error because preload needs to be resilient to being called in a variety of scopes @@ -217,13 +213,7 @@ export function preinit(href: string, options: PreinitOptions) { ); } } - const dispatcher = Dispatcher.current; - if ( - dispatcher && - typeof href === 'string' && - options && - typeof options.as === 'string' - ) { + if (typeof href === 'string' && options && typeof options.as === 'string') { const as = options.as; const crossOrigin = getCrossOriginStringAs(as, options.crossOrigin); const integrity = @@ -233,7 +223,7 @@ export function preinit(href: string, options: PreinitOptions) { ? options.fetchPriority : undefined; if (as === 'style') { - dispatcher.preinitStyle( + ReactDOMCurrentDispatcher.current.preinitStyle( href, typeof options.precedence === 'string' ? options.precedence : undefined, { @@ -243,7 +233,7 @@ export function preinit(href: string, options: PreinitOptions) { }, ); } else if (as === 'script') { - dispatcher.preinitScript(href, { + ReactDOMCurrentDispatcher.current.preinitScript(href, { crossOrigin, integrity, fetchPriority, @@ -301,15 +291,14 @@ export function preinitModule(href: string, options?: ?PreinitModuleOptions) { } } } - const dispatcher = Dispatcher.current; - if (dispatcher && typeof href === 'string') { + if (typeof href === 'string') { if (typeof options === 'object' && options !== null) { if (options.as == null || options.as === 'script') { const crossOrigin = getCrossOriginStringAs( options.as, options.crossOrigin, ); - dispatcher.preinitModuleScript(href, { + ReactDOMCurrentDispatcher.current.preinitModuleScript(href, { crossOrigin, integrity: typeof options.integrity === 'string' @@ -319,7 +308,7 @@ export function preinitModule(href: string, options?: ?PreinitModuleOptions) { }); } } else if (options == null) { - dispatcher.preinitModuleScript(href); + ReactDOMCurrentDispatcher.current.preinitModuleScript(href); } } // We don't error because preinit needs to be resilient to being called in a variety of scopes diff --git a/packages/react-dom/src/shared/ReactDOMTypes.js b/packages/react-dom/src/shared/ReactDOMTypes.js index 942515be5c81a..7578e182ae7c8 100644 --- a/packages/react-dom/src/shared/ReactDOMTypes.js +++ b/packages/react-dom/src/shared/ReactDOMTypes.js @@ -90,7 +90,7 @@ 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, 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-dom-webpack/src/__tests__/ReactFlightDOM-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js index 99a8c79a62a85..9ff119ac1419d 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js @@ -47,19 +47,19 @@ describe('ReactFlightDOM', () => { JSDOM = require('jsdom').JSDOM; // Simulate the condition resolution + jest.mock('react', () => require('react/react.react-server')); + FlightReact = require('react'); + FlightReactDOM = require('react-dom'); + jest.mock('react-server-dom-webpack/server', () => require('react-server-dom-webpack/server.node.unbundled'), ); - jest.mock('react', () => require('react/react.react-server')); - const WebpackMock = require('./utils/WebpackMock'); clientExports = WebpackMock.clientExports; clientModuleError = WebpackMock.clientModuleError; webpackMap = WebpackMock.webpackMap; ReactServerDOMServer = require('react-server-dom-webpack/server'); - FlightReact = require('react'); - FlightReactDOM = require('react-dom'); // This reset is to load modules for the SSR/Browser scope. jest.unmock('react-server-dom-webpack/server'); diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js index f5070711534ae..1f5034f4bf985 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js @@ -36,19 +36,19 @@ describe('ReactFlightDOMBrowser', () => { jest.resetModules(); // Simulate the condition resolution + jest.mock('react', () => require('react/react.react-server')); + ReactServer = require('react'); + ReactServerDOM = require('react-dom'); + jest.mock('react-server-dom-webpack/server', () => require('react-server-dom-webpack/server.browser'), ); - const WebpackMock = require('./utils/WebpackMock'); clientExports = WebpackMock.clientExports; serverExports = WebpackMock.serverExports; webpackMap = WebpackMock.webpackMap; webpackServerMap = WebpackMock.webpackServerMap; - - ReactServer = require('react'); - ReactServerDOM = require('react-dom'); ReactServerDOMServer = require('react-server-dom-webpack/server.browser'); __unmockReact(); @@ -1172,7 +1172,19 @@ describe('ReactFlightDOMBrowser', () => { root.render(); }); expect(document.head.innerHTML).toBe( - '', + // Currently the react-dom entrypoint loads the fiber implementation + // even if you never pull in the the client APIs. this causes the fiber + // dispatcher to be present even for Flight ReactDOM calls. This is not what + // you would have in a real application but given we're runnign flight and + // fiber the in the same scope it's unavoidable until we make the entrypoint + // not automatically pull in the fiber implementation. This test currently + // asserts this be demonstrating that the preload call after the await point + // is written to the document before the call before it. We still demonstrate that + // flight handled the sync call because if the fiber implementation did it would appear + // before the after call. In the future we will change this assertion once the fiber + // implementation no long automatically gets pulled in + '', + // '', ); expect(container.innerHTML).toBe('

hello world

'); }); diff --git a/packages/react-server/src/ReactFizzServer.js b/packages/react-server/src/ReactFizzServer.js index e17b58826fb83..7085a54a4c52b 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, @@ -377,7 +376,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 = { @@ -490,7 +488,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 5f18f46d7ffe9..02533c7ca305b 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -69,7 +69,6 @@ import { isServerReference, supportsRequestStorage, requestStorage, - prepareHostDispatcher, createHints, initAsyncDebugInfo, } from './ReactFlightServerConfig'; @@ -345,7 +344,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 27214b525b8cb..cf1e05a54165d 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 953f83bf4a4f1..b78f9e3816f37 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/scripts/rollup/forks.js b/scripts/rollup/forks.js index 173ff58379251..10a6324b23bb1 100644 --- a/scripts/rollup/forks.js +++ b/scripts/rollup/forks.js @@ -93,7 +93,8 @@ const forks = Object.freeze({ if ( entry === 'react-dom' || entry === 'react-dom/server-rendering-stub' || - entry === 'react-dom/src/ReactDOMServer.js' + entry === 'react-dom/src/ReactDOMServer.js' || + entry === 'react-dom/unstable_testing' ) { return './packages/react-dom/src/ReactDOMSharedInternals.js'; }