@samitouri / QOS-React-1 / commits / 9edf470d6e

[Fizz] declare bootstrap script preloads to be `fetchPriority: 'low'` (#27189)

Generally scripts should not be preloaded before images but if they arrive earlier than image preloads (or images) the network (or server) may be saturated responding to inflight script preloads and not sufficiently prioritize images arriving later. This change marks the preloaded bootstrap script with a `low` fetch priority to signal to supporting browsers that the request should be deprioritized. This should make the preload operate similar to async script fetch priority which is low by default according to https://web.dev/fetch-priority/ Additionally the bootstrap script preloads will emit before preinitialized scripts do. Normal script preloads will continue to be prioritized after stylesheets This change can land separatrely but is part of a larger effort to implement elevating image loading and making script loading less blocking. Later changes will emit used suspensey images earlier in the queue and will stop favoring scripts over images that are explicitly preloaded

Josh Story committed Aug 7, 2023 at 15:44 UTC 9edf470d6ed1f3ac12c23a248e64293f367c1797
7 files changed +83 -24
packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js
+11 -2
@@ -4245,6 +4245,8 @@ export function writePreamble(
4245 // Flush unblocked stylesheets by precedence
4246 resources.precedences.forEach(flushAllStylesInPreamble, destination);
4247
4248 + resources.bootstrapScripts.forEach(flushResourceInPreamble, destination);
4249 +
4250 resources.scripts.forEach(flushResourceInPreamble, destination);
4251 resources.scripts.clear();
4252
@@ -4322,6 +4324,9 @@ export function writeHoistables(
4324 // but we want to kick off preloading as soon as possible
4325 resources.precedences.forEach(preloadLateStyles, destination);
4326
4327 + // bootstrap scripts should flush above script priority but these can only flush in the preamble
4328 + // so we elide the code here for performance
4329 +
4330 resources.scripts.forEach(flushResourceLate, destination);
4331 resources.scripts.clear();
4332
@@ -4875,6 +4880,7 @@ export type Resources = {
4880 // usedImagePreloads: Set<PreloadResource>,
4881 precedences: Map<string, Set<StyleResource>>,
4882 stylePrecedences: Map<string, StyleTagResource>,
4883 + bootstrapScripts: Set<PreloadResource>,
4884 scripts: Set<ScriptResource>,
4885 explicitStylesheetPreloads: Set<PreloadResource>,
4886 // explicitImagePreloads: Set<PreloadResource>,
@@ -4901,6 +4907,7 @@ export function createResources(): Resources {
4907 // usedImagePreloads: new Set(),
4908 precedences: new Map(),
4909 stylePrecedences: new Map(),
4910 + bootstrapScripts: new Set(),
4911 scripts: new Set(),
4912 explicitStylesheetPreloads: new Set(),
4913 // explicitImagePreloads: new Set(),
@@ -5470,6 +5477,7 @@ function preloadBootstrapScript(
5477 rel: 'preload',
5478 href: src,
5479 as: 'script',
5480 + fetchPriority: 'low',
5481 nonce,
5482 integrity,
5483 crossOrigin,
@@ -5481,7 +5489,7 @@ function preloadBootstrapScript(
5489 props,
5490 };
5491 resources.preloadsMap.set(key, resource);
5484 - resources.explicitScriptPreloads.add(resource);
5492 + resources.bootstrapScripts.add(resource);
5493 pushLinkImpl(resource.chunks, props);
5494 }
5495
@@ -5511,6 +5519,7 @@ function preloadBootstrapModule(
5519 const props: PreloadModuleProps = {
5520 rel: 'modulepreload',
5521 href: src,
5522 + fetchPriority: 'low',
5523 nonce,
5524 integrity,
5525 crossOrigin,
@@ -5522,7 +5531,7 @@ function preloadBootstrapModule(
5531 props,
5532 };
5533 resources.preloadsMap.set(key, resource);
5525 - resources.explicitScriptPreloads.add(resource);
5534 + resources.bootstrapScripts.add(resource);
5535 pushLinkImpl(resource.chunks, props);
5536 return;
5537 }
packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js
+66 -16
@@ -610,17 +610,30 @@ describe('ReactDOMFizzServer', () => {
610 });
611
612 expect(getVisibleChildren(container)).toEqual([
613 - <link rel="preload" href="init.js" as="script" nonce={CSPnonce} />,
613 <link
614 rel="preload"
615 + fetchpriority="low"
616 + href="init.js"
617 + as="script"
618 + nonce={CSPnonce}
619 + />,
620 + <link
621 + rel="preload"
622 + fetchpriority="low"
623 href="init2.js"
624 as="script"
625 nonce={CSPnonce}
626 integrity="init2hash"
627 />,
621 - <link rel="modulepreload" href="init.mjs" nonce={CSPnonce} />,
628 <link
629 rel="modulepreload"
630 + fetchpriority="low"
631 + href="init.mjs"
632 + nonce={CSPnonce}
633 + />,
634 + <link
635 + rel="modulepreload"
636 + fetchpriority="low"
637 href="init2.mjs"
638 nonce={CSPnonce}
639 integrity="init2hash"
@@ -640,17 +653,30 @@ describe('ReactDOMFizzServer', () => {
653 resolve({default: Text});
654 });
655 expect(getVisibleChildren(container)).toEqual([
643 - <link rel="preload" href="init.js" as="script" nonce={CSPnonce} />,
656 <link
657 rel="preload"
658 + fetchpriority="low"
659 + href="init.js"
660 + as="script"
661 + nonce={CSPnonce}
662 + />,
663 + <link
664 + rel="preload"
665 + fetchpriority="low"
666 href="init2.js"
667 as="script"
668 nonce={CSPnonce}
669 integrity="init2hash"
670 />,
651 - <link rel="modulepreload" href="init.mjs" nonce={CSPnonce} />,
671 <link
672 rel="modulepreload"
673 + fetchpriority="low"
674 + href="init.mjs"
675 + nonce={CSPnonce}
676 + />,
677 + <link
678 + rel="modulepreload"
679 + fetchpriority="low"
680 href="init2.mjs"
681 nonce={CSPnonce}
682 integrity="init2hash"
@@ -3797,12 +3823,23 @@ describe('ReactDOMFizzServer', () => {
3823 expect(getVisibleChildren(document)).toEqual(
3824 <html>
3825 <head>
3800 - <link rel="preload" href="foo" as="script" />
3801 - <link rel="preload" href="bar" as="script" />
3802 - <link rel="preload" href="baz" as="script" integrity="qux" />
3803 - <link rel="modulepreload" href="quux" />
3804 - <link rel="modulepreload" href="corge" />
3805 - <link rel="modulepreload" href="grault" integrity="garply" />
3826 + <link rel="preload" fetchpriority="low" href="foo" as="script" />
3827 + <link rel="preload" fetchpriority="low" href="bar" as="script" />
3828 + <link
3829 + rel="preload"
3830 + fetchpriority="low"
3831 + href="baz"
3832 + as="script"
3833 + integrity="qux"
3834 + />
3835 + <link rel="modulepreload" fetchpriority="low" href="quux" />
3836 + <link rel="modulepreload" fetchpriority="low" href="corge" />
3837 + <link
3838 + rel="modulepreload"
3839 + fetchpriority="low"
3840 + href="grault"
3841 + integrity="garply"
3842 + />
3843 </head>
3844 <body>
3845 <div>hello world</div>
@@ -3866,14 +3903,27 @@ describe('ReactDOMFizzServer', () => {
3903 expect(getVisibleChildren(document)).toEqual(
3904 <html>
3905 <head>
3869 - <link rel="preload" href="foo" as="script" />
3870 - <link rel="preload" href="bar" as="script" />
3871 - <link rel="preload" href="baz" as="script" crossorigin="" />
3872 - <link rel="preload" href="qux" as="script" crossorigin="" />
3873 - <link rel="modulepreload" href="quux" />
3874 - <link rel="modulepreload" href="corge" />
3906 + <link rel="preload" fetchpriority="low" href="foo" as="script" />
3907 + <link rel="preload" fetchpriority="low" href="bar" as="script" />
3908 + <link
3909 + rel="preload"
3910 + fetchpriority="low"
3911 + href="baz"
3912 + as="script"
3913 + crossorigin=""
3914 + />
3915 + <link
3916 + rel="preload"
3917 + fetchpriority="low"
3918 + href="qux"
3919 + as="script"
3920 + crossorigin=""
3921 + />
3922 + <link rel="modulepreload" fetchpriority="low" href="quux" />
3923 + <link rel="modulepreload" fetchpriority="low" href="corge" />
3924 <link
3925 rel="modulepreload"
3926 + fetchpriority="low"
3927 href="grault"
3928 crossorigin="use-credentials"
3929 />
packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js
+2 -2
@@ -84,7 +84,7 @@ describe('ReactDOMFizzServerBrowser', () => {
84 );
85 const result = await readResult(stream);
86 expect(result).toMatchInlineSnapshot(
87 - `"<link rel="preload" href="init.js" as="script"/><link rel="modulepreload" href="init.mjs"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
87 + `"<link rel="preload" href="init.js" as="script" fetchPriority="low"/><link rel="modulepreload" href="init.mjs" fetchPriority="low"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
88 );
89 });
90
@@ -500,7 +500,7 @@ describe('ReactDOMFizzServerBrowser', () => {
500 );
501 const result = await readResult(stream);
502 expect(result).toMatchInlineSnapshot(
503 - `"<link rel="preload" href="init.js" as="script" nonce="R4nd0m"/><link rel="modulepreload" href="init.mjs" nonce="R4nd0m"/><div>hello world</div><script nonce="${nonce}">INIT();</script><script src="init.js" nonce="${nonce}" async=""></script><script type="module" src="init.mjs" nonce="${nonce}" async=""></script>"`,
503 + `"<link rel="preload" href="init.js" as="script" fetchPriority="low" nonce="R4nd0m"/><link rel="modulepreload" href="init.mjs" fetchPriority="low" nonce="R4nd0m"/><div>hello world</div><script nonce="${nonce}">INIT();</script><script src="init.js" nonce="${nonce}" async=""></script><script type="module" src="init.mjs" nonce="${nonce}" async=""></script>"`,
504 );
505 });
506 });
packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js
+1 -1
@@ -98,7 +98,7 @@ describe('ReactDOMFizzServerNode', () => {
98 pipe(writable);
99 jest.runAllTimers();
100 expect(output.result).toMatchInlineSnapshot(
101 - `"<link rel="preload" href="init.js" as="script"/><link rel="modulepreload" href="init.mjs"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
101 + `"<link rel="preload" href="init.js" as="script" fetchPriority="low"/><link rel="modulepreload" href="init.mjs" fetchPriority="low"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
102 );
103 });
104
packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js
+1 -1
@@ -84,7 +84,7 @@ describe('ReactDOMFizzStaticBrowser', () => {
84 });
85 const prelude = await readContent(result.prelude);
86 expect(prelude).toMatchInlineSnapshot(
87 - `"<link rel="preload" href="init.js" as="script"/><link rel="modulepreload" href="init.mjs"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
87 + `"<link rel="preload" href="init.js" as="script" fetchPriority="low"/><link rel="modulepreload" href="init.mjs" fetchPriority="low"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
88 );
89 });
90
packages/react-dom/src/__tests__/ReactDOMFizzStaticNode-test.js
+1 -1
@@ -86,7 +86,7 @@ describe('ReactDOMFizzStaticNode', () => {
86 );
87 const prelude = await readContent(result.prelude);
88 expect(prelude).toMatchInlineSnapshot(
89 - `"<link rel="preload" href="init.js" as="script"/><link rel="modulepreload" href="init.mjs"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
89 + `"<link rel="preload" href="init.js" as="script" fetchPriority="low"/><link rel="modulepreload" href="init.mjs" fetchPriority="low"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
90 );
91 });
92
packages/react-server-dom-fb/src/__tests__/ReactDOMServerFB-test.internal.js
+1 -1
@@ -59,7 +59,7 @@ describe('ReactDOMServerFB', () => {
59 });
60 const result = readResult(stream);
61 expect(result).toMatchInlineSnapshot(
62 - `"<link rel="preload" href="init.js" as="script"/><link rel="modulepreload" href="init.mjs"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
62 + `"<link rel="preload" href="init.js" as="script" fetchPriority="low"/><link rel="modulepreload" href="init.mjs" fetchPriority="low"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
63 );
64 });
65