From 9f891f9478688998cbaaf29bd3a454f616d11c14 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 28 Jul 2023 12:58:34 -0700 Subject: [PATCH] switch to launch builder instead of macro for fullstack --- packages/fullstack/Cargo.toml | 10 +- .../fullstack/examples/axum-auth/src/main.rs | 2 +- .../examples/axum-desktop/src/server.rs | 4 +- .../examples/axum-hello-world/Cargo.toml | 8 +- .../examples/axum-hello-world/src/main.rs | 10 +- .../fullstack/examples/axum-router/Cargo.toml | 6 +- .../examples/axum-router/src/main.rs | 16 +- .../examples/salvo-hello-world/src/main.rs | 4 +- .../examples/warp-hello-world/src/main.rs | 4 +- .../fullstack/src/adapters/axum_adapter.rs | 3 +- .../fullstack/src/adapters/salvo_adapter.rs | 10 +- packages/fullstack/src/launch.rs | 371 ++++++++++-------- packages/fullstack/src/lib.rs | 4 +- 13 files changed, 259 insertions(+), 193 deletions(-) diff --git a/packages/fullstack/Cargo.toml b/packages/fullstack/Cargo.toml index 57f414194..948e53a24 100644 --- a/packages/fullstack/Cargo.toml +++ b/packages/fullstack/Cargo.toml @@ -34,7 +34,13 @@ dioxus-ssr = { workspace = true, optional = true } hyper = { version = "0.14.25", optional = true } http = { version = "0.2.9", optional = true } -# Router Intigration +# Web Integration +dioxus-web = { workspace = true, features = ["hydrate"], optional = true } + +# Desktop Integration +dioxus-desktop = { workspace = true, optional = true } + +# Router Integration dioxus-router = { workspace = true, optional = true } log = { workspace = true } @@ -65,6 +71,8 @@ web-sys = { version = "0.3.61", features = ["Window", "Document", "Element", "Ht default = ["hot-reload", "default-tls"] router = ["dioxus-router"] hot-reload = ["serde_json", "futures-util"] +web = ["dioxus-web"] +desktop = ["dioxus-desktop"] warp = ["dep:warp", "ssr"] axum = ["dep:axum", "tower-http", "ssr"] salvo = ["dep:salvo", "ssr"] diff --git a/packages/fullstack/examples/axum-auth/src/main.rs b/packages/fullstack/examples/axum-auth/src/main.rs index fe94c9628..857287af5 100644 --- a/packages/fullstack/examples/axum-auth/src/main.rs +++ b/packages/fullstack/examples/axum-auth/src/main.rs @@ -73,7 +73,7 @@ fn main() { }); } } - +// fn app(cx: Scope) -> Element { let user_name = use_state(cx, || "?".to_string()); let permissions = use_state(cx, || "?".to_string()); diff --git a/packages/fullstack/examples/axum-desktop/src/server.rs b/packages/fullstack/examples/axum-desktop/src/server.rs index a8c61d28c..0024e6830 100644 --- a/packages/fullstack/examples/axum-desktop/src/server.rs +++ b/packages/fullstack/examples/axum-desktop/src/server.rs @@ -10,8 +10,8 @@ use dioxus_fullstack::prelude::*; async fn main() { let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080)); - PostServerData::register_explicit(); - GetServerData::register_explicit(); + let _ = PostServerData::register_explicit(); + let _ = GetServerData::register_explicit(); axum::Server::bind(&addr) .serve( diff --git a/packages/fullstack/examples/axum-hello-world/Cargo.toml b/packages/fullstack/examples/axum-hello-world/Cargo.toml index 3a95f5538..2073e4655 100644 --- a/packages/fullstack/examples/axum-hello-world/Cargo.toml +++ b/packages/fullstack/examples/axum-hello-world/Cargo.toml @@ -7,14 +7,10 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-web = { workspace = true, features = ["hydrate"], optional = true } dioxus = { workspace = true } dioxus-fullstack = { workspace = true } axum = { version = "0.6.12", optional = true } -tokio = { workspace = true, features = ["full"], optional = true } 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 @@ -22,5 +18,5 @@ reqwest = "0.11.18" [features] default = [] -ssr = ["axum", "tokio", "dioxus-fullstack/axum"] -web = ["dioxus-web"] +ssr = ["axum", "dioxus-fullstack/axum"] +web = ["dioxus-fullstack/web"] diff --git a/packages/fullstack/examples/axum-hello-world/src/main.rs b/packages/fullstack/examples/axum-hello-world/src/main.rs index 38d30ada0..985a5a717 100644 --- a/packages/fullstack/examples/axum-hello-world/src/main.rs +++ b/packages/fullstack/examples/axum-hello-world/src/main.rs @@ -7,8 +7,12 @@ #![allow(non_snake_case, unused)] use dioxus::prelude::*; -use dioxus_fullstack::{launch, prelude::*}; +use dioxus_fullstack::{ + launch::{self, LaunchBuilder}, + prelude::*, +}; use serde::{Deserialize, Serialize}; +use wasm_logger::Config; #[derive(Props, PartialEq, Debug, Default, Serialize, Deserialize, Clone)] struct AppProps { @@ -66,7 +70,5 @@ fn main() { #[cfg(feature = "ssr")] simple_logger::SimpleLogger::new().init().unwrap(); - launch!(@([127, 0, 0, 1], 8080), app, { - serve_cfg: ServeConfigBuilder::new(app, AppProps { count: 0 }), - }); + LaunchBuilder::new_with_props(app, AppProps { count: 0 }).launch() } diff --git a/packages/fullstack/examples/axum-router/Cargo.toml b/packages/fullstack/examples/axum-router/Cargo.toml index ee788bc05..a06e20443 100644 --- a/packages/fullstack/examples/axum-router/Cargo.toml +++ b/packages/fullstack/examples/axum-router/Cargo.toml @@ -9,7 +9,7 @@ publish = false [dependencies] dioxus-web = { workspace = true, features = ["hydrate"], optional = true } dioxus = { workspace = true } -dioxus-router = { workspace = true} +dioxus-router = { workspace = true } dioxus-fullstack = { workspace = true, features = ["router"] } axum = { version = "0.6.12", optional = true } tokio = {workspace = true, features = ["full"], optional = true } @@ -17,5 +17,5 @@ serde = { version = "1.0.159", features = ["derive"] } [features] default = [] -ssr = ["axum", "tokio", "dioxus-fullstack/axum"] -web = ["dioxus-web", "dioxus-router/web"] +ssr = ["axum", "dioxus-fullstack/axum"] +web = ["dioxus-fullstack/web", "dioxus-router/web"] diff --git a/packages/fullstack/examples/axum-router/src/main.rs b/packages/fullstack/examples/axum-router/src/main.rs index b9ad1e057..6780aec6f 100644 --- a/packages/fullstack/examples/axum-router/src/main.rs +++ b/packages/fullstack/examples/axum-router/src/main.rs @@ -12,12 +12,20 @@ use dioxus_fullstack::prelude::*; use dioxus_router::prelude::*; fn main() { - launch_router!(@([127, 0, 0, 1], 8080), Route, { - incremental: IncrementalRendererConfig::default().invalidate_after(std::time::Duration::from_secs(120)), - }); + let config = LaunchBuilder::>::router(); + #[cfg(feature = "ssr")] + config + .incremental( + IncrementalRendererConfig::default() + .invalidate_after(std::time::Duration::from_secs(120)), + ) + .launch(); + + #[cfg(not(feature = "ssr"))] + config.launch(); } -#[derive(Clone, Routable, Debug, PartialEq)] +#[derive(Clone, Routable, Debug, PartialEq, serde::Serialize, serde::Deserialize)] enum Route { #[route("/")] Home {}, diff --git a/packages/fullstack/examples/salvo-hello-world/src/main.rs b/packages/fullstack/examples/salvo-hello-world/src/main.rs index eed4362f4..df119b4dd 100644 --- a/packages/fullstack/examples/salvo-hello-world/src/main.rs +++ b/packages/fullstack/examples/salvo-hello-world/src/main.rs @@ -64,7 +64,5 @@ fn main() { #[cfg(feature = "ssr")] simple_logger::SimpleLogger::new().init().unwrap(); - launch!(@([127, 0, 0, 1], 8080), app, { - serve_cfg: ServeConfigBuilder::new(app, AppProps { count: 0 }), - }); + LaunchBuilder::new_with_props(app, AppProps { count: 0 }).launch() } diff --git a/packages/fullstack/examples/warp-hello-world/src/main.rs b/packages/fullstack/examples/warp-hello-world/src/main.rs index eed4362f4..df119b4dd 100644 --- a/packages/fullstack/examples/warp-hello-world/src/main.rs +++ b/packages/fullstack/examples/warp-hello-world/src/main.rs @@ -64,7 +64,5 @@ fn main() { #[cfg(feature = "ssr")] simple_logger::SimpleLogger::new().init().unwrap(); - launch!(@([127, 0, 0, 1], 8080), app, { - serve_cfg: ServeConfigBuilder::new(app, AppProps { count: 0 }), - }); + LaunchBuilder::new_with_props(app, AppProps { count: 0 }).launch() } diff --git a/packages/fullstack/src/adapters/axum_adapter.rs b/packages/fullstack/src/adapters/axum_adapter.rs index f1c5487e7..330d15bc3 100644 --- a/packages/fullstack/src/adapters/axum_adapter.rs +++ b/packages/fullstack/src/adapters/axum_adapter.rs @@ -369,7 +369,8 @@ fn apply_request_parts_to_response( } } -async fn render_handler( +/// SSR renderer handler for Axum +pub async fn render_handler( State((cfg, ssr_state)): State<(ServeConfig

, SSRState)>, request: Request, ) -> impl IntoResponse { diff --git a/packages/fullstack/src/adapters/salvo_adapter.rs b/packages/fullstack/src/adapters/salvo_adapter.rs index dc536376d..671128b65 100644 --- a/packages/fullstack/src/adapters/salvo_adapter.rs +++ b/packages/fullstack/src/adapters/salvo_adapter.rs @@ -375,10 +375,18 @@ async fn convert_response(response: HyperResponse, res: &mut Response) { } } -struct SSRHandler { +/// A handler that renders a Dioxus application to HTML using server-side rendering. +pub struct SSRHandler { cfg: ServeConfig

, } +impl SSRHandler

{ + /// Creates a new SSR handler with the given configuration. + pub fn new(cfg: ServeConfig

) -> Self { + Self { cfg } + } +} + #[async_trait] impl Handler for SSRHandler

{ async fn handle( diff --git a/packages/fullstack/src/launch.rs b/packages/fullstack/src/launch.rs index 6432e69ce..8d125680b 100644 --- a/packages/fullstack/src/launch.rs +++ b/packages/fullstack/src/launch.rs @@ -1,173 +1,218 @@ //! Launch helper macros for fullstack apps #![allow(unused)] +use crate::prelude::*; +use dioxus::prelude::*; +#[cfg(feature = "router")] +use dioxus_router::prelude::*; -#[macro_export] -/// Launch a server with a router -macro_rules! launch_router { - (@router_config) => { - dioxus_fullstack::router::FullstackRouterConfig::default() - }; - - (@router_config $router_cfg:expr) => { - $router_cfg - }; - - (@$address:expr, $route:ty, $(cfg: $router_cfg:expr,)? {$($rule:ident $(: $cfg:expr)?,)*}) => { - dioxus_fullstack::launch!( - @$address, - dioxus_fullstack::router::RouteWithCfg::<$route>, - (dioxus_fullstack::launch_router!(@router_config $($router_cfg)?)), - { - $($rule $(: $cfg)?,)* - } - ) - }; -} - -#[macro_export] -/// Launch a server -macro_rules! launch { - (@web_cfg $server_cfg:ident $wcfg:expr) => { - #[cfg(feature = "web")] - let web_cfg = $wcfg; - }; - - (@web_cfg $server_cfg:ident) => { - #[cfg(feature = "web")] - let web_cfg = dioxus_web::Config::new(); - }; - - (@serve_cfg $server_cfg:ident $cfg:expr) => { - #[cfg(feature = "ssr")] - let $server_cfg = $cfg; - }; - - (@hot_reload $server_cfg:ident) => { - #[cfg(feature = "ssr")] - { - dioxus_fullstack::prelude::hot_reload_init!(dioxus_hot_reload::Config::new().with_rebuild_callback(|| { - std::process::Command::new("cargo") - .args(&["run", "--features", "ssr"]) - .spawn() - .unwrap() - .wait() - .unwrap(); - std::process::Command::new("cargo") - .args(&["run", "--features", "web"]) - .spawn() - .unwrap() - .wait() - .unwrap(); - true - })); - } - }; - - (@hot_reload $server_cfg:ident $hot_reload_cfg:expr) => { - #[cfg(feature = "ssr")] - { - dioxus_fullstack::prelude::hot_reload_init!($hot_reload_cfg); - } - }; - - (@incremental $server_cfg:ident) => { - #[cfg(feature = "ssr")] - let $server_cfg = $server_cfg.incremental(dioxus_fullstack::prelude::IncrementalRendererConfig::default()); - }; - - (@incremental $server_cfg:ident $cfg:expr) => { - #[cfg(feature = "ssr")] - let $server_cfg = $server_cfg.incremental($cfg); - }; - - (@props_type) => { - Default::default() - }; - - (@props_type $props:expr) => { - $props - }; - - (@ $address:expr, $comp:path, $(( $props:expr ),)? {$($rule:ident $(: $cfg:expr)?,)*}) => { - #[cfg(feature = "web")] - { - #[allow(unused)] - let web_cfg = dioxus_web::Config::new(); - - $( - dioxus_fullstack::prelude::launch!(@$rule server_cfg $($cfg)?); - )* - - dioxus_web::launch_with_props( - $comp, - dioxus_fullstack::prelude::get_root_props_from_document().expect("Failed to get root props from document"), - web_cfg.hydrate(true), - ); - } - #[cfg(feature = "ssr")] - { - let server_cfg = dioxus_fullstack::prelude::ServeConfigBuilder::new($comp, dioxus_fullstack::prelude::launch!(@props_type $($props)?)); - - $( - dioxus_fullstack::prelude::launch!(@$rule server_cfg $($cfg)?); - )* - - tokio::runtime::Runtime::new() - .unwrap() - .block_on(async move { - let addr = std::net::SocketAddr::from($address); - - dioxus_fullstack::launch::launch_server(addr, server_cfg.build()).await; - }); - } - }; -} - -/// Launch a server with the given configeration -/// This will use the routing intigration of the currently enabled intigration feature -#[cfg(feature = "ssr")] -pub async fn launch_server( +/// A builder for a fullstack app. +pub struct LaunchBuilder { + component: Component, + #[cfg(not(feature = "ssr"))] + props: Props, + #[cfg(feature = "ssr")] + server_fn_route: &'static str, + #[cfg(feature = "ssr")] + server_cfg: ServeConfigBuilder, + #[cfg(feature = "ssr")] addr: std::net::SocketAddr, - cfg: crate::prelude::ServeConfig

, -) { - #[cfg(all(feature = "axum", not(feature = "warp"), not(feature = "salvo")))] - { - use crate::adapters::axum_adapter::DioxusRouterExt; - use tower::ServiceBuilder; + #[cfg(feature = "web")] + web_cfg: dioxus_web::Config, + #[cfg(feature = "desktop")] + desktop_cfg: dioxus_desktop::Config, +} - axum::Server::bind(&addr) - .serve( - axum::Router::new() - .serve_dioxus_application("", cfg) - .layer( - ServiceBuilder::new() - .layer(tower_http::compression::CompressionLayer::new().gzip(true)), - ) - .into_make_service(), - ) - .await - .unwrap(); - } - #[cfg(all(feature = "warp", not(feature = "axum"), not(feature = "salvo")))] +impl + LaunchBuilder +{ + /// Create a new builder for a fullstack app. + pub fn new(component: Component) -> Self + where + Props: Default, { - use warp::Filter; - warp::serve( - crate::prelude::serve_dioxus_application("", cfg) - .with(warp::filters::compression::gzip()), - ) - .run(addr) - .await; + Self::new_with_props(component, Default::default()) } - #[cfg(all(feature = "salvo", not(feature = "axum"), not(feature = "warp")))] + + /// Create a new builder for a fullstack app with props. + pub fn new_with_props(component: Component, props: Props) -> Self + where + Props: Default, { - use crate::adapters::salvo_adapter::DioxusRouterExt; - use salvo::conn::Listener; - let router = salvo::Router::new().serve_dioxus_application("", cfg).hoop( - salvo::compression::Compression::new() - .enable_gzip(salvo::prelude::CompressionLevel::Default), - ); - salvo::Server::new(salvo::conn::tcp::TcpListener::new(addr).bind().await) - .serve(router) - .await; + Self { + component, + #[cfg(not(feature = "ssr"))] + props, + #[cfg(feature = "ssr")] + server_fn_route: "", + #[cfg(feature = "ssr")] + addr: std::net::SocketAddr::from(([127, 0, 0, 1], 8080)), + #[cfg(feature = "ssr")] + server_cfg: ServeConfigBuilder::new(component, props), + #[cfg(feature = "web")] + web_cfg: dioxus_web::Config::default(), + #[cfg(feature = "desktop")] + desktop_cfg: dioxus_desktop::Config::default(), + } + } + + /// Set the address to serve the app on. + #[cfg(feature = "ssr")] + pub fn addr(self, addr: impl Into) -> Self { + let addr = addr.into(); + Self { addr, ..self } + } + + /// Set the route to the server functions. + #[cfg(feature = "ssr")] + pub fn server_fn_route(self, server_fn_route: &'static str) -> Self { + Self { + server_fn_route, + ..self + } + } + + /// Set the incremental renderer config. + #[cfg(feature = "ssr")] + pub fn incremental(self, cfg: IncrementalRendererConfig) -> Self { + Self { + server_cfg: self.server_cfg.incremental(cfg), + ..self + } + } + + /// Set the server config. + #[cfg(feature = "ssr")] + pub fn server_cfg(self, server_cfg: ServeConfigBuilder) -> Self { + Self { server_cfg, ..self } + } + + /// Set the web config. + #[cfg(feature = "web")] + pub fn web_cfg(self, web_cfg: dioxus_web::Config) -> Self { + Self { web_cfg, ..self } + } + + /// Set the desktop config. + #[cfg(feature = "desktop")] + pub fn desktop_cfg(self, desktop_cfg: dioxus_desktop::Config) -> Self { + Self { + desktop_cfg, + ..self + } + } + + /// Launch the app. + pub fn launch(self) { + #[cfg(feature = "ssr")] + tokio::runtime::Runtime::new() + .unwrap() + .block_on(async move { + self.launch_server().await; + }); + #[cfg(not(feature = "ssr"))] + { + #[cfg(feature = "web")] + self.launch_web(); + #[cfg(feature = "desktop")] + self.launch_desktop(); + } + } + + #[cfg(feature = "web")] + fn launch_web(self) { + let cfg = self.web_cfg.hydrate(true); + dioxus_web::launch_with_props(self.component, get_root_props_from_document().unwrap(), cfg); + } + + #[cfg(feature = "desktop")] + fn launch_desktop(self) { + let cfg = self.desktop_cfg; + dioxus_desktop::launch_with_props(self.component, self.props, cfg); + } + + /// Launch a server with the given configuration + /// This will use the routing integration of the currently enabled integration feature + #[cfg(feature = "ssr")] + async fn launch_server(self) { + let addr = self.addr; + println!("Listening on {}", addr); + let cfg = self.server_cfg.build(); + let server_fn_route = self.server_fn_route; + #[cfg(all(feature = "axum", not(feature = "warp"), not(feature = "salvo")))] + { + use crate::adapters::axum_adapter::{render_handler, DioxusRouterExt}; + use axum::routing::get; + use tower::ServiceBuilder; + + let ssr_state = SSRState::new(&cfg); + let router = axum::Router::new().register_server_fns(server_fn_route); + #[cfg(not(feature = "desktop"))] + let router = router + .serve_static_assets(cfg.assets_path) + .connect_hot_reload() + .fallback(get(render_handler).with_state((cfg, ssr_state))); + let router = router + .layer( + ServiceBuilder::new() + .layer(tower_http::compression::CompressionLayer::new().gzip(true)), + ) + .into_make_service(); + axum::Server::bind(&addr).serve(router).await.unwrap(); + } + #[cfg(all(feature = "warp", not(feature = "axum"), not(feature = "salvo")))] + { + use warp::Filter; + // First register the server functions + let router = register_server_fns(server_fn_route); + #[cfg(not(feature = "desktop"))] + let router = { + // Serve the dist folder and the index.html file + let serve_dir = warp::fs::dir(cfg.assets_path); + + router + .or(connect_hot_reload()) + // Then the index route + .or(warp::path::end().and(render_ssr(cfg.clone()))) + // Then the static assets + .or(serve_dir) + // Then all other routes + .or(render_ssr(cfg)) + }; + warp::serve(router.boxed().with(warp::filters::compression::gzip())) + .run(addr) + .await; + } + #[cfg(all(feature = "salvo", not(feature = "axum"), not(feature = "warp")))] + { + use crate::adapters::salvo_adapter::{DioxusRouterExt, SSRHandler}; + use salvo::conn::Listener; + let router = salvo::Router::new().register_server_fns(server_fn_route); + #[cfg(not(feature = "desktop"))] + let router = router + .serve_static_assets(cfg.assets_path) + .connect_hot_reload() + .push(salvo::Router::with_path("/<**any_path>").get(SSRHandler::new(cfg))); + let router = router.hoop( + salvo::compression::Compression::new() + .enable_gzip(salvo::prelude::CompressionLevel::Default), + ); + salvo::Server::new(salvo::conn::tcp::TcpListener::new(addr).bind().await) + .serve(router) + .await; + } + } +} + +#[cfg(feature = "router")] +impl LaunchBuilder> +where + ::Err: std::fmt::Display, + R: Clone + serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static, +{ + /// Create a new launch builder for the given router. + pub fn router() -> Self { + let component = crate::router::RouteWithCfg::; + let props = crate::router::FullstackRouterConfig::default(); + Self::new_with_props(component, props) } } diff --git a/packages/fullstack/src/lib.rs b/packages/fullstack/src/lib.rs index 04b65ba4c..f2d3e5533 100644 --- a/packages/fullstack/src/lib.rs +++ b/packages/fullstack/src/lib.rs @@ -39,10 +39,13 @@ pub mod prelude { use crate::hooks; #[cfg(not(feature = "ssr"))] pub use crate::html_storage::deserialize::get_root_props_from_document; + pub use crate::launch::LaunchBuilder; #[cfg(all(feature = "ssr", feature = "router"))] pub use crate::render::pre_cache_static_routes_with_props; #[cfg(feature = "ssr")] pub use crate::render::SSRState; + #[cfg(feature = "router")] + pub use crate::router::FullstackRouterConfig; #[cfg(feature = "ssr")] pub use crate::serve_config::{ServeConfig, ServeConfigBuilder}; #[cfg(all(feature = "ssr", feature = "axum"))] @@ -54,7 +57,6 @@ pub mod prelude { pub use crate::server_fn::DioxusServerFn; #[cfg(feature = "ssr")] pub use crate::server_fn::{ServerFnMiddleware, ServerFnTraitObj, ServerFunction}; - pub use crate::{launch, launch_router}; pub use dioxus_server_macro::*; #[cfg(feature = "ssr")] pub use dioxus_ssr::incremental::IncrementalRendererConfig;