//! Dioxus utilities for the [Axum](https://docs.rs/axum/latest/axum/index.html) server framework. //! //! # Example //! ```rust, no_run //! #![allow(non_snake_case)] //! use dioxus::prelude::*; //! //! fn main() { //! #[cfg(feature = "web")] //! // Hydrate the application on the client //! dioxus::launch(app); //! #[cfg(feature = "server")] //! { //! tokio::runtime::Runtime::new() //! .unwrap() //! .block_on(async move { //! // Get the address the server should run on. If the CLI is running, the CLI proxies fullstack into the main address //! // and we use the generated address the CLI gives us //! let address = dioxus::cli_config::fullstack_address_or_localhost(); //! let listener = tokio::net::TcpListener::bind(address) //! .await //! .unwrap(); //! axum::serve( //! listener, //! axum::Router::new() //! // Server side render the application, serve static assets, and register server functions //! .serve_dioxus_application(ServeConfigBuilder::default(), app) //! .into_make_service(), //! ) //! .await //! .unwrap(); //! }); //! } //! } //! //! fn app() -> Element { //! let mut text = use_signal(|| "...".to_string()); //! //! rsx! { //! button { //! onclick: move |_| async move { //! if let Ok(data) = get_server_data().await { //! text.set(data); //! } //! }, //! "Run a server function" //! } //! "Server said: {text}" //! } //! } //! //! #[server(GetServerData)] //! async fn get_server_data() -> Result { //! Ok("Hello from the server!".to_string()) //! } //! ``` pub mod launch; use axum::routing::*; use dioxus_lib::prelude::Element; use crate::prelude::*; /// A extension trait with utilities for integrating Dioxus with your Axum router. pub trait DioxusRouterExt: DioxusRouterFnExt { /// Serves the static WASM for your Dioxus application (except the generated index.html). /// /// # Example /// ```rust, no_run /// # #![allow(non_snake_case)] /// # use dioxus_lib::prelude::*; /// # use dioxus_fullstack::prelude::*; /// #[tokio::main] /// async fn main() { /// let addr = dioxus::cli_config::fullstack_address_or_localhost(); /// let router = axum::Router::new() /// // Server side render the application, serve static assets, and register server functions /// .serve_static_assets() /// // Server render the application /// // ... /// .into_make_service(); /// let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); /// axum::serve(listener, router).await.unwrap(); /// } /// ``` fn serve_static_assets(self) -> Self where Self: Sized; /// Serves the Dioxus application. This will serve a complete server side rendered application. /// This will serve static assets, server render the application, register server functions, and integrate with hot reloading. /// /// # Example /// ```rust, no_run /// # #![allow(non_snake_case)] /// # use dioxus_lib::prelude::*; /// # use dioxus_fullstack::prelude::*; /// #[tokio::main] /// async fn main() { /// let addr = dioxus::cli_config::fullstack_address_or_localhost(); /// let router = axum::Router::new() /// // Server side render the application, serve static assets, and register server functions /// .serve_dioxus_application(ServeConfig::new().unwrap(), app) /// .into_make_service(); /// let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); /// axum::serve(listener, router).await.unwrap(); /// } /// /// fn app() -> Element { /// rsx! { "Hello World" } /// } /// ``` fn serve_dioxus_application(self, cfg: Cfg, app: fn() -> Element) -> Self where Cfg: TryInto, Error: std::error::Error, Self: Sized; } impl DioxusRouterExt for Router where S: Send + Sync + Clone + 'static, { fn serve_static_assets(mut self) -> Self { use tower_http::services::{ServeDir, ServeFile}; let public_path = crate::public_path(); if !public_path.exists() { return self; } // Serve all files in public folder except index.html let dir = std::fs::read_dir(&public_path).unwrap_or_else(|e| { panic!( "Couldn't read public directory at {:?}: {}", &public_path, e ) }); for entry in dir.flatten() { let path = entry.path(); if path.ends_with("index.html") { continue; } let route = path .strip_prefix(&public_path) .unwrap() .iter() .map(|segment| { segment.to_str().unwrap_or_else(|| { panic!("Failed to convert path segment {:?} to string", segment) }) }) .collect::>() .join("/"); let route = format!("/{}", route); if path.is_dir() { self = self.nest_service(&route, ServeDir::new(path).precompressed_br()); } else { self = self.nest_service(&route, ServeFile::new(path).precompressed_br()); } } self } fn serve_dioxus_application(self, cfg: Cfg, app: fn() -> Element) -> Self where Cfg: TryInto, Error: std::error::Error, { let cfg = cfg.try_into(); let context_providers = cfg .as_ref() .map(|cfg| cfg.context_providers.clone()) .unwrap_or_default(); // Add server functions and render index.html let server = self .serve_static_assets() .register_server_functions_with_context(context_providers); match cfg { Ok(cfg) => { let ssr_state = SSRState::new(&cfg); server.fallback( get(render_handler) .with_state(RenderHandleState::new(cfg, app).with_ssr_state(ssr_state)), ) } Err(err) => { tracing::trace!("Failed to create render handler. This is expected if you are only using fullstack for desktop/mobile server functions: {}", err); server } } } }