feat: synchronous serialized values with SharedValue
This commit is contained in:
parent
9e4c0b86f2
commit
602ac60a85
|
@ -68,4 +68,12 @@ impl SharedContext for CsrSharedContext {
|
|||
|
||||
#[inline(always)]
|
||||
fn seal_errors(&self, _boundary_id: &SerializedDataId) {}
|
||||
|
||||
#[inline(always)]
|
||||
fn during_hydration(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn hydration_complete(&self) {}
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ impl std::error::Error for SerializedError {}
|
|||
pub struct HydrateSharedContext {
|
||||
id: AtomicUsize,
|
||||
is_hydrating: AtomicBool,
|
||||
during_hydration: AtomicBool,
|
||||
errors: Lazy<Vec<(SerializedDataId, ErrorId, Error)>>,
|
||||
}
|
||||
|
||||
|
@ -64,6 +65,7 @@ impl HydrateSharedContext {
|
|||
Self {
|
||||
id: AtomicUsize::new(0),
|
||||
is_hydrating: AtomicBool::new(true),
|
||||
during_hydration: AtomicBool::new(true),
|
||||
errors: Lazy::new(serialized_errors),
|
||||
}
|
||||
}
|
||||
|
@ -76,6 +78,7 @@ impl HydrateSharedContext {
|
|||
Self {
|
||||
id: AtomicUsize::new(0),
|
||||
is_hydrating: AtomicBool::new(false),
|
||||
during_hydration: AtomicBool::new(true),
|
||||
errors: Lazy::new(serialized_errors),
|
||||
}
|
||||
}
|
||||
|
@ -111,6 +114,14 @@ impl SharedContext for HydrateSharedContext {
|
|||
None
|
||||
}
|
||||
|
||||
fn during_hydration(&self) -> bool {
|
||||
self.during_hydration.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn hydration_complete(&self) {
|
||||
self.during_hydration.store(false, Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn get_is_hydrating(&self) -> bool {
|
||||
self.is_hydrating.load(Ordering::Relaxed)
|
||||
}
|
||||
|
|
|
@ -85,6 +85,15 @@ pub trait SharedContext: Debug {
|
|||
/// In browser implementations, this return `None`.
|
||||
fn pending_data(&self) -> Option<PinnedStream<String>>;
|
||||
|
||||
/// Whether the page is currently being hydrated.
|
||||
///
|
||||
/// Should always be `false` on the server or when client-rendering, including after the
|
||||
/// initial hydration in the client.
|
||||
fn during_hydration(&self) -> bool;
|
||||
|
||||
/// Tells the shared context that the hydration process is complete.
|
||||
fn hydration_complete(&self);
|
||||
|
||||
/// Returns `true` if you are currently in a part of the application tree that should be
|
||||
/// hydrated.
|
||||
///
|
||||
|
|
|
@ -180,6 +180,12 @@ impl SharedContext for SsrSharedContext {
|
|||
stream::once(async move { initial_chunk }).chain(async_data);
|
||||
Some(Box::pin(stream))
|
||||
}
|
||||
|
||||
fn during_hydration(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn hydration_complete(&self) {}
|
||||
}
|
||||
|
||||
struct AsyncDataStream {
|
||||
|
|
|
@ -66,6 +66,10 @@ where
|
|||
)
|
||||
});
|
||||
|
||||
if let Some(sc) = Owner::current_shared_context() {
|
||||
sc.hydration_complete();
|
||||
}
|
||||
|
||||
// returns a handle that owns the owner
|
||||
// when this is dropped, it will clean up the reactive system and unmount the view
|
||||
UnmountHandle {
|
||||
|
|
|
@ -8,6 +8,8 @@ pub use multi_action::*;
|
|||
mod resource;
|
||||
pub mod serializers;
|
||||
pub use resource::*;
|
||||
mod shared;
|
||||
pub use shared::*;
|
||||
|
||||
////! # Leptos Server Functions
|
||||
////!
|
||||
|
@ -223,7 +225,10 @@ mod view_implementations {
|
|||
|
||||
fn to_html_async_with_buf<const OUT_OF_ORDER: bool>(
|
||||
self,
|
||||
buf: &mut StreamBuilder, position: &mut Position, escape: bool) where
|
||||
buf: &mut StreamBuilder,
|
||||
position: &mut Position,
|
||||
escape: bool,
|
||||
) where
|
||||
Self: Sized,
|
||||
{
|
||||
(move || Suspend(async move { self.await }))
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
use crate::serializers::{SerdeJson, SerializableData, Serializer};
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
marker::PhantomData,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
/// A smart pointer that allows you to share identical, synchronously-loaded data between the
|
||||
/// server and the client.
|
||||
///
|
||||
/// If this constructed on the server, it serializes its value into the shared context. If it is
|
||||
/// constructed on the client during hydration, it reads its value from the shared context. If
|
||||
/// it it constructed on the client at any other time, it simply runs on the client.
|
||||
pub struct SharedValue<T, Ser = SerdeJson> {
|
||||
value: T,
|
||||
ser: PhantomData<Ser>,
|
||||
}
|
||||
|
||||
impl<T, Ser> SharedValue<T, Ser> {
|
||||
pub fn into_inner(self) -> T {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Ser> SharedValue<T, Ser>
|
||||
where
|
||||
T: Debug + SerializableData<Ser>,
|
||||
T::SerErr: Debug,
|
||||
Ser: Serializer,
|
||||
{
|
||||
pub fn new(initial: impl FnOnce() -> T) -> Self {
|
||||
let value: T;
|
||||
#[cfg(feature = "hydration")]
|
||||
{
|
||||
use reactive_graph::owner::Owner;
|
||||
|
||||
let sc = Owner::current_shared_context();
|
||||
let id = sc.as_ref().map(|sc| sc.next_id()).unwrap_or_default();
|
||||
let serialized = sc.as_ref().and_then(|sc| sc.read_data(&id));
|
||||
let hydrating =
|
||||
sc.as_ref().map(|sc| sc.during_hydration()).unwrap_or(false);
|
||||
value = if hydrating {
|
||||
serialized
|
||||
.as_ref()
|
||||
.and_then(|data| T::de(data).ok())
|
||||
.unwrap_or_else(|| {
|
||||
#[cfg(feature = "tracing")]
|
||||
tracing::error!(
|
||||
"couldn't deserialize from {serialized:?}"
|
||||
);
|
||||
initial()
|
||||
})
|
||||
} else {
|
||||
let init = initial();
|
||||
#[cfg(feature = "ssr")]
|
||||
if let Some(sc) = sc {
|
||||
match init.ser() {
|
||||
Ok(value) => {
|
||||
sc.write_async(id, Box::pin(async move { value }))
|
||||
}
|
||||
Err(e) => {
|
||||
#[cfg(feature = "tracing")]
|
||||
tracing::error!(
|
||||
"couldn't serialize {init:?}: {e:?}"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
init
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "hydration"))]
|
||||
{
|
||||
value = initial();
|
||||
}
|
||||
Self {
|
||||
value,
|
||||
ser: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Ser> Deref for SharedValue<T, Ser> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Ser> DerefMut for SharedValue<T, Ser> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.value
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue