switch to launch builder instead of macro for fullstack

This commit is contained in:
Evan Almloff 2023-07-28 12:58:34 -07:00
parent cfd62e274e
commit 9f891f9478
13 changed files with 259 additions and 193 deletions

View file

@ -34,7 +34,13 @@ dioxus-ssr = { workspace = true, optional = true }
hyper = { version = "0.14.25", optional = true } hyper = { version = "0.14.25", optional = true }
http = { version = "0.2.9", 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 } dioxus-router = { workspace = true, optional = true }
log = { workspace = true } log = { workspace = true }
@ -65,6 +71,8 @@ web-sys = { version = "0.3.61", features = ["Window", "Document", "Element", "Ht
default = ["hot-reload", "default-tls"] default = ["hot-reload", "default-tls"]
router = ["dioxus-router"] router = ["dioxus-router"]
hot-reload = ["serde_json", "futures-util"] hot-reload = ["serde_json", "futures-util"]
web = ["dioxus-web"]
desktop = ["dioxus-desktop"]
warp = ["dep:warp", "ssr"] warp = ["dep:warp", "ssr"]
axum = ["dep:axum", "tower-http", "ssr"] axum = ["dep:axum", "tower-http", "ssr"]
salvo = ["dep:salvo", "ssr"] salvo = ["dep:salvo", "ssr"]

View file

@ -73,7 +73,7 @@ fn main() {
}); });
} }
} }
//
fn app(cx: Scope) -> Element { fn app(cx: Scope) -> Element {
let user_name = use_state(cx, || "?".to_string()); let user_name = use_state(cx, || "?".to_string());
let permissions = use_state(cx, || "?".to_string()); let permissions = use_state(cx, || "?".to_string());

View file

@ -10,8 +10,8 @@ use dioxus_fullstack::prelude::*;
async fn main() { async fn main() {
let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080)); let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080));
PostServerData::register_explicit(); let _ = PostServerData::register_explicit();
GetServerData::register_explicit(); let _ = GetServerData::register_explicit();
axum::Server::bind(&addr) axum::Server::bind(&addr)
.serve( .serve(

View file

@ -7,14 +7,10 @@ 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 } dioxus = { workspace = true }
dioxus-fullstack = { workspace = true } dioxus-fullstack = { workspace = true }
axum = { version = "0.6.12", optional = true } axum = { version = "0.6.12", optional = true }
tokio = { workspace = true, features = ["full"], optional = true }
serde = "1.0.159" serde = "1.0.159"
execute = "0.2.12"
tower-http = { version = "0.4.1", features = ["auth"] }
simple_logger = "4.2.0" simple_logger = "4.2.0"
wasm-logger = "0.2.0" wasm-logger = "0.2.0"
log.workspace = true log.workspace = true
@ -22,5 +18,5 @@ reqwest = "0.11.18"
[features] [features]
default = [] default = []
ssr = ["axum", "tokio", "dioxus-fullstack/axum"] ssr = ["axum", "dioxus-fullstack/axum"]
web = ["dioxus-web"] web = ["dioxus-fullstack/web"]

View file

@ -7,8 +7,12 @@
#![allow(non_snake_case, unused)] #![allow(non_snake_case, unused)]
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_fullstack::{launch, prelude::*}; use dioxus_fullstack::{
launch::{self, LaunchBuilder},
prelude::*,
};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use wasm_logger::Config;
#[derive(Props, PartialEq, Debug, Default, Serialize, Deserialize, Clone)] #[derive(Props, PartialEq, Debug, Default, Serialize, Deserialize, Clone)]
struct AppProps { struct AppProps {
@ -66,7 +70,5 @@ fn main() {
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
simple_logger::SimpleLogger::new().init().unwrap(); simple_logger::SimpleLogger::new().init().unwrap();
launch!(@([127, 0, 0, 1], 8080), app, { LaunchBuilder::new_with_props(app, AppProps { count: 0 }).launch()
serve_cfg: ServeConfigBuilder::new(app, AppProps { count: 0 }),
});
} }

View file

@ -17,5 +17,5 @@ serde = { version = "1.0.159", features = ["derive"] }
[features] [features]
default = [] default = []
ssr = ["axum", "tokio", "dioxus-fullstack/axum"] ssr = ["axum", "dioxus-fullstack/axum"]
web = ["dioxus-web", "dioxus-router/web"] web = ["dioxus-fullstack/web", "dioxus-router/web"]

View file

@ -12,12 +12,20 @@ use dioxus_fullstack::prelude::*;
use dioxus_router::prelude::*; use dioxus_router::prelude::*;
fn main() { fn main() {
launch_router!(@([127, 0, 0, 1], 8080), Route, { let config = LaunchBuilder::<FullstackRouterConfig<Route>>::router();
incremental: IncrementalRendererConfig::default().invalidate_after(std::time::Duration::from_secs(120)), #[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 { enum Route {
#[route("/")] #[route("/")]
Home {}, Home {},

View file

@ -64,7 +64,5 @@ fn main() {
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
simple_logger::SimpleLogger::new().init().unwrap(); simple_logger::SimpleLogger::new().init().unwrap();
launch!(@([127, 0, 0, 1], 8080), app, { LaunchBuilder::new_with_props(app, AppProps { count: 0 }).launch()
serve_cfg: ServeConfigBuilder::new(app, AppProps { count: 0 }),
});
} }

View file

@ -64,7 +64,5 @@ fn main() {
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
simple_logger::SimpleLogger::new().init().unwrap(); simple_logger::SimpleLogger::new().init().unwrap();
launch!(@([127, 0, 0, 1], 8080), app, { LaunchBuilder::new_with_props(app, AppProps { count: 0 }).launch()
serve_cfg: ServeConfigBuilder::new(app, AppProps { count: 0 }),
});
} }

View file

@ -369,7 +369,8 @@ fn apply_request_parts_to_response<B>(
} }
} }
async fn render_handler<P: Clone + serde::Serialize + Send + Sync + 'static>( /// SSR renderer handler for Axum
pub async fn render_handler<P: Clone + serde::Serialize + Send + Sync + 'static>(
State((cfg, ssr_state)): State<(ServeConfig<P>, SSRState)>, State((cfg, ssr_state)): State<(ServeConfig<P>, SSRState)>,
request: Request<Body>, request: Request<Body>,
) -> impl IntoResponse { ) -> impl IntoResponse {

View file

@ -375,10 +375,18 @@ async fn convert_response(response: HyperResponse, res: &mut Response) {
} }
} }
struct SSRHandler<P: Clone> { /// A handler that renders a Dioxus application to HTML using server-side rendering.
pub struct SSRHandler<P: Clone> {
cfg: ServeConfig<P>, cfg: ServeConfig<P>,
} }
impl<P: Clone> SSRHandler<P> {
/// Creates a new SSR handler with the given configuration.
pub fn new(cfg: ServeConfig<P>) -> Self {
Self { cfg }
}
}
#[async_trait] #[async_trait]
impl<P: Clone + serde::Serialize + Send + Sync + 'static> Handler for SSRHandler<P> { impl<P: Clone + serde::Serialize + Send + Sync + 'static> Handler for SSRHandler<P> {
async fn handle( async fn handle(

View file

@ -1,168 +1,198 @@
//! Launch helper macros for fullstack apps //! Launch helper macros for fullstack apps
#![allow(unused)] #![allow(unused)]
use crate::prelude::*;
use dioxus::prelude::*;
#[cfg(feature = "router")]
use dioxus_router::prelude::*;
#[macro_export] /// A builder for a fullstack app.
/// Launch a server with a router pub struct LaunchBuilder<Props: Clone> {
macro_rules! launch_router { component: Component<Props>,
(@router_config) => { #[cfg(not(feature = "ssr"))]
dioxus_fullstack::router::FullstackRouterConfig::default() props: Props,
}; #[cfg(feature = "ssr")]
server_fn_route: &'static str,
(@router_config $router_cfg:expr) => { #[cfg(feature = "ssr")]
$router_cfg server_cfg: ServeConfigBuilder<Props>,
}; #[cfg(feature = "ssr")]
addr: std::net::SocketAddr,
(@$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")] #[cfg(feature = "web")]
let web_cfg = $wcfg; web_cfg: dioxus_web::Config,
}; #[cfg(feature = "desktop")]
desktop_cfg: dioxus_desktop::Config,
}
(@web_cfg $server_cfg:ident) => { impl<Props: Clone + serde::Serialize + serde::de::DeserializeOwned + Send + Sync + 'static>
LaunchBuilder<Props>
{
/// Create a new builder for a fullstack app.
pub fn new(component: Component<Props>) -> Self
where
Props: Default,
{
Self::new_with_props(component, Default::default())
}
/// Create a new builder for a fullstack app with props.
pub fn new_with_props(component: Component<Props>, props: Props) -> Self
where
Props: Default,
{
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")] #[cfg(feature = "web")]
let web_cfg = dioxus_web::Config::new(); web_cfg: dioxus_web::Config::default(),
}; #[cfg(feature = "desktop")]
desktop_cfg: dioxus_desktop::Config::default(),
(@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) => { /// Set the address to serve the app on.
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
let $server_cfg = $server_cfg.incremental(dioxus_fullstack::prelude::IncrementalRendererConfig::default()); pub fn addr(self, addr: impl Into<std::net::SocketAddr>) -> Self {
}; let addr = addr.into();
Self { addr, ..self }
}
(@incremental $server_cfg:ident $cfg:expr) => { /// Set the route to the server functions.
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
let $server_cfg = $server_cfg.incremental($cfg); pub fn server_fn_route(self, server_fn_route: &'static str) -> Self {
}; Self {
server_fn_route,
..self
}
}
(@props_type) => { /// Set the incremental renderer config.
Default::default() #[cfg(feature = "ssr")]
}; pub fn incremental(self, cfg: IncrementalRendererConfig) -> Self {
Self {
server_cfg: self.server_cfg.incremental(cfg),
..self
}
}
(@props_type $props:expr) => { /// Set the server config.
$props #[cfg(feature = "ssr")]
}; pub fn server_cfg(self, server_cfg: ServeConfigBuilder<Props>) -> Self {
Self { server_cfg, ..self }
}
(@ $address:expr, $comp:path, $(( $props:expr ),)? {$($rule:ident $(: $cfg:expr)?,)*}) => { /// Set the web config.
#[cfg(feature = "web")] #[cfg(feature = "web")]
{ pub fn web_cfg(self, web_cfg: dioxus_web::Config) -> Self {
#[allow(unused)] Self { web_cfg, ..self }
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),
);
} }
/// 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")] #[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() tokio::runtime::Runtime::new()
.unwrap() .unwrap()
.block_on(async move { .block_on(async move {
let addr = std::net::SocketAddr::from($address); self.launch_server().await;
dioxus_fullstack::launch::launch_server(addr, server_cfg.build()).await;
}); });
#[cfg(not(feature = "ssr"))]
{
#[cfg(feature = "web")]
self.launch_web();
#[cfg(feature = "desktop")]
self.launch_desktop();
} }
};
} }
/// Launch a server with the given configeration #[cfg(feature = "web")]
/// This will use the routing intigration of the currently enabled intigration feature 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")] #[cfg(feature = "ssr")]
pub async fn launch_server<P: Clone + serde::Serialize + Send + Sync + 'static>( async fn launch_server(self) {
addr: std::net::SocketAddr, let addr = self.addr;
cfg: crate::prelude::ServeConfig<P>, 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")))] #[cfg(all(feature = "axum", not(feature = "warp"), not(feature = "salvo")))]
{ {
use crate::adapters::axum_adapter::DioxusRouterExt; use crate::adapters::axum_adapter::{render_handler, DioxusRouterExt};
use axum::routing::get;
use tower::ServiceBuilder; use tower::ServiceBuilder;
axum::Server::bind(&addr) let ssr_state = SSRState::new(&cfg);
.serve( let router = axum::Router::new().register_server_fns(server_fn_route);
axum::Router::new() #[cfg(not(feature = "desktop"))]
.serve_dioxus_application("", cfg) 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( .layer(
ServiceBuilder::new() ServiceBuilder::new()
.layer(tower_http::compression::CompressionLayer::new().gzip(true)), .layer(tower_http::compression::CompressionLayer::new().gzip(true)),
) )
.into_make_service(), .into_make_service();
) axum::Server::bind(&addr).serve(router).await.unwrap();
.await
.unwrap();
} }
#[cfg(all(feature = "warp", not(feature = "axum"), not(feature = "salvo")))] #[cfg(all(feature = "warp", not(feature = "axum"), not(feature = "salvo")))]
{ {
use warp::Filter; use warp::Filter;
warp::serve( // First register the server functions
crate::prelude::serve_dioxus_application("", cfg) let router = register_server_fns(server_fn_route);
.with(warp::filters::compression::gzip()), #[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) .run(addr)
.await; .await;
} }
#[cfg(all(feature = "salvo", not(feature = "axum"), not(feature = "warp")))] #[cfg(all(feature = "salvo", not(feature = "axum"), not(feature = "warp")))]
{ {
use crate::adapters::salvo_adapter::DioxusRouterExt; use crate::adapters::salvo_adapter::{DioxusRouterExt, SSRHandler};
use salvo::conn::Listener; use salvo::conn::Listener;
let router = salvo::Router::new().serve_dioxus_application("", cfg).hoop( 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() salvo::compression::Compression::new()
.enable_gzip(salvo::prelude::CompressionLevel::Default), .enable_gzip(salvo::prelude::CompressionLevel::Default),
); );
@ -171,3 +201,18 @@ pub async fn launch_server<P: Clone + serde::Serialize + Send + Sync + 'static>(
.await; .await;
} }
} }
}
#[cfg(feature = "router")]
impl<R: Routable> LaunchBuilder<crate::router::FullstackRouterConfig<R>>
where
<R as std::str::FromStr>::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::<R>;
let props = crate::router::FullstackRouterConfig::default();
Self::new_with_props(component, props)
}
}

View file

@ -39,10 +39,13 @@ pub mod prelude {
use crate::hooks; use crate::hooks;
#[cfg(not(feature = "ssr"))] #[cfg(not(feature = "ssr"))]
pub use crate::html_storage::deserialize::get_root_props_from_document; pub use crate::html_storage::deserialize::get_root_props_from_document;
pub use crate::launch::LaunchBuilder;
#[cfg(all(feature = "ssr", feature = "router"))] #[cfg(all(feature = "ssr", feature = "router"))]
pub use crate::render::pre_cache_static_routes_with_props; pub use crate::render::pre_cache_static_routes_with_props;
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
pub use crate::render::SSRState; pub use crate::render::SSRState;
#[cfg(feature = "router")]
pub use crate::router::FullstackRouterConfig;
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
pub use crate::serve_config::{ServeConfig, ServeConfigBuilder}; pub use crate::serve_config::{ServeConfig, ServeConfigBuilder};
#[cfg(all(feature = "ssr", feature = "axum"))] #[cfg(all(feature = "ssr", feature = "axum"))]
@ -54,7 +57,6 @@ pub mod prelude {
pub use crate::server_fn::DioxusServerFn; pub use crate::server_fn::DioxusServerFn;
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
pub use crate::server_fn::{ServerFnMiddleware, ServerFnTraitObj, ServerFunction}; pub use crate::server_fn::{ServerFnMiddleware, ServerFnTraitObj, ServerFunction};
pub use crate::{launch, launch_router};
pub use dioxus_server_macro::*; pub use dioxus_server_macro::*;
#[cfg(feature = "ssr")] #[cfg(feature = "ssr")]
pub use dioxus_ssr::incremental::IncrementalRendererConfig; pub use dioxus_ssr::incremental::IncrementalRendererConfig;