@samitouri / QOS-React / commits / 1173a17e6e

[Float][Fizz][Fiber] implement preconnect and prefetchDNS float methods (#26237)

Adds two new ReactDOM methods ### `ReactDOM.prefetchDNS(href: string)` In SSR this method will cause a `<link rel="dns-prefetch" href="..." />` to flush before most other content both on intial flush (Shell) and late flushes. It will only emit one link per href. On the client, this method will case the same kind of link to be inserted into the document immediately (when called during render, not during commit) if there is not already a matching element in the document. ### `ReactDOM.preconnect(href: string, options?: { crossOrigin?: string })` In SSR this method will cause a `<link rel="dns-prefetch" href="..." [corssorigin="..."] />` to flush before most other content both on intial flush (Shell) and late flushes. It will only emit one link per href + crossorigin combo. On the client, this method will case the same kind of link to be inserted into the document immediately (when called during render, not during commit) if there is not already a matching element in the document.

Josh Story committed Feb 25, 2023 at 11:04 UTC 1173a17e6eea08599005e9f8e7d7a4245783edba
10 files changed +432 -14
packages/react-dom-bindings/src/client/ReactDOMFloatClient.js
+104 -3
@@ -18,6 +18,8 @@ import {DOCUMENT_NODE} from '../shared/HTMLNodeType';
18 import {
19 validatePreloadArguments,
20 validatePreinitArguments,
21 + getValueDescriptorExpectingObjectForWarning,
22 + getValueDescriptorExpectingEnumForWarning,
23 } from '../shared/ReactDOMResourceValidation';
24 import {createElement, setInitialProperties} from './ReactDOMComponent';
25 import {
@@ -103,12 +105,18 @@ export function cleanupAfterRenderResources() {
105 // We want this to be the default dispatcher on ReactDOMSharedInternals but we don't want to mutate
106 // internals in Module scope. Instead we export it and Internals will import it. There is already a cycle
107 // from Internals -> ReactDOM -> FloatClient -> Internals so this doesn't introduce a new one.
106 -export const ReactDOMClientDispatcher = {preload, preinit};
108 +export const ReactDOMClientDispatcher = {
109 + prefetchDNS,
110 + preconnect,
111 + preload,
112 + preinit,
113 +};
114
115 export type HoistableRoot = Document | ShadowRoot;
116
110 -// global maps of Resources
117 +// global collections of Resources
118 const preloadPropsMap: Map<string, PreloadProps> = new Map();
119 +const preconnectsSet: Set<string> = new Set();
120
121 // getRootNode is missing from IE and old jsdom versions
122 export function getHoistableRoot(container: Container): HoistableRoot {
@@ -148,8 +156,101 @@ function getDocumentFromRoot(root: HoistableRoot): Document {
156 return root.ownerDocument || root;
157 }
158
159 +function preconnectAs(
160 + rel: 'preconnect' | 'dns-prefetch',
161 + crossOrigin: null | '' | 'use-credentials',
162 + href: string,
163 +) {
164 + const ownerDocument = getDocumentForPreloads();
165 + if (typeof href === 'string' && href && ownerDocument) {
166 + const limitedEscapedHref =
167 + escapeSelectorAttributeValueInsideDoubleQuotes(href);
168 + let key = `link[rel="${rel}"][href="${limitedEscapedHref}"]`;
169 + if (typeof crossOrigin === 'string') {
170 + key += `[crossorigin="${crossOrigin}"]`;
171 + }
172 + if (!preconnectsSet.has(key)) {
173 + preconnectsSet.add(key);
174 +
175 + const preconnectProps = {rel, crossOrigin, href};
176 + if (null === ownerDocument.querySelector(key)) {
177 + const preloadInstance = createElement(
178 + 'link',
179 + preconnectProps,
180 + ownerDocument,
181 + HTML_NAMESPACE,
182 + );
183 + setInitialProperties(preloadInstance, 'link', preconnectProps);
184 + markNodeAsResource(preloadInstance);
185 + (ownerDocument.head: any).appendChild(preloadInstance);
186 + }
187 + }
188 + }
189 +}
190 +
191 +// --------------------------------------
192 +// ReactDOM.prefetchDNS
193 +// --------------------------------------
194 +function prefetchDNS(href: string, options?: mixed) {
195 + if (__DEV__) {
196 + if (typeof href !== 'string' || !href) {
197 + console.error(
198 + 'ReactDOM.prefetchDNS(): Expected the `href` argument (first) to be a non-empty string but encountered %s instead.',
199 + getValueDescriptorExpectingObjectForWarning(href),
200 + );
201 + } else if (options != null) {
202 + if (
203 + typeof options === 'object' &&
204 + options.hasOwnProperty('crossOrigin')
205 + ) {
206 + console.error(
207 + 'ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered %s as a second argument instead. This argument is reserved for future options and is currently disallowed. It looks like the you are attempting to set a crossOrigin property for this DNS lookup hint. Browsers do not perform DNS queries using CORS and setting this attribute on the resource hint has no effect. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
208 + getValueDescriptorExpectingEnumForWarning(options),
209 + );
210 + } else {
211 + console.error(
212 + 'ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered %s as a second argument instead. This argument is reserved for future options and is currently disallowed. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
213 + getValueDescriptorExpectingEnumForWarning(options),
214 + );
215 + }
216 + }
217 + }
218 + preconnectAs('dns-prefetch', null, href);
219 +}
220 +
221 +// --------------------------------------
222 +// ReactDOM.preconnect
223 +// --------------------------------------
224 +function preconnect(href: string, options?: {crossOrigin?: string}) {
225 + if (__DEV__) {
226 + if (typeof href !== 'string' || !href) {
227 + console.error(
228 + 'ReactDOM.preconnect(): Expected the `href` argument (first) to be a non-empty string but encountered %s instead.',
229 + getValueDescriptorExpectingObjectForWarning(href),
230 + );
231 + } else if (options != null && typeof options !== 'object') {
232 + console.error(
233 + 'ReactDOM.preconnect(): Expected the `options` argument (second) to be an object but encountered %s instead. The only supported option at this time is `crossOrigin` which accepts a string.',
234 + getValueDescriptorExpectingEnumForWarning(options),
235 + );
236 + } else if (options != null && typeof options.crossOrigin !== 'string') {
237 + console.error(
238 + 'ReactDOM.preconnect(): Expected the `crossOrigin` option (second argument) to be a string but encountered %s instead. Try removing this option or passing a string value instead.',
239 + getValueDescriptorExpectingObjectForWarning(options.crossOrigin),
240 + );
241 + }
242 + }
243 + const crossOrigin =
244 + options == null || typeof options.crossOrigin !== 'string'
245 + ? null
246 + : options.crossOrigin === 'use-credentials'
247 + ? 'use-credentials'
248 + : '';
249 + preconnectAs('preconnect', crossOrigin, href);
250 +}
251 +
252 // --------------------------------------
152 -// ReactDOM.Preload
253 +// ReactDOM.preload
254 // --------------------------------------
255 type PreloadAs = ResourceType;
256 type PreloadOptions = {as: PreloadAs, crossOrigin?: string, integrity?: string};
packages/react-dom-bindings/src/server/ReactDOMServerFormatConfig.js
+143 -2
@@ -86,6 +86,8 @@ const ReactDOMCurrentDispatcher = ReactDOMSharedInternals.Dispatcher;
86
87 const ReactDOMServerDispatcher = enableFloat
88 ? {
89 + prefetchDNS,
90 + preconnect,
91 preload,
92 preinit,
93 }
@@ -3464,6 +3466,10 @@ export function writePreamble(
3466 }
3467 charsetChunks.length = 0;
3468
3469 + // emit preconnect resources
3470 + resources.preconnects.forEach(flushResourceInPreamble, destination);
3471 + resources.preconnects.clear();
3472 +
3473 const preconnectChunks = responseState.preconnectChunks;
3474 for (i = 0; i < preconnectChunks.length; i++) {
3475 writeChunk(destination, preconnectChunks[i]);
@@ -3559,6 +3565,9 @@ export function writeHoistables(
3565 // We omit charsetChunks because we have already sent the shell and if it wasn't
3566 // already sent it is too late now.
3567
3568 + resources.preconnects.forEach(flushResourceLate, destination);
3569 + resources.preconnects.clear();
3570 +
3571 const preconnectChunks = responseState.preconnectChunks;
3572 for (i = 0; i < preconnectChunks.length; i++) {
3573 writeChunk(destination, preconnectChunks[i]);
@@ -4068,7 +4077,10 @@ const Blocked /* */ = 0b0100;
4077 // This generally only makes sense for Resources other than PreloadResource
4078 const PreloadFlushed /* */ = 0b1000;
4079
4071 -type TResource<T: 'stylesheet' | 'style' | 'script' | 'preload', P> = {
4080 +type TResource<
4081 + T: 'stylesheet' | 'style' | 'script' | 'preload' | 'preconnect',
4082 + P,
4083 +> = {
4084 type: T,
4085 chunks: Array<Chunk | PrecomputedChunk>,
4086 state: ResourceStateTag,
@@ -4099,6 +4111,13 @@ type ResourceDEV =
4111 | ImperativeResourceDEV
4112 | ImplicitResourceDEV;
4113
4114 +type PreconnectProps = {
4115 + rel: 'preconnect' | 'dns-prefetch',
4116 + href: string,
4117 + [string]: mixed,
4118 +};
4119 +type PreconnectResource = TResource<'preconnect', null>;
4120 +
4121 type PreloadProps = {
4122 rel: 'preload',
4123 as: string,
@@ -4131,15 +4150,21 @@ type ScriptProps = {
4150 };
4151 type ScriptResource = TResource<'script', null>;
4152
4134 -type Resource = StyleResource | ScriptResource | PreloadResource;
4153 +type Resource =
4154 + | StyleResource
4155 + | ScriptResource
4156 + | PreloadResource
4157 + | PreconnectResource;
4158
4159 export type Resources = {
4160 // Request local cache
4161 preloadsMap: Map<string, PreloadResource>,
4162 + preconnectsMap: Map<string, PreconnectResource>,
4163 stylesMap: Map<string, StyleResource>,
4164 scriptsMap: Map<string, ScriptResource>,
4165
4166 // Flushing queues for Resource dependencies
4167 + preconnects: Set<PreconnectResource>,
4168 fontPreloads: Set<PreloadResource>,
4169 // usedImagePreloads: Set<PreloadResource>,
4170 precedences: Map<string, Set<StyleResource>>,
@@ -4161,10 +4186,12 @@ export function createResources(): Resources {
4186 return {
4187 // persistent
4188 preloadsMap: new Map(),
4189 + preconnectsMap: new Map(),
4190 stylesMap: new Map(),
4191 scriptsMap: new Map(),
4192
4193 // cleared on flush
4194 + preconnects: new Set(),
4195 fontPreloads: new Set(),
4196 // usedImagePreloads: new Set(),
4197 precedences: new Map(),
@@ -4198,6 +4225,120 @@ function getResourceKey(as: string, href: string): string {
4225 return `[${as}]${href}`;
4226 }
4227
4228 +export function prefetchDNS(href: string, options?: mixed) {
4229 + if (!currentResources) {
4230 + // While we expect that preconnect calls are primarily going to be observed
4231 + // during render because effects and events don't run on the server it is
4232 + // still possible that these get called in module scope. This is valid on
4233 + // the client since there is still a document to interact with but on the
4234 + // server we need a request to associate the call to. Because of this we
4235 + // simply return and do not warn.
4236 + return;
4237 + }
4238 + const resources = currentResources;
4239 + if (__DEV__) {
4240 + if (typeof href !== 'string' || !href) {
4241 + console.error(
4242 + 'ReactDOM.prefetchDNS(): Expected the `href` argument (first) to be a non-empty string but encountered %s instead.',
4243 + getValueDescriptorExpectingObjectForWarning(href),
4244 + );
4245 + } else if (options != null) {
4246 + if (
4247 + typeof options === 'object' &&
4248 + options.hasOwnProperty('crossOrigin')
4249 + ) {
4250 + console.error(
4251 + 'ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered %s as a second argument instead. This argument is reserved for future options and is currently disallowed. It looks like the you are attempting to set a crossOrigin property for this DNS lookup hint. Browsers do not perform DNS queries using CORS and setting this attribute on the resource hint has no effect. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
4252 + getValueDescriptorExpectingEnumForWarning(options),
4253 + );
4254 + } else {
4255 + console.error(
4256 + 'ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered %s as a second argument instead. This argument is reserved for future options and is currently disallowed. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
4257 + getValueDescriptorExpectingEnumForWarning(options),
4258 + );
4259 + }
4260 + }
4261 + }
4262 +
4263 + if (typeof href === 'string' && href) {
4264 + const key = getResourceKey('prefetchDNS', href);
4265 + let resource = resources.preconnectsMap.get(key);
4266 + if (!resource) {
4267 + resource = {
4268 + type: 'preconnect',
4269 + chunks: [],
4270 + state: NoState,
4271 + props: null,
4272 + };
4273 + resources.preconnectsMap.set(key, resource);
4274 + pushLinkImpl(
4275 + resource.chunks,
4276 + ({href, rel: 'dns-prefetch'}: PreconnectProps),
4277 + );
4278 + }
4279 + resources.preconnects.add(resource);
4280 + }
4281 +}
4282 +
4283 +export function preconnect(href: string, options?: {crossOrigin?: string}) {
4284 + if (!currentResources) {
4285 + // While we expect that preconnect calls are primarily going to be observed
4286 + // during render because effects and events don't run on the server it is
4287 + // still possible that these get called in module scope. This is valid on
4288 + // the client since there is still a document to interact with but on the
4289 + // server we need a request to associate the call to. Because of this we
4290 + // simply return and do not warn.
4291 + return;
4292 + }
4293 + const resources = currentResources;
4294 + if (__DEV__) {
4295 + if (typeof href !== 'string' || !href) {
4296 + console.error(
4297 + 'ReactDOM.preconnect(): Expected the `href` argument (first) to be a non-empty string but encountered %s instead.',
4298 + getValueDescriptorExpectingObjectForWarning(href),
4299 + );
4300 + } else if (options != null && typeof options !== 'object') {
4301 + console.error(
4302 + 'ReactDOM.preconnect(): Expected the `options` argument (second) to be an object but encountered %s instead. The only supported option at this time is `crossOrigin` which accepts a string.',
4303 + getValueDescriptorExpectingEnumForWarning(options),
4304 + );
4305 + } else if (options != null && typeof options.crossOrigin !== 'string') {
4306 + console.error(
4307 + 'ReactDOM.preconnect(): Expected the `crossOrigin` option (second argument) to be a string but encountered %s instead. Try removing this option or passing a string value instead.',
4308 + getValueDescriptorExpectingObjectForWarning(options.crossOrigin),
4309 + );
4310 + }
4311 + }
4312 +
4313 + if (typeof href === 'string' && href) {
4314 + const crossOrigin =
4315 + options == null || typeof options.crossOrigin !== 'string'
4316 + ? null
4317 + : options.crossOrigin === 'use-credentials'
4318 + ? 'use-credentials'
4319 + : '';
4320 +
4321 + const key = `[preconnect][${
4322 + crossOrigin === null ? 'null' : crossOrigin
4323 + }]${href}`;
4324 + let resource = resources.preconnectsMap.get(key);
4325 + if (!resource) {
4326 + resource = {
4327 + type: 'preconnect',
4328 + chunks: [],
4329 + state: NoState,
4330 + props: null,
4331 + };
4332 + resources.preconnectsMap.set(key, resource);
4333 + pushLinkImpl(
4334 + resource.chunks,
4335 + ({rel: 'preconnect', href, crossOrigin}: PreconnectProps),
4336 + );
4337 + }
4338 + resources.preconnects.add(resource);
4339 + }
4340 +}
4341 +
4342 type PreloadAs = 'style' | 'font' | 'script';
4343 type PreloadOptions = {
4344 as: PreloadAs,
packages/react-dom-bindings/src/shared/ReactDOMFloat.js
+23 -3
@@ -1,11 +1,21 @@
1 import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals';
2
3 -export function preinit() {
3 +export function prefetchDNS() {
4 const dispatcher = ReactDOMSharedInternals.Dispatcher.current;
5 if (dispatcher) {
6 - dispatcher.preinit.apply(this, arguments);
6 + dispatcher.prefetchDNS.apply(this, arguments);
7 }
8 - // We don't error because preinit needs to be resilient to being called in a variety of scopes
8 + // We don't error because preconnect needs to be resilient to being called in a variety of scopes
9 + // and the runtime may not be capable of responding. The function is optimistic and not critical
10 + // so we favor silent bailout over warning or erroring.
11 +}
12 +
13 +export function preconnect() {
14 + const dispatcher = ReactDOMSharedInternals.Dispatcher.current;
15 + if (dispatcher) {
16 + dispatcher.preconnect.apply(this, arguments);
17 + }
18 + // We don't error because preconnect needs to be resilient to being called in a variety of scopes
19 // and the runtime may not be capable of responding. The function is optimistic and not critical
20 // so we favor silent bailout over warning or erroring.
21 }
@@ -19,3 +29,13 @@ export function preload() {
29 // and the runtime may not be capable of responding. The function is optimistic and not critical
30 // so we favor silent bailout over warning or erroring.
31 }
32 +
33 +export function preinit() {
34 + const dispatcher = ReactDOMSharedInternals.Dispatcher.current;
35 + if (dispatcher) {
36 + dispatcher.preinit.apply(this, arguments);
37 + }
38 + // We don't error because preinit needs to be resilient to being called in a variety of scopes
39 + // and the runtime may not be capable of responding. The function is optimistic and not critical
40 + // so we favor silent bailout over warning or erroring.
41 +}
packages/react-dom/index.classic.fb.js
+3 -1
@@ -32,8 +32,10 @@ export {
32 unstable_flushControlled,
33 unstable_renderSubtreeIntoContainer,
34 unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
35 - preinit,
35 + prefetchDNS,
36 + preconnect,
37 preload,
38 + preinit,
39 version,
40 } from './src/client/ReactDOM';
41
packages/react-dom/index.experimental.js
+3 -1
@@ -20,7 +20,9 @@ export {
20 unstable_batchedUpdates,
21 unstable_renderSubtreeIntoContainer,
22 unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
23 - preinit,
23 + prefetchDNS,
24 + preconnect,
25 preload,
26 + preinit,
27 version,
28 } from './src/client/ReactDOM';
packages/react-dom/index.js
+3 -1
@@ -24,7 +24,9 @@ export {
24 unstable_flushControlled,
25 unstable_renderSubtreeIntoContainer,
26 unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
27 - preinit,
27 + prefetchDNS,
28 + preconnect,
29 preload,
30 + preinit,
31 version,
32 } from './src/client/ReactDOM';
packages/react-dom/index.modern.fb.js
+3 -1
@@ -17,7 +17,9 @@ export {
17 unstable_createEventHandle,
18 unstable_flushControlled,
19 unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
20 - preinit,
20 + prefetchDNS,
21 + preconnect,
22 preload,
23 + preinit,
24 version,
25 } from './src/client/ReactDOM';
packages/react-dom/index.stable.js
+3 -1
@@ -19,7 +19,9 @@ export {
19 unmountComponentAtNode,
20 unstable_batchedUpdates,
21 unstable_renderSubtreeIntoContainer,
22 - preinit,
22 + prefetchDNS,
23 + preconnect,
24 preload,
25 + preinit,
26 version,
27 } from './src/client/ReactDOM';
packages/react-dom/src/__tests__/ReactDOMFloat-test.js
+141
@@ -2295,6 +2295,147 @@ body {
2295 );
2296 });
2297
2298 + describe('ReactDOM.prefetchDNS(href)', () => {
2299 + it('creates a dns-prefetch resource when called', async () => {
2300 + function App({url}) {
2301 + ReactDOM.prefetchDNS(url);
2302 + ReactDOM.prefetchDNS(url);
2303 + ReactDOM.prefetchDNS(url, {});
2304 + ReactDOM.prefetchDNS(url, {crossOrigin: 'use-credentials'});
2305 + return (
2306 + <html>
2307 + <body>hello world</body>
2308 + </html>
2309 + );
2310 + }
2311 +
2312 + await expect(async () => {
2313 + await actIntoEmptyDocument(() => {
2314 + renderToPipeableStream(<App url="foo" />).pipe(writable);
2315 + });
2316 + }).toErrorDev([
2317 + 'ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered something with type "object" as a second argument instead. This argument is reserved for future options and is currently disallowed. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
2318 + 'ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered something with type "object" as a second argument instead. This argument is reserved for future options and is currently disallowed. It looks like the you are attempting to set a crossOrigin property for this DNS lookup hint. Browsers do not perform DNS queries using CORS and setting this attribute on the resource hint has no effect. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
2319 + ]);
2320 +
2321 + expect(getMeaningfulChildren(document)).toEqual(
2322 + <html>
2323 + <head>
2324 + <link rel="dns-prefetch" href="foo" />
2325 + </head>
2326 + <body>hello world</body>
2327 + </html>,
2328 + );
2329 +
2330 + const root = ReactDOMClient.hydrateRoot(document, <App url="foo" />);
2331 + expect(() => {
2332 + expect(Scheduler).toFlushWithoutYielding();
2333 + }).toErrorDev([
2334 + 'ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered something with type "object" as a second argument instead. This argument is reserved for future options and is currently disallowed. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
2335 + 'ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered something with type "object" as a second argument instead. This argument is reserved for future options and is currently disallowed. It looks like the you are attempting to set a crossOrigin property for this DNS lookup hint. Browsers do not perform DNS queries using CORS and setting this attribute on the resource hint has no effect. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
2336 + ]);
2337 + expect(getMeaningfulChildren(document)).toEqual(
2338 + <html>
2339 + <head>
2340 + <link rel="dns-prefetch" href="foo" />
2341 + </head>
2342 + <body>hello world</body>
2343 + </html>,
2344 + );
2345 +
2346 + root.render(<App url="bar" />);
2347 + expect(() => {
2348 + expect(Scheduler).toFlushWithoutYielding();
2349 + }).toErrorDev([
2350 + 'ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered something with type "object" as a second argument instead. This argument is reserved for future options and is currently disallowed. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
2351 + 'ReactDOM.prefetchDNS(): Expected only one argument, `href`, but encountered something with type "object" as a second argument instead. This argument is reserved for future options and is currently disallowed. It looks like the you are attempting to set a crossOrigin property for this DNS lookup hint. Browsers do not perform DNS queries using CORS and setting this attribute on the resource hint has no effect. Try calling ReactDOM.prefetchDNS() with just a single string argument, `href`.',
2352 + ]);
2353 + expect(getMeaningfulChildren(document)).toEqual(
2354 + <html>
2355 + <head>
2356 + <link rel="dns-prefetch" href="foo" />
2357 + <link rel="dns-prefetch" href="bar" />
2358 + </head>
2359 + <body>hello world</body>
2360 + </html>,
2361 + );
2362 + });
2363 + });
2364 +
2365 + describe('ReactDOM.preconnect(href, { crossOrigin })', () => {
2366 + it('creates a preconnect resource when called', async () => {
2367 + function App({url}) {
2368 + ReactDOM.preconnect(url);
2369 + ReactDOM.preconnect(url);
2370 + ReactDOM.preconnect(url, {crossOrigin: true});
2371 + ReactDOM.preconnect(url, {crossOrigin: ''});
2372 + ReactDOM.preconnect(url, {crossOrigin: 'anonymous'});
2373 + ReactDOM.preconnect(url, {crossOrigin: 'use-credentials'});
2374 + return (
2375 + <html>
2376 + <body>hello world</body>
2377 + </html>
2378 + );
2379 + }
2380 +
2381 + await expect(async () => {
2382 + await actIntoEmptyDocument(() => {
2383 + renderToPipeableStream(<App url="foo" />).pipe(writable);
2384 + });
2385 + }).toErrorDev(
2386 + 'ReactDOM.preconnect(): Expected the `crossOrigin` option (second argument) to be a string but encountered something with type "boolean" instead. Try removing this option or passing a string value instead.',
2387 + );
2388 +
2389 + expect(getMeaningfulChildren(document)).toEqual(
2390 + <html>
2391 + <head>
2392 + <link rel="preconnect" href="foo" />
2393 + <link rel="preconnect" href="foo" crossorigin="" />
2394 + <link rel="preconnect" href="foo" crossorigin="use-credentials" />
2395 + </head>
2396 + <body>hello world</body>
2397 + </html>,
2398 + );
2399 +
2400 + const root = ReactDOMClient.hydrateRoot(document, <App url="foo" />);
2401 + expect(() => {
2402 + expect(Scheduler).toFlushWithoutYielding();
2403 + }).toErrorDev(
2404 + 'ReactDOM.preconnect(): Expected the `crossOrigin` option (second argument) to be a string but encountered something with type "boolean" instead. Try removing this option or passing a string value instead.',
2405 + );
2406 + expect(getMeaningfulChildren(document)).toEqual(
2407 + <html>
2408 + <head>
2409 + <link rel="preconnect" href="foo" />
2410 + <link rel="preconnect" href="foo" crossorigin="" />
2411 + <link rel="preconnect" href="foo" crossorigin="use-credentials" />
2412 + </head>
2413 + <body>hello world</body>
2414 + </html>,
2415 + );
2416 +
2417 + root.render(<App url="bar" />);
2418 + expect(() => {
2419 + expect(Scheduler).toFlushWithoutYielding();
2420 + }).toErrorDev(
2421 + 'ReactDOM.preconnect(): Expected the `crossOrigin` option (second argument) to be a string but encountered something with type "boolean" instead. Try removing this option or passing a string value instead.',
2422 + );
2423 + expect(getMeaningfulChildren(document)).toEqual(
2424 + <html>
2425 + <head>
2426 + <link rel="preconnect" href="foo" />
2427 + <link rel="preconnect" href="foo" crossorigin="" />
2428 + <link rel="preconnect" href="foo" crossorigin="use-credentials" />
2429 + <link rel="preconnect" href="bar" />
2430 + <link rel="preconnect" href="bar" crossorigin="" />
2431 + <link rel="preconnect" href="bar" crossorigin="use-credentials" />
2432 + </head>
2433 + <body>hello world</body>
2434 + </html>,
2435 + );
2436 + });
2437 + });
2438 +
2439 describe('ReactDOM.preload(href, { as: ... })', () => {
2440 // @gate enableFloat
2441 it('creates a preload resource when called', async () => {
packages/react-dom/src/client/ReactDOM.js
+6 -1
@@ -75,7 +75,12 @@ import {
75 } from 'react-dom-bindings/src/events/ReactDOMControlledComponent';
76 import Internals from '../ReactDOMSharedInternals';
77
78 -export {preinit, preload} from 'react-dom-bindings/src/shared/ReactDOMFloat';
78 +export {
79 + prefetchDNS,
80 + preconnect,
81 + preload,
82 + preinit,
83 +} from 'react-dom-bindings/src/shared/ReactDOMFloat';
84
85 setAttemptSynchronousHydration(attemptSynchronousHydration);
86 setAttemptDiscreteHydration(attemptDiscreteHydration);