Skip to content

Commit ae31d2e

Browse files
authored
[Fizz] preload bootstrapModules (#26754)
stacked on #26753 Adds support for preloading bootstrapModules. We don't yet support modules in Float's public interface but this implementation should be compatible with what we do when we add it.
1 parent b864ad4 commit ae31d2e

File tree

7 files changed

+84
-18
lines changed

7 files changed

+84
-18
lines changed

packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,8 @@ export function createResponseState(
296296
const integrity =
297297
typeof scriptConfig === 'string' ? undefined : scriptConfig.integrity;
298298

299+
preloadBootstrapModule(resources, src, nonce, integrity);
300+
299301
bootstrapChunks.push(
300302
startModuleSrc,
301303
stringToChunk(escapeTextForBrowser(src)),
@@ -1977,7 +1979,7 @@ function pushLink(
19771979
}
19781980
}
19791981
pushLinkImpl(resource.chunks, resource.props);
1980-
resources.usedStylesheets.add(resource);
1982+
resources.usedStylesheets.set(key, resource);
19811983
return pushLinkImpl(target, props);
19821984
} else {
19831985
// This stylesheet refers to a Resource and we create a new one if necessary
@@ -4249,8 +4251,7 @@ export function writePreamble(
42494251
// Flush unblocked stylesheets by precedence
42504252
resources.precedences.forEach(flushAllStylesInPreamble, destination);
42514253

4252-
resources.usedStylesheets.forEach(resource => {
4253-
const key = getResourceKey(resource.props.as, resource.props.href);
4254+
resources.usedStylesheets.forEach((resource, key) => {
42544255
if (resources.stylesMap.has(key)) {
42554256
// The underlying stylesheet is represented both as a used stylesheet
42564257
// (a regular component we will attempt to preload) and as a StylesheetResource.
@@ -4345,8 +4346,7 @@ export function writeHoistables(
43454346
// but we want to kick off preloading as soon as possible
43464347
resources.precedences.forEach(preloadLateStyles, destination);
43474348

4348-
resources.usedStylesheets.forEach(resource => {
4349-
const key = getResourceKey(resource.props.as, resource.props.href);
4349+
resources.usedStylesheets.forEach((resource, key) => {
43504350
if (resources.stylesMap.has(key)) {
43514351
// The underlying stylesheet is represented both as a used stylesheet
43524352
// (a regular component we will attempt to preload) and as a StylesheetResource.
@@ -4861,12 +4861,18 @@ type PreconnectProps = {
48614861
};
48624862
type PreconnectResource = TResource<'preconnect', null>;
48634863

4864-
type PreloadProps = {
4864+
type PreloadAsProps = {
48654865
rel: 'preload',
48664866
as: string,
48674867
href: string,
48684868
[string]: mixed,
48694869
};
4870+
type PreloadModuleProps = {
4871+
rel: 'modulepreload',
4872+
href: string,
4873+
[string]: mixed,
4874+
};
4875+
type PreloadProps = PreloadAsProps | PreloadModuleProps;
48704876
type PreloadResource = TResource<'preload', PreloadProps>;
48714877

48724878
type StylesheetProps = {
@@ -4911,7 +4917,7 @@ export type Resources = {
49114917
// usedImagePreloads: Set<PreloadResource>,
49124918
precedences: Map<string, Set<StyleResource>>,
49134919
stylePrecedences: Map<string, StyleTagResource>,
4914-
usedStylesheets: Set<PreloadResource>,
4920+
usedStylesheets: Map<string, PreloadResource>,
49154921
scripts: Set<ScriptResource>,
49164922
usedScripts: Set<PreloadResource>,
49174923
explicitStylesheetPreloads: Set<PreloadResource>,
@@ -4939,7 +4945,7 @@ export function createResources(): Resources {
49394945
// usedImagePreloads: new Set(),
49404946
precedences: new Map(),
49414947
stylePrecedences: new Map(),
4942-
usedStylesheets: new Set(),
4948+
usedStylesheets: new Map(),
49434949
scripts: new Set(),
49444950
usedScripts: new Set(),
49454951
explicitStylesheetPreloads: new Set(),
@@ -5512,6 +5518,46 @@ function preloadBootstrapScript(
55125518
pushLinkImpl(resource.chunks, props);
55135519
}
55145520

5521+
// This function is only safe to call at Request start time since it assumes
5522+
// that each module has not already been preloaded. If we find a need to preload
5523+
// scripts at any other point in time we will need to check whether the preload
5524+
// already exists and not assume it
5525+
function preloadBootstrapModule(
5526+
resources: Resources,
5527+
src: string,
5528+
nonce: ?string,
5529+
integrity: ?string,
5530+
): void {
5531+
const key = getResourceKey('script', src);
5532+
if (__DEV__) {
5533+
if (resources.preloadsMap.has(key)) {
5534+
// This is coded as a React error because it should be impossible for a userspace preload to preempt this call
5535+
// If a userspace preload can preempt it then this assumption is broken and we need to reconsider this strategy
5536+
// rather than instruct the user to not preload their bootstrap scripts themselves
5537+
console.error(
5538+
'Internal React Error: React expected bootstrap module with src "%s" to not have been preloaded already. please file an issue',
5539+
src,
5540+
);
5541+
}
5542+
}
5543+
const props: PreloadModuleProps = {
5544+
rel: 'modulepreload',
5545+
href: src,
5546+
nonce,
5547+
integrity,
5548+
};
5549+
const resource: PreloadResource = {
5550+
type: 'preload',
5551+
chunks: [],
5552+
state: NoState,
5553+
props,
5554+
};
5555+
resources.preloadsMap.set(key, resource);
5556+
resources.explicitScriptPreloads.add(resource);
5557+
pushLinkImpl(resource.chunks, props);
5558+
return;
5559+
}
5560+
55155561
function internalPreinitScript(
55165562
resources: Resources,
55175563
src: string,

packages/react-dom/src/__tests__/ReactDOMFizzServer-test.js

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,10 @@ describe('ReactDOMFizzServer', () => {
600600
'init.js',
601601
{src: 'init2.js', integrity: 'init2hash'},
602602
],
603-
bootstrapModules: ['init.mjs'],
603+
bootstrapModules: [
604+
'init.mjs',
605+
{src: 'init2.mjs', integrity: 'init2hash'},
606+
],
604607
},
605608
);
606609
pipe(writable);
@@ -615,16 +618,23 @@ describe('ReactDOMFizzServer', () => {
615618
nonce={CSPnonce}
616619
integrity="init2hash"
617620
/>,
621+
<link rel="modulepreload" href="init.mjs" nonce={CSPnonce} />,
622+
<link
623+
rel="modulepreload"
624+
href="init2.mjs"
625+
nonce={CSPnonce}
626+
integrity="init2hash"
627+
/>,
618628
<div>Loading...</div>,
619629
]);
620630

621-
// check that there are 4 scripts with a matching nonce:
622-
// The runtime script, an inline bootstrap script, and two src scripts
631+
// check that there are 6 scripts with a matching nonce:
632+
// The runtime script, an inline bootstrap script, two bootstrap scripts and two bootstrap modules
623633
expect(
624634
Array.from(container.getElementsByTagName('script')).filter(
625635
node => node.getAttribute('nonce') === CSPnonce,
626636
).length,
627-
).toEqual(5);
637+
).toEqual(6);
628638

629639
await act(() => {
630640
resolve({default: Text});
@@ -638,6 +648,13 @@ describe('ReactDOMFizzServer', () => {
638648
nonce={CSPnonce}
639649
integrity="init2hash"
640650
/>,
651+
<link rel="modulepreload" href="init.mjs" nonce={CSPnonce} />,
652+
<link
653+
rel="modulepreload"
654+
href="init2.mjs"
655+
nonce={CSPnonce}
656+
integrity="init2hash"
657+
/>,
641658
<div>Hello</div>,
642659
]);
643660
} finally {
@@ -3783,6 +3800,9 @@ describe('ReactDOMFizzServer', () => {
37833800
<link rel="preload" href="foo" as="script" />
37843801
<link rel="preload" href="bar" as="script" />
37853802
<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" />
37863806
</head>
37873807
<body>
37883808
<div>hello world</div>

packages/react-dom/src/__tests__/ReactDOMFizzServerBrowser-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ describe('ReactDOMFizzServerBrowser', () => {
8484
);
8585
const result = await readResult(stream);
8686
expect(result).toMatchInlineSnapshot(
87-
`"<link rel="preload" href="init.js" as="script"/><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"/><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>"`,
8888
);
8989
});
9090

@@ -500,7 +500,7 @@ describe('ReactDOMFizzServerBrowser', () => {
500500
);
501501
const result = await readResult(stream);
502502
expect(result).toMatchInlineSnapshot(
503-
`"<link rel="preload" href="init.js" as="script" 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" 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>"`,
504504
);
505505
});
506506
});

packages/react-dom/src/__tests__/ReactDOMFizzServerNode-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ describe('ReactDOMFizzServerNode', () => {
9898
pipe(writable);
9999
jest.runAllTimers();
100100
expect(output.result).toMatchInlineSnapshot(
101-
`"<link rel="preload" href="init.js" as="script"/><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"/><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>"`,
102102
);
103103
});
104104

packages/react-dom/src/__tests__/ReactDOMFizzStaticBrowser-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ describe('ReactDOMFizzStaticBrowser', () => {
8484
});
8585
const prelude = await readContent(result.prelude);
8686
expect(prelude).toMatchInlineSnapshot(
87-
`"<link rel="preload" href="init.js" as="script"/><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"/><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>"`,
8888
);
8989
});
9090

packages/react-dom/src/__tests__/ReactDOMFizzStaticNode-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ describe('ReactDOMFizzStaticNode', () => {
8686
);
8787
const prelude = await readContent(result.prelude);
8888
expect(prelude).toMatchInlineSnapshot(
89-
`"<link rel="preload" href="init.js" as="script"/><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"/><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>"`,
9090
);
9191
});
9292

packages/react-server-dom-fb/src/__tests__/ReactDOMServerFB-test.internal.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ describe('ReactDOMServerFB', () => {
5959
});
6060
const result = readResult(stream);
6161
expect(result).toMatchInlineSnapshot(
62-
`"<link rel="preload" href="init.js" as="script"/><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"/><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>"`,
6363
);
6464
});
6565

0 commit comments

Comments
 (0)