Fix hydration of suspense fragments that contain text nodes (#3287)

* Fix hydration of suspense fragments that contain text nodes

* fix release builds

* disable link deduplication

* restore meta tag deduplication

* Add a test for the failed hydration case

* make changes to the document trait non-breaking

* remove info log from server future hook

* restore default implementation - this was also a breaking change

* fix semver breakage

* clean up server_future

* Cleanup unused code

* fix clippy

---------

Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
Evan AlmloffandJonathan Kelley authored and GitHub committed 2025-01-07 19:47:23 -08:00
1 parent 57d637184f
commit d41ab3a756
33 files changed
+644 -222

No files matched your search

+17 -38
View File
@@ -59,12 +59,13 @@ impl ServerDocument {
/// Write the head element into the serialized context for hydration
/// We write true if the head element was written to the DOM during server side rendering
#[track_caller]
pub(crate) fn serialize_for_hydration(&self) {
// We only serialize the head elements if the web document feature is enabled
#[cfg(feature = "document")]
{
let serialize = crate::html_storage::serialize_context();
serialize.push(&!self.0.borrow().streaming);
serialize.push(&!self.0.borrow().streaming, std::panic::Location::caller());
}
}
}
@@ -76,13 +77,10 @@ impl Document for ServerDocument {
fn set_title(&self, title: String) {
self.warn_if_streaming();
self.serialize_for_hydration();
self.0.borrow_mut().title = Some(title);
}
fn create_meta(&self, props: MetaProps) {
self.warn_if_streaming();
self.serialize_for_hydration();
self.0.borrow_mut().meta.push(rsx! {
meta {
name: props.name,
@@ -96,8 +94,6 @@ impl Document for ServerDocument {
}
fn create_script(&self, props: ScriptProps) {
self.warn_if_streaming();
self.serialize_for_hydration();
let children = props.script_contents().ok();
self.0.borrow_mut().script.push(rsx! {
script {
@@ -117,42 +113,19 @@ impl Document for ServerDocument {
}
fn create_style(&self, props: StyleProps) {
self.warn_if_streaming();
self.serialize_for_hydration();
match (&props.href, props.style_contents()) {
// The style has inline contents, render it as a style tag
(_, Ok(contents)) => self.0.borrow_mut().script.push(rsx! {
style {
media: props.media,
nonce: props.nonce,
title: props.title,
..props.additional_attributes,
{contents}
}
}),
// The style has a href, render it as a link tag
(Some(_), _) => {
self.0.borrow_mut().script.push(rsx! {
link {
rel: "stylesheet",
href: props.href,
media: props.media,
nonce: props.nonce,
title: props.title,
..props.additional_attributes,
}
});
let contents = props.style_contents().ok();
self.0.borrow_mut().script.push(rsx! {
style {
media: props.media,
nonce: props.nonce,
title: props.title,
..props.additional_attributes,
{contents}
}
// The style has neither contents nor src, log an error
(None, Err(err)) => {
err.log("Style");
}
}
})
}
fn create_link(&self, props: LinkProps) {
self.warn_if_streaming();
self.serialize_for_hydration();
self.0.borrow_mut().link.push(rsx! {
link {
rel: props.rel,
@@ -172,4 +145,10 @@ impl Document for ServerDocument {
}
})
}
fn create_head_component(&self) -> bool {
self.warn_if_streaming();
self.serialize_for_hydration();
true
}
}
+11 -16
View File
@@ -1,7 +1,7 @@
#![allow(unused)]
//! On the client, we use the [`WebDocument`] implementation to render the head for any elements that were not rendered on the server.
use dioxus_lib::document::*;
use dioxus_lib::{document::*, prelude::queue_effect};
use dioxus_web::WebDocument;
fn head_element_written_on_server() -> bool {
@@ -12,6 +12,7 @@ fn head_element_written_on_server() -> bool {
}
/// A document provider for fullstack web clients
#[derive(Clone)]
pub struct FullstackWebDocument;
impl Document for FullstackWebDocument {
@@ -19,38 +20,32 @@ impl Document for FullstackWebDocument {
WebDocument.eval(js)
}
/// Set the title of the document
fn set_title(&self, title: String) {
if head_element_written_on_server() {
return;
}
WebDocument.set_title(title);
}
/// Create a new meta tag in the head
fn create_meta(&self, props: MetaProps) {
if head_element_written_on_server() {
return;
}
WebDocument.create_meta(props);
}
/// Create a new script tag in the head
fn create_script(&self, props: ScriptProps) {
if head_element_written_on_server() {
return;
}
WebDocument.create_script(props);
}
/// Create a new style tag in the head
fn create_style(&self, props: StyleProps) {
if head_element_written_on_server() {
return;
}
WebDocument.create_style(props);
}
/// Create a new link tag in the head
fn create_link(&self, props: LinkProps) {
if head_element_written_on_server() {
return;
}
WebDocument.create_link(props);
}
fn create_head_component(&self) -> bool {
!head_element_written_on_server()
}
}