20
import type {AncestorInfoDev} from './validateDOMNesting';
21
import type {FormStatus} from 'react-dom-bindings/src/shared/ReactDOMFormActions';
22
import type {
23
- PrefetchDNSOptions,
24
- PreconnectOptions,
25
- PreloadOptions,
26
- PreloadModuleOptions,
27
- PreinitOptions,
28
- PreinitModuleOptions,
23
+ CrossOriginEnum,
24
+ PreloadImplOptions,
25
+ PreloadModuleImplOptions,
26
+ PreinitStyleOptions,
27
+ PreinitScriptOptions,
28
+ PreinitModuleScriptOptions,
29
} from 'react-dom/src/shared/ReactDOMTypes';
30
31
import {NotPending} from 'react-dom-bindings/src/shared/ReactDOMFormActions';
107
HostSingleton,
108
} from 'react-reconciler/src/ReactWorkTags';
109
import {listenToAllSupportedEvents} from '../events/DOMPluginEventSystem';
110
-import {
111
- validatePreinitArguments,
112
- validateLinkPropsForStyleResource,
113
- getValueDescriptorExpectingObjectForWarning,
114
- getValueDescriptorExpectingEnumForWarning,
115
-} from '../shared/ReactDOMResourceValidation';
110
+import {validateLinkPropsForStyleResource} from '../shared/ReactDOMResourceValidation';
111
import escapeSelectorAttributeValueInsideDoubleQuotes from './escapeSelectorAttributeValueInsideDoubleQuotes';
112
113
export type Type = string;
2115
preconnect,
2116
preload,
2117
preloadModule,
2123
- preinit,
2124
- preinitModule,
2118
+ preinitStyle,
2119
+ preinitScript,
2120
+ preinitModuleScript,
2121
};
2122
2123
// We expect this to get inlined. It is a function mostly to communicate the special nature of
2132
2133
function preconnectAs(
2134
rel: 'preconnect' | 'dns-prefetch',
2139
- crossOrigin: null | '' | 'use-credentials',
2135
href: string,
2136
+ crossOrigin: ?CrossOriginEnum,
2137
) {
2138
const ownerDocument = getDocumentForImperativeFloatMethods();
2139
if (typeof href === 'string' && href) {
2157
}
2158
}
2159
2164
-function prefetchDNS(href: string, options?: ?PrefetchDNSOptions) {
2160
+function prefetchDNS(href: string) {
2161
if (!enableFloat) {
2162
return;
2163
}
2168
- if (__DEV__) {
2169
- if (typeof href !== 'string' || !href) {
2170
- console.error(
2171
- 'ReactDOM.prefetchDNS(): Expected the `href` argument (first) to be a non-empty string but encountered %s instead.',
2172
- getValueDescriptorExpectingObjectForWarning(href),
2173
- );
2174
- } else if (options != null) {
2175
- if (
2176
- typeof options === 'object' &&
2177
- hasOwnProperty.call(options, 'crossOrigin')
2178
- ) {
2179
- console.error(
2180
- '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`.',
2181
- getValueDescriptorExpectingEnumForWarning(options),
2182
- );
2183
- } else {
2184
- console.error(
2185
- '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`.',
2186
- getValueDescriptorExpectingEnumForWarning(options),
2187
- );
2188
- }
2189
- }
2190
- }
2191
- preconnectAs('dns-prefetch', null, href);
2164
+ preconnectAs('dns-prefetch', href, null);
2165
}
2166
2194
-function preconnect(href: string, options?: ?PreconnectOptions) {
2167
+function preconnect(href: string, crossOrigin?: ?CrossOriginEnum) {
2168
if (!enableFloat) {
2169
return;
2170
}
2198
- if (__DEV__) {
2199
- if (typeof href !== 'string' || !href) {
2200
- console.error(
2201
- 'ReactDOM.preconnect(): Expected the `href` argument (first) to be a non-empty string but encountered %s instead.',
2202
- getValueDescriptorExpectingObjectForWarning(href),
2203
- );
2204
- } else if (options != null && typeof options !== 'object') {
2205
- console.error(
2206
- '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.',
2207
- getValueDescriptorExpectingEnumForWarning(options),
2208
- );
2209
- } else if (options != null && typeof options.crossOrigin !== 'string') {
2210
- console.error(
2211
- '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.',
2212
- getValueDescriptorExpectingObjectForWarning(options.crossOrigin),
2213
- );
2214
- }
2215
- }
2216
- const crossOrigin =
2217
- options == null || typeof options.crossOrigin !== 'string'
2218
- ? null
2219
- : options.crossOrigin === 'use-credentials'
2220
- ? 'use-credentials'
2221
- : '';
2222
- preconnectAs('preconnect', crossOrigin, href);
2171
+ preconnectAs('preconnect', href, crossOrigin);
2172
}
2173
2225
-function preload(href: string, options: PreloadOptions) {
2174
+function preload(href: string, as: string, options?: ?PreloadImplOptions) {
2175
if (!enableFloat) {
2176
return;
2177
}
2229
- if (__DEV__) {
2230
- // TODO move this to ReactDOMFloat and expose a stricter function interface or possibly
2231
- // typed functions (preloadImage, preloadStyle, ...)
2232
- let encountered = '';
2233
- if (typeof href !== 'string' || !href) {
2234
- encountered += `The \`href\` argument encountered was ${getValueDescriptorExpectingObjectForWarning(
2235
- href,
2236
- )}.`;
2237
- }
2238
- if (options == null || typeof options !== 'object') {
2239
- encountered += `The \`options\` argument encountered was ${getValueDescriptorExpectingObjectForWarning(
2240
- options,
2241
- )}.`;
2242
- } else if (typeof options.as !== 'string' || !options.as) {
2243
- encountered += `The \`as\` option encountered was ${getValueDescriptorExpectingObjectForWarning(
2244
- options.as,
2245
- )}.`;
2246
- }
2247
- if (encountered) {
2248
- console.error(
2249
- 'ReactDOM.preload(): Expected two arguments, a non-empty `href` string and an `options` object with an `as` property valid for a `<link rel="preload" as="..." />` tag. %s',
2250
- encountered,
2251
- );
2252
- }
2253
- }
2178
const ownerDocument = getDocumentForImperativeFloatMethods();
2255
- if (
2256
- typeof href === 'string' &&
2257
- href &&
2258
- typeof options === 'object' &&
2259
- options !== null &&
2260
- typeof options.as === 'string' &&
2261
- options.as &&
2262
- ownerDocument
2263
- ) {
2264
- const as = options.as;
2179
+ if (href && as && ownerDocument) {
2180
let preloadSelector = `link[rel="preload"][as="${escapeSelectorAttributeValueInsideDoubleQuotes(
2181
as,
2182
)}"]`;
2183
if (as === 'image') {
2269
- const {imageSrcSet, imageSizes} = options;
2270
- if (typeof imageSrcSet === 'string' && imageSrcSet !== '') {
2184
+ if (options && options.imageSrcSet) {
2185
preloadSelector += `[imagesrcset="${escapeSelectorAttributeValueInsideDoubleQuotes(
2272
- imageSrcSet,
2186
+ options.imageSrcSet,
2187
)}"]`;
2274
- if (typeof imageSizes === 'string') {
2188
+ if (typeof options.imageSizes === 'string') {
2189
preloadSelector += `[imagesizes="${escapeSelectorAttributeValueInsideDoubleQuotes(
2276
- imageSizes,
2190
+ options.imageSizes,
2191
)}"]`;
2192
}
2193
} else {
2213
break;
2214
}
2215
if (!preloadPropsMap.has(key)) {
2302
- const preloadProps = preloadPropsFromPreloadOptions(href, as, options);
2216
+ const preloadProps = Object.assign(
2217
+ ({
2218
+ rel: 'preload',
2219
+ // There is a bug in Safari where imageSrcSet is not respected on preload links
2220
+ // so we omit the href here if we have imageSrcSet b/c safari will load the wrong image.
2221
+ // This harms older browers that do not support imageSrcSet by making their preloads not work
2222
+ // but this population is shrinking fast and is already small so we accept this tradeoff.
2223
+ href:
2224
+ as === 'image' && options && options.imageSrcSet ? undefined : href,
2225
+ as,
2226
+ }: PreloadProps),
2227
+ options,
2228
+ );
2229
preloadPropsMap.set(key, preloadProps);
2230
2231
if (null === ownerDocument.querySelector(preloadSelector)) {
2251
}
2252
}
2253
2328
-function preloadModule(href: string, options?: ?PreloadModuleOptions) {
2254
+function preloadModule(href: string, options?: ?PreloadModuleImplOptions) {
2255
if (!enableFloat) {
2256
return;
2257
}
2332
- if (__DEV__) {
2333
- let encountered = '';
2334
- if (typeof href !== 'string' || !href) {
2335
- encountered += ` The \`href\` argument encountered was ${getValueDescriptorExpectingObjectForWarning(
2336
- href,
2337
- )}.`;
2338
- }
2339
- if (options !== undefined && typeof options !== 'object') {
2340
- encountered += ` The \`options\` argument encountered was ${getValueDescriptorExpectingObjectForWarning(
2341
- options,
2342
- )}.`;
2343
- } else if (options && 'as' in options && typeof options.as !== 'string') {
2344
- encountered += ` The \`as\` option encountered was ${getValueDescriptorExpectingObjectForWarning(
2345
- options.as,
2346
- )}.`;
2347
- }
2348
- if (encountered) {
2349
- console.error(
2350
- 'ReactDOM.preloadModule(): Expected two arguments, a non-empty `href` string and, optionally, an `options` object with an `as` property valid for a `<link rel="modulepreload" as="..." />` tag.%s',
2351
- encountered,
2352
- );
2353
- }
2354
- }
2258
const ownerDocument = getDocumentForImperativeFloatMethods();
2356
- if (typeof href === 'string' && href) {
2259
+ if (href) {
2260
const as =
2261
options && typeof options.as === 'string' ? options.as : 'script';
2262
const preloadSelector = `link[rel="modulepreload"][as="${escapeSelectorAttributeValueInsideDoubleQuotes(
2279
}
2280
2281
if (!preloadPropsMap.has(key)) {
2379
- const preloadProps = preloadModulePropsFromPreloadModuleOptions(
2380
- href,
2381
- as,
2282
+ const props: PreloadModuleProps = Object.assign(
2283
+ ({
2284
+ rel: 'modulepreload',
2285
+ href,
2286
+ }: PreloadModuleProps),
2287
options,
2288
);
2384
- preloadPropsMap.set(key, preloadProps);
2289
+ preloadPropsMap.set(key, props);
2290
2291
if (null === ownerDocument.querySelector(preloadSelector)) {
2292
switch (as) {
2302
}
2303
}
2304
const instance = ownerDocument.createElement('link');
2400
- setInitialProperties(instance, 'link', preloadProps);
2305
+ setInitialProperties(instance, 'link', props);
2306
markNodeAsHoistable(instance);
2307
(ownerDocument.head: any).appendChild(instance);
2308
}
2310
}
2311
}
2312
2408
-function preloadPropsFromPreloadOptions(
2313
+function preinitStyle(
2314
href: string,
2410
- as: string,
2411
- options: PreloadOptions,
2412
-): PreloadProps {
2413
- return {
2414
- rel: 'preload',
2415
- as,
2416
- // There is a bug in Safari where imageSrcSet is not respected on preload links
2417
- // so we omit the href here if we have imageSrcSet b/c safari will load the wrong image.
2418
- // This harms older browers that do not support imageSrcSet by making their preloads not work
2419
- // but this population is shrinking fast and is already small so we accept this tradeoff.
2420
- href: as === 'image' && options.imageSrcSet ? undefined : href,
2421
- crossOrigin: as === 'font' ? '' : options.crossOrigin,
2422
- integrity: options.integrity,
2423
- type: options.type,
2424
- nonce: options.nonce,
2425
- fetchPriority: options.fetchPriority,
2426
- imageSrcSet: options.imageSrcSet,
2427
- imageSizes: options.imageSizes,
2428
- referrerPolicy: options.referrerPolicy,
2429
- };
2430
-}
2431
-
2432
-function preloadModulePropsFromPreloadModuleOptions(
2433
- href: string,
2434
- as: string,
2435
- options: ?PreloadModuleOptions,
2436
-): PreloadModuleProps {
2437
- return {
2438
- rel: 'modulepreload',
2439
- as: as !== 'script' ? as : undefined,
2440
- href,
2441
- crossOrigin: options ? options.crossOrigin : undefined,
2442
- integrity: options ? options.integrity : undefined,
2443
- };
2444
-}
2445
-
2446
-function preinit(href: string, options: PreinitOptions) {
2315
+ precedence: ?string,
2316
+ options?: ?PreinitStyleOptions,
2317
+) {
2318
if (!enableFloat) {
2319
return;
2320
}
2450
- if (__DEV__) {
2451
- validatePreinitArguments(href, options);
2452
- }
2321
const ownerDocument = getDocumentForImperativeFloatMethods();
2322
2455
- if (
2456
- typeof href === 'string' &&
2457
- href &&
2458
- typeof options === 'object' &&
2459
- options !== null
2460
- ) {
2461
- const as = options.as;
2462
-
2463
- switch (as) {
2464
- case 'style': {
2465
- const styles = getResourcesFromRoot(ownerDocument).hoistableStyles;
2466
-
2467
- const key = getStyleKey(href);
2468
- const precedence = options.precedence || 'default';
2323
+ if (href) {
2324
+ const styles = getResourcesFromRoot(ownerDocument).hoistableStyles;
2325
2470
- // Check if this resource already exists
2471
- let resource = styles.get(key);
2472
- if (resource) {
2473
- // We can early return. The resource exists and there is nothing
2474
- // more to do
2475
- return;
2476
- }
2326
+ const key = getStyleKey(href);
2327
+ precedence = precedence || 'default';
2328
2478
- const state = {
2479
- loading: NotLoaded,
2480
- preload: null,
2481
- };
2329
+ // Check if this resource already exists
2330
+ let resource = styles.get(key);
2331
+ if (resource) {
2332
+ // We can early return. The resource exists and there is nothing
2333
+ // more to do
2334
+ return;
2335
+ }
2336
2483
- // Attempt to hydrate instance from DOM
2484
- let instance: null | Instance = ownerDocument.querySelector(
2485
- getStylesheetSelectorFromKey(key),
2486
- );
2487
- if (instance) {
2488
- state.loading = Loaded;
2489
- } else {
2490
- // Construct a new instance and insert it
2491
- const stylesheetProps = stylesheetPropsFromPreinitOptions(
2492
- href,
2493
- precedence,
2494
- options,
2495
- );
2496
- const preloadProps = preloadPropsMap.get(key);
2497
- if (preloadProps) {
2498
- adoptPreloadPropsForStylesheet(stylesheetProps, preloadProps);
2499
- }
2500
- const link = (instance = ownerDocument.createElement('link'));
2501
- markNodeAsHoistable(link);
2502
- setInitialProperties(link, 'link', stylesheetProps);
2503
-
2504
- (link: any)._p = new Promise((resolve, reject) => {
2505
- link.onload = resolve;
2506
- link.onerror = reject;
2507
- });
2508
- link.addEventListener('load', () => {
2509
- state.loading |= Loaded;
2510
- });
2511
- link.addEventListener('error', () => {
2512
- state.loading |= Errored;
2513
- });
2514
-
2515
- state.loading |= Inserted;
2516
- insertStylesheet(instance, precedence, ownerDocument);
2517
- }
2337
+ const state = {
2338
+ loading: NotLoaded,
2339
+ preload: null,
2340
+ };
2341
2519
- // Construct a Resource and cache it
2520
- resource = {
2521
- type: 'stylesheet',
2522
- instance,
2523
- count: 1,
2524
- state,
2525
- };
2526
- styles.set(key, resource);
2527
- return;
2342
+ // Attempt to hydrate instance from DOM
2343
+ let instance: null | Instance = ownerDocument.querySelector(
2344
+ getStylesheetSelectorFromKey(key),
2345
+ );
2346
+ if (instance) {
2347
+ state.loading = Loaded;
2348
+ } else {
2349
+ // Construct a new instance and insert it
2350
+ const stylesheetProps = Object.assign(
2351
+ ({
2352
+ rel: 'stylesheet',
2353
+ href,
2354
+ 'data-precedence': precedence,
2355
+ }: StylesheetProps),
2356
+ options,
2357
+ );
2358
+ const preloadProps = preloadPropsMap.get(key);
2359
+ if (preloadProps) {
2360
+ adoptPreloadPropsForStylesheet(stylesheetProps, preloadProps);
2361
}
2529
- case 'script': {
2530
- const src = href;
2531
- const scripts = getResourcesFromRoot(ownerDocument).hoistableScripts;
2362
+ const link = (instance = ownerDocument.createElement('link'));
2363
+ markNodeAsHoistable(link);
2364
+ setInitialProperties(link, 'link', stylesheetProps);
2365
2533
- const key = getScriptKey(src);
2534
-
2535
- // Check if this resource already exists
2536
- let resource = scripts.get(key);
2537
- if (resource) {
2538
- // We can early return. The resource exists and there is nothing
2539
- // more to do
2540
- return;
2541
- }
2542
-
2543
- // Attempt to hydrate instance from DOM
2544
- let instance: null | Instance = ownerDocument.querySelector(
2545
- getScriptSelectorFromKey(key),
2546
- );
2547
- if (!instance) {
2548
- // Construct a new instance and insert it
2549
- const scriptProps = scriptPropsFromPreinitOptions(src, options);
2550
- // Adopt certain preload props
2551
- const preloadProps = preloadPropsMap.get(key);
2552
- if (preloadProps) {
2553
- adoptPreloadPropsForScript(scriptProps, preloadProps);
2554
- }
2555
- instance = ownerDocument.createElement('script');
2556
- markNodeAsHoistable(instance);
2557
- setInitialProperties(instance, 'link', scriptProps);
2558
- (ownerDocument.head: any).appendChild(instance);
2559
- }
2366
+ (link: any)._p = new Promise((resolve, reject) => {
2367
+ link.onload = resolve;
2368
+ link.onerror = reject;
2369
+ });
2370
+ link.addEventListener('load', () => {
2371
+ state.loading |= Loaded;
2372
+ });
2373
+ link.addEventListener('error', () => {
2374
+ state.loading |= Errored;
2375
+ });
2376
2561
- // Construct a Resource and cache it
2562
- resource = {
2563
- type: 'script',
2564
- instance,
2565
- count: 1,
2566
- state: null,
2567
- };
2568
- scripts.set(key, resource);
2569
- return;
2570
- }
2377
+ state.loading |= Inserted;
2378
+ insertStylesheet(instance, precedence, ownerDocument);
2379
}
2380
+
2381
+ // Construct a Resource and cache it
2382
+ resource = {
2383
+ type: 'stylesheet',
2384
+ instance,
2385
+ count: 1,
2386
+ state,
2387
+ };
2388
+ styles.set(key, resource);
2389
+ return;
2390
}
2391
}
2392
2575
-function preinitModule(href: string, options?: ?PreinitModuleOptions) {
2393
+function preinitScript(src: string, options?: ?PreinitScriptOptions) {
2394
if (!enableFloat) {
2395
return;
2396
}
2579
- if (__DEV__) {
2580
- let encountered = '';
2581
- if (typeof href !== 'string' || !href) {
2582
- encountered += ` The \`href\` argument encountered was ${getValueDescriptorExpectingObjectForWarning(
2583
- href,
2584
- )}.`;
2397
+ const ownerDocument = getDocumentForImperativeFloatMethods();
2398
+
2399
+ if (src) {
2400
+ const scripts = getResourcesFromRoot(ownerDocument).hoistableScripts;
2401
+
2402
+ const key = getScriptKey(src);
2403
+
2404
+ // Check if this resource already exists
2405
+ let resource = scripts.get(key);
2406
+ if (resource) {
2407
+ // We can early return. The resource exists and there is nothing
2408
+ // more to do
2409
+ return;
2410
}
2586
- if (options !== undefined && typeof options !== 'object') {
2587
- encountered += ` The \`options\` argument encountered was ${getValueDescriptorExpectingObjectForWarning(
2411
+
2412
+ // Attempt to hydrate instance from DOM
2413
+ let instance: null | Instance = ownerDocument.querySelector(
2414
+ getScriptSelectorFromKey(key),
2415
+ );
2416
+ if (!instance) {
2417
+ // Construct a new instance and insert it
2418
+ const scriptProps = Object.assign(
2419
+ ({
2420
+ src,
2421
+ async: true,
2422
+ }: ScriptProps),
2423
options,
2589
- )}.`;
2590
- } else if (options && 'as' in options && options.as !== 'script') {
2591
- encountered += ` The \`as\` option encountered was ${getValueDescriptorExpectingEnumForWarning(
2592
- options.as,
2593
- )}.`;
2594
- }
2595
- if (encountered) {
2596
- console.error(
2597
- 'ReactDOM.preinitModule(): Expected up to two arguments, a non-empty `href` string and, optionally, an `options` object with a valid `as` property.%s',
2598
- encountered,
2424
);
2600
- } else {
2601
- const as =
2602
- options && typeof options.as === 'string' ? options.as : 'script';
2603
- switch (as) {
2604
- case 'script': {
2605
- break;
2606
- }
2607
-
2608
- // We have an invalid as type and need to warn
2609
- default: {
2610
- const typeOfAs = getValueDescriptorExpectingEnumForWarning(as);
2611
- console.error(
2612
- 'ReactDOM.preinitModule(): Currently the only supported "as" type for this function is "script"' +
2613
- ' but received "%s" instead. This warning was generated for `href` "%s". In the future other' +
2614
- ' module types will be supported, aligning with the import-attributes proposal. Learn more here:' +
2615
- ' (https://github.com/tc39/proposal-import-attributes)',
2616
- typeOfAs,
2617
- href,
2618
- );
2619
- }
2425
+ // Adopt certain preload props
2426
+ const preloadProps = preloadPropsMap.get(key);
2427
+ if (preloadProps) {
2428
+ adoptPreloadPropsForScript(scriptProps, preloadProps);
2429
}
2430
+ instance = ownerDocument.createElement('script');
2431
+ markNodeAsHoistable(instance);
2432
+ setInitialProperties(instance, 'link', scriptProps);
2433
+ (ownerDocument.head: any).appendChild(instance);
2434
}
2622
- }
2623
- const ownerDocument = getDocumentForImperativeFloatMethods();
2435
2625
- if (typeof href === 'string' && href) {
2626
- const as =
2627
- options && typeof options.as === 'string' ? options.as : 'script';
2436
+ // Construct a Resource and cache it
2437
+ resource = {
2438
+ type: 'script',
2439
+ instance,
2440
+ count: 1,
2441
+ state: null,
2442
+ };
2443
+ scripts.set(key, resource);
2444
+ return;
2445
+ }
2446
+}
2447
2629
- switch (as) {
2630
- case 'script': {
2631
- const src = href;
2632
- const scripts = getResourcesFromRoot(ownerDocument).hoistableScripts;
2448
+function preinitModuleScript(
2449
+ src: string,
2450
+ options?: ?PreinitModuleScriptOptions,
2451
+) {
2452
+ if (!enableFloat) {
2453
+ return;
2454
+ }
2455
+ const ownerDocument = getDocumentForImperativeFloatMethods();
2456
2634
- const key = getScriptKey(src);
2457
+ if (src) {
2458
+ const scripts = getResourcesFromRoot(ownerDocument).hoistableScripts;
2459
2636
- // Check if this resource already exists
2637
- let resource = scripts.get(key);
2638
- if (resource) {
2639
- // We can early return. The resource exists and there is nothing
2640
- // more to do
2641
- return;
2642
- }
2460
+ const key = getScriptKey(src);
2461
2644
- // Attempt to hydrate instance from DOM
2645
- let instance: null | Instance = ownerDocument.querySelector(
2646
- getScriptSelectorFromKey(key),
2647
- );
2648
- if (!instance) {
2649
- // Construct a new instance and insert it
2650
- const scriptProps = modulePropsFromPreinitModuleOptions(src, options);
2651
- // Adopt certain preload props
2652
- const preloadProps = preloadPropsMap.get(key);
2653
- if (preloadProps) {
2654
- adoptPreloadPropsForScript(scriptProps, preloadProps);
2655
- }
2656
- instance = ownerDocument.createElement('script');
2657
- markNodeAsHoistable(instance);
2658
- setInitialProperties(instance, 'link', scriptProps);
2659
- (ownerDocument.head: any).appendChild(instance);
2660
- }
2462
+ // Check if this resource already exists
2463
+ let resource = scripts.get(key);
2464
+ if (resource) {
2465
+ // We can early return. The resource exists and there is nothing
2466
+ // more to do
2467
+ return;
2468
+ }
2469
2662
- // Construct a Resource and cache it
2663
- resource = {
2664
- type: 'script',
2665
- instance,
2666
- count: 1,
2667
- state: null,
2668
- };
2669
- scripts.set(key, resource);
2670
- return;
2470
+ // Attempt to hydrate instance from DOM
2471
+ let instance: null | Instance = ownerDocument.querySelector(
2472
+ getScriptSelectorFromKey(key),
2473
+ );
2474
+ if (!instance) {
2475
+ // Construct a new instance and insert it
2476
+ const scriptProps = Object.assign(
2477
+ ({
2478
+ src,
2479
+ async: true,
2480
+ type: 'module',
2481
+ }: ScriptProps),
2482
+ options,
2483
+ );
2484
+ // Adopt certain preload props
2485
+ const preloadProps = preloadPropsMap.get(key);
2486
+ if (preloadProps) {
2487
+ adoptPreloadPropsForScript(scriptProps, preloadProps);
2488
}
2489
+ instance = ownerDocument.createElement('script');
2490
+ markNodeAsHoistable(instance);
2491
+ setInitialProperties(instance, 'link', scriptProps);
2492
+ (ownerDocument.head: any).appendChild(instance);
2493
}
2673
- }
2674
-}
2675
-
2676
-function stylesheetPropsFromPreinitOptions(
2677
- href: string,
2678
- precedence: string,
2679
- options: PreinitOptions,
2680
-): StylesheetProps {
2681
- return {
2682
- rel: 'stylesheet',
2683
- href,
2684
- 'data-precedence': precedence,
2685
- crossOrigin: options.crossOrigin,
2686
- integrity: options.integrity,
2687
- fetchPriority: options.fetchPriority,
2688
- };
2689
-}
2690
-
2691
-function scriptPropsFromPreinitOptions(
2692
- src: string,
2693
- options: PreinitOptions,
2694
-): ScriptProps {
2695
- return {
2696
- src,
2697
- async: true,
2698
- crossOrigin: options.crossOrigin,
2699
- integrity: options.integrity,
2700
- nonce: options.nonce,
2701
- fetchPriority: options.fetchPriority,
2702
- };
2703
-}
2494
2705
-function modulePropsFromPreinitModuleOptions(
2706
- src: string,
2707
- options: ?PreinitModuleOptions,
2708
-): ScriptProps {
2709
- return {
2710
- src,
2711
- async: true,
2712
- type: 'module',
2713
- crossOrigin: options ? options.crossOrigin : undefined,
2714
- integrity: options ? options.integrity : undefined,
2715
- };
2495
+ // Construct a Resource and cache it
2496
+ resource = {
2497
+ type: 'script',
2498
+ instance,
2499
+ count: 1,
2500
+ state: null,
2501
+ };
2502
+ scripts.set(key, resource);
2503
+ return;
2504
+ }
2505
}
2506
2507
type StyleTagQualifyingProps = {