Skip to content

Commit

Permalink
small cleanups
Browse files Browse the repository at this point in the history
  • Loading branch information
jkelleyrtp committed Jan 8, 2025
1 parent 322eb87 commit 12e92a4
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 67 deletions.
14 changes: 10 additions & 4 deletions packages/fullstack/src/hooks/server_cached.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,30 @@ pub fn use_server_cached<O: 'static + Clone + Serialize + DeserializeOwned>(
use_hook(|| server_cached(server_fn, location))
}

#[allow(unreachable_code)]
pub(crate) fn server_cached<O: 'static + Clone + Serialize + DeserializeOwned>(
value: impl FnOnce() -> O,
#[allow(unused)] location: &'static std::panic::Location<'static>,
) -> O {
// On the server, we serialize the data into the HTML Data context (which gets serialized into the HTML itself)
#[cfg(feature = "server")]
{
let serialize = crate::html_storage::serialize_context();
let data = value();
serialize.push(&data, location);
data
return data;
}
#[cfg(all(not(feature = "server"), feature = "web"))]

// On the web, we try to take the data from the server data
#[cfg(feature = "web")]
{
dioxus_web::take_server_data()
return dioxus_web::take_server_data()
.ok()
.flatten()
.unwrap_or_else(value)
.unwrap_or_else(value);
}

// A no-op on all other platforms
#[cfg(not(any(feature = "server", feature = "web")))]
{
value()
Expand Down
21 changes: 21 additions & 0 deletions packages/fullstack/src/html_storage/data.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use serde::Serialize;

/// Serialize a type to CBOR bytes.
///
/// This trait is implemented for all types that implement `Serialize`.
///
/// Todo(jon): we want to allocate into one big block per request instead of a bunch of small blocks.
pub trait SerializeCbor {
fn to_cbor_bytes(&self) -> Result<Vec<u8>, ciborium::ser::Error<std::io::Error>>;
}

impl<T> SerializeCbor for T
where
T: Serialize,
{
fn to_cbor_bytes(&self) -> Result<Vec<u8>, ciborium::ser::Error<std::io::Error>> {
let mut serialized = Vec::new();
ciborium::into_writer(self, &mut serialized)?;
Ok(serialized)
}
}
44 changes: 20 additions & 24 deletions packages/fullstack/src/html_storage/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
#![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 data::SerializeCbor;
use dioxus_lib::prelude::{has_context, provide_context, use_hook};
use serde::Serialize;
use std::{any::type_name, cell::RefCell, rc::Rc};

pub(crate) mod data;
pub(crate) mod serialize;

pub(crate) fn use_serialize_context() -> SerializeContext {
use_hook(serialize_context)
}

pub(crate) fn serialize_context() -> SerializeContext {
has_context().unwrap_or_else(|| provide_context(SerializeContext::default()))
}

#[derive(Default, Clone)]
pub(crate) struct SerializeContext {
data: Rc<RefCell<HTMLData>>,
Expand Down Expand Up @@ -40,14 +47,6 @@ impl SerializeContext {
}
}

pub(crate) fn use_serialize_context() -> SerializeContext {
use_hook(serialize_context)
}

pub(crate) fn serialize_context() -> SerializeContext {
has_context().unwrap_or_else(|| provide_context(SerializeContext::default()))
}

#[derive(Default)]
pub(crate) struct HTMLData {
/// The data required for hydration
Expand All @@ -69,11 +68,13 @@ impl HTMLData {
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
}

Expand All @@ -84,27 +85,22 @@ impl HTMLData {
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);
self.data[id] = Some(value.to_cbor_bytes().unwrap());

#[cfg(debug_assertions)]
{
self.debug_types[id] = Some(std::any::type_name::<T>().to_string());
self.debug_types[id] = Some(type_name::<T>().to_string());
self.debug_locations[id] = Some(location.to_string());
}
}

/// Push resolved data into the serialized server data
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));
self.data.push(Some(data.to_cbor_bytes().unwrap()));

#[cfg(debug_assertions)]
{
self.debug_types
.push(Some(std::any::type_name::<T>().to_string()));
self.debug_types.push(Some(type_name::<T>().to_string()));
self.debug_locations.push(Some(location.to_string()));
}
}
Expand Down
69 changes: 30 additions & 39 deletions packages/fullstack/src/html_storage/serialize.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,20 @@
use dioxus_lib::prelude::dioxus_core::DynamicNode;
use dioxus_lib::prelude::{
has_context, try_consume_context, ErrorContext, ScopeId, SuspenseBoundaryProps,
SuspenseContext, VNode, VirtualDom,
};
use dioxus_lib::prelude::{has_context, ErrorContext, ScopeId, SuspenseContext, VNode, VirtualDom};
use serde::Serialize;

use base64::engine::general_purpose::STANDARD;
use base64::Engine;

use super::data::SerializeCbor;
use super::SerializeContext;
use crate::html_storage::HTMLData;

#[allow(unused)]
pub(crate) fn serde_to_writable<T: Serialize>(
value: &T,
write_to: &mut impl std::fmt::Write,
) -> Result<(), ciborium::ser::Error<std::fmt::Error>> {
let mut serialized = Vec::new();
ciborium::into_writer(value, &mut serialized).unwrap();
write_to.write_str(STANDARD.encode(serialized).as_str())?;
write_to.write_str(STANDARD.encode(value.to_cbor_bytes().unwrap()).as_str())?;
Ok(())
}

Expand All @@ -35,27 +32,20 @@ impl super::HTMLData {
fn serialize_errors(&mut self, vdom: &VirtualDom, scope: ScopeId) {
// If there is an error boundary on the suspense boundary, grab the error from the context API
// and throw it on the client so that it bubbles up to the nearest error boundary
let mut error = vdom.in_runtime(|| {
let error = vdom.in_runtime(|| {
scope
.consume_context::<ErrorContext>()
.and_then(|error_context| error_context.errors().first().cloned())
});
self.push(&error, std::panic::Location::caller());
}

fn take_from_virtual_dom(&mut self, vdom: &VirtualDom) {
self.take_from_scope(vdom, ScopeId::ROOT)
}

fn take_from_scope(&mut self, vdom: &VirtualDom, scope: ScopeId) {
vdom.in_runtime(|| {
scope.in_runtime(|| {
// Grab any serializable server context from this scope
let context: Option<SerializeContext> = has_context();
if let Some(context) = context {
let borrow = context.data.borrow();
let mut data = borrow.data.iter().cloned();
self.extend(&borrow);
if let Some(context) = has_context::<SerializeContext>() {
self.extend(&context.data.borrow());
}
});
});
Expand All @@ -70,6 +60,7 @@ impl super::HTMLData {
self.take_from_vnode(vdom, &node);
}
}

if let Some(node) = scope.try_root_node() {
self.take_from_vnode(vdom, node);
}
Expand Down Expand Up @@ -97,30 +88,32 @@ impl super::HTMLData {
#[cfg(feature = "server")]
/// Encode data as base64. This is intended to be used in the server to send data to the client.
pub(crate) fn serialized(&self) -> SerializedHydrationData {
let mut serialized = Vec::new();
ciborium::into_writer(&self.data, &mut serialized).unwrap();
let data = base64::engine::general_purpose::STANDARD.encode(serialized);

let format_js_list_of_strings = |list: &[Option<String>]| {
let body = list
.iter()
.map(|s| match s {
Some(s) => format!(r#""{s}""#),
None => r#""unknown""#.to_string(),
})
.collect::<Vec<_>>()
.join(",");
format!("[{}]", body)
};
let serialized = self.data.to_cbor_bytes().unwrap();

SerializedHydrationData {
data,
data: base64::engine::general_purpose::STANDARD.encode(serialized),

#[cfg(debug_assertions)]
debug_types: format_js_list_of_strings(&self.debug_types),
debug_types: Self::serialize_js_list(&self.debug_types),

#[cfg(debug_assertions)]
debug_locations: format_js_list_of_strings(&self.debug_locations),
debug_locations: Self::serialize_js_list(&self.debug_locations),
}
}

// quick and dirty helper to serialize a list of strings into a javascript list for serialization
fn serialize_js_list(list: &[Option<String>]) -> String {
let body = list
.iter()
.map(|s| match s {
Some(s) => format!(r#""{s}""#),
None => r#""unknown""#.to_string(),
})
.collect::<Vec<_>>()
.join(",");

format!("[{}]", body)
}
}

#[cfg(feature = "server")]
Expand All @@ -142,11 +135,9 @@ impl SerializedHydrationData {
pub(crate) fn new(virtual_dom: &VirtualDom, scope: ScopeId) -> Self {
// After we replace the placeholder in the dom with javascript, we need to send down the resolved data so that the client can hydrate the node
// Extract any data we serialized for hydration (from server futures)
let html_data =
crate::html_storage::HTMLData::extract_from_suspense_boundary(virtual_dom, scope);

//
// serialize the server state into a base64 string
html_data.serialized()
HTMLData::extract_from_suspense_boundary(virtual_dom, scope).serialized()
}

/// Write the additional script element that contains the hydration data
Expand Down

0 comments on commit 12e92a4

Please sign in to comment.