diff --git a/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js b/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js index 97b222d080d4e..c9e8607eaaf7d 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js +++ b/packages/react-dom-bindings/src/client/ReactDOMFloatClient.js @@ -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( diff --git a/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js b/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js index cfd3131e9ba9a..a4d394a986108 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js +++ b/packages/react-dom-bindings/src/client/ReactDOMHostConfig.js @@ -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)); } @@ -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); @@ -927,12 +944,6 @@ function getNextHydratable(node) { } break; } - case 'TITLE': - case 'HTML': - case 'HEAD': - case 'BODY': { - continue; - } } break; } else if (nodeType === TEXT_NODE) { @@ -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': { diff --git a/packages/react-dom-bindings/src/server/ReactDOMFloatServer.js b/packages/react-dom-bindings/src/server/ReactDOMFloatServer.js index ebfedb750254e..46cfe75c95510 100644 --- a/packages/react-dom-bindings/src/server/ReactDOMFloatServer.js +++ b/packages/react-dom-bindings/src/server/ReactDOMFloatServer.js @@ -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 : ''; diff --git a/packages/react-dom/src/__tests__/ReactDOMFloat-test.js b/packages/react-dom/src/__tests__/ReactDOMFloat-test.js index 61c8d137acd33..7c4e826b22f8c 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFloat-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFloat-test.js @@ -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( + +
+ + {}} /> +