Skip to content

Commit

Permalink
fix coordination of resource identity and hydration
Browse files Browse the repository at this point in the history
  • Loading branch information
gnoff committed Oct 26, 2022
1 parent fecc288 commit 54cb45a
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 22 deletions.
4 changes: 2 additions & 2 deletions packages/react-dom-bindings/src/client/ReactDOMFloatClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -1465,9 +1465,9 @@ export function isHostResourceType(type: string, props: Props): boolean {
}
return (async: any) && typeof src === 'string' && !onLoad && !onError;
}
case 'noscript':
case 'template':
case 'style':
case 'noscript': {
case 'style': {
if (__DEV__) {
if (resourceFormOnly) {
console.error(
Expand Down
52 changes: 33 additions & 19 deletions packages/react-dom-bindings/src/client/ReactDOMHostConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -802,7 +802,15 @@ export const supportsHydration = true;
// inserted without breaking hydration
export function isHydratable(type: string, props: Props): boolean {
if (enableFloat) {
if (type === 'script') {
if (type === 'link') {
if (
(props: any).rel === 'stylesheet' &&
typeof (props: any).precedence !== 'string'
) {
return true;
}
return false;
} else if (type === 'script') {
const {async, onLoad, onError} = (props: any);
return !(async && (onLoad || onError));
}
Expand Down Expand Up @@ -902,16 +910,25 @@ function getNextHydratable(node) {
if (nodeType === ELEMENT_NODE) {
const element: Element = (node: any);
switch (element.tagName) {
case 'TITLE':
case 'META':
case 'BASE':
case 'HTML':
case 'HEAD':
case 'BODY': {
continue;
}
case 'LINK': {
const linkEl: HTMLLinkElement = (element: any);
const rel = linkEl.rel;
// All links that are server rendered are resources except
// stylesheets that do not have a precedence
if (
rel === 'preload' ||
(rel === 'stylesheet' && linkEl.hasAttribute('data-precedence'))
linkEl.rel === 'stylesheet' &&
!linkEl.hasAttribute('data-precedence')
) {
continue;
break;
}
break;
continue;
}
case 'STYLE': {
const styleEl: HTMLStyleElement = (element: any);
Expand All @@ -927,12 +944,6 @@ function getNextHydratable(node) {
}
break;
}
case 'TITLE':
case 'HTML':
case 'HEAD':
case 'BODY': {
continue;
}
}
break;
} else if (nodeType === TEXT_NODE) {
Expand All @@ -942,18 +953,21 @@ function getNextHydratable(node) {
if (nodeType === ELEMENT_NODE) {
const element: Element = (node: any);
switch (element.tagName) {
case 'TITLE':
case 'META':
case 'BASE': {
continue;
}
case 'LINK': {
const linkEl: HTMLLinkElement = (element: any);
const rel = linkEl.rel;
// All links that are server rendered are resources except
// stylesheets that do not have a precedence
if (
rel === 'preload' ||
(rel === 'stylesheet' && linkEl.hasAttribute('data-precedence'))
linkEl.rel === 'stylesheet' &&
!linkEl.hasAttribute('data-precedence')
) {
continue;
break;
}
break;
}
case 'TITLE': {
continue;
}
case 'STYLE': {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,7 @@ export function resourcesFromLink(props: Props): boolean {
}
}
if (props.onLoad || props.onError) {
return false;
return true;
}

const sizes = typeof props.sizes === 'string' ? props.sizes : '';
Expand Down
58 changes: 58 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMFloat-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,64 @@ describe('ReactDOMFloat', () => {
});
}

// @gate enableFloat
it('can hydrate non Resources in head when Resources are also inserted there', async () => {
await actIntoEmptyDocument(() => {
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
<html>
<head>
<meta property="foo" content="bar" />
<link rel="foo" href="bar" onLoad={() => {}} />
<title>foo</title>
<base target="foo" href="bar" />
<script async={true} src="foo" onLoad={() => {}} />
</head>
<body>foo</body>
</html>,
);
pipe(writable);
});
expect(getMeaningfulChildren(document)).toEqual(
<html>
<head>
<base target="foo" href="bar" />
<link rel="preload" href="foo" as="script" />
<meta property="foo" content="bar" />
<title>foo</title>
</head>
<body>foo</body>
</html>,
);

ReactDOMClient.hydrateRoot(
document,
<html>
<head>
<meta property="foo" content="bar" />
<link rel="foo" href="bar" onLoad={() => {}} />
<title>foo</title>
<base target="foo" href="bar" />
<script async={true} src="foo" onLoad={() => {}} />
</head>
<body>foo</body>
</html>,
);
expect(Scheduler).toFlushWithoutYielding();
expect(getMeaningfulChildren(document)).toEqual(
<html>
<head>
<base target="foo" href="bar" />
<link rel="preload" href="foo" as="script" />
<meta property="foo" content="bar" />
<title>foo</title>
<link rel="foo" href="bar" />
<script async="" src="foo" />
</head>
<body>foo</body>
</html>,
);
});

// @gate enableFloat || !__DEV__
it('warns if you render resource-like elements above <head> or <body>', async () => {
const root = ReactDOMClient.createRoot(document);
Expand Down

0 comments on commit 54cb45a

Please sign in to comment.