main
js 272 lines 6.9 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import type {
11 CrossOriginEnum,
12 PreloadImplOptions,
13 PreloadModuleImplOptions,
14 PreinitStyleOptions,
15 PreinitScriptOptions,
16 PreinitModuleScriptOptions,
17 } from 'react-dom/src/shared/ReactDOMTypes';
18
19 import {
20 emitHint,
21 getHints,
22 resolveRequest,
23 } from 'react-server/src/ReactFlightServer';
24
25 import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals';
26
27 const previousDispatcher =
28 ReactDOMSharedInternals.d; /* ReactDOMCurrentDispatcher */
29 ReactDOMSharedInternals.d /* ReactDOMCurrentDispatcher */ = {
30 f /* flushSyncWork */: previousDispatcher.f /* flushSyncWork */,
31 r /* requestFormReset */: previousDispatcher.r /* requestFormReset */,
32 D /* prefetchDNS */: prefetchDNS,
33 C /* preconnect */: preconnect,
34 L /* preload */: preload,
35 m /* preloadModule */: preloadModule,
36 X /* preinitScript */: preinitScript,
37 S /* preinitStyle */: preinitStyle,
38 M /* preinitModuleScript */: preinitModuleScript,
39 };
40
41 function prefetchDNS(href: string) {
42 if (typeof href === 'string' && href) {
43 const request = resolveRequest();
44 if (request) {
45 const hints = getHints(request);
46 const key = 'D|' + href;
47 if (hints.has(key)) {
48 // duplicate hint
49 return;
50 }
51 hints.add(key);
52 emitHint(request, 'D', href);
53 } else {
54 previousDispatcher.D(/* prefetchDNS */ href);
55 }
56 }
57 }
58
59 function preconnect(href: string, crossOrigin?: ?CrossOriginEnum) {
60 if (typeof href === 'string') {
61 const request = resolveRequest();
62 if (request) {
63 const hints = getHints(request);
64
65 const key = `C|${crossOrigin == null ? 'null' : crossOrigin}|${href}`;
66 if (hints.has(key)) {
67 // duplicate hint
68 return;
69 }
70 hints.add(key);
71 if (typeof crossOrigin === 'string') {
72 emitHint(request, 'C', [href, crossOrigin]);
73 } else {
74 emitHint(request, 'C', href);
75 }
76 } else {
77 previousDispatcher.C(/* preconnect */ href, crossOrigin);
78 }
79 }
80 }
81
82 export function preload(
83 href: string,
84 as: string,
85 options?: ?PreloadImplOptions,
86 ) {
87 if (typeof href === 'string') {
88 const request = resolveRequest();
89 if (request) {
90 const hints = getHints(request);
91 let key = 'L';
92 if (as === 'image' && options) {
93 key += getImagePreloadKey(
94 href,
95 options.imageSrcSet,
96 options.imageSizes,
97 );
98 } else {
99 key += `[${as}]${href}`;
100 }
101 if (hints.has(key)) {
102 // duplicate hint
103 return;
104 }
105 hints.add(key);
106
107 const trimmed = trimOptions(options);
108 if (trimmed) {
109 emitHint(request, 'L', [href, as, trimmed]);
110 } else {
111 emitHint(request, 'L', [href, as]);
112 }
113 } else {
114 previousDispatcher.L(/* preload */ href, as, options);
115 }
116 }
117 }
118
119 export function preloadModule(
120 href: string,
121 options?: ?PreloadModuleImplOptions,
122 ): void {
123 if (typeof href === 'string') {
124 const request = resolveRequest();
125 if (request) {
126 const hints = getHints(request);
127 const key = 'm|' + href;
128 if (hints.has(key)) {
129 // duplicate hint
130 return;
131 }
132 hints.add(key);
133
134 const trimmed = trimOptions(options);
135 if (trimmed) {
136 return emitHint(request, 'm', [href, trimmed]);
137 } else {
138 return emitHint(request, 'm', href);
139 }
140 } else {
141 previousDispatcher.m(/* preloadModule */ href, options);
142 }
143 }
144 }
145
146 function preinitStyle(
147 href: string,
148 precedence: ?string,
149 options?: ?PreinitStyleOptions,
150 ) {
151 if (typeof href === 'string') {
152 const request = resolveRequest();
153 if (request) {
154 const hints = getHints(request);
155 const key = 'S|' + href;
156 if (hints.has(key)) {
157 // duplicate hint
158 return;
159 }
160 hints.add(key);
161
162 const trimmed = trimOptions(options);
163 if (trimmed) {
164 return emitHint(request, 'S', [
165 href,
166 typeof precedence === 'string' ? precedence : 0,
167 trimmed,
168 ]);
169 } else if (typeof precedence === 'string') {
170 return emitHint(request, 'S', [href, precedence]);
171 } else {
172 return emitHint(request, 'S', href);
173 }
174 } else {
175 previousDispatcher.S(/* preinitStyle */ href, precedence, options);
176 }
177 }
178 }
179
180 function preinitScript(src: string, options?: ?PreinitScriptOptions) {
181 if (typeof src === 'string') {
182 const request = resolveRequest();
183 if (request) {
184 const hints = getHints(request);
185 const key = 'X|' + src;
186 if (hints.has(key)) {
187 // duplicate hint
188 return;
189 }
190 hints.add(key);
191
192 const trimmed = trimOptions(options);
193 if (trimmed) {
194 return emitHint(request, 'X', [src, trimmed]);
195 } else {
196 return emitHint(request, 'X', src);
197 }
198 } else {
199 previousDispatcher.X(/* preinitScript */ src, options);
200 }
201 }
202 }
203
204 function preinitModuleScript(
205 src: string,
206 options?: ?PreinitModuleScriptOptions,
207 ) {
208 if (typeof src === 'string') {
209 const request = resolveRequest();
210 if (request) {
211 const hints = getHints(request);
212 const key = 'M|' + src;
213 if (hints.has(key)) {
214 // duplicate hint
215 return;
216 }
217 hints.add(key);
218
219 const trimmed = trimOptions(options);
220 if (trimmed) {
221 return emitHint(request, 'M', [src, trimmed]);
222 } else {
223 return emitHint(request, 'M', src);
224 }
225 } else {
226 previousDispatcher.M(/* preinitModuleScript */ src, options);
227 }
228 }
229 }
230
231 // Flight normally encodes undefined as a special character however for directive option
232 // arguments we don't want to send unnecessary keys and bloat the payload so we create a
233 // trimmed object which omits any keys with null or undefined values.
234 // This is only typesafe because these option objects have entirely optional fields where
235 // null and undefined represent the same thing as no property.
236 function trimOptions<
237 T:
238 | PreloadImplOptions
239 | PreloadModuleImplOptions
240 | PreinitStyleOptions
241 | PreinitScriptOptions
242 | PreinitModuleScriptOptions,
243 >(options: ?T): ?T {
244 if (options == null) return null;
245 let hasProperties = false;
246 const trimmed: T = {} as any;
247 for (const key in options) {
248 // $FlowFixMe[invalid-computed-prop]
249 if (options[key] != null) {
250 hasProperties = true;
251 (trimmed as any)[key] = options[key];
252 }
253 }
254 return hasProperties ? trimmed : null;
255 }
256
257 function getImagePreloadKey(
258 href: string,
259 imageSrcSet: ?string,
260 imageSizes: ?string,
261 ) {
262 let uniquePart = '';
263 if (typeof imageSrcSet === 'string' && imageSrcSet !== '') {
264 uniquePart += '[' + imageSrcSet + ']';
265 if (typeof imageSizes === 'string') {
266 uniquePart += '[' + imageSizes + ']';
267 }
268 } else {
269 uniquePart += '[][]' + href;
270 }
271 return `[image]${uniquePart}`;
272 }