diff --git a/packages/cli/src/cli/build.rs b/packages/cli/src/cli/build.rs index 3053a6e5a..5d111b797 100644 --- a/packages/cli/src/cli/build.rs +++ b/packages/cli/src/cli/build.rs @@ -47,6 +47,32 @@ impl Build { Platform::Desktop => { crate::builder::build_desktop(&crate_config, false)?; } + Platform::Fullstack => { + { + let mut web_config = crate_config.clone(); + let web_feature = self.build.client_feature; + let features = &mut web_config.features; + match features { + Some(features) => { + features.push(web_feature); + } + None => web_config.features = Some(vec![web_feature]), + }; + crate::builder::build(&crate_config, false)?; + } + { + let mut desktop_config = crate_config.clone(); + let desktop_feature = self.build.server_feature; + let features = &mut desktop_config.features; + match features { + Some(features) => { + features.push(desktop_feature); + } + None => desktop_config.features = Some(vec![desktop_feature]), + }; + crate::builder::build_desktop(&desktop_config, false)?; + } + } } let temp = gen_page(&crate_config.dioxus_config, false); diff --git a/packages/cli/src/cli/cfg.rs b/packages/cli/src/cli/cfg.rs index 2a441a1c8..bbe3119d3 100644 --- a/packages/cli/src/cli/cfg.rs +++ b/packages/cli/src/cli/cfg.rs @@ -35,6 +35,30 @@ pub struct ConfigOptsBuild { /// Space separated list of features to activate #[clap(long)] pub features: Option>, + + /// 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, + + /// The feature to use for the server in a fullstack app [default: "ssr"] + #[clap(long, default_value_t = { "ssr".to_string() })] + pub server_feature: String, +} + +impl From for ConfigOptsBuild { + fn from(serve: ConfigOptsServe) -> Self { + Self { + target: serve.target, + release: serve.release, + verbose: serve.verbose, + example: serve.example, + profile: serve.profile, + platform: serve.platform, + features: serve.features, + client_feature: serve.client_feature, + server_feature: serve.server_feature, + } + } } #[derive(Clone, Debug, Default, Deserialize, Parser)] @@ -89,6 +113,14 @@ pub struct ConfigOptsServe { /// Space separated list of features to activate #[clap(long)] pub features: Option>, + + /// 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, + + /// The feature to use for the server in a fullstack app [default: "ssr"] + #[clap(long, default_value_t = { "ssr".to_string() })] + pub server_feature: String, } #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Serialize, Deserialize, Debug)] @@ -99,6 +131,9 @@ pub enum Platform { #[clap(name = "desktop")] #[serde(rename = "desktop")] Desktop, + #[clap(name = "fullstack")] + #[serde(rename = "fullstack")] + Fullstack, } /// Config options for the bundling system. diff --git a/packages/cli/src/cli/serve.rs b/packages/cli/src/cli/serve.rs index 433d611b7..74cfcbe6e 100644 --- a/packages/cli/src/cli/serve.rs +++ b/packages/cli/src/cli/serve.rs @@ -12,6 +12,7 @@ pub struct Serve { impl Serve { pub async fn serve(self, bin: Option) -> Result<()> { let mut crate_config = crate::CrateConfig::new(bin)?; + let serve_cfg = self.serve.clone(); // change the relase state. crate_config.with_hot_reload(self.serve.hot_reload); @@ -49,7 +50,10 @@ impl Serve { .await?; } cfg::Platform::Desktop => { - server::desktop::startup(crate_config.clone()).await?; + server::desktop::startup(crate_config.clone(), &serve_cfg).await?; + } + cfg::Platform::Fullstack => { + server::fullstack::startup(crate_config.clone(), &serve_cfg).await?; } } Ok(()) diff --git a/packages/cli/src/server/desktop/mod.rs b/packages/cli/src/server/desktop/mod.rs index 9b79bdea9..3662ac986 100644 --- a/packages/cli/src/server/desktop/mod.rs +++ b/packages/cli/src/server/desktop/mod.rs @@ -1,4 +1,5 @@ use crate::{ + cfg::ConfigOptsServe, server::{ output::{print_console_info, PrettierOptions}, setup_file_watcher, setup_file_watcher_hot_reload, @@ -19,7 +20,16 @@ use tokio::sync::broadcast::{self}; #[cfg(feature = "plugin")] use plugin::PluginManager; -pub async fn startup(config: CrateConfig) -> Result<()> { +use super::Platform; + +pub async fn startup(config: CrateConfig, serve: &ConfigOptsServe) -> Result<()> { + startup_with_platform::(config, serve).await +} + +pub(crate) async fn startup_with_platform( + config: CrateConfig, + serve: &ConfigOptsServe, +) -> Result<()> { // ctrl-c shutdown checker let _crate_config = config.clone(); let _ = ctrlc::set_handler(move || { @@ -29,17 +39,19 @@ pub async fn startup(config: CrateConfig) -> Result<()> { }); match config.hot_reload { - true => serve_hot_reload(config).await?, - false => serve_default(config).await?, + true => serve_hot_reload::

(config, serve).await?, + false => serve_default::

(config, serve).await?, } Ok(()) } /// Start the server without hot reload -pub async fn serve_default(config: CrateConfig) -> Result<()> { - let (child, first_build_result) = start_desktop(&config)?; - let currently_running_child: RwLock = RwLock::new(child); +async fn serve_default( + config: CrateConfig, + serve: &ConfigOptsServe, +) -> Result<()> { + let platform = RwLock::new(P::start(&config, serve)?); log::info!("🚀 Starting development server..."); @@ -49,49 +61,29 @@ pub async fn serve_default(config: CrateConfig) -> Result<()> { { let config = config.clone(); - move || { - let mut current_child = currently_running_child.write().unwrap(); - current_child.kill()?; - let (child, result) = start_desktop(&config)?; - *current_child = child; - Ok(result) - } + move || platform.write().unwrap().rebuild(&config) }, &config, None, ) .await?; - // Print serve info - print_console_info( - &config, - PrettierOptions { - changed: vec![], - warnings: first_build_result.warnings, - elapsed_time: first_build_result.elapsed_time, - }, - None, - ); - std::future::pending::<()>().await; Ok(()) } -/// Start the server without hot reload - /// Start dx serve with hot reload -pub async fn serve_hot_reload(config: CrateConfig) -> Result<()> { - let (_, first_build_result) = start_desktop(&config)?; - - println!("🚀 Starting development server..."); +async fn serve_hot_reload( + config: CrateConfig, + serve: &ConfigOptsServe, +) -> Result<()> { + let platform = RwLock::new(P::start(&config, serve)?); // Setup hot reload let FileMapBuildResult { map, errors } = FileMap::::create(config.crate_dir.clone()).unwrap(); - println!("🚀 Starting development server..."); - for err in errors { log::error!("{}", err); } @@ -119,24 +111,13 @@ pub async fn serve_hot_reload(config: CrateConfig) -> Result<()> { for channel in &mut *channels.lock().unwrap() { send_msg(HotReloadMsg::Shutdown, channel); } - Ok(start_desktop(&config)?.1) + Ok(platform.write().unwrap().rebuild(&config)?) } }, None, ) .await?; - // Print serve info - print_console_info( - &config, - PrettierOptions { - changed: vec![], - warnings: first_build_result.warnings, - elapsed_time: first_build_result.elapsed_time, - }, - None, - ); - clear_paths(); match LocalSocketListener::bind("@dioxusin") { @@ -228,7 +209,7 @@ fn send_msg(msg: HotReloadMsg, channel: &mut impl std::io::Write) -> bool { } } -pub fn start_desktop(config: &CrateConfig) -> Result<(Child, BuildResult)> { +fn start_desktop(config: &CrateConfig) -> Result<(Child, BuildResult)> { // Run the desktop application let result = crate::builder::build_desktop(config, true)?; @@ -246,3 +227,37 @@ pub fn start_desktop(config: &CrateConfig) -> Result<(Child, BuildResult)> { } } } + +pub(crate) struct DesktopPlatform { + currently_running_child: Child, +} + +impl Platform for DesktopPlatform { + fn start(config: &CrateConfig, _serve: &ConfigOptsServe) -> Result { + let (child, first_build_result) = start_desktop(&config)?; + + log::info!("🚀 Starting development server..."); + + // Print serve info + print_console_info( + &config, + PrettierOptions { + changed: vec![], + warnings: first_build_result.warnings, + elapsed_time: first_build_result.elapsed_time, + }, + None, + ); + + Ok(Self { + currently_running_child: child, + }) + } + + fn rebuild(&mut self, config: &CrateConfig) -> Result { + self.currently_running_child.kill()?; + let (child, result) = start_desktop(&config)?; + self.currently_running_child = child; + Ok(result) + } +} diff --git a/packages/cli/src/server/fullstack/mod.rs b/packages/cli/src/server/fullstack/mod.rs new file mode 100644 index 000000000..16ab31c44 --- /dev/null +++ b/packages/cli/src/server/fullstack/mod.rs @@ -0,0 +1,72 @@ +use crate::{ + cfg::{ConfigOptsBuild, ConfigOptsServe}, + CrateConfig, Result, +}; + +use super::{desktop, Platform}; + +pub async fn startup(config: CrateConfig, serve: &ConfigOptsServe) -> Result<()> { + desktop::startup_with_platform::(config, serve).await +} + +struct FullstackPlatform { + serve: ConfigOptsServe, + desktop: desktop::DesktopPlatform, +} + +impl Platform for FullstackPlatform { + fn start(config: &CrateConfig, serve: &ConfigOptsServe) -> Result + where + Self: Sized, + { + { + build_web(serve.clone())?; + } + + let mut desktop_config = config.clone(); + let desktop_feature = serve.server_feature.clone(); + let features = &mut desktop_config.features; + match features { + Some(features) => { + features.push(desktop_feature); + } + None => desktop_config.features = Some(vec![desktop_feature]), + }; + let desktop = desktop::DesktopPlatform::start(&desktop_config, serve)?; + + Ok(Self { + desktop, + serve: serve.clone(), + }) + } + + fn rebuild(&mut self, crate_config: &CrateConfig) -> Result { + build_web(self.serve.clone())?; + { + let mut desktop_config = crate_config.clone(); + let desktop_feature = self.serve.server_feature.clone(); + let features = &mut desktop_config.features; + match features { + Some(features) => { + features.push(desktop_feature); + } + None => desktop_config.features = Some(vec![desktop_feature]), + }; + self.desktop.rebuild(&desktop_config) + } + } +} + +fn build_web(serve: ConfigOptsServe) -> Result<()> { + let mut web_config: ConfigOptsBuild = serve.into(); + let web_feature = web_config.client_feature.clone(); + let features = &mut web_config.features; + match features { + Some(features) => { + features.push(web_feature); + } + None => web_config.features = Some(vec![web_feature]), + }; + web_config.platform = Some(crate::cfg::Platform::Web); + crate::cli::build::Build { build: web_config }.build(None) +} diff --git a/packages/cli/src/server/mod.rs b/packages/cli/src/server/mod.rs index 19dc44b75..1571a198e 100644 --- a/packages/cli/src/server/mod.rs +++ b/packages/cli/src/server/mod.rs @@ -1,4 +1,4 @@ -use crate::{BuildResult, CrateConfig, Result}; +use crate::{cfg::ConfigOptsServe, BuildResult, CrateConfig, Result}; use cargo_metadata::diagnostic::Diagnostic; use dioxus_core::Template; @@ -14,6 +14,7 @@ use tokio::sync::broadcast::Sender; mod output; use output::*; pub mod desktop; +pub mod fullstack; pub mod web; /// Sets up a file watcher @@ -180,3 +181,10 @@ async fn setup_file_watcher_hot_reload Result + Send + ' Ok(watcher) } + +pub(crate) trait Platform { + fn start(config: &CrateConfig, serve: &ConfigOptsServe) -> Result + where + Self: Sized; + fn rebuild(&mut self, config: &CrateConfig) -> Result; +} diff --git a/packages/cli/src/server/web/mod.rs b/packages/cli/src/server/web/mod.rs index 78eddb574..7f09dca6c 100644 --- a/packages/cli/src/server/web/mod.rs +++ b/packages/cli/src/server/web/mod.rs @@ -67,7 +67,7 @@ pub async fn startup(port: u16, config: CrateConfig, start_browser: bool) -> Res } /// Start the server without hot reload -pub async fn serve_default( +async fn serve_default( ip: String, port: u16, config: CrateConfig, @@ -128,7 +128,7 @@ pub async fn serve_default( } /// Start dx serve with hot reload -pub async fn serve_hot_reload( +async fn serve_hot_reload( ip: String, port: u16, config: CrateConfig,