From 5a7a91323a15deb6067cd808955851acec19b859 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 20 Aug 2024 23:57:51 +0200 Subject: [PATCH] Make desktop fullstack work with the CLI (#2862) * Make desktop fullstack work with the CLI * Simplify desktop fullstack example * move the profiles to the workspace --- .cargo/config.toml | 10 ++ Cargo.lock | 2 - packages/cli/Cargo.toml | 2 +- packages/cli/src/builder/cargo.rs | 3 +- packages/cli/src/builder/fullstack.rs | 109 +++++++++++------- packages/cli/src/builder/mod.rs | 25 +++- packages/cli/src/cli/build.rs | 84 ++++++++++++-- packages/cli/src/cli/clean.rs | 6 - packages/cli/src/dioxus_crate.rs | 16 --- packages/cli/src/serve/builder.rs | 6 +- packages/cli/src/serve/mod.rs | 6 +- packages/cli/src/serve/watcher.rs | 1 - packages/fullstack/examples/auth/src/main.rs | 2 +- .../fullstack/examples/desktop/Cargo.toml | 16 +-- .../fullstack/examples/desktop/src/client.rs | 15 --- .../fullstack/examples/desktop/src/lib.rs | 36 ------ .../fullstack/examples/desktop/src/main.rs | 33 +----- .../fullstack/examples/desktop/src/server.rs | 27 ----- packages/fullstack/src/axum_adapter.rs | 18 ++- packages/fullstack/src/launch.rs | 30 +++-- packages/fullstack/src/render.rs | 1 - packages/fullstack/src/serve_config.rs | 48 ++++---- packages/static-generation/src/config.rs | 2 +- 23 files changed, 239 insertions(+), 259 deletions(-) delete mode 100644 packages/fullstack/examples/desktop/src/client.rs delete mode 100644 packages/fullstack/examples/desktop/src/lib.rs delete mode 100644 packages/fullstack/examples/desktop/src/server.rs diff --git a/.cargo/config.toml b/.cargo/config.toml index 464189e01..81a637782 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -3,3 +3,13 @@ DIOXUS_FRONT_ISSUER_URL = "" DIOXUS_FRONT_CLIENT_ID = "" DIOXUS_FRONT_URL = "" + +[profile] + +[profile.dioxus-client] +inherits = "dev" +opt-level = 2 + +[profile.dioxus-server] +inherits = "dev" +opt-level = 2 diff --git a/Cargo.lock b/Cargo.lock index 3a8f13112..e93189ab8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3638,10 +3638,8 @@ dependencies = [ name = "fullstack-desktop-example" version = "0.1.0" dependencies = [ - "axum 0.7.5", "dioxus", "serde", - "tokio", ] [[package]] diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index 8e55a0874..37dceed9e 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -73,7 +73,7 @@ once_cell = "1.19.0" # plugin packages open = "5.0.1" cargo-generate = "=0.21.1" -toml_edit = "0.22.15" +toml_edit = "0.22.20" # bundling tauri-bundler = { workspace = true } diff --git a/packages/cli/src/builder/cargo.rs b/packages/cli/src/builder/cargo.rs index 0ca178626..696a48418 100644 --- a/packages/cli/src/builder/cargo.rs +++ b/packages/cli/src/builder/cargo.rs @@ -231,7 +231,7 @@ impl BuildRequest { Ok(Some(assets)) }) .await - .unwrap() + .map_err(|e| anyhow::anyhow!(e))? } pub fn copy_assets_dir(&self) -> anyhow::Result<()> { @@ -257,6 +257,7 @@ impl BuildRequest { match self.build_arguments.platform { Some(Platform::Fullstack | Platform::StaticGeneration) => match self.target_platform { TargetPlatform::Web => out_dir.join("public"), + TargetPlatform::Desktop => out_dir.join("desktop"), _ => out_dir, }, _ => out_dir, diff --git a/packages/cli/src/builder/fullstack.rs b/packages/cli/src/builder/fullstack.rs index cc56dc80f..4cfdea9a4 100644 --- a/packages/cli/src/builder/fullstack.rs +++ b/packages/cli/src/builder/fullstack.rs @@ -1,41 +1,60 @@ +use toml_edit::Item; + use crate::builder::Build; use crate::dioxus_crate::DioxusCrate; use crate::builder::BuildRequest; -use std::path::PathBuf; +use std::io::Write; use super::TargetPlatform; -static CLIENT_RUST_FLAGS: &[&str] = &["-Cdebuginfo=none", "-Cstrip=debuginfo"]; +static CLIENT_PROFILE: &str = "dioxus-client"; +static SERVER_PROFILE: &str = "dioxus-server"; + // The `opt-level=2` increases build times, but can noticeably decrease time // between saving changes and being able to interact with an app. The "overall" // time difference (between having and not having the optimization) can be // almost imperceptible (~1 s) but also can be very noticeable (~6 s) — depends // on setup (hardware, OS, browser, idle load). -static SERVER_RUST_FLAGS: &[&str] = &["-O"]; -static DEBUG_RUST_FLAG: &str = "-Cdebug-assertions"; +// Find or create the client and server profiles in the .cargo/config.toml file +fn initialize_profiles(config: &DioxusCrate) -> crate::Result<()> { + let config_path = config.workspace_dir().join(".cargo/config.toml"); + let mut config = match std::fs::read_to_string(&config_path) { + Ok(config) => config.parse::().map_err(|e| { + crate::Error::Other(anyhow::anyhow!("Failed to parse .cargo/config.toml: {}", e)) + })?, + Err(_) => Default::default(), + }; -fn add_debug_rust_flags(build: &Build, flags: &mut Vec) { - if !build.release { - flags.push(DEBUG_RUST_FLAG.to_string()); - } -} + if let Item::Table(table) = config + .as_table_mut() + .entry("profile") + .or_insert(Item::Table(Default::default())) + { + if let toml_edit::Entry::Vacant(entry) = table.entry(CLIENT_PROFILE) { + let mut client = toml_edit::Table::new(); + client.insert("inherits", Item::Value("dev".into())); + client.insert("opt-level", Item::Value(2.into())); + entry.insert(Item::Table(client)); + } -fn fullstack_rust_flags(build: &Build, base_flags: &[&str]) -> Vec { - // If we are forcing debug mode, don't add any debug flags - if build.force_debug { - return Default::default(); + if let toml_edit::Entry::Vacant(entry) = table.entry(SERVER_PROFILE) { + let mut server = toml_edit::Table::new(); + server.insert("inherits", Item::Value("dev".into())); + server.insert("opt-level", Item::Value(2.into())); + entry.insert(Item::Table(server)); + } } - let mut rust_flags = base_flags.iter().map(ToString::to_string).collect(); - add_debug_rust_flags(build, &mut rust_flags); - rust_flags -} + // Write the config back to the file + if let Some(parent) = config_path.parent() { + std::fs::create_dir_all(parent)?; + } + let file = std::fs::File::create(config_path)?; + let mut buf_writer = std::io::BufWriter::new(file); + write!(buf_writer, "{}", config)?; -// Fullstack builds run the server and client builds parallel by default -// To make them run in parallel, we need to set up different target directories for the server and client within /.dioxus -fn get_target_directory(build: &Build, target: PathBuf) -> Option { - (!build.force_sequential).then_some(target) + Ok(()) } impl BuildRequest { @@ -43,63 +62,67 @@ impl BuildRequest { config: DioxusCrate, build_arguments: Build, serve: bool, - ) -> Vec { - vec![ + ) -> Result, crate::Error> { + initialize_profiles(&config)?; + + Ok(vec![ Self::new_client(serve, &config, &build_arguments), Self::new_server(serve, &config, &build_arguments), - ] + ]) } fn new_with_target_directory_rust_flags_and_features( serve: bool, config: &DioxusCrate, build: &Build, - target_directory: PathBuf, - rust_flags: &[&str], - feature: String, + feature: Option, target_platform: TargetPlatform, ) -> Self { let config = config.clone(); let mut build = build.clone(); - // Set the target directory we are building the server in - let target_dir = get_target_directory(&build, target_directory); // Add the server feature to the features we pass to the build - build.target_args.features.push(feature); + if let Some(feature) = feature { + build.target_args.features.push(feature); + } // Add the server flags to the build arguments - let rust_flags = fullstack_rust_flags(&build, rust_flags); - Self { serve, build_arguments: build.clone(), dioxus_crate: config, - rust_flags, - target_dir, + rust_flags: Default::default(), + target_dir: None, target_platform, } } fn new_server(serve: bool, config: &DioxusCrate, build: &Build) -> Self { + let mut build = build.clone(); + if build.profile.is_none() { + build.profile = Some(CLIENT_PROFILE.to_string()); + } + let client_feature = build.auto_detect_server_feature(config); Self::new_with_target_directory_rust_flags_and_features( serve, config, - build, - config.server_target_dir(), - SERVER_RUST_FLAGS, - build.target_args.server_feature.clone(), + &build, + build.target_args.server_feature.clone().or(client_feature), TargetPlatform::Server, ) } fn new_client(serve: bool, config: &DioxusCrate, build: &Build) -> Self { + let mut build = build.clone(); + if build.profile.is_none() { + build.profile = Some(SERVER_PROFILE.to_string()); + } + let (client_feature, client_platform) = build.auto_detect_client_platform(config); Self::new_with_target_directory_rust_flags_and_features( serve, config, - build, - config.client_target_dir(), - CLIENT_RUST_FLAGS, - build.target_args.client_feature.clone(), - TargetPlatform::Web, + &build, + build.target_args.client_feature.clone().or(client_feature), + client_platform, ) } } diff --git a/packages/cli/src/builder/mod.rs b/packages/cli/src/builder/mod.rs index c6c84f67b..8900adba2 100644 --- a/packages/cli/src/builder/mod.rs +++ b/packages/cli/src/builder/mod.rs @@ -6,6 +6,7 @@ use dioxus_cli_config::{Platform, RuntimeCLIArguments}; use futures_util::stream::select_all; use futures_util::StreamExt; use std::net::SocketAddr; +use std::str::FromStr; use std::{path::PathBuf, process::Stdio}; use tokio::process::{Child, Command}; @@ -29,6 +30,20 @@ pub enum TargetPlatform { Liveview, } +impl FromStr for TargetPlatform { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "web" => Ok(Self::Web), + "desktop" => Ok(Self::Desktop), + "axum" | "server" => Ok(Self::Server), + "liveview" => Ok(Self::Liveview), + _ => Err(()), + } + } +} + impl std::fmt::Display for TargetPlatform { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { @@ -62,7 +77,7 @@ impl BuildRequest { serve: bool, dioxus_crate: &DioxusCrate, build_arguments: impl Into, - ) -> Vec { + ) -> crate::Result> { let build_arguments = build_arguments.into(); let platform = build_arguments.platform(); let single_platform = |platform| { @@ -76,15 +91,15 @@ impl BuildRequest { target_dir: Default::default(), }] }; - match platform { - Platform::Web => single_platform(TargetPlatform::Web), + Ok(match platform { Platform::Liveview => single_platform(TargetPlatform::Liveview), + Platform::Web => single_platform(TargetPlatform::Web), Platform::Desktop => single_platform(TargetPlatform::Desktop), Platform::StaticGeneration | Platform::Fullstack => { - Self::new_fullstack(dioxus_crate.clone(), build_arguments, serve) + Self::new_fullstack(dioxus_crate.clone(), build_arguments, serve)? } _ => unimplemented!("Unknown platform: {platform:?}"), - } + }) } pub(crate) async fn build_all_parallel( diff --git a/packages/cli/src/cli/build.rs b/packages/cli/src/cli/build.rs index e7c653faa..f8e96dc71 100644 --- a/packages/cli/src/cli/build.rs +++ b/packages/cli/src/cli/build.rs @@ -1,7 +1,12 @@ +use std::str::FromStr; + use anyhow::Context; use dioxus_cli_config::Platform; -use crate::{builder::BuildRequest, dioxus_crate::DioxusCrate}; +use crate::{ + builder::{BuildRequest, TargetPlatform}, + dioxus_crate::DioxusCrate, +}; use super::*; @@ -29,12 +34,12 @@ pub struct TargetArgs { pub features: Vec, /// The feature to use for the client in a fullstack app [default: "web"] - #[clap(long, default_value_t = { "web".to_string() })] - pub client_feature: String, + #[clap(long)] + pub client_feature: Option, /// The feature to use for the server in a fullstack app [default: "server"] - #[clap(long, default_value_t = { "server".to_string() })] - pub server_feature: String, + #[clap(long)] + pub server_feature: Option, /// Rustc platform triple #[clap(long)] @@ -109,7 +114,7 @@ impl Build { pub async fn build(&mut self, dioxus_crate: &mut DioxusCrate) -> Result<()> { self.resolve(dioxus_crate)?; - let build_requests = BuildRequest::create(false, dioxus_crate, self.clone()); + let build_requests = BuildRequest::create(false, dioxus_crate, self.clone())?; BuildRequest::build_all_parallel(build_requests).await?; Ok(()) } @@ -121,7 +126,47 @@ impl Build { Ok(()) } + pub(crate) fn auto_detect_client_platform( + &self, + resolved: &DioxusCrate, + ) -> (Option, TargetPlatform) { + self.find_dioxus_feature(resolved, |platform| { + matches!(platform, TargetPlatform::Web | TargetPlatform::Desktop) + }) + .unwrap_or_else(|| (Some("web".to_string()), TargetPlatform::Web)) + } + + pub(crate) fn auto_detect_server_feature(&self, resolved: &DioxusCrate) -> Option { + self.find_dioxus_feature(resolved, |platform| { + matches!(platform, TargetPlatform::Server) + }) + .map(|(feature, _)| feature) + .unwrap_or_else(|| Some("server".to_string())) + } + fn auto_detect_platform(&self, resolved: &DioxusCrate) -> Platform { + self.auto_detect_platform_with_filter(resolved, |_| true).1 + } + + fn auto_detect_platform_with_filter( + &self, + resolved: &DioxusCrate, + filter_platform: fn(&Platform) -> bool, + ) -> (Option, Platform) { + self.find_dioxus_feature(resolved, filter_platform) + .unwrap_or_else(|| { + let default_platform = resolved.dioxus_config.application.default_platform; + + (Some(default_platform.to_string()), default_platform) + }) + } + + fn find_dioxus_feature( + &self, + resolved: &DioxusCrate, + filter_platform: fn(&P) -> bool, + ) -> Option<(Option, P)> { + // First check the enabled features for any renderer enabled for dioxus in resolved.krates.krates_by_name("dioxus") { let Some(features) = resolved.krates.get_enabled_features(dioxus.kid) else { continue; @@ -129,13 +174,34 @@ impl Build { if let Some(platform) = features .iter() - .find_map(|platform| platform.parse::().ok()) + .find_map(|platform| platform.parse::

().ok()) + .filter(filter_platform) { - return platform; + return Some((None, platform)); } } - resolved.dioxus_config.application.default_platform + // Then check the features that might get enabled + if let Some(platform) = resolved + .package() + .features + .iter() + .find_map(|(feature, enables)| { + enables + .iter() + .find_map(|f| { + f.strip_prefix("dioxus/") + .or_else(|| feature.strip_prefix("dep:dioxus/")) + .and_then(|f| f.parse::

().ok()) + .filter(filter_platform) + }) + .map(|platform| (Some(feature.clone()), platform)) + }) + { + return Some(platform); + } + + None } /// Get the platform from the build arguments diff --git a/packages/cli/src/cli/clean.rs b/packages/cli/src/cli/clean.rs index cf8f9c652..2ea627b6c 100644 --- a/packages/cli/src/cli/clean.rs +++ b/packages/cli/src/cli/clean.rs @@ -29,12 +29,6 @@ impl Clean { remove_dir_all(out_dir)?; } - let fullstack_out_dir = dioxus_crate.fullstack_out_dir(); - - if fullstack_out_dir.is_dir() { - remove_dir_all(fullstack_out_dir)?; - } - Ok(()) } } diff --git a/packages/cli/src/dioxus_crate.rs b/packages/cli/src/dioxus_crate.rs index 8e63efe6f..3ecadd224 100644 --- a/packages/cli/src/dioxus_crate.rs +++ b/packages/cli/src/dioxus_crate.rs @@ -192,22 +192,6 @@ impl DioxusCrate { .join(&self.dioxus_config.application.out_dir) } - /// Compose an out directory for the fullstack platform. See `out_dir()` - /// method. - pub fn fullstack_out_dir(&self) -> PathBuf { - self.workspace_dir().join(".dioxus") - } - - /// Compose a target directory for the server (fullstack-only?). - pub fn server_target_dir(&self) -> PathBuf { - self.fullstack_out_dir().join("ssr") - } - - /// Compose a target directory for the client (fullstack-only?). - pub fn client_target_dir(&self) -> PathBuf { - self.fullstack_out_dir().join("web") - } - /// Get the workspace directory for the crate pub fn workspace_dir(&self) -> PathBuf { self.krates.workspace_root().as_std_path().to_path_buf() diff --git a/packages/cli/src/serve/builder.rs b/packages/cli/src/serve/builder.rs index c89488094..79028b67d 100644 --- a/packages/cli/src/serve/builder.rs +++ b/packages/cli/src/serve/builder.rs @@ -49,10 +49,10 @@ impl Builder { } /// Start a new build - killing the current one if it exists - pub fn build(&mut self) { + pub fn build(&mut self) -> Result<()> { self.shutdown(); let build_requests = - BuildRequest::create(true, &self.config, self.serve.build_arguments.clone()); + BuildRequest::create(true, &self.config, self.serve.build_arguments.clone())?; let mut set = tokio::task::JoinSet::new(); @@ -85,6 +85,8 @@ impl Builder { } Ok(all_results) })); + + Ok(()) } /// Wait for any new updates to the builder - either it completed or gave us a message etc diff --git a/packages/cli/src/serve/mod.rs b/packages/cli/src/serve/mod.rs index 43c27bde7..b2e13e57d 100644 --- a/packages/cli/src/serve/mod.rs +++ b/packages/cli/src/serve/mod.rs @@ -57,7 +57,7 @@ pub async fn serve_all( let mut builder = Builder::new(&dioxus_crate, &serve); // Start the first build - builder.build(); + builder.build()?; let mut server = Server::start(&serve, &dioxus_crate); let mut watcher = Watcher::start(&serve, &dioxus_crate); @@ -94,7 +94,7 @@ pub async fn serve_all( } else { // If the change is not binary patchable, rebuild the project // We're going to kick off a new build, interrupting the current build if it's ongoing - builder.build(); + builder.build()?; // Clear the hot reload changes watcher.clear_hot_reload_changes(); @@ -196,7 +196,7 @@ pub async fn serve_all( Ok(false) => {} // Request a rebuild. Ok(true) => { - builder.build(); + builder.build()?; server.start_build().await }, // Shutdown the server. diff --git a/packages/cli/src/serve/watcher.rs b/packages/cli/src/serve/watcher.rs index 571ac9841..f6400f479 100644 --- a/packages/cli/src/serve/watcher.rs +++ b/packages/cli/src/serve/watcher.rs @@ -57,7 +57,6 @@ impl Watcher { "target", "node_modules", "dist", - ".dioxus", &out_dir_str, ]; for path in excluded_paths { diff --git a/packages/fullstack/examples/auth/src/main.rs b/packages/fullstack/examples/auth/src/main.rs index 21fb7d2fe..ec8097997 100644 --- a/packages/fullstack/examples/auth/src/main.rs +++ b/packages/fullstack/examples/auth/src/main.rs @@ -49,7 +49,7 @@ fn main() { // build our application with some routes let app = Router::new() // Server side render the application, serve static assets, and register server functions - .serve_dioxus_application(ServeConfig::default(), app) + .serve_dioxus_application(ServeConfig::new().unwrap(), app) .layer( axum_session_auth::AuthSessionLayer::< crate::auth::User, diff --git a/packages/fullstack/examples/desktop/Cargo.toml b/packages/fullstack/examples/desktop/Cargo.toml index 60fa58bdd..008a6a9f1 100644 --- a/packages/fullstack/examples/desktop/Cargo.toml +++ b/packages/fullstack/examples/desktop/Cargo.toml @@ -4,25 +4,11 @@ version = "0.1.0" edition = "2021" publish = false -[lib] - [dependencies] dioxus = { workspace = true, features = ["launch", "fullstack"] } -axum = { workspace = true, optional = true } -tokio = { workspace = true, features = ["full"], optional = true } serde = "1.0.159" [features] default = [] -server = ["axum", "tokio", "dioxus/axum"] +server = ["dioxus/server"] desktop = ["dioxus/desktop"] - -[[bin]] -name = "client" -path = "src/client.rs" -required-features = ["desktop"] - -[[bin]] -name = "server" -path = "src/server.rs" -required-features = ["server"] diff --git a/packages/fullstack/examples/desktop/src/client.rs b/packages/fullstack/examples/desktop/src/client.rs deleted file mode 100644 index 5c35f1691..000000000 --- a/packages/fullstack/examples/desktop/src/client.rs +++ /dev/null @@ -1,15 +0,0 @@ -// Run with: -// ```bash -// cargo run --bin client --features desktop -// ``` - -use fullstack_desktop_example::*; - -fn main() { - // Set the url of the server where server functions are hosted. - #[cfg(not(feature = "server"))] - dioxus::fullstack::prelude::server_fn::client::set_server_url("http://127.0.0.1:8080"); - - #[cfg(feature = "desktop")] - dioxus::prelude::launch_desktop(app) -} diff --git a/packages/fullstack/examples/desktop/src/lib.rs b/packages/fullstack/examples/desktop/src/lib.rs deleted file mode 100644 index 8ca97331e..000000000 --- a/packages/fullstack/examples/desktop/src/lib.rs +++ /dev/null @@ -1,36 +0,0 @@ -#![allow(non_snake_case)] -use dioxus::prelude::*; - -pub fn app() -> Element { - let mut count = use_signal(|| 0); - let mut text = use_signal(|| "...".to_string()); - - rsx! { - h1 { "High-Five counter: {count}" } - button { onclick: move |_| count += 1, "Up high!" } - button { onclick: move |_| count -= 1, "Down low!" } - button { - onclick: move |_| async move { - if let Ok(data) = get_server_data().await { - println!("Client received: {}", data); - text.set(data.clone()); - post_server_data(data).await.unwrap(); - } - }, - "Run a server function" - } - "Server said: {text}" - } -} - -#[server(PostServerData)] -async fn post_server_data(data: String) -> Result<(), ServerFnError> { - println!("Server received: {}", data); - - Ok(()) -} - -#[server(GetServerData)] -async fn get_server_data() -> Result { - Ok("Hello from the server!".to_string()) -} diff --git a/packages/fullstack/examples/desktop/src/main.rs b/packages/fullstack/examples/desktop/src/main.rs index 473bde60c..0e39cc69d 100644 --- a/packages/fullstack/examples/desktop/src/main.rs +++ b/packages/fullstack/examples/desktop/src/main.rs @@ -1,42 +1,13 @@ #![allow(non_snake_case)] use dioxus::prelude::*; -/// Run with `cargo run --features desktop` -#[cfg(feature = "desktop")] fn main() { // Set the url of the server where server functions are hosted. + #[cfg(not(feature = "server"))] dioxus::fullstack::prelude::server_fn::client::set_server_url("http://127.0.0.1:8080"); - - // And then launch the app - dioxus::prelude::launch_desktop(app); + launch(app); } -/// Run with `cargo run --features server` -#[cfg(all(feature = "server", not(feature = "desktop")))] -#[tokio::main] -async fn main() { - use server_fn::axum::register_explicit; - - let listener = tokio::net::TcpListener::bind("127.0.0.01:8080") - .await - .unwrap(); - - register_explicit::(); - register_explicit::(); - - axum::serve( - listener, - axum::Router::new() - .register_server_functions() - .into_make_service(), - ) - .await - .unwrap(); -} - -#[cfg(all(not(feature = "desktop"), not(feature = "server")))] -fn main() {} - pub fn app() -> Element { let mut count = use_signal(|| 0); let mut text = use_signal(|| "...".to_string()); diff --git a/packages/fullstack/examples/desktop/src/server.rs b/packages/fullstack/examples/desktop/src/server.rs deleted file mode 100644 index 35e7cae17..000000000 --- a/packages/fullstack/examples/desktop/src/server.rs +++ /dev/null @@ -1,27 +0,0 @@ -// Run with: -// ```bash -// cargo run --bin server --features server -// ``` - -use dioxus::prelude::*; -use fullstack_desktop_example::*; -use server_fn::axum::register_explicit; - -#[tokio::main] -async fn main() { - let listener = tokio::net::TcpListener::bind("127.0.0.01:8080") - .await - .unwrap(); - - register_explicit::(); - register_explicit::(); - - axum::serve( - listener, - axum::Router::new() - .register_server_functions() - .into_make_service(), - ) - .await - .unwrap(); -} diff --git a/packages/fullstack/src/axum_adapter.rs b/packages/fullstack/src/axum_adapter.rs index 38030b4b3..4d3a1c613 100644 --- a/packages/fullstack/src/axum_adapter.rs +++ b/packages/fullstack/src/axum_adapter.rs @@ -21,7 +21,7 @@ //! listener, //! axum::Router::new() //! // Server side render the application, serve static assets, and register server functions -//! .serve_dioxus_application(ServeConfig::default(), app) +//! .serve_dioxus_application(ServeConfig::new().unwrap(), app) //! .into_make_service(), //! ) //! .await @@ -150,7 +150,7 @@ pub trait DioxusRouterExt { /// let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080)); /// let router = axum::Router::new() /// // Server side render the application, serve static assets, and register server functions - /// .serve_dioxus_application(ServeConfig::default(), app) + /// .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(); @@ -252,11 +252,8 @@ where let server = self.serve_static_assets().register_server_functions(); server.fallback( - get(render_handler).with_state( - RenderHandleState::new(app) - .with_config(cfg) - .with_ssr_state(ssr_state), - ), + get(render_handler) + .with_state(RenderHandleState::new(cfg, app).with_ssr_state(ssr_state)), ) } } @@ -281,9 +278,9 @@ pub struct RenderHandleState { impl RenderHandleState { /// Create a new [`RenderHandleState`] - pub fn new(root: fn() -> Element) -> Self { + pub fn new(config: ServeConfig, root: fn() -> Element) -> Self { Self { - config: ServeConfig::default(), + config, build_virtual_dom: Arc::new(move || VirtualDom::new(root)), ssr_state: Default::default(), } @@ -291,10 +288,11 @@ impl RenderHandleState { /// Create a new [`RenderHandleState`] with a custom [`VirtualDom`] factory. This method can be used to pass context into the root component of your application. pub fn new_with_virtual_dom_factory( + config: ServeConfig, build_virtual_dom: impl Fn() -> VirtualDom + Send + Sync + 'static, ) -> Self { Self { - config: ServeConfig::default(), + config, build_virtual_dom: Arc::new(build_virtual_dom), ssr_state: Default::default(), } diff --git a/packages/fullstack/src/launch.rs b/packages/fullstack/src/launch.rs index cbf73dc54..6f2bbd668 100644 --- a/packages/fullstack/src/launch.rs +++ b/packages/fullstack/src/launch.rs @@ -146,24 +146,30 @@ async fn launch_server( { use crate::axum_adapter::DioxusRouterExt; - let router = axum::Router::new().register_server_functions_with_context(context_providers); + #[allow(unused_mut)] + let mut router = + axum::Router::new().register_server_functions_with_context(context_providers); #[cfg(not(any(feature = "desktop", feature = "mobile")))] - let router = { + { use crate::prelude::RenderHandleState; use crate::prelude::SSRState; - let cfg = platform_config.server_cfg.build(); + match platform_config.server_cfg.build() { + Ok(cfg) => { + router = router.serve_static_assets(); - let mut router = router.serve_static_assets(); - - router.fallback( - axum::routing::get(crate::axum_adapter::render_handler).with_state( - RenderHandleState::new_with_virtual_dom_factory(build_virtual_dom) - .with_config(cfg), - ), - ) - }; + router = router.fallback( + axum::routing::get(crate::axum_adapter::render_handler).with_state( + RenderHandleState::new_with_virtual_dom_factory(cfg, build_virtual_dom), + ), + ); + } + Err(err) => { + tracing::trace!("Failed to create render handler. This is expected if you are only using fullstack for desktop/mobile server functions: {}", err); + } + } + } let router = router.into_make_service(); let listener = tokio::net::TcpListener::bind(address).await.unwrap(); diff --git a/packages/fullstack/src/render.rs b/packages/fullstack/src/render.rs index ea06477ea..c0f5c010a 100644 --- a/packages/fullstack/src/render.rs +++ b/packages/fullstack/src/render.rs @@ -418,7 +418,6 @@ impl SSRState { } /// The template that wraps the body of the HTML for a fullstack page. This template contains the data needed to hydrate server functions that were run on the server. -#[derive(Default)] pub struct FullstackHTMLTemplate { cfg: ServeConfig, } diff --git a/packages/fullstack/src/serve_config.rs b/packages/fullstack/src/serve_config.rs index 25a4b70e8..0d30136f7 100644 --- a/packages/fullstack/src/serve_config.rs +++ b/packages/fullstack/src/serve_config.rs @@ -50,7 +50,7 @@ impl ServeConfigBuilder { } /// Build the ServeConfig - pub fn build(self) -> ServeConfig { + pub fn build(self) -> Result { // The CLI always bundles static assets into the exe/public directory let public_path = public_path(); @@ -61,16 +61,17 @@ impl ServeConfigBuilder { let root_id = self.root_id.unwrap_or("main"); - let index_html = self - .index_html - .unwrap_or_else(|| load_index_path(index_path)); + let index_html = match self.index_html { + Some(index) => index, + None => load_index_path(index_path)?, + }; let index = load_index_html(index_html, root_id); - ServeConfig { + Ok(ServeConfig { index, incremental: self.incremental, - } + }) } } @@ -84,13 +85,25 @@ pub(crate) fn public_path() -> PathBuf { .join("public") } -fn load_index_path(path: PathBuf) -> String { - let mut file = File::open(&path).unwrap_or_else(|_| panic!("Failed to find index.html. Make sure the index_path is set correctly and the WASM application has been built. Tried to open file at path: {path:?}")); +/// An error that can occur when loading the index.html file +#[derive(Debug)] +pub struct UnableToLoadIndex(PathBuf); + +impl std::fmt::Display for UnableToLoadIndex { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Failed to find index.html. Make sure the index_path is set correctly and the WASM application has been built. Tried to open file at path: {:?}", self.0) + } +} + +impl std::error::Error for UnableToLoadIndex {} + +fn load_index_path(path: PathBuf) -> Result { + let mut file = File::open(&path).map_err(|_| UnableToLoadIndex(path))?; let mut contents = String::new(); file.read_to_string(&mut contents) .expect("Failed to read index.html"); - contents + Ok(contents) } fn load_index_html(contents: String, root_id: &'static str) -> IndexHtml { @@ -156,21 +169,14 @@ pub struct ServeConfig { pub(crate) incremental: Option, } -impl Default for ServeConfig { - fn default() -> Self { - Self::builder().build() - } -} - impl ServeConfig { + /// Create a new ServeConfig + pub fn new() -> Result { + ServeConfigBuilder::new().build() + } + /// Create a new builder for a ServeConfig pub fn builder() -> ServeConfigBuilder { ServeConfigBuilder::new() } } - -impl From for ServeConfig { - fn from(builder: ServeConfigBuilder) -> Self { - builder.build() - } -} diff --git a/packages/static-generation/src/config.rs b/packages/static-generation/src/config.rs index 5321607ea..c96a28613 100644 --- a/packages/static-generation/src/config.rs +++ b/packages/static-generation/src/config.rs @@ -172,7 +172,7 @@ impl Config { } let cfg = cfg_builder.build(); - FullstackHTMLTemplate::new(&cfg) + FullstackHTMLTemplate::new(&cfg.unwrap()) } pub(crate) fn create_cache(&mut self) -> dioxus_ssr::incremental::IncrementalRenderer {