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

+64 -16
View File
@@ -1,11 +1,8 @@
#![allow(unused)]
use base64::Engine;
use dioxus_lib::prelude::{has_context, provide_context, use_hook};
use serialize::serde_to_writable;
use std::{cell::RefCell, io::Cursor, rc::Rc, sync::atomic::AtomicUsize};
#![cfg(feature = "server")]
use base64::engine::general_purpose::STANDARD;
use serde::{de::DeserializeOwned, Serialize};
use dioxus_lib::prelude::{has_context, provide_context, use_hook};
use serde::Serialize;
use std::{cell::RefCell, rc::Rc};
pub(crate) mod serialize;
@@ -21,13 +18,22 @@ impl SerializeContext {
}
/// Insert data into an entry that was created with [`Self::create_entry`]
pub(crate) fn insert<T: Serialize>(&self, id: usize, value: &T) {
self.data.borrow_mut().insert(id, value);
pub(crate) fn insert<T: Serialize>(
&self,
id: usize,
value: &T,
location: &'static std::panic::Location<'static>,
) {
self.data.borrow_mut().insert(id, value, location);
}
/// Push resolved data into the serialized server data
pub(crate) fn push<T: Serialize>(&self, data: &T) {
self.data.borrow_mut().push(data);
pub(crate) fn push<T: Serialize>(
&self,
data: &T,
location: &'static std::panic::Location<'static>,
) {
self.data.borrow_mut().push(data, location);
}
}
@@ -39,31 +45,73 @@ pub(crate) fn serialize_context() -> SerializeContext {
has_context().unwrap_or_else(|| provide_context(SerializeContext::default()))
}
#[derive(serde::Serialize, serde::Deserialize, Default)]
#[serde(transparent)]
#[derive(Default)]
pub(crate) struct HTMLData {
/// The data required for hydration
pub data: Vec<Option<Vec<u8>>>,
/// The types of each serialized data
///
/// NOTE: we don't store this in the main data vec because we don't want to include it in
/// release mode and we can't assume both the client and server are built with debug assertions
/// matching
#[cfg(debug_assertions)]
pub debug_types: Vec<Option<String>>,
/// The locations of each serialized data
#[cfg(debug_assertions)]
pub debug_locations: Vec<Option<String>>,
}
impl HTMLData {
/// Create a new entry in the data that will be sent to the client without inserting any data. Returns an id that can be used to insert data into the entry once it is ready.
pub(crate) fn create_entry(&mut self) -> usize {
fn create_entry(&mut self) -> usize {
let id = self.data.len();
self.data.push(None);
#[cfg(debug_assertions)]
{
self.debug_types.push(None);
self.debug_locations.push(None);
}
id
}
/// Insert data into an entry that was created with [`Self::create_entry`]
pub(crate) fn insert<T: Serialize>(&mut self, id: usize, value: &T) {
fn insert<T: Serialize>(
&mut self,
id: usize,
value: &T,
location: &'static std::panic::Location<'static>,
) {
let mut serialized = Vec::new();
ciborium::into_writer(value, &mut serialized).unwrap();
self.data[id] = Some(serialized);
#[cfg(debug_assertions)]
{
self.debug_types[id] = Some(std::any::type_name::<T>().to_string());
self.debug_locations[id] = Some(location.to_string());
}
}
/// Push resolved data into the serialized server data
pub(crate) fn push<T: Serialize>(&mut self, data: &T) {
fn push<T: Serialize>(&mut self, data: &T, location: &'static std::panic::Location<'static>) {
let mut serialized = Vec::new();
ciborium::into_writer(data, &mut serialized).unwrap();
self.data.push(Some(serialized));
#[cfg(debug_assertions)]
{
self.debug_types
.push(Some(std::any::type_name::<T>().to_string()));
self.debug_locations.push(Some(location.to_string()));
}
}
/// Extend this data with the data from another [`HTMLData`]
pub(crate) fn extend(&mut self, other: &Self) {
self.data.extend_from_slice(&other.data);
#[cfg(debug_assertions)]
{
self.debug_types.extend_from_slice(&other.debug_types);
self.debug_locations
.extend_from_slice(&other.debug_locations);
}
}
}