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

@@ -20,20 +20,23 @@ use serde::{de::DeserializeOwned, Serialize};
/// unimplemented!()
/// }
/// ```
#[track_caller]
pub fn use_server_cached<O: 'static + Clone + Serialize + DeserializeOwned>(
server_fn: impl Fn() -> O,
) -> O {
use_hook(|| server_cached(server_fn))
let location = std::panic::Location::caller();
use_hook(|| server_cached(server_fn, location))
}
pub(crate) fn server_cached<O: 'static + Clone + Serialize + DeserializeOwned>(
value: impl FnOnce() -> O,
#[allow(unused)] location: &'static std::panic::Location<'static>,
) -> O {
#[cfg(feature = "server")]
{
let serialize = crate::html_storage::serialize_context();
let data = value();
serialize.push(&data);
serialize.push(&data, location);
data
}
#[cfg(all(not(feature = "server"), feature = "web"))]
+27 -30
View File
@@ -56,6 +56,7 @@ use std::future::Future;
/// }
/// ```
#[must_use = "Consider using `cx.spawn` to run a future without reading its value"]
#[track_caller]
pub fn use_server_future<T, F>(
mut future: impl FnMut() -> F + 'static,
) -> Result<Resource<T>, RenderError>
@@ -65,10 +66,14 @@ where
{
#[cfg(feature = "server")]
let serialize_context = crate::html_storage::use_serialize_context();
// We always create a storage entry, even if the data isn't ready yet to make it possible to deserialize pending server futures on the client
#[cfg(feature = "server")]
let server_storage_entry = use_hook(|| serialize_context.create_entry());
#[cfg(feature = "server")]
let caller = std::panic::Location::caller();
// If this is the first run and we are on the web client, the data might be cached
#[cfg(feature = "web")]
let initial_web_result = use_hook(|| {
@@ -80,33 +85,28 @@ where
let resource = use_resource(move || {
#[cfg(feature = "server")]
let serialize_context = serialize_context.clone();
let user_fut = future();
#[cfg(feature = "web")]
let initial_web_result = initial_web_result.clone();
#[allow(clippy::let_and_return)]
async move {
// If this is the first run and we are on the web client, the data might be cached
#[cfg(feature = "web")]
{
let initial = initial_web_result.borrow_mut().take();
match initial {
// This isn't the first run
None => {}
// This is the first run
Some(first_run) => {
match first_run {
// THe data was deserialized successfully from the server
Ok(Some(o)) => return o,
// The data is still pending from the server. Don't try to resolve it on the client
Ok(None) => {
tracing::trace!("Waiting for server data");
std::future::pending::<()>().await;
}
// The data was not available on the server, rerun the future
Err(_) => {}
}
}
}
match initial_web_result.take() {
// The data was deserialized successfully from the server
Some(Ok(Some(o))) => return o,
// The data is still pending from the server. Don't try to resolve it on the client
Some(Ok(None)) => std::future::pending::<()>().await,
// The data was not available on the server, rerun the future
Some(Err(_)) => {}
// This isn't the first run, so we don't need do anything
None => {}
}
// Otherwise just run the future itself
@@ -114,9 +114,8 @@ where
// If this is the first run and we are on the server, cache the data in the slot we reserved for it
#[cfg(feature = "server")]
serialize_context.insert(server_storage_entry, &out);
serialize_context.insert(server_storage_entry, &out, caller);
#[allow(clippy::let_and_return)]
out
}
});
@@ -127,14 +126,12 @@ where
});
// Suspend if the value isn't ready
match resource.state().cloned() {
UseResourceState::Pending => {
let task = resource.task();
if !task.paused() {
return Err(suspend(task).unwrap_err());
}
Ok(resource)
if resource.state().cloned() == UseResourceState::Pending {
let task = resource.task();
if !task.paused() {
return Err(suspend(task).unwrap_err());
}
_ => Ok(resource),
}
Ok(resource)
}