The current `axum` feature and `server` feature are incompatible with wasm32-unknown-unknown target due to: 1. depends on axum features not compatible with wasm 2. SSR requires reading the filesystem for index.html With this patch I add the `server_fn_axum` feature which disables SSR and enables targeting wasm32-unknown-unknown with server functions enabled. Note that for this to work, `server_fn` depends on `inventory` and as stated in this docs depending on the context you might need to expose and call [`__wasm_call_ctors`](https://docs.rs/inventory/latest/inventory/#webassembly-and-constructors) (I needed that specifically for cloudflare workers) change axum_server_fn feature for axum_wasm and include SSR tools there This change additionaly make axum_server_fn feature additive by exposing a different `DioxusRouterWasmExt` with only the wasm-compatible parts instead of exposing the `DioxusRouter` trait under the same name. Docs are updated to reflect this change. rename axum_wasm to axum_core and update docs fix typo re-enable default-features for axum in dioxus-cli re-enable axum default-features for liveview re-enable default-features for axum for playwright-tests
200 lines
6.8 KiB
Rust
200 lines
6.8 KiB
Rust
//! 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<String, ServerFnError> {
|
|
//! 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<S>: DioxusRouterFnExt<S> {
|
|
/// 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<Cfg, Error>(self, cfg: Cfg, app: fn() -> Element) -> Self
|
|
where
|
|
Cfg: TryInto<ServeConfig, Error = Error>,
|
|
Error: std::error::Error,
|
|
Self: Sized;
|
|
}
|
|
|
|
impl<S> DioxusRouterExt<S> for Router<S>
|
|
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::<Vec<_>>()
|
|
.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<Cfg, Error>(self, cfg: Cfg, app: fn() -> Element) -> Self
|
|
where
|
|
Cfg: TryInto<ServeConfig, Error = Error>,
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|