fix other fullstack adapters

This commit is contained in:
Evan Almloff 2024-01-18 11:39:51 -06:00
parent 694bef0d93
commit 89b1e56fc3
9 changed files with 105 additions and 95 deletions

5
Cargo.lock generated
View file

@ -10966,16 +10966,11 @@ name = "warp-hello-world"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"dioxus", "dioxus",
"dioxus-fullstack",
"dioxus-web",
"execute",
"reqwest", "reqwest",
"serde", "serde",
"simple_logger",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
"tracing-wasm", "tracing-wasm",
"warp",
] ]
[[package]] [[package]]

View file

@ -1,8 +1,7 @@
//! Run with: //! Run with:
//! //!
//! ```sh //! ```sh
//! dx build --features web --release //! dx serve --platform fullstack
//! cargo run --features ssr --release
//! ``` //! ```
#![allow(non_snake_case, unused)] #![allow(non_snake_case, unused)]
@ -10,14 +9,10 @@ use dioxus::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
fn app() -> Element { fn app() -> Element {
// let state = use_server_future(|| async move { get_server_data().await.unwrap() })?;
// let state = state.value();
let mut count = use_signal(|| 0); let mut count = use_signal(|| 0);
let text = use_signal(|| "...".to_string()); let text = use_signal(|| "...".to_string());
rsx! { rsx! {
// div { "Server state: {state}" }
h1 { "High-Five counter: {count}" } h1 { "High-Five counter: {count}" }
button { onclick: move |_| count += 1, "Up high!" } button { onclick: move |_| count += 1, "Up high!" }
button { onclick: move |_| count -= 1, "Down low!" } button { onclick: move |_| count -= 1, "Down low!" }

View file

@ -1,8 +1,7 @@
//! Run with: //! Run with:
//! //!
//! ```sh //! ```sh
//! dx build --features web --release //! dx serve --platform fullstack
//! cargo run --features ssr --release
//! ``` //! ```
use dioxus::prelude::*; use dioxus::prelude::*;

View file

@ -1,8 +1,7 @@
//! Run with: //! Run with:
//! //!
//! ```sh //! ```sh
//! dx build --features web --release //! dx serve --platform fullstack
//! cargo run --features ssr --release
//! ``` //! ```
#![allow(non_snake_case, unused)] #![allow(non_snake_case, unused)]

View file

@ -7,19 +7,14 @@ publish = false
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
dioxus-web = { workspace = true, features=["hydrate"], optional = true } dioxus = { workspace = true, features = ["fullstack"] }
dioxus = { workspace = true }
dioxus-fullstack = { workspace = true }
serde = "1.0.159" serde = "1.0.159"
warp = { version = "0.3.3", optional = true }
execute = "0.2.12"
reqwest = "0.11.18"
simple_logger = "4.2.0"
tracing-wasm = "0.2.1" tracing-wasm = "0.2.1"
tracing.workspace = true tracing.workspace = true
tracing-subscriber = "0.3.17" tracing-subscriber = "0.3.17"
reqwest = "0.11.18"
[features] [features]
default = [] default = []
ssr = ["warp", "dioxus-fullstack/warp"] ssr = ["dioxus/warp"]
web = ["dioxus-web"] web = ["dioxus/web"]

View file

@ -1,31 +1,17 @@
//! Run with: //! Run with:
//! //!
//! ```sh //! ```sh
//! dx build --features web --release //! dx serve --platform fullstack
//! cargo run --features ssr --release
//! ``` //! ```
#![allow(non_snake_case, unused)] #![allow(non_snake_case, unused)]
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_fullstack::{launch, prelude::*};
use serde::{Deserialize, Serialize};
#[derive(Props, PartialEq, Debug, Default, Serialize, Deserialize, Clone)]
struct AppProps {
count: i32,
}
fn app(cx: Scope<AppProps>) -> Element {
let state =
use_server_future((), |()| async move { get_server_data().await.unwrap() })?.value();
fn app() -> Element {
let mut count = use_signal(|| 0); let mut count = use_signal(|| 0);
let text = use_signal(|| "...".to_string()); let text = use_signal(|| "...".to_string());
rsx! { rsx! {
div {
"Server state: {state}"
}
h1 { "High-Five counter: {count}" } h1 { "High-Five counter: {count}" }
button { onclick: move |_| count += 1, "Up high!" } button { onclick: move |_| count += 1, "Up high!" }
button { onclick: move |_| count -= 1, "Down low!" } button { onclick: move |_| count -= 1, "Down low!" }
@ -46,14 +32,14 @@ fn app(cx: Scope<AppProps>) -> Element {
} }
} }
#[server(PostServerData)] #[server]
async fn post_server_data(data: String) -> Result<(), ServerFnError> { async fn post_server_data(data: String) -> Result<(), ServerFnError> {
println!("Server received: {}", data); println!("Server received: {}", data);
Ok(()) Ok(())
} }
#[server(GetServerData)] #[server]
async fn get_server_data() -> Result<String, ServerFnError> { async fn get_server_data() -> Result<String, ServerFnError> {
Ok(reqwest::get("https://httpbin.org/ip").await?.text().await?) Ok(reqwest::get("https://httpbin.org/ip").await?.text().await?)
} }
@ -64,5 +50,5 @@ fn main() {
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
LaunchBuilder::new_with_props(app, AppProps { count: 0 }).launch() launch(app);
} }

View file

@ -49,6 +49,7 @@
//! } //! }
//! ``` //! ```
use dioxus_lib::prelude::*;
use http_body_util::{BodyExt, Limited}; use http_body_util::{BodyExt, Limited};
use hyper::body::Body as HyperBody; use hyper::body::Body as HyperBody;
use hyper::StatusCode; use hyper::StatusCode;
@ -201,10 +202,11 @@ pub trait DioxusRouterExt {
/// ///
/// fn app() -> Element {todo!()} /// fn app() -> Element {todo!()}
/// ``` /// ```
fn serve_dioxus_application<P: Clone + serde::Serialize + Send + Sync + 'static>( fn serve_dioxus_application(
self, self,
server_fn_path: &'static str, server_fn_path: &'static str,
cfg: impl Into<ServeConfig<P>>, cfg: impl Into<ServeConfig>,
virtual_dom_factory: impl Fn() -> VirtualDom + Send + Sync + 'static,
) -> Self; ) -> Self;
} }
@ -281,10 +283,11 @@ impl DioxusRouterExt for Router {
self self
} }
fn serve_dioxus_application<P: Clone + serde::Serialize + Send + Sync + 'static>( fn serve_dioxus_application(
self, self,
server_fn_path: &'static str, server_fn_path: &'static str,
cfg: impl Into<ServeConfig<P>>, cfg: impl Into<ServeConfig>,
virtual_dom_factory: impl Fn() -> VirtualDom + Send + Sync + 'static,
) -> Self { ) -> Self {
let cfg = cfg.into(); let cfg = cfg.into();
@ -376,19 +379,26 @@ async fn convert_response(response: HyperResponse, res: &mut Response) {
} }
/// A handler that renders a Dioxus application to HTML using server-side rendering. /// A handler that renders a Dioxus application to HTML using server-side rendering.
pub struct SSRHandler<P: Clone> { pub struct SSRHandler {
cfg: ServeConfig<P>, config: ServeConfig,
virtual_dom: Box<dyn Fn() -> VirtualDom + Send + Sync>,
} }
impl<P: Clone> SSRHandler<P> { impl SSRHandler {
/// Creates a new SSR handler with the given configuration. /// Creates a new SSR handler with the given configuration.
pub fn new(cfg: ServeConfig<P>) -> Self { pub fn new(
Self { cfg } config: ServeConfig,
virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static,
) -> Self {
Self {
config,
virtual_dom: Box::new(virtual_dom),
}
} }
} }
#[async_trait] #[async_trait]
impl<P: Clone + serde::Serialize + Send + Sync + 'static> Handler for SSRHandler<P> { impl Handler for SSRHandler {
async fn handle( async fn handle(
&self, &self,
req: &mut Request, req: &mut Request,

View file

@ -52,6 +52,7 @@ use crate::{
}; };
use crate::server_fn_service; use crate::server_fn_service;
use dioxus_lib::prelude::VirtualDom;
use server_fn::{Encoding, Payload, ServerFunctionRegistry}; use server_fn::{Encoding, Payload, ServerFunctionRegistry};
use std::error::Error; use std::error::Error;
use std::sync::Arc; use std::sync::Arc;
@ -179,68 +180,85 @@ pub fn register_server_fns(server_fn_route: &'static str) -> BoxedFilter<(impl R
/// todo!() /// todo!()
/// } /// }
/// ``` /// ```
pub fn serve_dioxus_application<P: Clone + serde::Serialize + Send + Sync + 'static>( pub fn serve_dioxus_application(
server_fn_route: &'static str, server_fn_route: &'static str,
cfg: impl Into<ServeConfig<P>>, cfg: impl Into<ServeConfig>,
virtual_dom_factory: impl Fn() -> VirtualDom + Send + Sync + 'static,
) -> BoxedFilter<(impl Reply,)> { ) -> BoxedFilter<(impl Reply,)> {
let cfg = cfg.into(); let cfg = cfg.into();
// Serve the dist folder and the index.html file // Serve the dist folder and the index.html file
let serve_dir = warp::fs::dir(cfg.assets_path); let serve_dir = warp::fs::dir(cfg.assets_path);
let virtual_dom_factory =
Arc::new(virtual_dom_factory) as Arc<dyn Fn() -> VirtualDom + Send + Sync + 'static>;
connect_hot_reload() connect_hot_reload()
// First register the server functions // First register the server functions
.or(register_server_fns(server_fn_route)) .or(register_server_fns(server_fn_route))
// Then the index route // Then the index route
.or(path::end().and(render_ssr(cfg.clone()))) .or(path::end().and(render_ssr(cfg.clone(), {
let virtual_dom_factory = virtual_dom_factory.clone();
move || virtual_dom_factory()
})))
// Then the static assets // Then the static assets
.or(serve_dir) .or(serve_dir)
// Then all other routes // Then all other routes
.or(render_ssr(cfg)) .or(render_ssr(cfg, move || virtual_dom_factory()))
.boxed() .boxed()
} }
/// Server render the application. /// Server render the application.
pub fn render_ssr<P: Clone + serde::Serialize + Send + Sync + 'static>( pub fn render_ssr(
cfg: ServeConfig<P>, cfg: ServeConfig,
virtual_dom_factory: impl Fn() -> VirtualDom + Send + Sync + 'static,
) -> impl Filter<Extract = (impl Reply,), Error = warp::Rejection> + Clone { ) -> impl Filter<Extract = (impl Reply,), Error = warp::Rejection> + Clone {
warp::get() warp::get()
.and(request_parts()) .and(request_parts())
.and(with_ssr_state(&cfg)) .and(with_ssr_state(&cfg, virtual_dom_factory))
.then(move |parts: http::request::Parts, renderer: SSRState| { .then(
let route = parts.uri.path().to_string(); move |parts: http::request::Parts,
let parts = Arc::new(RwLock::new(parts)); (renderer, virtual_dom_factory): (
let cfg = cfg.clone(); SSRState,
async move { Arc<dyn Fn() -> VirtualDom + Send + Sync + 'static>,
let server_context = DioxusServerContext::new(parts); )| {
let route = parts.uri.path().to_string();
let parts = Arc::new(RwLock::new(parts));
let cfg = cfg.clone();
async move {
let server_context = DioxusServerContext::new(parts);
match renderer.render(route, &cfg, &server_context).await { match renderer
Ok(rendered) => { .render(route, &cfg, move || virtual_dom_factory(), &server_context)
let crate::render::RenderResponse { html, freshness } = rendered; .await
{
Ok(rendered) => {
let crate::render::RenderResponse { html, freshness } = rendered;
let mut res = Response::builder() let mut res = Response::builder()
.header("Content-Type", "text/html") .header("Content-Type", "text/html")
.body(html) .body(html)
.unwrap(); .unwrap();
let headers_mut = res.headers_mut(); let headers_mut = res.headers_mut();
let headers = server_context.response_parts().unwrap().headers.clone(); let headers = server_context.response_parts().unwrap().headers.clone();
for (key, value) in headers.iter() { for (key, value) in headers.iter() {
headers_mut.insert(key, value.clone()); headers_mut.insert(key, value.clone());
}
freshness.write(headers_mut);
res
}
Err(err) => {
tracing::error!("Failed to render ssr: {}", err);
Response::builder()
.status(500)
.body("Failed to render ssr".into())
.unwrap()
} }
freshness.write(headers_mut);
res
}
Err(err) => {
tracing::error!("Failed to render ssr: {}", err);
Response::builder()
.status(500)
.body("Failed to render ssr".into())
.unwrap()
} }
} }
} },
}) )
} }
/// An extractor for the request parts (used in [DioxusServerContext]). This will extract the method, uri, query, and headers from the request. /// An extractor for the request parts (used in [DioxusServerContext]). This will extract the method, uri, query, and headers from the request.
@ -273,11 +291,20 @@ pub fn request_parts(
}) })
} }
fn with_ssr_state<P: Clone + serde::Serialize + Send + Sync + 'static>( fn with_ssr_state(
cfg: &ServeConfig<P>, cfg: &ServeConfig,
) -> impl Filter<Extract = (SSRState,), Error = std::convert::Infallible> + Clone { virtual_dom_factory: impl Fn() -> VirtualDom + Send + Sync + 'static,
) -> impl Filter<
Extract = ((
SSRState,
Arc<dyn Fn() -> VirtualDom + Send + Sync + 'static>,
),),
Error = std::convert::Infallible,
> + Clone {
let renderer = SSRState::new(cfg); let renderer = SSRState::new(cfg);
warp::any().map(move || renderer.clone()) let virtual_dom_factory =
Arc::new(virtual_dom_factory) as Arc<dyn Fn() -> VirtualDom + Send + Sync + 'static>;
warp::any().map(move || (renderer.clone(), virtual_dom_factory.clone()))
} }
#[derive(Debug)] #[derive(Debug)]

View file

@ -170,15 +170,19 @@ impl Config {
let router = { let router = {
// Serve the dist folder and the index.html file // Serve the dist folder and the index.html file
let serve_dir = warp::fs::dir(cfg.assets_path); let serve_dir = warp::fs::dir(cfg.assets_path);
let build_virtual_dom = Arc::new(build_virtual_dom);
router router
.or(connect_hot_reload()) .or(connect_hot_reload())
// Then the index route // Then the index route
.or(warp::path::end().and(render_ssr(cfg.clone()))) .or(warp::path::end().and(render_ssr(cfg.clone(), {
let build_virtual_dom = build_virtual_dom.clone();
move || build_virtual_dom()
})))
// Then the static assets // Then the static assets
.or(serve_dir) .or(serve_dir)
// Then all other routes // Then all other routes
.or(render_ssr(cfg)) .or(render_ssr(cfg, move || build_virtual_dom()))
}; };
warp::serve(router.boxed().with(warp::filters::compression::gzip())) warp::serve(router.boxed().with(warp::filters::compression::gzip()))
.run(addr) .run(addr)