mirror of
https://github.com/facebook/react.git
synced 2026-02-23 20:23:02 +00:00
[Fizz][Float] Refactor Resources (#27400)
Refactors Resources to have a more compact and memory efficient struture. Resources generally are just an Array of chunks. A resource is flushed when it's chunks is length zero. A resource does not have any other state. Stylesheets and Style tags are different and have been modeled as a unit as a StyleQueue. This object stores the style rules to flush as part of style tags using precedence as well as all the stylesheets associated with the precedence. Stylesheets still need to track state because it affects how we issue boundary completion instructions. Additionally stylesheets encode chunks lazily because we may never write them as html if they are discovered late. The preload props transfer is now maximally compact (only stores the props we would ever actually adopt) and only stores props for stylesheets and scripts because other preloads have no resource counterpart to adopt props into. The ResumableState maps that track which keys have been observed are being overloaded. Previously if a key was found it meant that a resource already exists (either in this render or in a prior prerender). Now we discriminate between null and object values. If map value is null we can assume the resource exists but if it is an object that represents a prior preload for that resource and the resource must still be constructed.
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -7,7 +7,12 @@
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {ResumableState, BoundaryResources} from './ReactFizzConfigDOM';
|
||||
import type {
|
||||
ResumableState,
|
||||
BoundaryResources,
|
||||
StyleQueue,
|
||||
Resource,
|
||||
} from './ReactFizzConfigDOM';
|
||||
|
||||
import {
|
||||
createRenderState as createRenderStateImpl,
|
||||
@@ -46,16 +51,20 @@ export type RenderState = {
|
||||
importMapChunks: Array<Chunk | PrecomputedChunk>,
|
||||
preloadChunks: Array<Chunk | PrecomputedChunk>,
|
||||
hoistableChunks: Array<Chunk | PrecomputedChunk>,
|
||||
preconnects: Set<any>,
|
||||
fontPreloads: Set<any>,
|
||||
highImagePreloads: Set<any>,
|
||||
// usedImagePreloads: Set<any>,
|
||||
precedences: Map<string, Map<any, any>>,
|
||||
stylePrecedences: Map<string, any>,
|
||||
bootstrapScripts: Set<any>,
|
||||
scripts: Set<any>,
|
||||
bulkPreloads: Set<any>,
|
||||
preloadsMap: Map<string, any>,
|
||||
preconnects: Set<Resource>,
|
||||
fontPreloads: Set<Resource>,
|
||||
highImagePreloads: Set<Resource>,
|
||||
// usedImagePreloads: Set<Resource>,
|
||||
styles: Map<string, StyleQueue>,
|
||||
bootstrapScripts: Set<Resource>,
|
||||
scripts: Set<Resource>,
|
||||
bulkPreloads: Set<Resource>,
|
||||
preloads: {
|
||||
images: Map<string, Resource>,
|
||||
stylesheets: Map<string, Resource>,
|
||||
scripts: Map<string, Resource>,
|
||||
moduleScripts: Map<string, Resource>,
|
||||
},
|
||||
boundaryResources: ?BoundaryResources,
|
||||
stylesToHoist: boolean,
|
||||
// This is an extra field for the legacy renderer
|
||||
@@ -94,12 +103,11 @@ export function createRenderState(
|
||||
fontPreloads: renderState.fontPreloads,
|
||||
highImagePreloads: renderState.highImagePreloads,
|
||||
// usedImagePreloads: renderState.usedImagePreloads,
|
||||
precedences: renderState.precedences,
|
||||
stylePrecedences: renderState.stylePrecedences,
|
||||
styles: renderState.styles,
|
||||
bootstrapScripts: renderState.bootstrapScripts,
|
||||
scripts: renderState.scripts,
|
||||
bulkPreloads: renderState.bulkPreloads,
|
||||
preloadsMap: renderState.preloadsMap,
|
||||
preloads: renderState.preloads,
|
||||
boundaryResources: renderState.boundaryResources,
|
||||
stylesToHoist: renderState.stylesToHoist,
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
);
|
||||
const result = await readResult(stream);
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"<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>"`,
|
||||
`"<link rel="preload" as="script" fetchPriority="low" href="init.js"/><link rel="modulepreload" fetchPriority="low" href="init.mjs"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -505,7 +505,7 @@ describe('ReactDOMFizzServerBrowser', () => {
|
||||
);
|
||||
const result = await readResult(stream);
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"<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>"`,
|
||||
`"<link rel="preload" as="script" fetchPriority="low" nonce="R4nd0m" href="init.js"/><link rel="modulepreload" fetchPriority="low" nonce="R4nd0m" href="init.mjs"/><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>"`,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ describe('ReactDOMFizzServerNode', () => {
|
||||
pipe(writable);
|
||||
jest.runAllTimers();
|
||||
expect(output.result).toMatchInlineSnapshot(
|
||||
`"<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>"`,
|
||||
`"<link rel="preload" as="script" fetchPriority="low" href="init.js"/><link rel="modulepreload" fetchPriority="low" href="init.mjs"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ describe('ReactDOMFizzStaticBrowser', () => {
|
||||
});
|
||||
const prelude = await readContent(result.prelude);
|
||||
expect(prelude).toMatchInlineSnapshot(
|
||||
`"<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>"`,
|
||||
`"<link rel="preload" as="script" fetchPriority="low" href="init.js"/><link rel="modulepreload" fetchPriority="low" href="init.mjs"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
275
packages/react-dom/src/__tests__/ReactDOMFizzStaticFloat-test.js
vendored
Normal file
275
packages/react-dom/src/__tests__/ReactDOMFizzStaticFloat-test.js
vendored
Normal file
@@ -0,0 +1,275 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
import {
|
||||
getVisibleChildren,
|
||||
insertNodesAndExecuteScripts,
|
||||
} from '../test-utils/FizzTestUtils';
|
||||
|
||||
// Polyfills for test environment
|
||||
global.ReadableStream =
|
||||
require('web-streams-polyfill/ponyfill/es6').ReadableStream;
|
||||
global.TextEncoder = require('util').TextEncoder;
|
||||
|
||||
let React;
|
||||
let ReactDOM;
|
||||
let ReactDOMFizzServer;
|
||||
let ReactDOMFizzStatic;
|
||||
let Suspense;
|
||||
let container;
|
||||
|
||||
describe('ReactDOMFizzStaticFloat', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
React = require('react');
|
||||
ReactDOM = require('react-dom');
|
||||
ReactDOMFizzServer = require('react-dom/server.browser');
|
||||
if (__EXPERIMENTAL__) {
|
||||
ReactDOMFizzStatic = require('react-dom/static.browser');
|
||||
}
|
||||
Suspense = React.Suspense;
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.removeChild(container);
|
||||
});
|
||||
|
||||
async function readIntoContainer(stream) {
|
||||
const reader = stream.getReader();
|
||||
let result = '';
|
||||
while (true) {
|
||||
const {done, value} = await reader.read();
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
result += Buffer.from(value).toString('utf8');
|
||||
}
|
||||
const temp = document.createElement('div');
|
||||
temp.innerHTML = result;
|
||||
await insertNodesAndExecuteScripts(temp, container, null);
|
||||
}
|
||||
|
||||
// @gate enablePostpone
|
||||
it('should transfer connection credentials across prerender and resume for stylesheets, scripts, and moduleScripts', async () => {
|
||||
let prerendering = true;
|
||||
function Postpone() {
|
||||
if (prerendering) {
|
||||
React.unstable_postpone();
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<link rel="stylesheet" href="style creds" precedence="default" />
|
||||
<script async={true} src="script creds" data-meaningful="" />
|
||||
<script
|
||||
type="module"
|
||||
async={true}
|
||||
src="module creds"
|
||||
data-meaningful=""
|
||||
/>
|
||||
<link rel="stylesheet" href="style anon" precedence="default" />
|
||||
<script async={true} src="script anon" data-meaningful="" />
|
||||
<script
|
||||
type="module"
|
||||
async={true}
|
||||
src="module default"
|
||||
data-meaningful=""
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function App() {
|
||||
ReactDOM.preload('style creds', {
|
||||
as: 'style',
|
||||
crossOrigin: 'use-credentials',
|
||||
});
|
||||
ReactDOM.preload('script creds', {
|
||||
as: 'script',
|
||||
crossOrigin: 'use-credentials',
|
||||
integrity: 'script-hash',
|
||||
});
|
||||
ReactDOM.preloadModule('module creds', {
|
||||
crossOrigin: 'use-credentials',
|
||||
integrity: 'module-hash',
|
||||
});
|
||||
ReactDOM.preload('style anon', {
|
||||
as: 'style',
|
||||
crossOrigin: 'anonymous',
|
||||
});
|
||||
ReactDOM.preload('script anon', {
|
||||
as: 'script',
|
||||
crossOrigin: 'foobar',
|
||||
});
|
||||
ReactDOM.preloadModule('module default', {
|
||||
integrity: 'module-hash',
|
||||
});
|
||||
return (
|
||||
<div>
|
||||
<Suspense fallback="Loading...">
|
||||
<Postpone />
|
||||
</Suspense>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
jest.mock('script creds', () => {}, {
|
||||
virtual: true,
|
||||
});
|
||||
jest.mock('module creds', () => {}, {
|
||||
virtual: true,
|
||||
});
|
||||
jest.mock('script anon', () => {}, {
|
||||
virtual: true,
|
||||
});
|
||||
jest.mock('module default', () => {}, {
|
||||
virtual: true,
|
||||
});
|
||||
|
||||
const prerendered = await ReactDOMFizzStatic.prerender(<App />);
|
||||
expect(prerendered.postponed).not.toBe(null);
|
||||
|
||||
await readIntoContainer(prerendered.prelude);
|
||||
|
||||
expect(getVisibleChildren(container)).toEqual([
|
||||
<link
|
||||
rel="preload"
|
||||
as="style"
|
||||
href="style creds"
|
||||
crossorigin="use-credentials"
|
||||
/>,
|
||||
<link
|
||||
rel="preload"
|
||||
as="script"
|
||||
href="script creds"
|
||||
crossorigin="use-credentials"
|
||||
integrity="script-hash"
|
||||
/>,
|
||||
<link
|
||||
rel="modulepreload"
|
||||
href="module creds"
|
||||
crossorigin="use-credentials"
|
||||
integrity="module-hash"
|
||||
/>,
|
||||
<link rel="preload" as="style" href="style anon" crossorigin="" />,
|
||||
<link rel="preload" as="script" href="script anon" crossorigin="" />,
|
||||
<link
|
||||
rel="modulepreload"
|
||||
href="module default"
|
||||
integrity="module-hash"
|
||||
/>,
|
||||
<div>Loading...</div>,
|
||||
]);
|
||||
|
||||
prerendering = false;
|
||||
const content = await ReactDOMFizzServer.resume(
|
||||
<App />,
|
||||
JSON.parse(JSON.stringify(prerendered.postponed)),
|
||||
);
|
||||
|
||||
await readIntoContainer(content);
|
||||
|
||||
// Dispatch load event to injected stylesheet
|
||||
const linkCreds = document.querySelector(
|
||||
'link[rel="stylesheet"][href="style creds"]',
|
||||
);
|
||||
const linkAnon = document.querySelector(
|
||||
'link[rel="stylesheet"][href="style anon"]',
|
||||
);
|
||||
const event = document.createEvent('Events');
|
||||
event.initEvent('load', true, true);
|
||||
linkCreds.dispatchEvent(event);
|
||||
linkAnon.dispatchEvent(event);
|
||||
|
||||
// Wait for the instruction microtasks to flush.
|
||||
await 0;
|
||||
await 0;
|
||||
|
||||
expect(getVisibleChildren(document)).toEqual(
|
||||
<html>
|
||||
<head>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="style creds"
|
||||
crossorigin="use-credentials"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="style anon"
|
||||
crossorigin=""
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<link
|
||||
rel="preload"
|
||||
as="style"
|
||||
href="style creds"
|
||||
crossorigin="use-credentials"
|
||||
/>
|
||||
<link
|
||||
rel="preload"
|
||||
as="script"
|
||||
href="script creds"
|
||||
crossorigin="use-credentials"
|
||||
integrity="script-hash"
|
||||
/>
|
||||
<link
|
||||
rel="modulepreload"
|
||||
href="module creds"
|
||||
crossorigin="use-credentials"
|
||||
integrity="module-hash"
|
||||
/>
|
||||
<link rel="preload" as="style" href="style anon" crossorigin="" />
|
||||
<link rel="preload" as="script" href="script anon" crossorigin="" />
|
||||
<link
|
||||
rel="modulepreload"
|
||||
href="module default"
|
||||
integrity="module-hash"
|
||||
/>
|
||||
<div />
|
||||
<script
|
||||
async=""
|
||||
src="script creds"
|
||||
crossorigin="use-credentials"
|
||||
integrity="script-hash"
|
||||
data-meaningful=""
|
||||
/>
|
||||
<script
|
||||
type="module"
|
||||
async=""
|
||||
src="module creds"
|
||||
crossorigin="use-credentials"
|
||||
integrity="module-hash"
|
||||
data-meaningful=""
|
||||
/>
|
||||
<script
|
||||
async=""
|
||||
src="script anon"
|
||||
crossorigin=""
|
||||
data-meaningful=""
|
||||
/>
|
||||
<script
|
||||
type="module"
|
||||
async=""
|
||||
src="module default"
|
||||
integrity="module-hash"
|
||||
data-meaningful=""
|
||||
/>
|
||||
</div>
|
||||
</body>
|
||||
</html>,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -86,7 +86,7 @@ describe('ReactDOMFizzStaticNode', () => {
|
||||
);
|
||||
const prelude = await readContent(result.prelude);
|
||||
expect(prelude).toMatchInlineSnapshot(
|
||||
`"<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>"`,
|
||||
`"<link rel="preload" as="script" fetchPriority="low" href="init.js"/><link rel="modulepreload" fetchPriority="low" href="init.mjs"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -4014,6 +4014,87 @@ body {
|
||||
);
|
||||
});
|
||||
|
||||
it('can promote images to high priority when at least one instance specifies a high fetchPriority', async () => {
|
||||
function App() {
|
||||
// If a ends up in a higher priority queue than b it will flush first
|
||||
ReactDOM.preload('a', {as: 'image'});
|
||||
ReactDOM.preload('b', {as: 'image'});
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<link rel="stylesheet" href="foo" precedence="default" />
|
||||
<img src="1" />
|
||||
<img src="2" />
|
||||
<img src="3" />
|
||||
<img src="4" />
|
||||
<img src="5" />
|
||||
<img src="6" />
|
||||
<img src="7" />
|
||||
<img src="8" />
|
||||
<img src="9" />
|
||||
<img src="10" />
|
||||
<img src="11" />
|
||||
<img src="12" />
|
||||
<img src="a" fetchPriority="low" />
|
||||
<img src="a" />
|
||||
<img src="a" fetchPriority="high" />
|
||||
<img src="a" />
|
||||
<img src="a" />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
await act(() => {
|
||||
renderToPipeableStream(<App />).pipe(writable);
|
||||
});
|
||||
expect(getMeaningfulChildren(document)).toEqual(
|
||||
<html>
|
||||
<head>
|
||||
{/* The First 10 high priority images were just the first 10 rendered images */}
|
||||
<link rel="preload" as="image" href="1" />
|
||||
<link rel="preload" as="image" href="2" />
|
||||
<link rel="preload" as="image" href="3" />
|
||||
<link rel="preload" as="image" href="4" />
|
||||
<link rel="preload" as="image" href="5" />
|
||||
<link rel="preload" as="image" href="6" />
|
||||
<link rel="preload" as="image" href="7" />
|
||||
<link rel="preload" as="image" href="8" />
|
||||
<link rel="preload" as="image" href="9" />
|
||||
<link rel="preload" as="image" href="10" />
|
||||
{/* The "a" image was rendered a few times but since at least one of those was with
|
||||
fetchPriorty="high" it ends up in the high priority queue */}
|
||||
<link rel="preload" as="image" href="a" />
|
||||
{/* Stylesheets come in between high priority images and regular preloads */}
|
||||
<link rel="stylesheet" href="foo" data-precedence="default" />
|
||||
{/* The remainig images that preloaded at regular priority */}
|
||||
<link rel="preload" as="image" href="b" />
|
||||
<link rel="preload" as="image" href="11" />
|
||||
<link rel="preload" as="image" href="12" />
|
||||
</head>
|
||||
<body>
|
||||
<img src="1" />
|
||||
<img src="2" />
|
||||
<img src="3" />
|
||||
<img src="4" />
|
||||
<img src="5" />
|
||||
<img src="6" />
|
||||
<img src="7" />
|
||||
<img src="8" />
|
||||
<img src="9" />
|
||||
<img src="10" />
|
||||
<img src="11" />
|
||||
<img src="12" />
|
||||
<img src="a" fetchpriority="low" />
|
||||
<img src="a" />
|
||||
<img src="a" fetchpriority="high" />
|
||||
<img src="a" />
|
||||
<img src="a" />
|
||||
</body>
|
||||
</html>,
|
||||
);
|
||||
});
|
||||
|
||||
it('preloads from rendered images properly use srcSet and sizes', async () => {
|
||||
function App() {
|
||||
ReactDOM.preload('1', {as: 'image', imageSrcSet: 'ss1'});
|
||||
@@ -4119,6 +4200,501 @@ body {
|
||||
);
|
||||
});
|
||||
|
||||
it('should warn if you preload a stylesheet and then render a style tag with the same href', async () => {
|
||||
const style = 'body { color: red; }';
|
||||
function App() {
|
||||
ReactDOM.preload('foo', {as: 'style'});
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
hello
|
||||
<style precedence="default" href="foo">
|
||||
{style}
|
||||
</style>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
await expect(async () => {
|
||||
await act(() => {
|
||||
renderToPipeableStream(<App />).pipe(writable);
|
||||
});
|
||||
}).toErrorDev([
|
||||
'React encountered a hoistable style tag for the same href as a preload: "foo". When using a style tag to inline styles you should not also preload it as a stylsheet.',
|
||||
]);
|
||||
|
||||
expect(getMeaningfulChildren(document)).toEqual(
|
||||
<html>
|
||||
<head>
|
||||
<style data-precedence="default" data-href="foo">
|
||||
{style}
|
||||
</style>
|
||||
<link rel="preload" as="style" href="foo" />
|
||||
</head>
|
||||
<body>hello</body>
|
||||
</html>,
|
||||
);
|
||||
});
|
||||
|
||||
it('should preload only once even if you discover a stylesheet, script, or moduleScript late', async () => {
|
||||
function App() {
|
||||
// We start with preinitializing some resources first
|
||||
ReactDOM.preinit('shell preinit/shell', {as: 'style'});
|
||||
ReactDOM.preinit('shell preinit/shell', {as: 'script'});
|
||||
ReactDOM.preinitModule('shell preinit/shell', {as: 'script'});
|
||||
|
||||
// We initiate all the shell preloads
|
||||
ReactDOM.preload('shell preinit/shell', {as: 'style'});
|
||||
ReactDOM.preload('shell preinit/shell', {as: 'script'});
|
||||
ReactDOM.preloadModule('shell preinit/shell', {as: 'script'});
|
||||
|
||||
ReactDOM.preload('shell/shell preinit', {as: 'style'});
|
||||
ReactDOM.preload('shell/shell preinit', {as: 'script'});
|
||||
ReactDOM.preloadModule('shell/shell preinit', {as: 'script'});
|
||||
|
||||
ReactDOM.preload('shell/shell render', {as: 'style'});
|
||||
ReactDOM.preload('shell/shell render', {as: 'script'});
|
||||
ReactDOM.preloadModule('shell/shell render');
|
||||
|
||||
ReactDOM.preload('shell/late preinit', {as: 'style'});
|
||||
ReactDOM.preload('shell/late preinit', {as: 'script'});
|
||||
ReactDOM.preloadModule('shell/late preinit');
|
||||
|
||||
ReactDOM.preload('shell/late render', {as: 'style'});
|
||||
ReactDOM.preload('shell/late render', {as: 'script'});
|
||||
ReactDOM.preloadModule('shell/late render');
|
||||
|
||||
// we preinit later ones that should be created by
|
||||
ReactDOM.preinit('shell/shell preinit', {as: 'style'});
|
||||
ReactDOM.preinit('shell/shell preinit', {as: 'script'});
|
||||
ReactDOM.preinitModule('shell/shell preinit');
|
||||
|
||||
ReactDOM.preinit('late/shell preinit', {as: 'style'});
|
||||
ReactDOM.preinit('late/shell preinit', {as: 'script'});
|
||||
ReactDOM.preinitModule('late/shell preinit');
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
precedence="default"
|
||||
href="shell/shell render"
|
||||
/>
|
||||
<script async={true} src="shell/shell render" />
|
||||
<script type="module" async={true} src="shell/shell render" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
precedence="default"
|
||||
href="late/shell render"
|
||||
/>
|
||||
<script async={true} src="late/shell render" />
|
||||
<script type="module" async={true} src="late/shell render" />
|
||||
<Suspense fallback="late...">
|
||||
<BlockedOn value="late">
|
||||
<Late />
|
||||
</BlockedOn>
|
||||
</Suspense>
|
||||
<Suspense fallback="later...">
|
||||
<BlockedOn value="later">
|
||||
<Later />
|
||||
</BlockedOn>
|
||||
</Suspense>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
function Late() {
|
||||
ReactDOM.preload('late/later preinit', {as: 'style'});
|
||||
ReactDOM.preload('late/later preinit', {as: 'script'});
|
||||
ReactDOM.preloadModule('late/later preinit');
|
||||
|
||||
ReactDOM.preload('late/later render', {as: 'style'});
|
||||
ReactDOM.preload('late/later render', {as: 'script'});
|
||||
ReactDOM.preloadModule('late/later render');
|
||||
|
||||
ReactDOM.preload('late/shell preinit', {as: 'style'});
|
||||
ReactDOM.preload('late/shell preinit', {as: 'script'});
|
||||
ReactDOM.preloadModule('late/shell preinit');
|
||||
|
||||
ReactDOM.preload('late/shell render', {as: 'style'});
|
||||
ReactDOM.preload('late/shell render', {as: 'script'});
|
||||
ReactDOM.preloadModule('late/shell render');
|
||||
|
||||
// late preinits don't actually flush so we won't see this in the DOM as a stylesehet but we should see
|
||||
// the preload for this resource
|
||||
ReactDOM.preinit('shell/late preinit', {as: 'style'});
|
||||
ReactDOM.preinit('shell/late preinit', {as: 'script'});
|
||||
ReactDOM.preinitModule('shell/late preinit');
|
||||
return (
|
||||
<>
|
||||
Late
|
||||
<link
|
||||
rel="stylesheet"
|
||||
precedence="default"
|
||||
href="shell/late render"
|
||||
/>
|
||||
<script async={true} src="shell/late render" />
|
||||
<script type="module" async={true} src="shell/late render" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function Later() {
|
||||
// late preinits don't actually flush so we won't see this in the DOM as a stylesehet but we should see
|
||||
// the preload for this resource
|
||||
ReactDOM.preinit('late/later preinit', {as: 'style'});
|
||||
ReactDOM.preinit('late/later preinit', {as: 'script'});
|
||||
ReactDOM.preinitModule('late/later preinit');
|
||||
return (
|
||||
<>
|
||||
Later
|
||||
<link
|
||||
rel="stylesheet"
|
||||
precedence="default"
|
||||
href="late/later render"
|
||||
/>
|
||||
<script async={true} src="late/later render" />
|
||||
<script type="module" async={true} src="late/later render" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
await act(() => {
|
||||
renderToPipeableStream(<App />).pipe(writable);
|
||||
});
|
||||
expect(getMeaningfulChildren(document)).toEqual(
|
||||
<html>
|
||||
<head>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="shell preinit/shell"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="shell/shell preinit"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="late/shell preinit"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="shell/shell render"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="late/shell render"
|
||||
/>
|
||||
<script async="" src="shell preinit/shell" />
|
||||
<script async="" src="shell preinit/shell" type="module" />
|
||||
<script async="" src="shell/shell preinit" />
|
||||
<script async="" src="shell/shell preinit" type="module" />
|
||||
<script async="" src="late/shell preinit" />
|
||||
<script async="" src="late/shell preinit" type="module" />
|
||||
<script async="" src="shell/shell render" />
|
||||
<script async="" src="shell/shell render" type="module" />
|
||||
<script async="" src="late/shell render" />
|
||||
<script async="" src="late/shell render" type="module" />
|
||||
<link rel="preload" as="style" href="shell/late preinit" />
|
||||
<link rel="preload" as="script" href="shell/late preinit" />
|
||||
<link rel="modulepreload" href="shell/late preinit" />
|
||||
<link rel="preload" as="style" href="shell/late render" />
|
||||
<link rel="preload" as="script" href="shell/late render" />
|
||||
<link rel="modulepreload" href="shell/late render" />
|
||||
</head>
|
||||
<body>
|
||||
{'late...'}
|
||||
{'later...'}
|
||||
</body>
|
||||
</html>,
|
||||
);
|
||||
|
||||
await act(() => {
|
||||
resolveText('late');
|
||||
});
|
||||
expect(getMeaningfulChildren(document)).toEqual(
|
||||
<html>
|
||||
<head>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="shell preinit/shell"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="shell/shell preinit"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="late/shell preinit"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="shell/shell render"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="late/shell render"
|
||||
/>
|
||||
{/* FROM HERE */}
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="shell/late render"
|
||||
/>
|
||||
{/** TO HERE:
|
||||
* This was hoisted by boundary complete instruction. The preload was already emitted in the
|
||||
* shell but we see it below because this was inserted clientside by precedence.
|
||||
* We don't observe the "shell/late preinit" because these do not flush unless they are flushing
|
||||
* with the shell
|
||||
* */}
|
||||
<script async="" src="shell preinit/shell" />
|
||||
<script async="" src="shell preinit/shell" type="module" />
|
||||
<script async="" src="shell/shell preinit" />
|
||||
<script async="" src="shell/shell preinit" type="module" />
|
||||
<script async="" src="late/shell preinit" />
|
||||
<script async="" src="late/shell preinit" type="module" />
|
||||
<script async="" src="shell/shell render" />
|
||||
<script async="" src="shell/shell render" type="module" />
|
||||
<script async="" src="late/shell render" />
|
||||
<script async="" src="late/shell render" type="module" />
|
||||
<link rel="preload" as="style" href="shell/late preinit" />
|
||||
<link rel="preload" as="script" href="shell/late preinit" />
|
||||
<link rel="modulepreload" href="shell/late preinit" />
|
||||
<link rel="preload" as="style" href="shell/late render" />
|
||||
<link rel="preload" as="script" href="shell/late render" />
|
||||
<link rel="modulepreload" href="shell/late render" />
|
||||
</head>
|
||||
<body>
|
||||
{'late...'}
|
||||
{'later...'}
|
||||
{/* FROM HERE */}
|
||||
<script async="" src="shell/late preinit" />
|
||||
<script async="" src="shell/late preinit" type="module" />
|
||||
<script async="" src="shell/late render" />
|
||||
<script async="" src="shell/late render" type="module" />
|
||||
<link rel="preload" as="style" href="late/later preinit" />
|
||||
<link rel="preload" as="script" href="late/later preinit" />
|
||||
<link rel="modulepreload" href="late/later preinit" />
|
||||
<link rel="preload" as="style" href="late/later render" />
|
||||
<link rel="preload" as="script" href="late/later render" />
|
||||
<link rel="modulepreload" href="late/later render" />
|
||||
{/** TO HERE:
|
||||
* These resources streamed into the body during the boundary flush. Scripts go first then
|
||||
* preloads according to our streaming queue priorities. Note also that late/shell resources
|
||||
* where the resource already emitted in the shell and the preload is invoked later do not
|
||||
* end up with a preload in the document at all.
|
||||
* */}
|
||||
</body>
|
||||
</html>,
|
||||
);
|
||||
|
||||
await act(() => {
|
||||
resolveText('later');
|
||||
});
|
||||
expect(getMeaningfulChildren(document)).toEqual(
|
||||
<html>
|
||||
<head>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="shell preinit/shell"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="shell/shell preinit"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="late/shell preinit"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="shell/shell render"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="late/shell render"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="shell/late render"
|
||||
/>
|
||||
{/* FROM HERE */}
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="late/later render"
|
||||
/>
|
||||
{/** TO HERE:
|
||||
* This was hoisted by boundary complete instruction. The preload was already emitted in the
|
||||
* shell but we see it below because this was inserted clientside by precedence
|
||||
* We don't observe the "late/later preinit" because these do not flush unless they are flushing
|
||||
* with the shell
|
||||
* */}
|
||||
<script async="" src="shell preinit/shell" />
|
||||
<script async="" src="shell preinit/shell" type="module" />
|
||||
<script async="" src="shell/shell preinit" />
|
||||
<script async="" src="shell/shell preinit" type="module" />
|
||||
<script async="" src="late/shell preinit" />
|
||||
<script async="" src="late/shell preinit" type="module" />
|
||||
<script async="" src="shell/shell render" />
|
||||
<script async="" src="shell/shell render" type="module" />
|
||||
<script async="" src="late/shell render" />
|
||||
<script async="" src="late/shell render" type="module" />
|
||||
<link rel="preload" as="style" href="shell/late preinit" />
|
||||
<link rel="preload" as="script" href="shell/late preinit" />
|
||||
<link rel="modulepreload" href="shell/late preinit" />
|
||||
<link rel="preload" as="style" href="shell/late render" />
|
||||
<link rel="preload" as="script" href="shell/late render" />
|
||||
<link rel="modulepreload" href="shell/late render" />
|
||||
</head>
|
||||
<body>
|
||||
{'late...'}
|
||||
{'later...'}
|
||||
<script async="" src="shell/late preinit" />
|
||||
<script async="" src="shell/late preinit" type="module" />
|
||||
<script async="" src="shell/late render" />
|
||||
<script async="" src="shell/late render" type="module" />
|
||||
<link rel="preload" as="style" href="late/later preinit" />
|
||||
<link rel="preload" as="script" href="late/later preinit" />
|
||||
<link rel="modulepreload" href="late/later preinit" />
|
||||
<link rel="preload" as="style" href="late/later render" />
|
||||
<link rel="preload" as="script" href="late/later render" />
|
||||
<link rel="modulepreload" href="late/later render" />
|
||||
{/* FROM HERE */}
|
||||
<script async="" src="late/later preinit" />
|
||||
<script async="" src="late/later preinit" type="module" />
|
||||
<script async="" src="late/later render" />
|
||||
<script async="" src="late/later render" type="module" />
|
||||
{/** TO HERE:
|
||||
* These resources streamed into the body during the boundary flush. Scripts go first then
|
||||
* preloads according to our streaming queue priorities
|
||||
* */}
|
||||
</body>
|
||||
</html>,
|
||||
);
|
||||
loadStylesheets();
|
||||
assertLog([
|
||||
'load stylesheet: shell preinit/shell',
|
||||
'load stylesheet: shell/shell preinit',
|
||||
'load stylesheet: late/shell preinit',
|
||||
'load stylesheet: shell/shell render',
|
||||
'load stylesheet: late/shell render',
|
||||
'load stylesheet: shell/late render',
|
||||
'load stylesheet: late/later render',
|
||||
]);
|
||||
|
||||
ReactDOMClient.hydrateRoot(document, <App />);
|
||||
await waitForAll([]);
|
||||
expect(getMeaningfulChildren(document)).toEqual(
|
||||
<html>
|
||||
<head>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="shell preinit/shell"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="shell/shell preinit"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="late/shell preinit"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="shell/shell render"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="late/shell render"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="shell/late render"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="late/later render"
|
||||
/>
|
||||
{/* FROM HERE */}
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="shell/late preinit"
|
||||
/>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
data-precedence="default"
|
||||
href="late/later preinit"
|
||||
/>
|
||||
{/** TO HERE:
|
||||
* The client render patches in the two missing preinit stylesheets when hydration happens
|
||||
* Note that this is only because we repeated the calls to preinit on the client
|
||||
* */}
|
||||
<script async="" src="shell preinit/shell" />
|
||||
<script async="" src="shell preinit/shell" type="module" />
|
||||
<script async="" src="shell/shell preinit" />
|
||||
<script async="" src="shell/shell preinit" type="module" />
|
||||
<script async="" src="late/shell preinit" />
|
||||
<script async="" src="late/shell preinit" type="module" />
|
||||
<script async="" src="shell/shell render" />
|
||||
<script async="" src="shell/shell render" type="module" />
|
||||
<script async="" src="late/shell render" />
|
||||
<script async="" src="late/shell render" type="module" />
|
||||
<link rel="preload" as="style" href="shell/late preinit" />
|
||||
<link rel="preload" as="script" href="shell/late preinit" />
|
||||
<link rel="modulepreload" href="shell/late preinit" />
|
||||
<link rel="preload" as="style" href="shell/late render" />
|
||||
<link rel="preload" as="script" href="shell/late render" />
|
||||
<link rel="modulepreload" href="shell/late render" />
|
||||
</head>
|
||||
<body>
|
||||
{'Late'}
|
||||
{'Later'}
|
||||
<script async="" src="shell/late preinit" />
|
||||
<script async="" src="shell/late preinit" type="module" />
|
||||
<script async="" src="shell/late render" />
|
||||
<script async="" src="shell/late render" type="module" />
|
||||
<link rel="preload" as="style" href="late/later preinit" />
|
||||
<link rel="preload" as="script" href="late/later preinit" />
|
||||
<link rel="modulepreload" href="late/later preinit" />
|
||||
<link rel="preload" as="style" href="late/later render" />
|
||||
<link rel="preload" as="script" href="late/later render" />
|
||||
<link rel="modulepreload" href="late/later render" />
|
||||
<script async="" src="late/later preinit" />
|
||||
<script async="" src="late/later preinit" type="module" />
|
||||
<script async="" src="late/later render" />
|
||||
<script async="" src="late/later render" type="module" />
|
||||
</body>
|
||||
</html>,
|
||||
);
|
||||
});
|
||||
|
||||
describe('ReactDOM.prefetchDNS(href)', () => {
|
||||
it('creates a dns-prefetch resource when called', async () => {
|
||||
function App({url}) {
|
||||
|
||||
@@ -24,6 +24,7 @@ export type PreloadModuleOptions = {
|
||||
as?: string,
|
||||
crossOrigin?: string,
|
||||
integrity?: string,
|
||||
nonce?: string,
|
||||
};
|
||||
export type PreinitOptions = {
|
||||
as: string,
|
||||
@@ -37,6 +38,7 @@ export type PreinitModuleOptions = {
|
||||
as?: string,
|
||||
crossOrigin?: string,
|
||||
integrity?: string,
|
||||
nonce?: string,
|
||||
};
|
||||
|
||||
export type CrossOriginEnum = '' | 'use-credentials';
|
||||
@@ -56,6 +58,7 @@ export type PreloadModuleImplOptions = {
|
||||
as?: ?string,
|
||||
crossOrigin?: ?CrossOriginEnum,
|
||||
integrity?: ?string,
|
||||
nonce?: ?string,
|
||||
};
|
||||
export type PreinitStyleOptions = {
|
||||
crossOrigin?: ?string,
|
||||
@@ -70,7 +73,8 @@ export type PreinitScriptOptions = {
|
||||
};
|
||||
export type PreinitModuleScriptOptions = {
|
||||
crossOrigin?: ?CrossOriginEnum,
|
||||
integrity?: string,
|
||||
integrity?: ?string,
|
||||
nonce?: ?string,
|
||||
};
|
||||
|
||||
export type HostDispatcher = {
|
||||
|
||||
@@ -93,6 +93,7 @@ async function executeScript(script: Element) {
|
||||
'You must set the current document to the global document to use script src in tests',
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// $FlowFixMe
|
||||
require(scriptSrc);
|
||||
@@ -177,8 +178,8 @@ function getVisibleChildren(element: Element): React$Node {
|
||||
while (node) {
|
||||
if (node.nodeType === 1) {
|
||||
if (
|
||||
node.tagName !== 'SCRIPT' &&
|
||||
node.tagName !== 'script' &&
|
||||
((node.tagName !== 'SCRIPT' && node.tagName !== 'script') ||
|
||||
node.hasAttribute('data-meaningful')) &&
|
||||
node.tagName !== 'TEMPLATE' &&
|
||||
node.tagName !== 'template' &&
|
||||
!node.hasAttribute('hidden') &&
|
||||
|
||||
@@ -59,7 +59,7 @@ describe('ReactDOMServerFB', () => {
|
||||
});
|
||||
const result = readResult(stream);
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"<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>"`,
|
||||
`"<link rel="preload" as="script" fetchPriority="low" href="init.js"/><link rel="modulepreload" fetchPriority="low" href="init.mjs"/><div>hello world</div><script>INIT();</script><script src="init.js" async=""></script><script type="module" src="init.mjs" async=""></script>"`,
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user