Skip to content

Commit 6b2ffd6

Browse files
gnoffAndyPengc12
authored andcommitted
[Flight][Fizz][Fiber] Chain HostDispatcher implementations (facebook#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.
1 parent 5f2afe0 commit 6b2ffd6

20 files changed

+202
-186
lines changed

packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
* @flow
88
*/
99

10-
import type {HostDispatcher} from 'react-dom/src/shared/ReactDOMTypes';
1110
import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities';
1211
import type {DOMEventName} from '../events/DOMEventNames';
1312
import type {Fiber, FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
@@ -107,6 +106,10 @@ import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem';
107106
import {validateLinkPropsForStyleResource} from '../shared/ReactDOMResourceValidation';
108107
import escapeSelectorAttributeValueInsideDoubleQuotes from './escapeSelectorAttributeValueInsideDoubleQuotes';
109108

109+
import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals';
110+
const ReactDOMCurrentDispatcher =
111+
ReactDOMSharedInternals.ReactDOMCurrentDispatcher;
112+
110113
export type Type = string;
111114
export type Props = {
112115
autoFocus?: boolean,
@@ -2108,10 +2111,8 @@ function getDocumentFromRoot(root: HoistableRoot): Document {
21082111
return root.ownerDocument || root;
21092112
}
21102113

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 = {
21152116
prefetchDNS,
21162117
preconnect,
21172118
preload,
@@ -2127,17 +2128,18 @@ export const ReactDOMClientDispatcher: HostDispatcher = {
21272128
// and so we have to fall back to something universal. Currently we just refer to the global document.
21282129
// This is notable because nowhere else in ReactDOM do we actually reference the global document or window
21292130
// 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;
21322134
}
21332135

21342136
function preconnectAs(
21352137
rel: 'preconnect' | 'dns-prefetch',
21362138
href: string,
21372139
crossOrigin: ?CrossOriginEnum,
21382140
) {
2139-
const ownerDocument = getDocumentForImperativeFloatMethods();
2140-
if (typeof href === 'string' && href) {
2141+
const ownerDocument = getGlobalDocument();
2142+
if (ownerDocument && typeof href === 'string' && href) {
21412143
const limitedEscapedHref =
21422144
escapeSelectorAttributeValueInsideDoubleQuotes(href);
21432145
let key = `link[rel="${rel}"][href="${limitedEscapedHref}"]`;
@@ -2162,22 +2164,25 @@ function prefetchDNS(href: string) {
21622164
if (!enableFloat) {
21632165
return;
21642166
}
2167+
previousDispatcher.prefetchDNS(href);
21652168
preconnectAs('dns-prefetch', href, null);
21662169
}
21672170

21682171
function preconnect(href: string, crossOrigin?: ?CrossOriginEnum) {
21692172
if (!enableFloat) {
21702173
return;
21712174
}
2175+
previousDispatcher.preconnect(href, crossOrigin);
21722176
preconnectAs('preconnect', href, crossOrigin);
21732177
}
21742178

21752179
function preload(href: string, as: string, options?: ?PreloadImplOptions) {
21762180
if (!enableFloat) {
21772181
return;
21782182
}
2179-
const ownerDocument = getDocumentForImperativeFloatMethods();
2180-
if (href && as && ownerDocument) {
2183+
previousDispatcher.preload(href, as, options);
2184+
const ownerDocument = getGlobalDocument();
2185+
if (ownerDocument && href && as) {
21812186
let preloadSelector = `link[rel="preload"][as="${escapeSelectorAttributeValueInsideDoubleQuotes(
21822187
as,
21832188
)}"]`;
@@ -2256,8 +2261,9 @@ function preloadModule(href: string, options?: ?PreloadModuleImplOptions) {
22562261
if (!enableFloat) {
22572262
return;
22582263
}
2259-
const ownerDocument = getDocumentForImperativeFloatMethods();
2260-
if (href) {
2264+
previousDispatcher.preloadModule(href, options);
2265+
const ownerDocument = getGlobalDocument();
2266+
if (ownerDocument && href) {
22612267
const as =
22622268
options && typeof options.as === 'string' ? options.as : 'script';
22632269
const preloadSelector = `link[rel="modulepreload"][as="${escapeSelectorAttributeValueInsideDoubleQuotes(
@@ -2319,9 +2325,10 @@ function preinitStyle(
23192325
if (!enableFloat) {
23202326
return;
23212327
}
2322-
const ownerDocument = getDocumentForImperativeFloatMethods();
2328+
previousDispatcher.preinitStyle(href, precedence, options);
23232329

2324-
if (href) {
2330+
const ownerDocument = getGlobalDocument();
2331+
if (ownerDocument && href) {
23252332
const styles = getResourcesFromRoot(ownerDocument).hoistableStyles;
23262333

23272334
const key = getStyleKey(href);
@@ -2395,9 +2402,10 @@ function preinitScript(src: string, options?: ?PreinitScriptOptions) {
23952402
if (!enableFloat) {
23962403
return;
23972404
}
2398-
const ownerDocument = getDocumentForImperativeFloatMethods();
2405+
previousDispatcher.preinitScript(src, options);
23992406

2400-
if (src) {
2407+
const ownerDocument = getGlobalDocument();
2408+
if (ownerDocument && src) {
24012409
const scripts = getResourcesFromRoot(ownerDocument).hoistableScripts;
24022410

24032411
const key = getScriptKey(src);
@@ -2453,9 +2461,10 @@ function preinitModuleScript(
24532461
if (!enableFloat) {
24542462
return;
24552463
}
2456-
const ownerDocument = getDocumentForImperativeFloatMethods();
2464+
previousDispatcher.preinitModuleScript(src, options);
24572465

2458-
if (src) {
2466+
const ownerDocument = getGlobalDocument();
2467+
if (ownerDocument && src) {
24592468
const scripts = getResourcesFromRoot(ownerDocument).hoistableScripts;
24602469

24612470
const key = getScriptKey(src);

packages/react-dom-bindings/src/server/ReactDOMFlightServerHostDispatcher.js

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
*/
99

1010
import type {
11-
HostDispatcher,
1211
CrossOriginEnum,
1312
PreloadImplOptions,
1413
PreloadModuleImplOptions,
@@ -25,7 +24,12 @@ import {
2524
resolveRequest,
2625
} from 'react-server/src/ReactFlightServer';
2726

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 = {
2933
prefetchDNS,
3034
preconnect,
3135
preload,
@@ -48,6 +52,8 @@ function prefetchDNS(href: string) {
4852
}
4953
hints.add(key);
5054
emitHint(request, 'D', href);
55+
} else {
56+
previousDispatcher.prefetchDNS(href);
5157
}
5258
}
5359
}
@@ -71,6 +77,8 @@ function preconnect(href: string, crossOrigin?: ?CrossOriginEnum) {
7177
} else {
7278
emitHint(request, 'C', href);
7379
}
80+
} else {
81+
previousDispatcher.preconnect(href, crossOrigin);
7482
}
7583
}
7684
}
@@ -104,6 +112,8 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) {
104112
} else {
105113
emitHint(request, 'L', [href, as]);
106114
}
115+
} else {
116+
previousDispatcher.preload(href, as, options);
107117
}
108118
}
109119
}
@@ -128,6 +138,8 @@ function preloadModule(href: string, options?: ?PreloadModuleImplOptions) {
128138
} else {
129139
return emitHint(request, 'm', href);
130140
}
141+
} else {
142+
previousDispatcher.preloadModule(href, options);
131143
}
132144
}
133145
}
@@ -162,18 +174,20 @@ function preinitStyle(
162174
} else {
163175
return emitHint(request, 'S', href);
164176
}
177+
} else {
178+
previousDispatcher.preinitStyle(href, precedence, options);
165179
}
166180
}
167181
}
168182
}
169183

170-
function preinitScript(href: string, options?: ?PreinitScriptOptions) {
184+
function preinitScript(src: string, options?: ?PreinitScriptOptions) {
171185
if (enableFloat) {
172-
if (typeof href === 'string') {
186+
if (typeof src === 'string') {
173187
const request = resolveRequest();
174188
if (request) {
175189
const hints = getHints(request);
176-
const key = 'X|' + href;
190+
const key = 'X|' + src;
177191
if (hints.has(key)) {
178192
// duplicate hint
179193
return;
@@ -182,25 +196,27 @@ function preinitScript(href: string, options?: ?PreinitScriptOptions) {
182196

183197
const trimmed = trimOptions(options);
184198
if (trimmed) {
185-
return emitHint(request, 'X', [href, trimmed]);
199+
return emitHint(request, 'X', [src, trimmed]);
186200
} else {
187-
return emitHint(request, 'X', href);
201+
return emitHint(request, 'X', src);
188202
}
203+
} else {
204+
previousDispatcher.preinitScript(src, options);
189205
}
190206
}
191207
}
192208
}
193209

194210
function preinitModuleScript(
195-
href: string,
211+
src: string,
196212
options?: ?PreinitModuleScriptOptions,
197213
) {
198214
if (enableFloat) {
199-
if (typeof href === 'string') {
215+
if (typeof src === 'string') {
200216
const request = resolveRequest();
201217
if (request) {
202218
const hints = getHints(request);
203-
const key = 'M|' + href;
219+
const key = 'M|' + src;
204220
if (hints.has(key)) {
205221
// duplicate hint
206222
return;
@@ -209,10 +225,12 @@ function preinitModuleScript(
209225

210226
const trimmed = trimOptions(options);
211227
if (trimmed) {
212-
return emitHint(request, 'M', [href, trimmed]);
228+
return emitHint(request, 'M', [src, trimmed]);
213229
} else {
214-
return emitHint(request, 'M', href);
230+
return emitHint(request, 'M', src);
215231
}
232+
} else {
233+
previousDispatcher.preinitModuleScript(src, options);
216234
}
217235
}
218236
}

packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -88,22 +88,20 @@ import {getValueDescriptorExpectingObjectForWarning} from '../shared/ReactDOMRes
8888
import {NotPending} from '../shared/ReactDOMFormActions';
8989

9090
import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals';
91-
const ReactDOMCurrentDispatcher = ReactDOMSharedInternals.Dispatcher;
91+
const ReactDOMCurrentDispatcher =
92+
ReactDOMSharedInternals.ReactDOMCurrentDispatcher;
9293

93-
const ReactDOMServerDispatcher = {
94+
const previousDispatcher = ReactDOMCurrentDispatcher.current;
95+
ReactDOMCurrentDispatcher.current = {
9496
prefetchDNS,
9597
preconnect,
9698
preload,
9799
preloadModule,
98-
preinitStyle,
99100
preinitScript,
101+
preinitStyle,
100102
preinitModuleScript,
101103
};
102104

103-
export function prepareHostDispatcher() {
104-
ReactDOMCurrentDispatcher.current = ReactDOMServerDispatcher;
105-
}
106-
107105
// We make every property of the descriptor optional because it is not a contract that
108106
// the headers provided by onHeaders has any particular header types.
109107
export type HeadersDescriptor = {
@@ -5342,6 +5340,7 @@ function prefetchDNS(href: string) {
53425340
// the resources for this call in either case we opt to do nothing. We can consider making this a warning
53435341
// but there may be times where calling a function outside of render is intentional (i.e. to warm up data
53445342
// fetching) and we don't want to warn in those cases.
5343+
previousDispatcher.prefetchDNS(href);
53455344
return;
53465345
}
53475346
const resumableState = getResumableState(request);
@@ -5397,6 +5396,7 @@ function preconnect(href: string, crossOrigin: ?CrossOriginEnum) {
53975396
// the resources for this call in either case we opt to do nothing. We can consider making this a warning
53985397
// but there may be times where calling a function outside of render is intentional (i.e. to warm up data
53995398
// fetching) and we don't want to warn in those cases.
5399+
previousDispatcher.preconnect(href, crossOrigin);
54005400
return;
54015401
}
54025402
const resumableState = getResumableState(request);
@@ -5460,6 +5460,7 @@ function preload(href: string, as: string, options?: ?PreloadImplOptions) {
54605460
// the resources for this call in either case we opt to do nothing. We can consider making this a warning
54615461
// but there may be times where calling a function outside of render is intentional (i.e. to warm up data
54625462
// fetching) and we don't want to warn in those cases.
5463+
previousDispatcher.preload(href, as, options);
54635464
return;
54645465
}
54655466
const resumableState = getResumableState(request);
@@ -5663,6 +5664,7 @@ function preloadModule(
56635664
// the resources for this call in either case we opt to do nothing. We can consider making this a warning
56645665
// but there may be times where calling a function outside of render is intentional (i.e. to warm up data
56655666
// fetching) and we don't want to warn in those cases.
5667+
previousDispatcher.preloadModule(href, options);
56665668
return;
56675669
}
56685670
const resumableState = getResumableState(request);
@@ -5739,6 +5741,7 @@ function preinitStyle(
57395741
// the resources for this call in either case we opt to do nothing. We can consider making this a warning
57405742
// but there may be times where calling a function outside of render is intentional (i.e. to warm up data
57415743
// fetching) and we don't want to warn in those cases.
5744+
previousDispatcher.preinitStyle(href, precedence, options);
57425745
return;
57435746
}
57445747
const resumableState = getResumableState(request);
@@ -5826,6 +5829,7 @@ function preinitScript(src: string, options?: ?PreinitScriptOptions): void {
58265829
// the resources for this call in either case we opt to do nothing. We can consider making this a warning
58275830
// but there may be times where calling a function outside of render is intentional (i.e. to warm up data
58285831
// fetching) and we don't want to warn in those cases.
5832+
previousDispatcher.preinitScript(src, options);
58295833
return;
58305834
}
58315835
const resumableState = getResumableState(request);
@@ -5891,6 +5895,7 @@ function preinitModuleScript(
58915895
// the resources for this call in either case we opt to do nothing. We can consider making this a warning
58925896
// but there may be times where calling a function outside of render is intentional (i.e. to warm up data
58935897
// fetching) and we don't want to warn in those cases.
5898+
previousDispatcher.preinitModuleScript(src, options);
58945899
return;
58955900
}
58965901
const resumableState = getResumableState(request);

packages/react-dom-bindings/src/server/ReactFizzConfigDOMLegacy.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,6 @@ export {
163163
writeHoistables,
164164
writePostamble,
165165
hoistHoistables,
166-
prepareHostDispatcher,
167166
resetResumableState,
168167
completeResumableState,
169168
emitEarlyPreloads,

packages/react-dom-bindings/src/server/ReactFlightServerConfigDOM.js

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,9 @@ import type {
1616
PreinitModuleScriptOptions,
1717
} from 'react-dom/src/shared/ReactDOMTypes';
1818

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';
2722

2823
// Used to distinguish these contexts from ones used in other renderers.
2924
// E.g. this can be used to distinguish legacy renderers from this modern one.

0 commit comments

Comments
 (0)