Skip to content

Commit 54cb45a

Browse files
committed
fix coordination of resource identity and hydration
1 parent fecc288 commit 54cb45a

File tree

4 files changed

+94
-22
lines changed

4 files changed

+94
-22
lines changed

packages/react-dom-bindings/src/client/ReactDOMFloatClient.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1465,9 +1465,9 @@ export function isHostResourceType(type: string, props: Props): boolean {
14651465
}
14661466
return (async: any) && typeof src === 'string' && !onLoad && !onError;
14671467
}
1468+
case 'noscript':
14681469
case 'template':
1469-
case 'style':
1470-
case 'noscript': {
1470+
case 'style': {
14711471
if (__DEV__) {
14721472
if (resourceFormOnly) {
14731473
console.error(

packages/react-dom-bindings/src/client/ReactDOMHostConfig.js

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,15 @@ export const supportsHydration = true;
802802
// inserted without breaking hydration
803803
export function isHydratable(type: string, props: Props): boolean {
804804
if (enableFloat) {
805-
if (type === 'script') {
805+
if (type === 'link') {
806+
if (
807+
(props: any).rel === 'stylesheet' &&
808+
typeof (props: any).precedence !== 'string'
809+
) {
810+
return true;
811+
}
812+
return false;
813+
} else if (type === 'script') {
806814
const {async, onLoad, onError} = (props: any);
807815
return !(async && (onLoad || onError));
808816
}
@@ -902,16 +910,25 @@ function getNextHydratable(node) {
902910
if (nodeType === ELEMENT_NODE) {
903911
const element: Element = (node: any);
904912
switch (element.tagName) {
913+
case 'TITLE':
914+
case 'META':
915+
case 'BASE':
916+
case 'HTML':
917+
case 'HEAD':
918+
case 'BODY': {
919+
continue;
920+
}
905921
case 'LINK': {
906922
const linkEl: HTMLLinkElement = (element: any);
907-
const rel = linkEl.rel;
923+
// All links that are server rendered are resources except
924+
// stylesheets that do not have a precedence
908925
if (
909-
rel === 'preload' ||
910-
(rel === 'stylesheet' && linkEl.hasAttribute('data-precedence'))
926+
linkEl.rel === 'stylesheet' &&
927+
!linkEl.hasAttribute('data-precedence')
911928
) {
912-
continue;
929+
break;
913930
}
914-
break;
931+
continue;
915932
}
916933
case 'STYLE': {
917934
const styleEl: HTMLStyleElement = (element: any);
@@ -927,12 +944,6 @@ function getNextHydratable(node) {
927944
}
928945
break;
929946
}
930-
case 'TITLE':
931-
case 'HTML':
932-
case 'HEAD':
933-
case 'BODY': {
934-
continue;
935-
}
936947
}
937948
break;
938949
} else if (nodeType === TEXT_NODE) {
@@ -942,18 +953,21 @@ function getNextHydratable(node) {
942953
if (nodeType === ELEMENT_NODE) {
943954
const element: Element = (node: any);
944955
switch (element.tagName) {
956+
case 'TITLE':
957+
case 'META':
958+
case 'BASE': {
959+
continue;
960+
}
945961
case 'LINK': {
946962
const linkEl: HTMLLinkElement = (element: any);
947-
const rel = linkEl.rel;
963+
// All links that are server rendered are resources except
964+
// stylesheets that do not have a precedence
948965
if (
949-
rel === 'preload' ||
950-
(rel === 'stylesheet' && linkEl.hasAttribute('data-precedence'))
966+
linkEl.rel === 'stylesheet' &&
967+
!linkEl.hasAttribute('data-precedence')
951968
) {
952-
continue;
969+
break;
953970
}
954-
break;
955-
}
956-
case 'TITLE': {
957971
continue;
958972
}
959973
case 'STYLE': {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,7 @@ export function resourcesFromLink(props: Props): boolean {
863863
}
864864
}
865865
if (props.onLoad || props.onError) {
866-
return false;
866+
return true;
867867
}
868868

869869
const sizes = typeof props.sizes === 'string' ? props.sizes : '';

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

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,64 @@ describe('ReactDOMFloat', () => {
281281
});
282282
}
283283

284+
// @gate enableFloat
285+
it('can hydrate non Resources in head when Resources are also inserted there', async () => {
286+
await actIntoEmptyDocument(() => {
287+
const {pipe} = ReactDOMFizzServer.renderToPipeableStream(
288+
<html>
289+
<head>
290+
<meta property="foo" content="bar" />
291+
<link rel="foo" href="bar" onLoad={() => {}} />
292+
<title>foo</title>
293+
<base target="foo" href="bar" />
294+
<script async={true} src="foo" onLoad={() => {}} />
295+
</head>
296+
<body>foo</body>
297+
</html>,
298+
);
299+
pipe(writable);
300+
});
301+
expect(getMeaningfulChildren(document)).toEqual(
302+
<html>
303+
<head>
304+
<base target="foo" href="bar" />
305+
<link rel="preload" href="foo" as="script" />
306+
<meta property="foo" content="bar" />
307+
<title>foo</title>
308+
</head>
309+
<body>foo</body>
310+
</html>,
311+
);
312+
313+
ReactDOMClient.hydrateRoot(
314+
document,
315+
<html>
316+
<head>
317+
<meta property="foo" content="bar" />
318+
<link rel="foo" href="bar" onLoad={() => {}} />
319+
<title>foo</title>
320+
<base target="foo" href="bar" />
321+
<script async={true} src="foo" onLoad={() => {}} />
322+
</head>
323+
<body>foo</body>
324+
</html>,
325+
);
326+
expect(Scheduler).toFlushWithoutYielding();
327+
expect(getMeaningfulChildren(document)).toEqual(
328+
<html>
329+
<head>
330+
<base target="foo" href="bar" />
331+
<link rel="preload" href="foo" as="script" />
332+
<meta property="foo" content="bar" />
333+
<title>foo</title>
334+
<link rel="foo" href="bar" />
335+
<script async="" src="foo" />
336+
</head>
337+
<body>foo</body>
338+
</html>,
339+
);
340+
});
341+
284342
// @gate enableFloat || !__DEV__
285343
it('warns if you render resource-like elements above <head> or <body>', async () => {
286344
const root = ReactDOMClient.createRoot(document);

0 commit comments

Comments
 (0)