Repository: facebook/react
Pull request: [Float] support fetchpriority on `ReactDOM.preload()` and `ReactDOM.preinit()`

exposes fetchPriority as an option for `ReactDOM.preload()` and `ReactDOM.preinit()`

the typings should be `'high' | 'low' | 'auto'`

Changed files:
filename: packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js
patch: @@ -2173,6 +2173,7 @@ type PreloadOptions = {
   crossOrigin?: string,
   integrity?: string,
   type?: string,
+  fetchPriority?: 'high' | 'low' | 'auto',
 };
 function preload(href: string, options: PreloadOptions) {
   if (!enableFloat) {
@@ -2245,6 +2246,7 @@ function preloadPropsFromPreloadOptions(
     crossOrigin: as === 'font' ? '' : options.crossOrigin,
     integrity: options.integrity,
     type: options.type,
+    fetchPriority: options.fetchPriority,
   };
 }
 
@@ -2254,6 +2256,7 @@ type PreinitOptions = {
   crossOrigin?: string,
   integrity?: string,
   nonce?: string,
+  fetchPriority?: 'high' | 'low' | 'auto',
 };
 function preinit(href: string, options: PreinitOptions) {
   if (!enableFloat) {
@@ -2395,6 +2398,7 @@ function stylesheetPropsFromPreinitOptions(
     'data-precedence': precedence,
     crossOrigin: options.crossOrigin,
     integrity: options.integrity,
+    fetchPriority: options.fetchPriority,
   };
 }
 
@@ -2408,6 +2412,7 @@ function scriptPropsFromPreinitOptions(
     crossOrigin: options.crossOrigin,
     integrity: options.integrity,
     nonce: options.nonce,
+    fetchPriority: options.fetchPriority,
   };
 }
 

filename: packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
patch: @@ -5102,6 +5102,7 @@ type PreloadOptions = {
   crossOrigin?: string,
   integrity?: string,
   type?: string,
+  fetchPriority?: 'high' | 'low' | 'auto',
 };
 export function preload(href: string, options: PreloadOptions) {
   if (!enableFloat) {
@@ -5247,6 +5248,7 @@ type PreinitOptions = {
   crossOrigin?: string,
   integrity?: string,
   nonce?: string,
+  fetchPriority?: 'high' | 'low' | 'auto',
 };
 function preinit(href: string, options: PreinitOptions): void {
   if (!enableFloat) {
@@ -5590,6 +5592,7 @@ function preloadPropsFromPreloadOptions(
     crossOrigin: as === 'font' ? '' : options.crossOrigin,
     integrity: options.integrity,
     type: options.type,
+    fetchPriority: options.fetchPriority,
   };
 }
 
@@ -5631,6 +5634,7 @@ function stylesheetPropsFromPreinitOptions(
     'data-precedence': precedence,
     crossOrigin: options.crossOrigin,
     integrity: options.integrity,
+    fetchPriority: options.fetchPriority,
   };
 }
 
@@ -5662,6 +5666,7 @@ function scriptPropsFromPreinitOptions(
     crossOrigin: options.crossOrigin,
     integrity: options.integrity,
     nonce: options.nonce,
+    fetchPriority: options.fetchPriority,
   };
 }
 

filename: packages/react-dom/src/ReactDOMDispatcher.js
patch: @@ -14,13 +14,15 @@ export type PreloadOptions = {
   crossOrigin?: string,
   integrity?: string,
   type?: string,
+  fetchPriority?: 'high' | 'low' | 'auto',
 };
 export type PreinitOptions = {
   as: string,
   precedence?: string,
   crossOrigin?: string,
   integrity?: string,
   nonce?: string,
+  fetchPriority?: 'high' | 'low' | 'auto',
 };
 
 export type HostDispatcher = {

filename: packages/react-dom/src/__tests__/ReactDOMFloat-test.js
patch: @@ -3789,6 +3789,7 @@ body {
           as: 'style',
           crossOrigin: 'use-credentials',
           integrity: 'some hash',
+          fetchPriority: 'low',
         });
         return (
           <html>
@@ -3909,6 +3910,113 @@ body {
         'ReactDOM.preload(): For `href` "foo", The options provided conflict with props on a matching <link rel="stylesheet" ... /> element. When the preload options disagree with the underlying resource it usually means the browser will not be able to use the preload when the resource is fetched, negating any benefit the preload would provide. React will preload the resource using props derived from the resource instead and ignore the options provided to the `ReactDOM.preload()` call. In general, preloading is useful when you expect to render a resource soon but have not yet done so. In this case since the underlying resource was already rendered the preload call may be extraneous. Try removing the call, otherwise try adjusting both the props on the <link rel="stylesheet" ... /> and the options passed to `ReactDOM.preload()` to agree.\n  "integrity" missing from options, underlying prop value: "some hash"\n  "media" missing from options, underlying prop value: "print"\n  "crossOrigin" option value: "use-credentials", missing from underlying props',
       ]);
     });
+
+    it('supports fetchPriority', async () => {
+      function Component({isServer}) {
+        ReactDOM.preload(isServer ? 'highserver' : 'highclient', {
+          as: 'script',
+          fetchPriority: 'high',
+        });
+        ReactDOM.preload(isServer ? 'lowserver' : 'lowclient', {
+          as: 'style',
+          fetchPriority: 'low',
+        });
+        ReactDOM.preload(isServer ? 'autoserver' : 'autoclient', {
+          as: 'style',
+          fetchPriority: 'auto',
+        });
+        return 'hello';
+      }
+
+      await act(() => {
+        renderToPipeableStream(
+          <html>
+            <body>
+              <Component isServer={true} />
+            </body>
+          </html>,
+        ).pipe(writable);
+      });
+
+      expect(getMeaningfulChildren(document)).toEqual(
+        <html>
+          <head>
+            <link
+              rel="preload"
+              as="style"
+              href="lowserver"
+              fetchpriority="low"
+            />
+            <link
+              rel="preload"
+              as="style"
+              href="autoserver"
+              fetchpriority="auto"
+            />
+            <link
+              rel="preload"
+              as="script"
+              href="highserver"
+              fetchpriority="high"
+            />
+          </head>
+          <body>hello</body>
+        </html>,
+      );
+
+      ReactDOMClient.hydrateRoot(
+        document,
+        <html>
+          <body>
+            <Component />
+          </body>
+        </html>,
+      );
+      await waitForAll([]);
+      expect(getMeaningfulChildren(document)).toEqual(
+        <html>
+          <head>
+            <link
+              rel="preload"
+              as="style"
+              href="lowserver"
+              fetchpriority="low"
+            />
+            <link
+              rel="preload"
+              as="style"
+              href="autoserver"
+              fetchpriority="auto"
+            />
+            <link
+              rel="preload"
+              as="script"
+              href="highserver"
+              fetchpriority="high"
+            />
+            <link
+              rel="preload"
+              as="script"
+              href="highclient"
+              fetchpriority="high"
+            />
+            <link
+              rel="preload"
+              as="style"
+              href="lowclient"
+              fetchpriority="low"
+            />
+            <link
+              rel="preload"
+              as="style"
+              href="autoclient"
+              fetchpriority="auto"
+            />
+          </head>
+          <body>hello</body>
+        </html>,
+      );
+    });
   });
 
   describe('ReactDOM.preinit(href, { as: ... })', () => {
@@ -4442,7 +4550,6 @@ body {
           <body>hello</body>
         </html>,
       );
-
       await clientAct(() => {
         ReactDOMClient.hydrateRoot(
           document,
@@ -4453,7 +4560,6 @@ body {
           </html>,
         );
       });
-
       expect(getMeaningfulChildren(document)).toEqual(
         <html>
           <head>
@@ -4474,6 +4580,97 @@ body {
         </html>,
       );
     });
+
+    it('supports fetchPriority', async () => {
+      function Component({isServer}) {
+        ReactDOM.preinit(isServer ? 'highserver' : 'highclient', {
+          as: 'script',
+          fetchPriority: 'high',
+        });
+        ReactDOM.preinit(isServer ? 'lowserver' : 'lowclient', {
+          as: 'style',
+          fetchPriority: 'low',
+        });
+        ReactDOM.preinit(isServer ? 'autoserver' : 'autoclient', {
+          as: 'style',
+          fetchPriority: 'auto',
+        });
+        return 'hello';
+      }
+
+      await act(() => {
+        renderToPipeableStream(
+          <html>
+            <body>
+              <Component isServer={true} />
+            </body>
+          </html>,
+        ).pipe(writable);
+      });
+
+      expect(getMeaningfulChildren(document)).toEqual(
+        <html>
+          <head>
+            <link
+              rel="stylesheet"
+              href="lowserver"
+              fetchpriority="low"
+              data-precedence="default"
+            />
+            <link
+              rel="stylesheet"
+              href="autoserver"
+              fetchpriority="auto"
+              data-precedence="default"
+            />
+            <script async="" src="highserver" fetchpriority="high" />
+          </head>
+          <body>hello</body>
+        </html>,
+      );
+      ReactDOMClient.hydrateRoot(
+        document,
+        <html>
+          <body>
+            <Component />
+          </body>
+        </html>,
+      );
+      await waitForAll([]);
+      expect(getMeaningfulChildren(document)).toEqual(
+        <html>
+          <head>
+            <link
+              rel="stylesheet"
+              href="lowserver"
+              fetchpriority="low"
+              data-precedence="default"
+            />
+            <link
+              rel="stylesheet"
+              href="autoserver"
+              fetchpriority="auto"
+              data-precedence="default"
+            />
+            <link
+              rel="stylesheet"
+              href="lowclient"
+              fetchpriority="low"
+              data-precedence="default"
+            />
+            <link
+              rel="stylesheet"
+              href="autoclient"
+              fetchpriority="auto"
+              data-precedence="default"
+            />
+            <script async="" src="highserver" fetchpriority="high" />
+            <script async="" src="highclient" fetchpriority="high" />
+          </head>
+          <body>hello</body>
+        </html>,
+      );
+    });
   });
 
   describe('Stylesheet Resources', () => {