Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fizz][Float] Refactor Resources #27400

Merged
merged 10 commits into from
Sep 26, 2023
1,489 changes: 823 additions & 666 deletions packages/react-dom-bindings/src/server/ReactFizzConfigDOM.js

Large diffs are not rendered by default.

36 changes: 22 additions & 14 deletions packages/react-dom-bindings/src/server/ReactFizzConfigDOMLegacy.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
* @flow
*/

import type {ResumableState, BoundaryResources} from './ReactFizzConfigDOM';
import type {
ResumableState,
BoundaryResources,
StyleQueue,
Resource,
} from './ReactFizzConfigDOM';

import {
createRenderState as createRenderStateImpl,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>"`,
);
});

Expand Down Expand Up @@ -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>"`,
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>"`,
);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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>"`,
);
});

Expand Down
275 changes: 275 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMFizzStaticFloat-test.js
Original file line number Diff line number Diff line change
@@ -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>,
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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>"`,
);
});

Expand Down
Loading