[Float] Support `fetchPriority` for module resources (#36835)
Alexander Lichter committed
Jun 24, 2026 at 19:41 UTC
39c2c1d45001021a99f507f83b946c6b08c8bbdf
4 files changed
+263
-16
packages/react-dom/src/__tests__/ReactDOMFloat-test.js
+167
@@ -6532,6 +6532,67 @@ body {
6532
);
6533
});
6534
6535
+ it('supports fetchPriority', async () => {
6536
+ function Component({isServer}) {
6537
+ const suffix = isServer ? 'server' : 'client';
6538
+ ReactDOM.preloadModule('high' + suffix, {
6539
+ fetchPriority: 'high',
6540
+ });
6541
+ ReactDOM.preloadModule('low' + suffix, {
6542
+ fetchPriority: 'low',
6543
+ });
6544
+ ReactDOM.preloadModule('auto' + suffix, {
6545
+ fetchPriority: 'auto',
6546
+ });
6547
+ return 'hello';
6548
+ }
6549
+
6550
+ await act(() => {
6551
+ renderToPipeableStream(
6552
+ <html>
6553
+ <body>
6554
+ <Component isServer={true} />
6555
+ </body>
6556
+ </html>,
6557
+ ).pipe(writable);
6558
+ });
6559
+
6560
+ expect(getMeaningfulChildren(document)).toEqual(
6561
+ <html>
6562
+ <head>
6563
+ <link rel="modulepreload" href="highserver" fetchpriority="high" />
6564
+ <link rel="modulepreload" href="lowserver" fetchpriority="low" />
6565
+ <link rel="modulepreload" href="autoserver" fetchpriority="auto" />
6566
+ </head>
6567
+ <body>hello</body>
6568
+ </html>,
6569
+ );
6570
+
6571
+ ReactDOMClient.hydrateRoot(
6572
+ document,
6573
+ <html>
6574
+ <body>
6575
+ <Component />
6576
+ </body>
6577
+ </html>,
6578
+ );
6579
+ await waitForAll([]);
6580
+
6581
+ expect(getMeaningfulChildren(document)).toEqual(
6582
+ <html>
6583
+ <head>
6584
+ <link rel="modulepreload" href="highserver" fetchpriority="high" />
6585
+ <link rel="modulepreload" href="lowserver" fetchpriority="low" />
6586
+ <link rel="modulepreload" href="autoserver" fetchpriority="auto" />
6587
+ <link rel="modulepreload" href="highclient" fetchpriority="high" />
6588
+ <link rel="modulepreload" href="lowclient" fetchpriority="low" />
6589
+ <link rel="modulepreload" href="autoclient" fetchpriority="auto" />
6590
+ </head>
6591
+ <body>hello</body>
6592
+ </html>,
6593
+ );
6594
+ });
6595
+
6596
it('preloads multiple non-script modules with the same as type', async () => {
6597
function App() {
6598
ReactDOM.preloadModule('serviceworker one', {as: 'serviceworker'});
@@ -7266,6 +7327,112 @@ body {
7327
);
7328
});
7329
7330
+ it('supports fetchPriority', async () => {
7331
+ function Component({isServer}) {
7332
+ const suffix = isServer ? 'server' : 'client';
7333
+ ReactDOM.preinitModule('high' + suffix, {
7334
+ fetchPriority: 'high',
7335
+ });
7336
+ ReactDOM.preinitModule('low' + suffix, {
7337
+ fetchPriority: 'low',
7338
+ });
7339
+ ReactDOM.preinitModule('auto' + suffix, {
7340
+ fetchPriority: 'auto',
7341
+ });
7342
+ return 'hello';
7343
+ }
7344
+
7345
+ await act(() => {
7346
+ renderToPipeableStream(
7347
+ <html>
7348
+ <body>
7349
+ <Component isServer={true} />
7350
+ </body>
7351
+ </html>,
7352
+ ).pipe(writable);
7353
+ });
7354
+
7355
+ expect(getMeaningfulChildren(document)).toEqual(
7356
+ <html>
7357
+ <head>
7358
+ <script
7359
+ type="module"
7360
+ src="highserver"
7361
+ fetchpriority="high"
7362
+ async=""
7363
+ />
7364
+ <script
7365
+ type="module"
7366
+ src="lowserver"
7367
+ fetchpriority="low"
7368
+ async=""
7369
+ />
7370
+ <script
7371
+ type="module"
7372
+ src="autoserver"
7373
+ fetchpriority="auto"
7374
+ async=""
7375
+ />
7376
+ </head>
7377
+ <body>hello</body>
7378
+ </html>,
7379
+ );
7380
+
7381
+ ReactDOMClient.hydrateRoot(
7382
+ document,
7383
+ <html>
7384
+ <body>
7385
+ <Component />
7386
+ </body>
7387
+ </html>,
7388
+ );
7389
+ await waitForAll([]);
7390
+
7391
+ expect(getMeaningfulChildren(document)).toEqual(
7392
+ <html>
7393
+ <head>
7394
+ <script
7395
+ type="module"
7396
+ src="highserver"
7397
+ fetchpriority="high"
7398
+ async=""
7399
+ />
7400
+ <script
7401
+ type="module"
7402
+ src="lowserver"
7403
+ fetchpriority="low"
7404
+ async=""
7405
+ />
7406
+ <script
7407
+ type="module"
7408
+ src="autoserver"
7409
+ fetchpriority="auto"
7410
+ async=""
7411
+ />
7412
+ <script
7413
+ type="module"
7414
+ src="highclient"
7415
+ fetchpriority="high"
7416
+ async=""
7417
+ />
7418
+ <script
7419
+ type="module"
7420
+ src="lowclient"
7421
+ fetchpriority="low"
7422
+ async=""
7423
+ />
7424
+ <script
7425
+ type="module"
7426
+ src="autoclient"
7427
+ fetchpriority="auto"
7428
+ async=""
7429
+ />
7430
+ </head>
7431
+ <body>hello</body>
7432
+ </html>,
7433
+ );
7434
+ });
7435
+
7436
it('warns if you provide invalid arguments', async () => {
7437
function App() {
7438
ReactDOM.preinitModule();
packages/react-dom/src/shared/ReactDOMFloat.js
+8
@@ -192,6 +192,10 @@ export function preloadModule(href: string, options?: ?PreloadModuleOptions) {
192
typeof options.integrity === 'string'
193
? options.integrity
194
: undefined,
195
+ fetchPriority:
196
+ typeof options.fetchPriority === 'string'
197
+ ? options.fetchPriority
198
+ : undefined,
199
});
200
} else {
201
ReactDOMSharedInternals.d /* ReactDOMCurrentDispatcher */
@@ -321,6 +325,10 @@ export function preinitModule(href: string, options?: ?PreinitModuleOptions) {
325
: undefined,
326
nonce:
327
typeof options.nonce === 'string' ? options.nonce : undefined,
328
+ fetchPriority:
329
+ typeof options.fetchPriority === 'string'
330
+ ? options.fetchPriority
331
+ : undefined,
332
});
333
}
334
} else if (options == null) {
packages/react-dom/src/shared/ReactDOMTypes.js
+4
@@ -28,6 +28,7 @@ export type PreloadModuleOptions = {
28
crossOrigin?: string,
29
integrity?: string,
30
nonce?: string,
31
+ fetchPriority?: FetchPriorityEnum,
32
};
33
export type PreinitOptions = {
34
as: string,
@@ -42,6 +43,7 @@ export type PreinitModuleOptions = {
43
crossOrigin?: string,
44
integrity?: string,
45
nonce?: string,
46
+ fetchPriority?: FetchPriorityEnum,
47
};
48
49
export type CrossOriginEnum = '' | 'use-credentials' | CrossOriginString;
@@ -63,6 +65,7 @@ export type PreloadModuleImplOptions = {
65
crossOrigin?: ?CrossOriginEnum,
66
integrity?: ?string,
67
nonce?: ?string,
68
+ fetchPriority?: ?string,
69
};
70
export type PreinitStyleOptions = {
71
crossOrigin?: ?CrossOriginEnum,
@@ -79,6 +82,7 @@ export type PreinitModuleScriptOptions = {
82
crossOrigin?: ?CrossOriginEnum,
83
integrity?: ?string,
84
nonce?: ?string,
85
+ fetchPriority?: ?string,
86
};
87
88
export type HostDispatcher = {
packages/react-server-dom-webpack/src/__tests__/ReactFlightDOM-test.js
+84
-16
@@ -1704,20 +1704,32 @@ describe('ReactFlightDOM', () => {
1704
FlightReactDOM.preconnect('c2 before', {crossOrigin: 'anonymous'});
1705
FlightReactDOM.preload('l before', {as: 'style'});
1706
FlightReactDOM.preloadModule('lm before');
1707
- FlightReactDOM.preloadModule('lm2 before', {crossOrigin: 'anonymous'});
1707
+ FlightReactDOM.preloadModule('lm2 before', {
1708
+ crossOrigin: 'anonymous',
1709
+ fetchPriority: 'low',
1710
+ });
1711
FlightReactDOM.preinit('i before', {as: 'script'});
1712
FlightReactDOM.preinitModule('m before');
1710
- FlightReactDOM.preinitModule('m2 before', {crossOrigin: 'anonymous'});
1713
+ FlightReactDOM.preinitModule('m2 before', {
1714
+ crossOrigin: 'anonymous',
1715
+ fetchPriority: 'high',
1716
+ });
1717
await 1;
1718
FlightReactDOM.prefetchDNS('d after');
1719
FlightReactDOM.preconnect('c after');
1720
FlightReactDOM.preconnect('c2 after', {crossOrigin: 'anonymous'});
1721
FlightReactDOM.preload('l after', {as: 'style'});
1722
FlightReactDOM.preloadModule('lm after');
1717
- FlightReactDOM.preloadModule('lm2 after', {crossOrigin: 'anonymous'});
1723
+ FlightReactDOM.preloadModule('lm2 after', {
1724
+ crossOrigin: 'anonymous',
1725
+ fetchPriority: 'low',
1726
+ });
1727
FlightReactDOM.preinit('i after', {as: 'script'});
1728
FlightReactDOM.preinitModule('m after');
1720
- FlightReactDOM.preinitModule('m2 after', {crossOrigin: 'anonymous'});
1729
+ FlightReactDOM.preinitModule('m2 after', {
1730
+ crossOrigin: 'anonymous',
1731
+ fetchPriority: 'high',
1732
+ });
1733
return <ClientComponent />;
1734
}
1735
@@ -1766,19 +1778,41 @@ describe('ReactFlightDOM', () => {
1778
<link rel="preconnect" href="c2 before" crossorigin="" />
1779
<link rel="preload" as="style" href="l before" />
1780
<link rel="modulepreload" href="lm before" />
1769
- <link rel="modulepreload" href="lm2 before" crossorigin="" />
1781
+ <link
1782
+ rel="modulepreload"
1783
+ href="lm2 before"
1784
+ crossorigin=""
1785
+ fetchpriority="low"
1786
+ />
1787
<script async="" src="i before" />
1788
<script type="module" async="" src="m before" />
1772
- <script type="module" async="" src="m2 before" crossorigin="" />
1789
+ <script
1790
+ type="module"
1791
+ async=""
1792
+ src="m2 before"
1793
+ crossorigin=""
1794
+ fetchpriority="high"
1795
+ />
1796
<link rel="dns-prefetch" href="d after" />
1797
<link rel="preconnect" href="c after" />
1798
<link rel="preconnect" href="c2 after" crossorigin="" />
1799
<link rel="preload" as="style" href="l after" />
1800
<link rel="modulepreload" href="lm after" />
1778
- <link rel="modulepreload" href="lm2 after" crossorigin="" />
1801
+ <link
1802
+ rel="modulepreload"
1803
+ href="lm2 after"
1804
+ crossorigin=""
1805
+ fetchpriority="low"
1806
+ />
1807
<script async="" src="i after" />
1808
<script type="module" async="" src="m after" />
1781
- <script type="module" async="" src="m2 after" crossorigin="" />
1809
+ <script
1810
+ type="module"
1811
+ async=""
1812
+ src="m2 after"
1813
+ crossorigin=""
1814
+ fetchpriority="high"
1815
+ />
1816
</head>
1817
<body />
1818
</html>,
@@ -1799,20 +1833,32 @@ describe('ReactFlightDOM', () => {
1833
FlightReactDOM.preconnect('c2 before', {crossOrigin: 'anonymous'});
1834
FlightReactDOM.preload('l before', {as: 'style'});
1835
FlightReactDOM.preloadModule('lm before');
1802
- FlightReactDOM.preloadModule('lm2 before', {crossOrigin: 'anonymous'});
1836
+ FlightReactDOM.preloadModule('lm2 before', {
1837
+ crossOrigin: 'anonymous',
1838
+ fetchPriority: 'low',
1839
+ });
1840
FlightReactDOM.preinit('i before', {as: 'script'});
1841
FlightReactDOM.preinitModule('m before');
1805
- FlightReactDOM.preinitModule('m2 before', {crossOrigin: 'anonymous'});
1842
+ FlightReactDOM.preinitModule('m2 before', {
1843
+ crossOrigin: 'anonymous',
1844
+ fetchPriority: 'high',
1845
+ });
1846
await 1;
1847
FlightReactDOM.prefetchDNS('d after');
1848
FlightReactDOM.preconnect('c after');
1849
FlightReactDOM.preconnect('c2 after', {crossOrigin: 'anonymous'});
1850
FlightReactDOM.preload('l after', {as: 'style'});
1851
FlightReactDOM.preloadModule('lm after');
1812
- FlightReactDOM.preloadModule('lm2 after', {crossOrigin: 'anonymous'});
1852
+ FlightReactDOM.preloadModule('lm2 after', {
1853
+ crossOrigin: 'anonymous',
1854
+ fetchPriority: 'low',
1855
+ });
1856
FlightReactDOM.preinit('i after', {as: 'script'});
1857
FlightReactDOM.preinitModule('m after');
1815
- FlightReactDOM.preinitModule('m2 after', {crossOrigin: 'anonymous'});
1858
+ FlightReactDOM.preinitModule('m2 after', {
1859
+ crossOrigin: 'anonymous',
1860
+ fetchPriority: 'high',
1861
+ });
1862
return <ClientComponent />;
1863
}
1864
@@ -1866,16 +1912,38 @@ describe('ReactFlightDOM', () => {
1912
<link rel="preconnect" href="c2 after" crossorigin="" />
1913
<script async="" src="i before" />
1914
<script type="module" async="" src="m before" />
1869
- <script type="module" async="" src="m2 before" crossorigin="" />
1915
+ <script
1916
+ type="module"
1917
+ async=""
1918
+ src="m2 before"
1919
+ crossorigin=""
1920
+ fetchpriority="high"
1921
+ />
1922
<script async="" src="i after" />
1923
<script type="module" async="" src="m after" />
1872
- <script type="module" async="" src="m2 after" crossorigin="" />
1924
+ <script
1925
+ type="module"
1926
+ async=""
1927
+ src="m2 after"
1928
+ crossorigin=""
1929
+ fetchpriority="high"
1930
+ />
1931
<link rel="preload" as="style" href="l before" />
1932
<link rel="modulepreload" href="lm before" />
1875
- <link rel="modulepreload" href="lm2 before" crossorigin="" />
1933
+ <link
1934
+ rel="modulepreload"
1935
+ href="lm2 before"
1936
+ crossorigin=""
1937
+ fetchpriority="low"
1938
+ />
1939
<link rel="preload" as="style" href="l after" />
1940
<link rel="modulepreload" href="lm after" />
1878
- <link rel="modulepreload" href="lm2 after" crossorigin="" />
1941
+ <link
1942
+ rel="modulepreload"
1943
+ href="lm2 after"
1944
+ crossorigin=""
1945
+ fetchpriority="low"
1946
+ />
1947
</head>
1948
<body>
1949
<p>hello world</p>