Skip to content

Commit 2119dff

Browse files
committed
[Fizz/Float] Float for stylesheet resources
This change implements Float for a minimal use case of hoisting stylesheet resources to the head and ensuring the flush in the appropriate spot in the stream. Subsequent commits will add support for client stylesheet hoisting and Flight resources. While there is some additional buildout of Float capabilities in general the public APIs have all been removed. The intent with this first implementation is to opt in <link rel="stylesheet"> use the more useful semantics of resources
1 parent c91a1e0 commit 2119dff

File tree

51 files changed

+5702
-669
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+5702
-669
lines changed

packages/react-art/src/ReactARTHostConfig.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ export * from 'react-reconciler/src/ReactFiberHostConfigWithNoHydration';
243243
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoScopes';
244244
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoTestSelectors';
245245
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoMicrotasks';
246+
export * from 'react-reconciler/src/ReactFiberHostConfigWithNoResources';
246247

247248
export function appendInitialChild(parentInstance, child) {
248249
if (typeof child === 'string') {

packages/react-dom/index.experimental.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,5 @@ export {
2222
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
2323
version,
2424
} from './src/client/ReactDOM';
25+
26+
export {preinit, preload} from './src/shared/ReactDOMFloat';

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

Lines changed: 8 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ describe('ReactDOMFizzServer', () => {
329329
);
330330
pipe(writable);
331331
});
332+
332333
expect(getVisibleChildren(container)).toEqual(
333334
<div>
334335
<div>Loading...</div>
@@ -4391,7 +4392,7 @@ describe('ReactDOMFizzServer', () => {
43914392
});
43924393

43934394
// @gate enableFloat
4394-
it('recognizes stylesheet links as attributes during hydration', async () => {
4395+
it('recognizes stylesheet links as resources during hydration', async () => {
43954396
await actIntoEmptyDocument(() => {
43964397
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
43974398
<>
@@ -4469,15 +4470,13 @@ describe('ReactDOMFizzServer', () => {
44694470
);
44704471
try {
44714472
expect(Scheduler).toFlushWithoutYielding();
4473+
// The reason data-bar is not found on the link is props are only used to generate the resource instance
4474+
// if it does not already exist but in this case it was left behind in the document. In the future
4475+
// changing props on resources will warn in dev
44724476
expect(getVisibleChildren(document)).toEqual(
44734477
<html>
44744478
<head>
4475-
<link
4476-
rel="stylesheet"
4477-
href="foo"
4478-
data-rprec="default"
4479-
data-bar="bar"
4480-
/>
4479+
<link rel="stylesheet" href="foo" data-rprec="default" />
44814480
</head>
44824481
<body>a body</body>
44834482
</html>,
@@ -4498,7 +4497,7 @@ describe('ReactDOMFizzServer', () => {
44984497

44994498
// Temporarily this test is expected to fail everywhere. When we have resource hoisting
45004499
// it should start to pass and we can adjust the gate accordingly
4501-
// @gate false && enableFloat
4500+
// @gate experimental && enableFloat
45024501
it('should insert missing resources during hydration', async () => {
45034502
await actIntoEmptyDocument(() => {
45044503
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
@@ -4525,7 +4524,7 @@ describe('ReactDOMFizzServer', () => {
45254524
expect(getVisibleChildren(document)).toEqual(
45264525
<html>
45274526
<head>
4528-
<link rel="stylesheet" href="foo" precedence="foo" />
4527+
<link rel="stylesheet" href="foo" data-rprec="foo" />
45294528
</head>
45304529
<body>foo</body>
45314530
</html>,
@@ -4546,111 +4545,6 @@ describe('ReactDOMFizzServer', () => {
45464545
}
45474546
});
45484547

4549-
// @gate experimental && enableFloat
4550-
it('fail hydration if a suitable resource cannot be found in the DOM for a given location (href)', async () => {
4551-
gate(flags => {
4552-
if (!(__EXPERIMENTAL__ && flags.enableFloat)) {
4553-
throw new Error('bailing out of test');
4554-
}
4555-
});
4556-
await actIntoEmptyDocument(() => {
4557-
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
4558-
<html>
4559-
<head />
4560-
<body>a body</body>
4561-
</html>,
4562-
);
4563-
pipe(writable);
4564-
});
4565-
4566-
const errors = [];
4567-
ReactDOMClient.hydrateRoot(
4568-
document,
4569-
<html>
4570-
<head>
4571-
<link rel="stylesheet" href="foo" precedence="low" />
4572-
</head>
4573-
<body>a body</body>
4574-
</html>,
4575-
{
4576-
onRecoverableError(err, errInfo) {
4577-
errors.push(err.message);
4578-
},
4579-
},
4580-
);
4581-
expect(() => {
4582-
expect(Scheduler).toFlushWithoutYielding();
4583-
}).toErrorDev(
4584-
[
4585-
'Warning: A matching Hydratable Resource was not found in the DOM for <link rel="stylesheet" href="foo">',
4586-
'Warning: An error occurred during hydration. The server HTML was replaced with client content in <#document>.',
4587-
],
4588-
{withoutStack: 1},
4589-
);
4590-
expect(errors).toEqual([
4591-
'Hydration failed because the initial UI does not match what was rendered on the server.',
4592-
'Hydration failed because the initial UI does not match what was rendered on the server.',
4593-
'There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering.',
4594-
]);
4595-
});
4596-
4597-
// @gate experimental && enableFloat
4598-
it('should error in dev when rendering more than one resource for a given location (href)', async () => {
4599-
gate(flags => {
4600-
if (!(__EXPERIMENTAL__ && flags.enableFloat)) {
4601-
throw new Error('bailing out of test');
4602-
}
4603-
});
4604-
await actIntoEmptyDocument(() => {
4605-
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
4606-
<>
4607-
<link rel="stylesheet" href="foo" precedence="low" />
4608-
<link rel="stylesheet" href="foo" precedence="high" />
4609-
<html>
4610-
<head />
4611-
<body>a body</body>
4612-
</html>
4613-
</>,
4614-
);
4615-
pipe(writable);
4616-
});
4617-
expect(getVisibleChildren(document)).toEqual(
4618-
<html>
4619-
<head>
4620-
<link rel="stylesheet" href="foo" data-rprec="low" />
4621-
<link rel="stylesheet" href="foo" data-rprec="high" />
4622-
</head>
4623-
<body>a body</body>
4624-
</html>,
4625-
);
4626-
4627-
const errors = [];
4628-
ReactDOMClient.hydrateRoot(
4629-
document,
4630-
<>
4631-
<html>
4632-
<head>
4633-
<link rel="stylesheet" href="foo" precedence="low" />
4634-
<link rel="stylesheet" href="foo" precedence="high" />
4635-
</head>
4636-
<body>a body</body>
4637-
</html>
4638-
</>,
4639-
{
4640-
onRecoverableError(err, errInfo) {
4641-
errors.push(err.message);
4642-
},
4643-
},
4644-
);
4645-
expect(() => {
4646-
expect(Scheduler).toFlushWithoutYielding();
4647-
}).toErrorDev([
4648-
'Warning: Stylesheet resources need a unique representation in the DOM while hydrating and more than one matching DOM Node was found. To fix, ensure you are only rendering one stylesheet link with an href attribute of "foo"',
4649-
'Warning: Stylesheet resources need a unique representation in the DOM while hydrating and more than one matching DOM Node was found. To fix, ensure you are only rendering one stylesheet link with an href attribute of "foo"',
4650-
]);
4651-
expect(errors).toEqual([]);
4652-
});
4653-
46544548
describe('text separators', () => {
46554549
// To force performWork to start before resolving AsyncText but before piping we need to wait until
46564550
// after scheduleWork which currently uses setImmediate to delay performWork

0 commit comments

Comments
 (0)