@samitouri / QOS-React-2 / commits / 113ab9af08

[Flight][Fizz][Fiber] Chain HostDispatcher implementations (#28488)

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.

Josh Story committed Mar 4, 2024 at 12:27 UTC 113ab9af08c46e8a548a397154f5c9dfeb96ab6a
20 files changed +202 -186
packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
+28 -19
@@ -7,7 +7,6 @@
7 * @flow
8 */
9
10 -import type {HostDispatcher} from 'react-dom/src/shared/ReactDOMTypes';
10 import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities';
11 import type {DOMEventName} from '../events/DOMEventNames';
12 import type {Fiber, FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
@@ -107,6 +106,10 @@ import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem';
106 import {validateLinkPropsForStyleResource} from '../shared/ReactDOMResourceValidation';
107 import escapeSelectorAttributeValueInsideDoubleQuotes from './escapeSelectorAttributeValueInsideDoubleQuotes';
108
109 +import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals';
110 +const ReactDOMCurrentDispatcher =
111 + ReactDOMSharedInternals.ReactDOMCurrentDispatcher;
112 +
113 export type Type = string;
114 export type Props = {
115 autoFocus?: boolean,
@@ -2108,10 +2111,8 @@ function getDocumentFromRoot(root: HoistableRoot): Document {
2111 return root.ownerDocument || root;
2112 }
2113
2111 -// We want this to be the default dispatcher on ReactDOMSharedInternals but we don't want to mutate
2112 -// internals in Module scope. Instead we export it and Internals will import it. There is already a cycle
2113 -// from Internals -> ReactDOM -> HostConfig -> Internals so this doesn't introduce a new one.
2114 -export const ReactDOMClientDispatcher: HostDispatcher = {
2114 +const previousDispatcher = ReactDOMCurrentDispatcher.current;
2115 +ReactDOMCurrentDispatcher.current = {
2116 prefetchDNS,
2117 preconnect,
2118 preload,
@@ -2127,8 +2128,9 @@ export const ReactDOMClientDispatcher: HostDispatcher = {
2128 // and so we have to fall back to something universal. Currently we just refer to the global document.
2129 // This is notable because nowhere else in ReactDOM do we actually reference the global document or window
2130 // because we may be rendering inside an iframe.
2130 -function getDocumentForImperativeFloatMethods(): Document {
2131 - return document;
2131 +const globalDocument = typeof document === 'undefined' ? null : document;
2132 +function getGlobalDocument(): ?Document {
2133 + return globalDocument;
2134 }
2135
2136 function preconnectAs(
@@ -2136,8 +2138,8 @@ function preconnectAs(
2138 href: string,
2139 crossOrigin: ?CrossOriginEnum,
2140 ) {
2139 - const ownerDocument = getDocumentForImperativeFloatMethods();
2140 - if (typeof href === 'string' && href) {
2141 + const ownerDocument = getGlobalDocument();
2142 + if (ownerDocument && typeof href === 'string' && href) {
2143 const limitedEscapedHref =
2144 escapeSelectorAttributeValueInsideDoubleQuotes(href);
2145 let key = `link[rel="${rel}"][href="${limitedEscapedHref}"]`;
@@ -2162,6 +2164,7 @@ function prefetchDNS(href: string) {
2164 if (!enableFloat) {
2165 return;
2166 }
2167 + previousDispatcher.prefetchDNS(href);
2168 preconnectAs('dns-prefetch', href, null);
2169 }
2170
@@ -2169,6 +2172,7 @@ function preconnect(href: string, crossOrigin?: ?CrossOriginEnum) {
2172 if (!enableFloat) {
2173 return;
2174 }
2175 + previousDispatcher.preconnect(href, crossOrigin);
2176 preconnectAs('preconnect', href, crossOrigin);
2177 }
2178
@@ -2176,8 +2180,9 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) {
2180 if (!enableFloat) {
2181 return;
2182 }
2179 - const ownerDocument = getDocumentForImperativeFloatMethods();
2180 - if (href && as && ownerDocument) {
2183 + previousDispatcher.preload(href, as, options);
2184 + const ownerDocument = getGlobalDocument();
2185 + if (ownerDocument && href && as) {
2186 let preloadSelector = `link[rel="preload"][as="${escapeSelectorAttributeValueInsideDoubleQuotes(
2187 as,
2188 )}"]`;
@@ -2256,8 +2261,9 @@ function preloadModule(href: string, options?: ?PreloadModuleImplOptions) {
2261 if (!enableFloat) {
2262 return;
2263 }
2259 - const ownerDocument = getDocumentForImperativeFloatMethods();
2260 - if (href) {
2264 + previousDispatcher.preloadModule(href, options);
2265 + const ownerDocument = getGlobalDocument();
2266 + if (ownerDocument && href) {
2267 const as =
2268 options && typeof options.as === 'string' ? options.as : 'script';
2269 const preloadSelector = `link[rel="modulepreload"][as="${escapeSelectorAttributeValueInsideDoubleQuotes(
@@ -2319,9 +2325,10 @@ function preinitStyle(
2325 if (!enableFloat) {
2326 return;
2327 }
2322 - const ownerDocument = getDocumentForImperativeFloatMethods();
2328 + previousDispatcher.preinitStyle(href, precedence, options);
2329
2324 - if (href) {
2330 + const ownerDocument = getGlobalDocument();
2331 + if (ownerDocument && href) {
2332 const styles = getResourcesFromRoot(ownerDocument).hoistableStyles;
2333
2334 const key = getStyleKey(href);
@@ -2395,9 +2402,10 @@ function preinitScript(src: string, options?: ?PreinitScriptOptions) {
2402 if (!enableFloat) {
2403 return;
2404 }
2398 - const ownerDocument = getDocumentForImperativeFloatMethods();
2405 + previousDispatcher.preinitScript(src, options);
2406
2400 - if (src) {
2407 + const ownerDocument = getGlobalDocument();
2408 + if (ownerDocument && src) {
2409 const scripts = getResourcesFromRoot(ownerDocument).hoistableScripts;
2410
2411 const key = getScriptKey(src);
@@ -2453,9 +2461,10 @@ function preinitModuleScript(
2461 if (!enableFloat) {
2462 return;
2463 }
2456 - const ownerDocument = getDocumentForImperativeFloatMethods();
2464 + previousDispatcher.preinitModuleScript(src, options);
2465
2458 - if (src) {
2466 + const ownerDocument = getGlobalDocument();
2467 + if (ownerDocument && src) {
2468 const scripts = getResourcesFromRoot(ownerDocument).hoistableScripts;
2469
2470 const key = getScriptKey(src);
packages/react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js
+30 -12
@@ -8,7 +8,6 @@
8 */
9
10 import type {
11 - HostDispatcher,
11 CrossOriginEnum,
12 PreloadImplOptions,
13 PreloadModuleImplOptions,
@@ -25,7 +24,12 @@ import {
24 resolveRequest,
25 } from 'react-server/src/ReactFlightServer';
26
28 -export const ReactDOMFlightServerDispatcher: HostDispatcher = {
27 +import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals';
28 +const ReactDOMCurrentDispatcher =
29 + ReactDOMSharedInternals.ReactDOMCurrentDispatcher;
30 +
31 +const previousDispatcher = ReactDOMCurrentDispatcher.current;
32 +ReactDOMCurrentDispatcher.current = {
33 prefetchDNS,
34 preconnect,
35 preload,
@@ -48,6 +52,8 @@ function prefetchDNS(href: string) {
52 }
53 hints.add(key);
54 emitHint(request, 'D', href);
55 + } else {
56 + previousDispatcher.prefetchDNS(href);
57 }
58 }
59 }
@@ -71,6 +77,8 @@ function preconnect(href: string, crossOrigin?: ?CrossOriginEnum) {
77 } else {
78 emitHint(request, 'C', href);
79 }
80 + } else {
81 + previousDispatcher.preconnect(href, crossOrigin);
82 }
83 }
84 }
@@ -104,6 +112,8 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) {
112 } else {
113 emitHint(request, 'L', [href, as]);
114 }
115 + } else {
116 + previousDispatcher.preload(href, as, options);
117 }
118 }
119 }
@@ -128,6 +138,8 @@ function preloadModule(href: string, options?: ?PreloadModuleImplOptions) {
138 } else {
139 return emitHint(request, 'm', href);
140 }
141 + } else {
142 + previousDispatcher.preloadModule(href, options);
143 }
144 }
145 }
@@ -162,18 +174,20 @@ function preinitStyle(
174 } else {
175 return emitHint(request, 'S', href);
176 }
177 + } else {
178 + previousDispatcher.preinitStyle(href, precedence, options);
179 }
180 }
181 }
182 }
183
170 -function preinitScript(href: string, options?: ?PreinitScriptOptions) {
184 +function preinitScript(src: string, options?: ?PreinitScriptOptions) {
185 if (enableFloat) {
172 - if (typeof href === 'string') {
186 + if (typeof src === 'string') {
187 const request = resolveRequest();
188 if (request) {
189 const hints = getHints(request);
176 - const key = 'X|' + href;
190 + const key = 'X|' + src;
191 if (hints.has(key)) {
192 // duplicate hint
193 return;
@@ -182,25 +196,27 @@ function preinitScript(href: string, options?: ?PreinitScriptOptions) {
196
197 const trimmed = trimOptions(options);
198 if (trimmed) {
185 - return emitHint(request, 'X', [href, trimmed]);
199 + return emitHint(request, 'X', [src, trimmed]);
200 } else {
187 - return emitHint(request, 'X', href);
201 + return emitHint(request, 'X', src);
202 }
203 + } else {
204 + previousDispatcher.preinitScript(src, options);
205 }
206 }
207 }
208 }
209
210 function preinitModuleScript(
195 - href: string,
211 + src: string,
212 options?: ?PreinitModuleScriptOptions,
213 ) {
214 if (enableFloat) {
199 - if (typeof href === 'string') {
215 + if (typeof src === 'string') {
216 const request = resolveRequest();
217 if (request) {
218 const hints = getHints(request);
203 - const key = 'M|' + href;
219 + const key = 'M|' + src;
220 if (hints.has(key)) {
221 // duplicate hint
222 return;
@@ -209,10 +225,12 @@ function preinitModuleScript(
225
226 const trimmed = trimOptions(options);
227 if (trimmed) {
212 - return emitHint(request, 'M', [href, trimmed]);
228 + return emitHint(request, 'M', [src, trimmed]);
229 } else {
214 - return emitHint(request, 'M', href);
230 + return emitHint(request, 'M', src);
231 }
232 + } else {
233 + previousDispatcher.preinitModuleScript(src, options);
234 }
235 }
236 }
packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
+12 -7
@@ -88,22 +88,20 @@ import {getValueDescriptorExpectingObjectForWarning} from '../shared/ReactDOMRes
88 import {NotPending} from '../shared/ReactDOMFormActions';
89
90 import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals';
91 -const ReactDOMCurrentDispatcher = ReactDOMSharedInternals.Dispatcher;
91 +const ReactDOMCurrentDispatcher =
92 + ReactDOMSharedInternals.ReactDOMCurrentDispatcher;
93
93 -const ReactDOMServerDispatcher = {
94 +const previousDispatcher = ReactDOMCurrentDispatcher.current;
95 +ReactDOMCurrentDispatcher.current = {
96 prefetchDNS,
97 preconnect,
98 preload,
99 preloadModule,
98 - preinitStyle,
100 preinitScript,
101 + preinitStyle,
102 preinitModuleScript,
103 };
104
103 -export function prepareHostDispatcher() {
104 - ReactDOMCurrentDispatcher.current = ReactDOMServerDispatcher;
105 -}
106 -
105 // We make every property of the descriptor optional because it is not a contract that
106 // the headers provided by onHeaders has any particular header types.
107 export type HeadersDescriptor = {
@@ -5342,6 +5340,7 @@ function prefetchDNS(href: string) {
5340 // the resources for this call in either case we opt to do nothing. We can consider making this a warning
5341 // but there may be times where calling a function outside of render is intentional (i.e. to warm up data
5342 // fetching) and we don't want to warn in those cases.
5343 + previousDispatcher.prefetchDNS(href);
5344 return;
5345 }
5346 const resumableState = getResumableState(request);
@@ -5397,6 +5396,7 @@ function preconnect(href: string, crossOrigin: ?CrossOriginEnum) {
5396 // the resources for this call in either case we opt to do nothing. We can consider making this a warning
5397 // but there may be times where calling a function outside of render is intentional (i.e. to warm up data
5398 // fetching) and we don't want to warn in those cases.
5399 + previousDispatcher.preconnect(href, crossOrigin);
5400 return;
5401 }
5402 const resumableState = getResumableState(request);
@@ -5460,6 +5460,7 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) {
5460 // the resources for this call in either case we opt to do nothing. We can consider making this a warning
5461 // but there may be times where calling a function outside of render is intentional (i.e. to warm up data
5462 // fetching) and we don't want to warn in those cases.
5463 + previousDispatcher.preload(href, as, options);
5464 return;
5465 }
5466 const resumableState = getResumableState(request);
@@ -5663,6 +5664,7 @@ function preloadModule(
5664 // the resources for this call in either case we opt to do nothing. We can consider making this a warning
5665 // but there may be times where calling a function outside of render is intentional (i.e. to warm up data
5666 // fetching) and we don't want to warn in those cases.
5667 + previousDispatcher.preloadModule(href, options);
5668 return;
5669 }
5670 const resumableState = getResumableState(request);
@@ -5739,6 +5741,7 @@ function preinitStyle(
5741 // the resources for this call in either case we opt to do nothing. We can consider making this a warning
5742 // but there may be times where calling a function outside of render is intentional (i.e. to warm up data
5743 // fetching) and we don't want to warn in those cases.
5744 + previousDispatcher.preinitStyle(href, precedence, options);
5745 return;
5746 }
5747 const resumableState = getResumableState(request);
@@ -5826,6 +5829,7 @@ function preinitScript(src: string, options?: ?PreinitScriptOptions): void {
5829 // the resources for this call in either case we opt to do nothing. We can consider making this a warning
5830 // but there may be times where calling a function outside of render is intentional (i.e. to warm up data
5831 // fetching) and we don't want to warn in those cases.
5832 + previousDispatcher.preinitScript(src, options);
5833 return;
5834 }
5835 const resumableState = getResumableState(request);
@@ -5891,6 +5895,7 @@ function preinitModuleScript(
5895 // the resources for this call in either case we opt to do nothing. We can consider making this a warning
5896 // but there may be times where calling a function outside of render is intentional (i.e. to warm up data
5897 // fetching) and we don't want to warn in those cases.
5898 + previousDispatcher.preinitModuleScript(src, options);
5899 return;
5900 }
5901 const resumableState = getResumableState(request);
packages/react-dom-bindings/src/server/ReactFizzConfigDOMLegacy.js
-1
@@ -163,7 +163,6 @@ export {
163 writeHoistables,
164 writePostamble,
165 hoistHoistables,
166 - prepareHostDispatcher,
166 resetResumableState,
167 completeResumableState,
168 emitEarlyPreloads,
packages/react-dom-bindings/src/server/ReactFlightServerConfigDOM.js
+3 -8
@@ -16,14 +16,9 @@ import type {
16 PreinitModuleScriptOptions,
17 } from 'react-dom/src/shared/ReactDOMTypes';
18
19 -import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals';
20 -const ReactDOMCurrentDispatcher = ReactDOMSharedInternals.Dispatcher;
21 -
22 -import {ReactDOMFlightServerDispatcher} from './ReactDOMFlightServerHostDispatcher';
23 -
24 -export function prepareHostDispatcher(): void {
25 - ReactDOMCurrentDispatcher.current = ReactDOMFlightServerDispatcher;
26 -}
19 +// This module registers the host dispatcher so it needs to be imported
20 +// but it does not have any exports
21 +import './ReactDOMFlightServerHostDispatcher';
22
23 // Used to distinguish these contexts from ones used in other renderers.
24 // E.g. this can be used to distinguish legacy renderers from this modern one.
packages/react-dom-bindings/src/shared/ReactFlightClientConfigDOM.js
+73 -74
@@ -13,7 +13,8 @@
13 import type {HintCode, HintModel} from '../server/ReactFlightServerConfigDOM';
14
15 import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals';
16 -const ReactDOMCurrentDispatcher = ReactDOMSharedInternals.Dispatcher;
16 +const ReactDOMCurrentDispatcher =
17 + ReactDOMSharedInternals.ReactDOMCurrentDispatcher;
18
19 import {getCrossOriginString} from './crossOriginStrings';
20
@@ -22,87 +23,85 @@ export function dispatchHint<Code: HintCode>(
23 model: HintModel<Code>,
24 ): void {
25 const dispatcher = ReactDOMCurrentDispatcher.current;
25 - if (dispatcher) {
26 - switch (code) {
27 - case 'D': {
28 - const refined = refineModel(code, model);
26 + switch (code) {
27 + case 'D': {
28 + const refined = refineModel(code, model);
29 + const href = refined;
30 + dispatcher.prefetchDNS(href);
31 + return;
32 + }
33 + case 'C': {
34 + const refined = refineModel(code, model);
35 + if (typeof refined === 'string') {
36 const href = refined;
30 - dispatcher.prefetchDNS(href);
31 - return;
37 + dispatcher.preconnect(href);
38 + } else {
39 + const href = refined[0];
40 + const crossOrigin = refined[1];
41 + dispatcher.preconnect(href, crossOrigin);
42 }
33 - case 'C': {
34 - const refined = refineModel(code, model);
35 - if (typeof refined === 'string') {
36 - const href = refined;
37 - dispatcher.preconnect(href);
38 - } else {
39 - const href = refined[0];
40 - const crossOrigin = refined[1];
41 - dispatcher.preconnect(href, crossOrigin);
42 - }
43 - return;
43 + return;
44 + }
45 + case 'L': {
46 + const refined = refineModel(code, model);
47 + const href = refined[0];
48 + const as = refined[1];
49 + if (refined.length === 3) {
50 + const options = refined[2];
51 + dispatcher.preload(href, as, options);
52 + } else {
53 + dispatcher.preload(href, as);
54 }
45 - case 'L': {
46 - const refined = refineModel(code, model);
55 + return;
56 + }
57 + case 'm': {
58 + const refined = refineModel(code, model);
59 + if (typeof refined === 'string') {
60 + const href = refined;
61 + dispatcher.preloadModule(href);
62 + } else {
63 const href = refined[0];
48 - const as = refined[1];
49 - if (refined.length === 3) {
50 - const options = refined[2];
51 - dispatcher.preload(href, as, options);
52 - } else {
53 - dispatcher.preload(href, as);
54 - }
55 - return;
64 + const options = refined[1];
65 + dispatcher.preloadModule(href, options);
66 }
57 - case 'm': {
58 - const refined = refineModel(code, model);
59 - if (typeof refined === 'string') {
60 - const href = refined;
61 - dispatcher.preloadModule(href);
62 - } else {
63 - const href = refined[0];
64 - const options = refined[1];
65 - dispatcher.preloadModule(href, options);
66 - }
67 - return;
68 - }
69 - case 'S': {
70 - const refined = refineModel(code, model);
71 - if (typeof refined === 'string') {
72 - const href = refined;
73 - dispatcher.preinitStyle(href);
74 - } else {
75 - const href = refined[0];
76 - const precedence = refined[1] === 0 ? undefined : refined[1];
77 - const options = refined.length === 3 ? refined[2] : undefined;
78 - dispatcher.preinitStyle(href, precedence, options);
79 - }
80 - return;
67 + return;
68 + }
69 + case 'S': {
70 + const refined = refineModel(code, model);
71 + if (typeof refined === 'string') {
72 + const href = refined;
73 + dispatcher.preinitStyle(href);
74 + } else {
75 + const href = refined[0];
76 + const precedence = refined[1] === 0 ? undefined : refined[1];
77 + const options = refined.length === 3 ? refined[2] : undefined;
78 + dispatcher.preinitStyle(href, precedence, options);
79 }
82 - case 'X': {
83 - const refined = refineModel(code, model);
84 - if (typeof refined === 'string') {
85 - const href = refined;
86 - dispatcher.preinitScript(href);
87 - } else {
88 - const href = refined[0];
89 - const options = refined[1];
90 - dispatcher.preinitScript(href, options);
91 - }
92 - return;
80 + return;
81 + }
82 + case 'X': {
83 + const refined = refineModel(code, model);
84 + if (typeof refined === 'string') {
85 + const href = refined;
86 + dispatcher.preinitScript(href);
87 + } else {
88 + const href = refined[0];
89 + const options = refined[1];
90 + dispatcher.preinitScript(href, options);
91 }
94 - case 'M': {
95 - const refined = refineModel(code, model);
96 - if (typeof refined === 'string') {
97 - const href = refined;
98 - dispatcher.preinitModuleScript(href);
99 - } else {
100 - const href = refined[0];
101 - const options = refined[1];
102 - dispatcher.preinitModuleScript(href, options);
103 - }
104 - return;
92 + return;
93 + }
94 + case 'M': {
95 + const refined = refineModel(code, model);
96 + if (typeof refined === 'string') {
97 + const href = refined;
98 + dispatcher.preinitModuleScript(href);
99 + } else {
100 + const href = refined[0];
101 + const options = refined[1];
102 + dispatcher.preinitModuleScript(href, options);
103 }
104 + return;
105 }
106 }
107 }
packages/react-dom/src/ReactDOMSharedInternals.js
+16 -4
@@ -12,16 +12,28 @@ import type {HostDispatcher} from './shared/ReactDOMTypes';
12 type InternalsType = {
13 usingClientEntryPoint: boolean,
14 Events: [any, any, any, any, any, any],
15 - Dispatcher: {
16 - current: null | HostDispatcher,
15 + ReactDOMCurrentDispatcher: {
16 + current: HostDispatcher,
17 },
18 };
19
20 +function noop() {}
21 +
22 +const DefaultDispatcher: HostDispatcher = {
23 + prefetchDNS: noop,
24 + preconnect: noop,
25 + preload: noop,
26 + preloadModule: noop,
27 + preinitScript: noop,
28 + preinitStyle: noop,
29 + preinitModuleScript: noop,
30 +};
31 +
32 const Internals: InternalsType = ({
33 usingClientEntryPoint: false,
34 Events: null,
23 - Dispatcher: {
24 - current: null,
35 + ReactDOMCurrentDispatcher: {
36 + current: DefaultDispatcher,
37 },
38 }: any);
39
packages/react-dom/src/client/ReactDOMRoot.js
-11
@@ -13,24 +13,15 @@ import type {
13 TransitionTracingCallbacks,
14 } from 'react-reconciler/src/ReactInternalTypes';
15
16 -import {ReactDOMClientDispatcher} from 'react-dom-bindings/src/client/ReactFiberConfigDOM';
16 import {queueExplicitHydrationTarget} from 'react-dom-bindings/src/events/ReactDOMEventReplaying';
17 import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
18 import {
20 - enableFloat,
19 allowConcurrentByDefault,
20 disableCommentsAsDOMContainers,
21 enableAsyncActions,
22 enableFormActions,
23 } from 'shared/ReactFeatureFlags';
24
27 -import ReactDOMSharedInternals from '../ReactDOMSharedInternals';
28 -const {Dispatcher} = ReactDOMSharedInternals;
29 -if (enableFloat && typeof document !== 'undefined') {
30 - // Set the default dispatcher to the client dispatcher
31 - Dispatcher.current = ReactDOMClientDispatcher;
32 -}
33 -
25 export type RootType = {
26 render(children: ReactNodeList): void,
27 unmount(): void,
@@ -228,7 +219,6 @@ export function createRoot(
219 transitionCallbacks,
220 );
221 markContainerAsRoot(root.current, container);
231 - Dispatcher.current = ReactDOMClientDispatcher;
222
223 const rootContainerElement: Document | Element | DocumentFragment =
224 container.nodeType === COMMENT_NODE
@@ -322,7 +312,6 @@ export function hydrateRoot(
312 formState,
313 );
314 markContainerAsRoot(root.current, container);
325 - Dispatcher.current = ReactDOMClientDispatcher;
315 // This can't be a comment node since hydration doesn't work on comment nodes anyway.
316 listenToAllSupportedEvents(container);
317
packages/react-dom/src/shared/ReactDOMFloat.js
+16 -27
@@ -15,7 +15,8 @@ import type {
15 } from './ReactDOMTypes';
16
17 import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals';
18 -const Dispatcher = ReactDOMSharedInternals.Dispatcher;
18 +const ReactDOMCurrentDispatcher =
19 + ReactDOMSharedInternals.ReactDOMCurrentDispatcher;
20
21 import {
22 getCrossOriginString,
@@ -47,9 +48,8 @@ export function prefetchDNS(href: string) {
48 }
49 }
50 }
50 - const dispatcher = Dispatcher.current;
51 - if (dispatcher && typeof href === 'string') {
52 - dispatcher.prefetchDNS(href);
51 + if (typeof href === 'string') {
52 + ReactDOMCurrentDispatcher.current.prefetchDNS(href);
53 }
54 // We don't error because preconnect needs to be resilient to being called in a variety of scopes
55 // 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) {
75 );
76 }
77 }
78 - const dispatcher = Dispatcher.current;
79 - if (dispatcher && typeof href === 'string') {
78 + if (typeof href === 'string') {
79 const crossOrigin = options
80 ? getCrossOriginString(options.crossOrigin)
81 : null;
83 - dispatcher.preconnect(href, crossOrigin);
82 + ReactDOMCurrentDispatcher.current.preconnect(href, crossOrigin);
83 }
84 // We don't error because preconnect needs to be resilient to being called in a variety of scopes
85 // 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) {
110 );
111 }
112 }
114 - const dispatcher = Dispatcher.current;
113 if (
116 - dispatcher &&
114 typeof href === 'string' &&
115 // We check existence because we cannot enforce this function is actually called with the stated type
116 typeof options === 'object' &&
@@ -122,7 +119,7 @@ export function preload(href: string, options: PreloadOptions) {
119 ) {
120 const as = options.as;
121 const crossOrigin = getCrossOriginStringAs(as, options.crossOrigin);
125 - dispatcher.preload(href, as, {
122 + ReactDOMCurrentDispatcher.current.preload(href, as, {
123 crossOrigin,
124 integrity:
125 typeof options.integrity === 'string' ? options.integrity : undefined,
@@ -173,14 +170,13 @@ export function preloadModule(href: string, options?: ?PreloadModuleOptions) {
170 );
171 }
172 }
176 - const dispatcher = Dispatcher.current;
177 - if (dispatcher && typeof href === 'string') {
173 + if (typeof href === 'string') {
174 if (options) {
175 const crossOrigin = getCrossOriginStringAs(
176 options.as,
177 options.crossOrigin,
178 );
183 - dispatcher.preloadModule(href, {
179 + ReactDOMCurrentDispatcher.current.preloadModule(href, {
180 as:
181 typeof options.as === 'string' && options.as !== 'script'
182 ? options.as
@@ -190,7 +186,7 @@ export function preloadModule(href: string, options?: ?PreloadModuleOptions) {
186 typeof options.integrity === 'string' ? options.integrity : undefined,
187 });
188 } else {
193 - dispatcher.preloadModule(href);
189 + ReactDOMCurrentDispatcher.current.preloadModule(href);
190 }
191 }
192 // 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) {
213 );
214 }
215 }
220 - const dispatcher = Dispatcher.current;
221 - if (
222 - dispatcher &&
223 - typeof href === 'string' &&
224 - options &&
225 - typeof options.as === 'string'
226 - ) {
216 + if (typeof href === 'string' && options && typeof options.as === 'string') {
217 const as = options.as;
218 const crossOrigin = getCrossOriginStringAs(as, options.crossOrigin);
219 const integrity =
@@ -233,7 +223,7 @@ export function preinit(href: string, options: PreinitOptions) {
223 ? options.fetchPriority
224 : undefined;
225 if (as === 'style') {
236 - dispatcher.preinitStyle(
226 + ReactDOMCurrentDispatcher.current.preinitStyle(
227 href,
228 typeof options.precedence === 'string' ? options.precedence : undefined,
229 {
@@ -243,7 +233,7 @@ export function preinit(href: string, options: PreinitOptions) {
233 },
234 );
235 } else if (as === 'script') {
246 - dispatcher.preinitScript(href, {
236 + ReactDOMCurrentDispatcher.current.preinitScript(href, {
237 crossOrigin,
238 integrity,
239 fetchPriority,
@@ -301,15 +291,14 @@ export function preinitModule(href: string, options?: ?PreinitModuleOptions) {
291 }
292 }
293 }
304 - const dispatcher = Dispatcher.current;
305 - if (dispatcher && typeof href === 'string') {
294 + if (typeof href === 'string') {
295 if (typeof options === 'object' && options !== null) {
296 if (options.as == null || options.as === 'script') {
297 const crossOrigin = getCrossOriginStringAs(
298 options.as,
299 options.crossOrigin,
300 );
312 - dispatcher.preinitModuleScript(href, {
301 + ReactDOMCurrentDispatcher.current.preinitModuleScript(href, {
302 crossOrigin,
303 integrity:
304 typeof options.integrity === 'string'
@@ -319,7 +308,7 @@ export function preinitModule(href: string, options?: ?PreinitModuleOptions) {
308 });
309 }
310 } else if (options == null) {
322 - dispatcher.preinitModuleScript(href);
311 + ReactDOMCurrentDispatcher.current.preinitModuleScript(href);
312 }
313 }
314 // We don't error because preinit needs to be resilient to being called in a variety of scopes
packages/react-dom/src/shared/ReactDOMTypes.js
+1 -1
@@ -90,7 +90,7 @@ export type HostDispatcher = {
90 precedence: ?string,
91 options?: ?PreinitStyleOptions,
92 ) => void,
93 - preinitScript: (src: string, options?: PreinitScriptOptions) => void,
93 + preinitScript: (src: string, options?: ?PreinitScriptOptions) => void,
94 preinitModuleScript: (
95 src: string,
96 options?: ?PreinitModuleScriptOptions,
packages/react-noop-renderer/src/ReactNoopFlightServer.js
-1
@@ -64,7 +64,6 @@ const ReactNoopFlightServer = ReactFlightServer({
64 ) {
65 return saveModule(reference.value);
66 },
67 - prepareHostDispatcher() {},
67 });
68
69 type Options = {
packages/react-noop-renderer/src/ReactNoopServer.js
-2
@@ -261,8 +261,6 @@ const ReactNoopServer = ReactFizzServer({
261 boundary.status = 'client-render';
262 },
263
264 - prepareHostDispatcher() {},
265 -
264 writePreamble() {},
265 writeHoistables() {},
266 writeHoistablesForBoundary() {},
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js
+4 -4
@@ -47,19 +47,19 @@ describe('ReactFlightDOM', () => {
47 JSDOM = require('jsdom').JSDOM;
48
49 // Simulate the condition resolution
50 + jest.mock('react', () => require('react/react.react-server'));
51 + FlightReact = require('react');
52 + FlightReactDOM = require('react-dom');
53 +
54 jest.mock('react-server-dom-webpack/server', () =>
55 require('react-server-dom-webpack/server.node.unbundled'),
56 );
53 - jest.mock('react', () => require('react/react.react-server'));
54 -
57 const WebpackMock = require('./utils/WebpackMock');
58 clientExports = WebpackMock.clientExports;
59 clientModuleError = WebpackMock.clientModuleError;
60 webpackMap = WebpackMock.webpackMap;
61
62 ReactServerDOMServer = require('react-server-dom-webpack/server');
61 - FlightReact = require('react');
62 - FlightReactDOM = require('react-dom');
63
64 // This reset is to load modules for the SSR/Browser scope.
65 jest.unmock('react-server-dom-webpack/server');
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMBrowser-test.js
+17 -5
@@ -36,19 +36,19 @@ describe('ReactFlightDOMBrowser', () => {
36 jest.resetModules();
37
38 // Simulate the condition resolution
39 +
40 jest.mock('react', () => require('react/react.react-server'));
41 + ReactServer = require('react');
42 + ReactServerDOM = require('react-dom');
43 +
44 jest.mock('react-server-dom-webpack/server', () =>
45 require('react-server-dom-webpack/server.browser'),
46 );
43 -
47 const WebpackMock = require('./utils/WebpackMock');
48 clientExports = WebpackMock.clientExports;
49 serverExports = WebpackMock.serverExports;
50 webpackMap = WebpackMock.webpackMap;
51 webpackServerMap = WebpackMock.webpackServerMap;
49 -
50 - ReactServer = require('react');
51 - ReactServerDOM = require('react-dom');
52 ReactServerDOMServer = require('react-server-dom-webpack/server.browser');
53
54 __unmockReact();
@@ -1172,7 +1172,19 @@ describe('ReactFlightDOMBrowser', () => {
1172 root.render(<App />);
1173 });
1174 expect(document.head.innerHTML).toBe(
1175 - '<link rel="preload" href="before" as="style">',
1175 + // Currently the react-dom entrypoint loads the fiber implementation
1176 + // even if you never pull in the the client APIs. this causes the fiber
1177 + // dispatcher to be present even for Flight ReactDOM calls. This is not what
1178 + // you would have in a real application but given we're runnign flight and
1179 + // fiber the in the same scope it's unavoidable until we make the entrypoint
1180 + // not automatically pull in the fiber implementation. This test currently
1181 + // asserts this be demonstrating that the preload call after the await point
1182 + // is written to the document before the call before it. We still demonstrate that
1183 + // flight handled the sync call because if the fiber implementation did it would appear
1184 + // before the after call. In the future we will change this assertion once the fiber
1185 + // implementation no long automatically gets pulled in
1186 + '<link rel="preload" href="after" as="style"><link rel="preload" href="before" as="style">',
1187 + // '<link rel="preload" href="before" as="style">',
1188 );
1189 expect(container.innerHTML).toBe('<p>hello world</p>');
1190 });
packages/react-server/src/ReactFizzServer.js
-3
@@ -71,7 +71,6 @@ import {
71 writePostamble,
72 hoistHoistables,
73 createHoistableState,
74 - prepareHostDispatcher,
74 supportsRequestStorage,
75 requestStorage,
76 pushFormStateMarkerIsMatching,
@@ -377,7 +376,6 @@ export function createRequest(
376 onPostpone: void | ((reason: string, postponeInfo: PostponeInfo) => void),
377 formState: void | null | ReactFormState<any, any>,
378 ): Request {
380 - prepareHostDispatcher();
379 const pingedTasks: Array<Task> = [];
380 const abortSet: Set<Task> = new Set();
381 const request: Request = {
@@ -490,7 +488,6 @@ export function resumeRequest(
488 onFatalError: void | ((error: mixed) => void),
489 onPostpone: void | ((reason: string, postponeInfo: PostponeInfo) => void),
490 ): Request {
493 - prepareHostDispatcher();
491 const pingedTasks: Array<Task> = [];
492 const abortSet: Set<Task> = new Set();
493 const request: Request = {
packages/react-server/src/ReactFlightServer.js
-2
@@ -69,7 +69,6 @@ import {
69 isServerReference,
70 supportsRequestStorage,
71 requestStorage,
72 - prepareHostDispatcher,
72 createHints,
73 initAsyncDebugInfo,
74 } from './ReactFlightServerConfig';
@@ -345,7 +344,6 @@ export function createRequest(
344 'Currently React only supports one RSC renderer at a time.',
345 );
346 }
348 - prepareHostDispatcher();
347 ReactCurrentCache.current = DefaultCacheDispatcher;
348
349 const abortSet: Set<Task> = new Set();
packages/react-server/src/ReactFlightServerConfigBundlerCustom.js
-1
@@ -23,4 +23,3 @@ export const resolveClientReferenceMetadata =
23 export const getServerReferenceId = $$$config.getServerReferenceId;
24 export const getServerReferenceBoundArguments =
25 $$$config.getServerReferenceBoundArguments;
26 -export const prepareHostDispatcher = $$$config.prepareHostDispatcher;
packages/react-server/src/forks/ReactFizzConfig.custom.js
-1
@@ -78,7 +78,6 @@ export const writeCompletedBoundaryInstruction =
78 $$$config.writeCompletedBoundaryInstruction;
79 export const writeClientRenderBoundaryInstruction =
80 $$$config.writeClientRenderBoundaryInstruction;
81 -export const prepareHostDispatcher = $$$config.prepareHostDispatcher;
81 export const NotPendingTransition = $$$config.NotPendingTransition;
82
83 // -------------------------
packages/react-server/src/forks/ReactFlightServerConfig.custom.js
-2
@@ -20,8 +20,6 @@ export type HintModel<T: any> = any;
20
21 export const isPrimaryRenderer = false;
22
23 -export const prepareHostDispatcher = () => {};
24 -
23 export const supportsRequestStorage = false;
24 export const requestStorage: AsyncLocalStorage<Request | void> = (null: any);
25
scripts/rollup/forks.js
+2 -1
@@ -93,7 +93,8 @@ const forks = Object.freeze({
93 if (
94 entry === 'react-dom' ||
95 entry === 'react-dom/server-rendering-stub' ||
96 - entry === 'react-dom/src/ReactDOMServer.js'
96 + entry === 'react-dom/src/ReactDOMServer.js' ||
97 + entry === 'react-dom/unstable_testing'
98 ) {
99 return './packages/react-dom/src/ReactDOMSharedInternals.js';
100 }