mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-23 04:33:06 +00:00
Merge pull request #1199 from Demonthos/use-server-function
Use server function hook
This commit is contained in:
commit
3af0355196
19 changed files with 696 additions and 198 deletions
|
@ -16,6 +16,9 @@ serde = "1.0.159"
|
|||
execute = "0.2.12"
|
||||
tower-http = { version = "0.4.1", features = ["auth"] }
|
||||
simple_logger = "4.2.0"
|
||||
wasm-logger = "0.2.0"
|
||||
log.workspace = true
|
||||
reqwest = "0.11.18"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
|
|
@ -16,10 +16,22 @@ struct AppProps {
|
|||
}
|
||||
|
||||
fn app(cx: Scope<AppProps>) -> Element {
|
||||
let mut count = use_state(cx, || cx.props.count);
|
||||
render! {
|
||||
Child {}
|
||||
}
|
||||
}
|
||||
|
||||
fn Child(cx: Scope) -> Element {
|
||||
let state =
|
||||
use_server_future(cx, (), |()| async move { get_server_data().await.unwrap() })?.value();
|
||||
|
||||
let mut count = use_state(cx, || 0);
|
||||
let text = use_state(cx, || "...".to_string());
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
"Server state: {state}"
|
||||
}
|
||||
h1 { "High-Five counter: {count}" }
|
||||
button { onclick: move |_| count += 1, "Up high!" }
|
||||
button { onclick: move |_| count -= 1, "Down low!" }
|
||||
|
@ -42,7 +54,7 @@ fn app(cx: Scope<AppProps>) -> Element {
|
|||
|
||||
#[server(PostServerData)]
|
||||
async fn post_server_data(data: String) -> Result<(), ServerFnError> {
|
||||
let axum::extract::Host(host): axum::extract::Host = extract()?;
|
||||
let axum::extract::Host(host): axum::extract::Host = extract().await?;
|
||||
println!("Server received: {}", data);
|
||||
println!("{:?}", host);
|
||||
|
||||
|
@ -51,10 +63,15 @@ async fn post_server_data(data: String) -> Result<(), ServerFnError> {
|
|||
|
||||
#[server(GetServerData)]
|
||||
async fn get_server_data() -> Result<String, ServerFnError> {
|
||||
Ok("Hello from the server!".to_string())
|
||||
Ok(reqwest::get("https://httpbin.org/ip").await?.text().await?)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
#[cfg(feature = "web")]
|
||||
wasm_logger::init(wasm_logger::Config::new(log::Level::Trace));
|
||||
#[cfg(feature = "ssr")]
|
||||
simple_logger::SimpleLogger::new().init().unwrap();
|
||||
|
||||
launch!(@([127, 0, 0, 1], 8080), app, {
|
||||
serve_cfg: ServeConfigBuilder::new(app, AppProps { count: 0 }),
|
||||
});
|
||||
|
|
|
@ -14,6 +14,10 @@ tokio = { workspace = true, features = ["full"], optional = true }
|
|||
serde = "1.0.159"
|
||||
salvo = { version = "0.37.9", optional = true }
|
||||
execute = "0.2.12"
|
||||
reqwest = "0.11.18"
|
||||
simple_logger = "4.2.0"
|
||||
log.workspace = true
|
||||
wasm-logger = "0.2.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
|
|
@ -7,25 +7,31 @@
|
|||
|
||||
#![allow(non_snake_case, unused)]
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_fullstack::prelude::*;
|
||||
use dioxus_fullstack::{launch, prelude::*};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
fn main() {
|
||||
launch!(@([127, 0, 0, 1], 8080), app, (AppProps { count: 5 }), {
|
||||
incremental: IncrementalRendererConfig::default().invalidate_after(std::time::Duration::from_secs(120)),
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Props, PartialEq, Debug, Default, Serialize, Deserialize, Clone)]
|
||||
struct AppProps {
|
||||
count: i32,
|
||||
}
|
||||
|
||||
fn app(cx: Scope<AppProps>) -> Element {
|
||||
let mut count = use_state(cx, || cx.props.count);
|
||||
render! {
|
||||
Child {}
|
||||
}
|
||||
}
|
||||
|
||||
fn Child(cx: Scope) -> Element {
|
||||
let state =
|
||||
use_server_future(cx, (), |()| async move { get_server_data().await.unwrap() })?.value();
|
||||
|
||||
let mut count = use_state(cx, || 0);
|
||||
let text = use_state(cx, || "...".to_string());
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
"Server state: {state}"
|
||||
}
|
||||
h1 { "High-Five counter: {count}" }
|
||||
button { onclick: move |_| count += 1, "Up high!" }
|
||||
button { onclick: move |_| count -= 1, "Down low!" }
|
||||
|
@ -40,7 +46,7 @@ fn app(cx: Scope<AppProps>) -> Element {
|
|||
}
|
||||
}
|
||||
},
|
||||
"Run a server function"
|
||||
"Run a server function!"
|
||||
}
|
||||
"Server said: {text}"
|
||||
})
|
||||
|
@ -48,15 +54,23 @@ fn app(cx: Scope<AppProps>) -> Element {
|
|||
|
||||
#[server(PostServerData)]
|
||||
async fn post_server_data(data: String) -> Result<(), ServerFnError> {
|
||||
// The server context contains information about the current request and allows you to modify the response.
|
||||
let cx = server_context();
|
||||
println!("Server received: {}", data);
|
||||
println!("Request parts are {:?}", cx.request_parts());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[server(GetServerData)]
|
||||
async fn get_server_data() -> Result<String, ServerFnError> {
|
||||
Ok("Hello from the server!".to_string())
|
||||
Ok(reqwest::get("https://httpbin.org/ip").await?.text().await?)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
#[cfg(feature = "web")]
|
||||
wasm_logger::init(wasm_logger::Config::new(log::Level::Trace));
|
||||
#[cfg(feature = "ssr")]
|
||||
simple_logger::SimpleLogger::new().init().unwrap();
|
||||
|
||||
launch!(@([127, 0, 0, 1], 8080), app, {
|
||||
serve_cfg: ServeConfigBuilder::new(app, AppProps { count: 0 }),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -14,6 +14,10 @@ tokio = { workspace = true, features = ["full"], optional = true }
|
|||
serde = "1.0.159"
|
||||
warp = { version = "0.3.3", optional = true }
|
||||
execute = "0.2.12"
|
||||
reqwest = "0.11.18"
|
||||
simple_logger = "4.2.0"
|
||||
log.workspace = true
|
||||
wasm-logger = "0.2.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
|
|
@ -7,25 +7,31 @@
|
|||
|
||||
#![allow(non_snake_case, unused)]
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_fullstack::prelude::*;
|
||||
use dioxus_fullstack::{launch, prelude::*};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
fn main() {
|
||||
launch!(@([127, 0, 0, 1], 8080), app, (AppProps { count: 5 }), {
|
||||
incremental: IncrementalRendererConfig::default().invalidate_after(std::time::Duration::from_secs(120)),
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Props, PartialEq, Debug, Default, Serialize, Deserialize, Clone)]
|
||||
struct AppProps {
|
||||
count: i32,
|
||||
}
|
||||
|
||||
fn app(cx: Scope<AppProps>) -> Element {
|
||||
let mut count = use_state(cx, || cx.props.count);
|
||||
render! {
|
||||
Child {}
|
||||
}
|
||||
}
|
||||
|
||||
fn Child(cx: Scope) -> Element {
|
||||
let state =
|
||||
use_server_future(cx, (), |()| async move { get_server_data().await.unwrap() })?.value();
|
||||
|
||||
let mut count = use_state(cx, || 0);
|
||||
let text = use_state(cx, || "...".to_string());
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
"Server state: {state}"
|
||||
}
|
||||
h1 { "High-Five counter: {count}" }
|
||||
button { onclick: move |_| count += 1, "Up high!" }
|
||||
button { onclick: move |_| count -= 1, "Down low!" }
|
||||
|
@ -40,7 +46,7 @@ fn app(cx: Scope<AppProps>) -> Element {
|
|||
}
|
||||
}
|
||||
},
|
||||
"Run a server function"
|
||||
"Run a server function!"
|
||||
}
|
||||
"Server said: {text}"
|
||||
})
|
||||
|
@ -48,15 +54,23 @@ fn app(cx: Scope<AppProps>) -> Element {
|
|||
|
||||
#[server(PostServerData)]
|
||||
async fn post_server_data(data: String) -> Result<(), ServerFnError> {
|
||||
// The server context contains information about the current request and allows you to modify the response.
|
||||
let cx = server_context();
|
||||
println!("Server received: {}", data);
|
||||
println!("Request parts are {:?}", cx.request_parts());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[server(GetServerData)]
|
||||
async fn get_server_data() -> Result<String, ServerFnError> {
|
||||
Ok("Hello from the server!".to_string())
|
||||
Ok(reqwest::get("https://httpbin.org/ip").await?.text().await?)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
#[cfg(feature = "web")]
|
||||
wasm_logger::init(wasm_logger::Config::new(log::Level::Trace));
|
||||
#[cfg(feature = "ssr")]
|
||||
simple_logger::SimpleLogger::new().init().unwrap();
|
||||
|
||||
launch!(@([127, 0, 0, 1], 8080), app, {
|
||||
serve_cfg: ServeConfigBuilder::new(app, AppProps { count: 0 }),
|
||||
});
|
||||
}
|
||||
|
|
2
packages/fullstack/src/hooks/mod.rs
Normal file
2
packages/fullstack/src/hooks/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod server_cached;
|
||||
pub mod server_future;
|
34
packages/fullstack/src/hooks/server_cached.rs
Normal file
34
packages/fullstack/src/hooks/server_cached.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
/// This allows you to send data from the server to the client. The data is serialized into the HTML on the server and hydrated on the client.
|
||||
///
|
||||
/// When you run this function on the client, you need to be careful to insure the order you run it initially is the same order you run it on the server.
|
||||
///
|
||||
/// If Dioxus fullstack cannot find the data on the client, it will run the closure again to get the data.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use dioxus::prelude::*;
|
||||
/// use dioxus_fullstack::prelude::*;
|
||||
///
|
||||
/// fn app(cx: Scope) -> Element {
|
||||
/// let state1 = use_state(cx, || from_server(|| {
|
||||
/// 1234
|
||||
/// }));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn server_cached<O: 'static + Serialize + DeserializeOwned>(server_fn: impl Fn() -> O) -> O {
|
||||
#[cfg(feature = "ssr")]
|
||||
{
|
||||
let data = server_fn();
|
||||
let sc = crate::prelude::server_context();
|
||||
if let Err(err) = sc.push_html_data(&data) {
|
||||
log::error!("Failed to push HTML data: {}", err);
|
||||
}
|
||||
data
|
||||
}
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
{
|
||||
crate::html_storage::deserialize::take_server_data().unwrap_or_else(server_fn)
|
||||
}
|
||||
}
|
152
packages/fullstack/src/hooks/server_future.rs
Normal file
152
packages/fullstack/src/hooks/server_future.rs
Normal file
|
@ -0,0 +1,152 @@
|
|||
use dioxus::prelude::*;
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
use std::any::Any;
|
||||
use std::cell::Cell;
|
||||
use std::cell::Ref;
|
||||
use std::cell::RefCell;
|
||||
use std::fmt::Debug;
|
||||
use std::future::Future;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A future that resolves to a value.
|
||||
///
|
||||
/// This runs the future only once - though the future may be regenerated
|
||||
/// through the [`UseServerFuture::restart`] method.
|
||||
///
|
||||
/// This is commonly used for components that cannot be rendered until some
|
||||
/// asynchronous operation has completed.
|
||||
///
|
||||
/// Whenever the hooks dependencies change, the future will be re-evaluated.
|
||||
/// If a future is pending when the dependencies change, the previous future
|
||||
/// will be allowed to continue
|
||||
///
|
||||
/// - dependencies: a tuple of references to values that are PartialEq + Clone
|
||||
pub fn use_server_future<T, F, D>(
|
||||
cx: &ScopeState,
|
||||
dependencies: D,
|
||||
future: impl FnOnce(D::Out) -> F,
|
||||
) -> Option<&UseServerFuture<T>>
|
||||
where
|
||||
T: 'static + Serialize + DeserializeOwned + Debug,
|
||||
F: Future<Output = T> + 'static,
|
||||
D: UseFutureDep,
|
||||
{
|
||||
let state = cx.use_hook(move || UseServerFuture {
|
||||
update: cx.schedule_update(),
|
||||
needs_regen: Cell::new(true),
|
||||
value: Default::default(),
|
||||
task: Cell::new(None),
|
||||
dependencies: Vec::new(),
|
||||
});
|
||||
|
||||
let first_run = { state.value.borrow().as_ref().is_none() && state.task.get().is_none() };
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
{
|
||||
if first_run {
|
||||
match crate::html_storage::deserialize::take_server_data() {
|
||||
Some(data) => {
|
||||
log::trace!("Loaded {data:?} from server");
|
||||
*state.value.borrow_mut() = Some(Box::new(data));
|
||||
state.needs_regen.set(false);
|
||||
return Some(state);
|
||||
}
|
||||
None => {
|
||||
log::trace!("Failed to load from server... running future");
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if dependencies.clone().apply(&mut state.dependencies) || state.needs_regen.get() {
|
||||
// We don't need regen anymore
|
||||
state.needs_regen.set(false);
|
||||
|
||||
// Create the new future
|
||||
let fut = future(dependencies.out());
|
||||
|
||||
// Clone in our cells
|
||||
let value = state.value.clone();
|
||||
let schedule_update = state.update.clone();
|
||||
|
||||
// Cancel the current future
|
||||
if let Some(current) = state.task.take() {
|
||||
cx.remove_future(current);
|
||||
}
|
||||
|
||||
state.task.set(Some(cx.push_future(async move {
|
||||
let data;
|
||||
#[cfg(feature = "ssr")]
|
||||
{
|
||||
data = fut.await;
|
||||
if first_run {
|
||||
if let Err(err) = crate::prelude::server_context().push_html_data(&data) {
|
||||
log::error!("Failed to push HTML data: {}", err);
|
||||
};
|
||||
}
|
||||
}
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
{
|
||||
data = fut.await;
|
||||
}
|
||||
*value.borrow_mut() = Some(Box::new(data));
|
||||
|
||||
schedule_update();
|
||||
})));
|
||||
}
|
||||
|
||||
if first_run {
|
||||
#[cfg(feature = "ssr")]
|
||||
{
|
||||
log::trace!("Suspending first run of use_server_future");
|
||||
cx.suspend();
|
||||
}
|
||||
None
|
||||
} else {
|
||||
Some(state)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct UseServerFuture<T> {
|
||||
update: Arc<dyn Fn()>,
|
||||
needs_regen: Cell<bool>,
|
||||
task: Cell<Option<TaskId>>,
|
||||
dependencies: Vec<Box<dyn Any>>,
|
||||
value: Rc<RefCell<Option<Box<T>>>>,
|
||||
}
|
||||
|
||||
impl<T> UseServerFuture<T> {
|
||||
/// Restart the future with new dependencies.
|
||||
///
|
||||
/// Will not cancel the previous future, but will ignore any values that it
|
||||
/// generates.
|
||||
pub fn restart(&self) {
|
||||
self.needs_regen.set(true);
|
||||
(self.update)();
|
||||
}
|
||||
|
||||
/// Forcefully cancel a future
|
||||
pub fn cancel(&self, cx: &ScopeState) {
|
||||
if let Some(task) = self.task.take() {
|
||||
cx.remove_future(task);
|
||||
}
|
||||
}
|
||||
|
||||
/// Return any value, even old values if the future has not yet resolved.
|
||||
///
|
||||
/// If the future has never completed, the returned value will be `None`.
|
||||
pub fn value(&self) -> Ref<'_, T> {
|
||||
Ref::map(self.value.borrow(), |v| v.as_deref().unwrap())
|
||||
}
|
||||
|
||||
/// Get the ID of the future in Dioxus' internal scheduler
|
||||
pub fn task(&self) -> Option<TaskId> {
|
||||
self.task.get()
|
||||
}
|
||||
|
||||
/// Get the current state of the future.
|
||||
pub fn reloading(&self) -> bool {
|
||||
self.task.get().is_some()
|
||||
}
|
||||
}
|
79
packages/fullstack/src/html_storage/deserialize.rs
Normal file
79
packages/fullstack/src/html_storage/deserialize.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
use serde::de::DeserializeOwned;
|
||||
|
||||
use base64::engine::general_purpose::STANDARD;
|
||||
use base64::Engine;
|
||||
|
||||
use super::HTMLDataCursor;
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn serde_from_bytes<T: DeserializeOwned>(string: &[u8]) -> Option<T> {
|
||||
let decompressed = match STANDARD.decode(string) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(err) => {
|
||||
log::error!("Failed to decode base64: {}", err);
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
match postcard::from_bytes(&decompressed) {
|
||||
Ok(data) => Some(data),
|
||||
Err(err) => {
|
||||
log::error!("Failed to deserialize: {}", err);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static SERVER_DATA: once_cell::sync::Lazy<Option<HTMLDataCursor>> =
|
||||
once_cell::sync::Lazy::new(|| {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let window = web_sys::window()?.document()?;
|
||||
let element = match window.get_element_by_id("dioxus-storage-data") {
|
||||
Some(element) => element,
|
||||
None => {
|
||||
log::error!("Failed to get element by id: dioxus-storage-data");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
let attribute = match element.get_attribute("data-serialized") {
|
||||
Some(attribute) => attribute,
|
||||
None => {
|
||||
log::error!("Failed to get attribute: data-serialized");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let data: super::HTMLData = serde_from_bytes(attribute.as_bytes())?;
|
||||
|
||||
Some(data.cursor())
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
pub(crate) fn take_server_data<T: DeserializeOwned>() -> Option<T> {
|
||||
SERVER_DATA.as_ref()?.take()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
/// Get the props from the document. This is only available in the browser.
|
||||
///
|
||||
/// When dioxus-fullstack renders the page, it will serialize the root props and put them in the document. This function gets them from the document.
|
||||
pub fn get_root_props_from_document<T: DeserializeOwned>() -> Option<T> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
None
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let attribute = web_sys::window()?
|
||||
.document()?
|
||||
.get_element_by_id("dioxus-storage-props")?
|
||||
.get_attribute("data-serialized")?;
|
||||
|
||||
serde_from_bytes(attribute.as_bytes())
|
||||
}
|
||||
}
|
107
packages/fullstack/src/html_storage/mod.rs
Normal file
107
packages/fullstack/src/html_storage/mod.rs
Normal file
|
@ -0,0 +1,107 @@
|
|||
#![allow(unused)]
|
||||
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
pub(crate) mod deserialize;
|
||||
|
||||
pub(crate) mod serialize;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Default)]
|
||||
pub(crate) struct HTMLData {
|
||||
pub data: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl HTMLData {
|
||||
pub(crate) fn push<T: Serialize>(&mut self, value: &T) {
|
||||
let serialized = postcard::to_allocvec(value).unwrap();
|
||||
self.data.push(serialized);
|
||||
}
|
||||
|
||||
pub(crate) fn cursor(self) -> HTMLDataCursor {
|
||||
HTMLDataCursor {
|
||||
data: self.data,
|
||||
index: AtomicUsize::new(0),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct HTMLDataCursor {
|
||||
data: Vec<Vec<u8>>,
|
||||
index: AtomicUsize,
|
||||
}
|
||||
|
||||
impl HTMLDataCursor {
|
||||
pub fn take<T: DeserializeOwned>(&self) -> Option<T> {
|
||||
let current = self.index.load(std::sync::atomic::Ordering::SeqCst);
|
||||
if current >= self.data.len() {
|
||||
log::error!(
|
||||
"Tried to take more data than was available, len: {}, index: {}",
|
||||
self.data.len(),
|
||||
current
|
||||
);
|
||||
return None;
|
||||
}
|
||||
let mut cursor = &self.data[current];
|
||||
self.index.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||
match postcard::from_bytes(cursor) {
|
||||
Ok(x) => Some(x),
|
||||
Err(e) => {
|
||||
log::error!("Error deserializing data: {:?}", e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn serialized_and_deserializes() {
|
||||
use postcard::to_allocvec;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]
|
||||
struct Data {
|
||||
a: u32,
|
||||
b: String,
|
||||
bytes: Vec<u8>,
|
||||
nested: Nested,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]
|
||||
struct Nested {
|
||||
a: u32,
|
||||
b: u16,
|
||||
c: u8,
|
||||
}
|
||||
|
||||
for x in 0..10usize {
|
||||
for y in 0..10 {
|
||||
let mut as_string: Vec<u8> = Vec::new();
|
||||
let data = vec![
|
||||
Data {
|
||||
a: x as u32,
|
||||
b: "hello".to_string(),
|
||||
bytes: vec![0; x],
|
||||
nested: Nested {
|
||||
a: 1,
|
||||
b: x as u16,
|
||||
c: 3
|
||||
},
|
||||
};
|
||||
y
|
||||
];
|
||||
serialize::serde_to_writable(&data, &mut as_string).unwrap();
|
||||
|
||||
println!("{:?}", as_string);
|
||||
println!(
|
||||
"original size: {}",
|
||||
std::mem::size_of::<Data>() * data.len()
|
||||
);
|
||||
println!("serialized size: {}", to_allocvec(&data).unwrap().len());
|
||||
println!("compressed size: {}", as_string.len());
|
||||
|
||||
let decoded: Vec<Data> = deserialize::serde_from_bytes(&as_string).unwrap();
|
||||
assert_eq!(data, decoded);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,12 +15,26 @@ pub(crate) fn serde_to_writable<T: Serialize>(
|
|||
|
||||
#[cfg(feature = "ssr")]
|
||||
/// Encode data into a element. This is inteded to be used in the server to send data to the client.
|
||||
pub(crate) fn encode_in_element<T: Serialize>(
|
||||
data: T,
|
||||
pub(crate) fn encode_props_in_element<T: Serialize>(
|
||||
data: &T,
|
||||
write_to: &mut impl std::io::Write,
|
||||
) -> std::io::Result<()> {
|
||||
write_to
|
||||
.write_all(r#"<meta hidden="true" id="dioxus-storage" data-serialized=""#.as_bytes())?;
|
||||
write_to.write_all(
|
||||
r#"<meta hidden="true" id="dioxus-storage-props" data-serialized=""#.as_bytes(),
|
||||
)?;
|
||||
serde_to_writable(data, write_to)?;
|
||||
write_to.write_all(r#"" />"#.as_bytes())
|
||||
}
|
||||
|
||||
#[cfg(feature = "ssr")]
|
||||
/// Encode data into a element. This is inteded to be used in the server to send data to the client.
|
||||
pub(crate) fn encode_in_element(
|
||||
data: &super::HTMLData,
|
||||
write_to: &mut impl std::io::Write,
|
||||
) -> std::io::Result<()> {
|
||||
write_to.write_all(
|
||||
r#"<meta hidden="true" id="dioxus-storage-data" data-serialized=""#.as_bytes(),
|
||||
)?;
|
||||
serde_to_writable(&data, write_to)?;
|
||||
write_to.write_all(r#"" />"#.as_bytes())
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
pub use once_cell;
|
||||
|
||||
mod props_html;
|
||||
mod html_storage;
|
||||
|
||||
#[cfg(feature = "router")]
|
||||
pub mod router;
|
||||
|
@ -14,6 +14,7 @@ pub mod router;
|
|||
mod adapters;
|
||||
#[cfg(feature = "ssr")]
|
||||
pub use adapters::*;
|
||||
mod hooks;
|
||||
#[cfg(all(debug_assertions, feature = "hot-reload", feature = "ssr"))]
|
||||
mod hot_reload;
|
||||
pub mod launch;
|
||||
|
@ -35,8 +36,9 @@ pub mod prelude {
|
|||
pub use crate::adapters::salvo_adapter::*;
|
||||
#[cfg(feature = "warp")]
|
||||
pub use crate::adapters::warp_adapter::*;
|
||||
use crate::hooks;
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
pub use crate::props_html::deserialize_props::get_root_props_from_document;
|
||||
pub use crate::html_storage::deserialize::get_root_props_from_document;
|
||||
#[cfg(all(feature = "ssr", feature = "router"))]
|
||||
pub use crate::render::pre_cache_static_routes_with_props;
|
||||
#[cfg(feature = "ssr")]
|
||||
|
@ -57,4 +59,6 @@ pub mod prelude {
|
|||
#[cfg(feature = "ssr")]
|
||||
pub use dioxus_ssr::incremental::IncrementalRendererConfig;
|
||||
pub use server_fn::{self, ServerFn as _, ServerFnError};
|
||||
|
||||
pub use hooks::{server_cached::server_cached, server_future::use_server_future};
|
||||
}
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
use serde::de::DeserializeOwned;
|
||||
|
||||
use base64::engine::general_purpose::STANDARD;
|
||||
use base64::Engine;
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn serde_from_bytes<T: DeserializeOwned>(string: &[u8]) -> Option<T> {
|
||||
let decompressed = STANDARD.decode(string).ok()?;
|
||||
|
||||
postcard::from_bytes(&decompressed).ok()
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "ssr"))]
|
||||
/// Get the props from the document. This is only available in the browser.
|
||||
///
|
||||
/// When dioxus-fullstack renders the page, it will serialize the root props and put them in the document. This function gets them from the document.
|
||||
pub fn get_root_props_from_document<T: DeserializeOwned>() -> Option<T> {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
None
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
let attribute = web_sys::window()?
|
||||
.document()?
|
||||
.get_element_by_id("dioxus-storage")?
|
||||
.get_attribute("data-serialized")?;
|
||||
|
||||
serde_from_bytes(attribute.as_bytes())
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
pub(crate) mod deserialize_props;
|
||||
|
||||
pub(crate) mod serialize_props;
|
||||
|
||||
#[test]
|
||||
fn serialized_and_deserializes() {
|
||||
use postcard::to_allocvec;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]
|
||||
struct Data {
|
||||
a: u32,
|
||||
b: String,
|
||||
bytes: Vec<u8>,
|
||||
nested: Nested,
|
||||
}
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]
|
||||
struct Nested {
|
||||
a: u32,
|
||||
b: u16,
|
||||
c: u8,
|
||||
}
|
||||
|
||||
for x in 0..10usize {
|
||||
for y in 0..10 {
|
||||
let mut as_string: Vec<u8> = Vec::new();
|
||||
let data = vec![
|
||||
Data {
|
||||
a: x as u32,
|
||||
b: "hello".to_string(),
|
||||
bytes: vec![0; x],
|
||||
nested: Nested {
|
||||
a: 1,
|
||||
b: x as u16,
|
||||
c: 3
|
||||
},
|
||||
};
|
||||
y
|
||||
];
|
||||
serialize_props::serde_to_writable(&data, &mut as_string).unwrap();
|
||||
|
||||
println!("{:?}", as_string);
|
||||
println!(
|
||||
"original size: {}",
|
||||
std::mem::size_of::<Data>() * data.len()
|
||||
);
|
||||
println!("serialized size: {}", to_allocvec(&data).unwrap().len());
|
||||
println!("compressed size: {}", as_string.len());
|
||||
|
||||
let decoded: Vec<Data> = deserialize_props::serde_from_bytes(&as_string).unwrap();
|
||||
assert_eq!(data, decoded);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,19 +2,22 @@
|
|||
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::server_context::SERVER_CONTEXT;
|
||||
use dioxus::prelude::VirtualDom;
|
||||
use dioxus_ssr::{
|
||||
incremental::{IncrementalRendererConfig, RenderFreshness, WrapBody},
|
||||
Renderer,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use std::sync::RwLock;
|
||||
use tokio::task::spawn_blocking;
|
||||
|
||||
use crate::{prelude::*, server_context::with_server_context};
|
||||
use crate::prelude::*;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
enum SsrRendererPool {
|
||||
Renderer(object_pool::Pool<Renderer>),
|
||||
Incremental(object_pool::Pool<dioxus_ssr::incremental::IncrementalRenderer>),
|
||||
Renderer(RwLock<Vec<Renderer>>),
|
||||
Incremental(RwLock<Vec<dioxus_ssr::incremental::IncrementalRenderer>>),
|
||||
}
|
||||
|
||||
impl SsrRendererPool {
|
||||
|
@ -24,46 +27,130 @@ impl SsrRendererPool {
|
|||
route: String,
|
||||
component: Component<P>,
|
||||
props: P,
|
||||
to: &mut WriteBuffer,
|
||||
server_context: &DioxusServerContext,
|
||||
) -> Result<RenderFreshness, dioxus_ssr::incremental::IncrementalRendererError> {
|
||||
let wrapper = FullstackRenderer { cfg };
|
||||
) -> Result<(RenderFreshness, String), dioxus_ssr::incremental::IncrementalRendererError> {
|
||||
let wrapper = FullstackRenderer {
|
||||
cfg: cfg.clone(),
|
||||
server_context: server_context.clone(),
|
||||
};
|
||||
match self {
|
||||
Self::Renderer(pool) => {
|
||||
let server_context = Box::new(server_context.clone());
|
||||
let mut vdom = VirtualDom::new_with_props(component, props);
|
||||
let mut renderer = pool.write().unwrap().pop().unwrap_or_else(pre_renderer);
|
||||
|
||||
with_server_context(server_context, || {
|
||||
let _ = vdom.rebuild();
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
|
||||
spawn_blocking(move || {
|
||||
tokio::runtime::Runtime::new()
|
||||
.expect("couldn't spawn runtime")
|
||||
.block_on(async move {
|
||||
let mut vdom = VirtualDom::new_with_props(component, props);
|
||||
let mut to = WriteBuffer { buffer: Vec::new() };
|
||||
// before polling the future, we need to set the context
|
||||
let prev_context =
|
||||
SERVER_CONTEXT.with(|ctx| ctx.replace(server_context));
|
||||
// poll the future, which may call server_context()
|
||||
log::info!("Rebuilding vdom");
|
||||
let _ = vdom.rebuild();
|
||||
vdom.wait_for_suspense().await;
|
||||
log::info!("Suspense resolved");
|
||||
// after polling the future, we need to restore the context
|
||||
SERVER_CONTEXT.with(|ctx| ctx.replace(prev_context));
|
||||
|
||||
if let Err(err) = wrapper.render_before_body(&mut *to) {
|
||||
let _ = tx.send(Err(err));
|
||||
return;
|
||||
}
|
||||
if let Err(err) = renderer.render_to(&mut to, &vdom) {
|
||||
let _ = tx.send(Err(
|
||||
dioxus_router::prelude::IncrementalRendererError::RenderError(
|
||||
err,
|
||||
),
|
||||
));
|
||||
return;
|
||||
}
|
||||
if let Err(err) = wrapper.render_after_body(&mut *to) {
|
||||
let _ = tx.send(Err(err));
|
||||
return;
|
||||
}
|
||||
match String::from_utf8(to.buffer) {
|
||||
Ok(html) => {
|
||||
let _ =
|
||||
tx.send(Ok((renderer, RenderFreshness::now(None), html)));
|
||||
}
|
||||
Err(err) => {
|
||||
dioxus_ssr::incremental::IncrementalRendererError::Other(
|
||||
Box::new(err),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
let mut renderer = pool.pull(pre_renderer);
|
||||
|
||||
// SAFETY: The fullstack renderer will only write UTF-8 to the buffer.
|
||||
wrapper.render_before_body(&mut **to)?;
|
||||
renderer.render_to(to, &vdom)?;
|
||||
wrapper.render_after_body(&mut **to)?;
|
||||
|
||||
Ok(RenderFreshness::now(None))
|
||||
let (renderer, freshness, html) = rx.await.unwrap()?;
|
||||
pool.write().unwrap().push(renderer);
|
||||
Ok((freshness, html))
|
||||
}
|
||||
Self::Incremental(pool) => {
|
||||
let mut renderer =
|
||||
pool.pull(|| incremental_pre_renderer(cfg.incremental.as_ref().unwrap()));
|
||||
Ok(renderer
|
||||
.render(
|
||||
route,
|
||||
component,
|
||||
props,
|
||||
&mut **to,
|
||||
|vdom| {
|
||||
let server_context = Box::new(server_context.clone());
|
||||
with_server_context(server_context, || {
|
||||
let _ = vdom.rebuild();
|
||||
});
|
||||
},
|
||||
&wrapper,
|
||||
)
|
||||
.await?)
|
||||
pool.write().unwrap().pop().unwrap_or_else(|| {
|
||||
incremental_pre_renderer(cfg.incremental.as_ref().unwrap())
|
||||
});
|
||||
|
||||
let (tx, rx) = tokio::sync::oneshot::channel();
|
||||
|
||||
let server_context = server_context.clone();
|
||||
spawn_blocking(move || {
|
||||
tokio::runtime::Runtime::new()
|
||||
.expect("couldn't spawn runtime")
|
||||
.block_on(async move {
|
||||
let mut to = WriteBuffer { buffer: Vec::new() };
|
||||
match renderer
|
||||
.render(
|
||||
route,
|
||||
component,
|
||||
props,
|
||||
&mut *to,
|
||||
|vdom| {
|
||||
Box::pin(async move {
|
||||
// before polling the future, we need to set the context
|
||||
let prev_context = SERVER_CONTEXT
|
||||
.with(|ctx| ctx.replace(Box::new(server_context)));
|
||||
// poll the future, which may call server_context()
|
||||
log::info!("Rebuilding vdom");
|
||||
let _ = vdom.rebuild();
|
||||
vdom.wait_for_suspense().await;
|
||||
log::info!("Suspense resolved");
|
||||
// after polling the future, we need to restore the context
|
||||
SERVER_CONTEXT.with(|ctx| ctx.replace(prev_context));
|
||||
})
|
||||
},
|
||||
&wrapper,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(freshness) => {
|
||||
match String::from_utf8(to.buffer).map_err(|err| {
|
||||
dioxus_ssr::incremental::IncrementalRendererError::Other(
|
||||
Box::new(err),
|
||||
)
|
||||
}) {
|
||||
Ok(html) => {
|
||||
let _ = tx.send(Ok((freshness, html)));
|
||||
}
|
||||
Err(err) => {
|
||||
let _ = tx.send(Err(err));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
let _ = tx.send(Err(err));
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
let (freshness, html) = rx.await.unwrap()?;
|
||||
|
||||
Ok((freshness, html))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -80,18 +167,22 @@ impl SSRState {
|
|||
pub(crate) fn new<P: Clone>(cfg: &ServeConfig<P>) -> Self {
|
||||
if cfg.incremental.is_some() {
|
||||
return Self {
|
||||
renderers: Arc::new(SsrRendererPool::Incremental(object_pool::Pool::new(
|
||||
10,
|
||||
|| incremental_pre_renderer(cfg.incremental.as_ref().unwrap()),
|
||||
))),
|
||||
renderers: Arc::new(SsrRendererPool::Incremental(RwLock::new(vec![
|
||||
incremental_pre_renderer(cfg.incremental.as_ref().unwrap()),
|
||||
incremental_pre_renderer(cfg.incremental.as_ref().unwrap()),
|
||||
incremental_pre_renderer(cfg.incremental.as_ref().unwrap()),
|
||||
incremental_pre_renderer(cfg.incremental.as_ref().unwrap()),
|
||||
]))),
|
||||
};
|
||||
}
|
||||
|
||||
Self {
|
||||
renderers: Arc::new(SsrRendererPool::Renderer(object_pool::Pool::new(
|
||||
10,
|
||||
pre_renderer,
|
||||
))),
|
||||
renderers: Arc::new(SsrRendererPool::Renderer(RwLock::new(vec![
|
||||
pre_renderer(),
|
||||
pre_renderer(),
|
||||
pre_renderer(),
|
||||
pre_renderer(),
|
||||
]))),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,30 +197,25 @@ impl SSRState {
|
|||
> + Send
|
||||
+ 'a {
|
||||
async move {
|
||||
let mut html = WriteBuffer { buffer: Vec::new() };
|
||||
let ServeConfig { app, props, .. } = cfg;
|
||||
|
||||
let freshness = self
|
||||
let (freshness, html) = self
|
||||
.renderers
|
||||
.render_to(cfg, route, *app, props.clone(), &mut html, server_context)
|
||||
.render_to(cfg, route, *app, props.clone(), server_context)
|
||||
.await?;
|
||||
|
||||
Ok(RenderResponse {
|
||||
html: String::from_utf8(html.buffer).map_err(|err| {
|
||||
dioxus_ssr::incremental::IncrementalRendererError::Other(Box::new(err))
|
||||
})?,
|
||||
freshness,
|
||||
})
|
||||
Ok(RenderResponse { html, freshness })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct FullstackRenderer<'a, P: Clone + Send + Sync + 'static> {
|
||||
cfg: &'a ServeConfig<P>,
|
||||
struct FullstackRenderer<P: Clone + Send + Sync + 'static> {
|
||||
cfg: ServeConfig<P>,
|
||||
server_context: DioxusServerContext,
|
||||
}
|
||||
|
||||
impl<'a, P: Clone + Serialize + Send + Sync + 'static> dioxus_ssr::incremental::WrapBody
|
||||
for FullstackRenderer<'a, P>
|
||||
impl<P: Clone + Serialize + Send + Sync + 'static> dioxus_ssr::incremental::WrapBody
|
||||
for FullstackRenderer<P>
|
||||
{
|
||||
fn render_before_body<R: std::io::Write>(
|
||||
&self,
|
||||
|
@ -147,7 +233,29 @@ impl<'a, P: Clone + Serialize + Send + Sync + 'static> dioxus_ssr::incremental::
|
|||
to: &mut R,
|
||||
) -> Result<(), dioxus_ssr::incremental::IncrementalRendererError> {
|
||||
// serialize the props
|
||||
crate::props_html::serialize_props::encode_in_element(&self.cfg.props, to)?;
|
||||
crate::html_storage::serialize::encode_props_in_element(&self.cfg.props, to)?;
|
||||
// serialize the server state
|
||||
crate::html_storage::serialize::encode_in_element(
|
||||
&*self.server_context.html_data().map_err(|_| {
|
||||
dioxus_ssr::incremental::IncrementalRendererError::Other(Box::new({
|
||||
#[derive(Debug)]
|
||||
struct HTMLDataReadError;
|
||||
|
||||
impl std::fmt::Display for HTMLDataReadError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_str(
|
||||
"Failed to read the server data to serialize it into the HTML",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for HTMLDataReadError {}
|
||||
|
||||
HTMLDataReadError
|
||||
}))
|
||||
})?,
|
||||
to,
|
||||
)?;
|
||||
|
||||
#[cfg(all(debug_assertions, feature = "hot-reload"))]
|
||||
{
|
||||
|
@ -192,6 +300,7 @@ impl<'a, P: Clone + Serialize + Send + Sync + 'static> dioxus_ssr::incremental::
|
|||
}
|
||||
|
||||
/// A rendered response from the server.
|
||||
#[derive(Debug)]
|
||||
pub struct RenderResponse {
|
||||
pub(crate) html: String,
|
||||
pub(crate) freshness: RenderFreshness,
|
||||
|
@ -232,7 +341,10 @@ where
|
|||
Rt: dioxus_router::prelude::Routable + Send + Sync + Serialize,
|
||||
<Rt as std::str::FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
let wrapper = FullstackRenderer { cfg };
|
||||
let wrapper = FullstackRenderer {
|
||||
cfg: cfg.clone(),
|
||||
server_context: Default::default(),
|
||||
};
|
||||
let mut renderer = incremental_pre_renderer(
|
||||
cfg.incremental
|
||||
.as_ref()
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use crate::html_storage::HTMLData;
|
||||
pub use server_fn_impl::*;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
|
@ -13,6 +14,7 @@ pub struct DioxusServerContext {
|
|||
>,
|
||||
response_parts: std::sync::Arc<std::sync::RwLock<http::response::Parts>>,
|
||||
pub(crate) parts: Arc<RwLock<http::request::Parts>>,
|
||||
html_data: Arc<RwLock<HTMLData>>,
|
||||
}
|
||||
|
||||
#[allow(clippy::derivable_impls)]
|
||||
|
@ -24,6 +26,7 @@ impl Default for DioxusServerContext {
|
|||
http::response::Response::new(()).into_parts().0,
|
||||
)),
|
||||
parts: std::sync::Arc::new(RwLock::new(http::request::Request::new(()).into_parts().0)),
|
||||
html_data: Arc::new(RwLock::new(HTMLData::default())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +48,7 @@ mod server_fn_impl {
|
|||
response_parts: std::sync::Arc::new(RwLock::new(
|
||||
http::response::Response::new(()).into_parts().0,
|
||||
)),
|
||||
html_data: Arc::new(RwLock::new(HTMLData::default())),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -100,11 +104,26 @@ mod server_fn_impl {
|
|||
) -> Result<T, R> {
|
||||
T::from_request(self).await
|
||||
}
|
||||
|
||||
/// Insert some data into the html data store
|
||||
pub(crate) fn push_html_data<T: serde::Serialize>(
|
||||
&self,
|
||||
value: &T,
|
||||
) -> Result<(), PoisonError<RwLockWriteGuard<'_, HTMLData>>> {
|
||||
self.html_data.write().map(|mut map| {
|
||||
map.push(value);
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the html data store
|
||||
pub(crate) fn html_data(&self) -> LockResult<RwLockReadGuard<'_, HTMLData>> {
|
||||
self.html_data.read()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::thread_local! {
|
||||
static SERVER_CONTEXT: std::cell::RefCell<Box<DioxusServerContext>> = std::cell::RefCell::new(Box::new(DioxusServerContext::default() ));
|
||||
pub(crate) static SERVER_CONTEXT: std::cell::RefCell<Box<DioxusServerContext>> = std::cell::RefCell::new(Box::new(DioxusServerContext::default() ));
|
||||
}
|
||||
|
||||
/// Get information about the current server request.
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
//! Exentsions to the incremental renderer to support pre-caching static routes.
|
||||
use core::pin::Pin;
|
||||
use std::future::Future;
|
||||
use std::str::FromStr;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
|
@ -47,7 +49,9 @@ where
|
|||
route,
|
||||
&mut tokio::io::sink(),
|
||||
|vdom| {
|
||||
let _ = vdom.rebuild();
|
||||
Box::pin(async move {
|
||||
let _ = vdom.wait_for_suspense().await;
|
||||
})
|
||||
},
|
||||
wrapper,
|
||||
)
|
||||
|
@ -65,7 +69,12 @@ where
|
|||
}
|
||||
|
||||
/// Render a route to a writer.
|
||||
pub async fn render_route<R: WrapBody + Send + Sync, Rt, W, F: FnOnce(&mut VirtualDom)>(
|
||||
pub async fn render_route<
|
||||
R: WrapBody + Send + Sync,
|
||||
Rt,
|
||||
W,
|
||||
F: FnOnce(&mut VirtualDom) -> Pin<Box<dyn Future<Output = ()> + '_>>,
|
||||
>(
|
||||
renderer: &mut IncrementalRenderer,
|
||||
route: Rt,
|
||||
writer: &mut W,
|
||||
|
|
|
@ -6,10 +6,12 @@ use crate::fs_cache::ValidCachedPath;
|
|||
use dioxus_core::{Element, Scope, VirtualDom};
|
||||
use rustc_hash::FxHasher;
|
||||
use std::{
|
||||
future::Future,
|
||||
hash::BuildHasherDefault,
|
||||
io::Write,
|
||||
ops::{Deref, DerefMut},
|
||||
path::PathBuf,
|
||||
pin::Pin,
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
use tokio::io::{AsyncWrite, AsyncWriteExt, BufReader};
|
||||
|
@ -67,36 +69,29 @@ impl IncrementalRenderer {
|
|||
self.invalidate_after.is_some()
|
||||
}
|
||||
|
||||
fn render_and_cache<'a, P: 'static, R: WrapBody + Send + Sync>(
|
||||
async fn render_and_cache<'a, P: 'static, R: WrapBody + Send + Sync>(
|
||||
&'a mut self,
|
||||
route: String,
|
||||
comp: fn(Scope<P>) -> Element,
|
||||
props: P,
|
||||
output: &'a mut (impl AsyncWrite + Unpin + Send),
|
||||
rebuild_with: impl FnOnce(&mut VirtualDom),
|
||||
rebuild_with: impl FnOnce(&mut VirtualDom) -> Pin<Box<dyn Future<Output = ()> + '_>>,
|
||||
renderer: &'a R,
|
||||
) -> impl std::future::Future<Output = Result<RenderFreshness, IncrementalRendererError>> + 'a + Send
|
||||
{
|
||||
) -> Result<RenderFreshness, IncrementalRendererError> {
|
||||
let mut html_buffer = WriteBuffer { buffer: Vec::new() };
|
||||
let result_1;
|
||||
let result2;
|
||||
{
|
||||
let mut vdom = VirtualDom::new_with_props(comp, props);
|
||||
rebuild_with(&mut vdom);
|
||||
rebuild_with(&mut vdom).await;
|
||||
|
||||
result_1 = renderer.render_before_body(&mut *html_buffer);
|
||||
result2 = self.ssr_renderer.render_to(&mut html_buffer, &vdom);
|
||||
renderer.render_before_body(&mut *html_buffer)?;
|
||||
self.ssr_renderer.render_to(&mut html_buffer, &vdom)?;
|
||||
}
|
||||
async move {
|
||||
result_1?;
|
||||
result2?;
|
||||
renderer.render_after_body(&mut *html_buffer)?;
|
||||
let html_buffer = html_buffer.buffer;
|
||||
renderer.render_after_body(&mut *html_buffer)?;
|
||||
let html_buffer = html_buffer.buffer;
|
||||
|
||||
output.write_all(&html_buffer).await?;
|
||||
output.write_all(&html_buffer).await?;
|
||||
|
||||
self.add_to_cache(route, html_buffer)
|
||||
}
|
||||
self.add_to_cache(route, html_buffer)
|
||||
}
|
||||
|
||||
fn add_to_cache(
|
||||
|
@ -178,7 +173,7 @@ impl IncrementalRenderer {
|
|||
component: fn(Scope<P>) -> Element,
|
||||
props: P,
|
||||
output: &mut (impl AsyncWrite + Unpin + std::marker::Send),
|
||||
rebuild_with: impl FnOnce(&mut VirtualDom),
|
||||
rebuild_with: impl FnOnce(&mut VirtualDom) -> Pin<Box<dyn Future<Output = ()> + '_>>,
|
||||
renderer: &R,
|
||||
) -> Result<RenderFreshness, IncrementalRendererError> {
|
||||
// check if this route is cached
|
||||
|
|
Loading…
Reference in a new issue