diff --git a/packages/cli-config/src/config.rs b/packages/cli-config/src/config.rs index 3b6024b16..7ef92dcf0 100644 --- a/packages/cli-config/src/config.rs +++ b/packages/cli-config/src/config.rs @@ -453,6 +453,22 @@ impl CrateConfig { self.crate_dir.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.crate_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") + } + pub fn as_example(&mut self, example_name: String) -> &mut Self { self.executable = ExecutableType::Example(example_name); self diff --git a/packages/cli/src/builder.rs b/packages/cli/src/builder.rs index 40563fccd..17333ad17 100644 --- a/packages/cli/src/builder.rs +++ b/packages/cli/src/builder.rs @@ -11,6 +11,7 @@ use indicatif::{ProgressBar, ProgressStyle}; use lazy_static::lazy_static; use manganis_cli_support::{AssetManifest, ManganisSupportGuard}; use std::{ + env, fs::{copy, create_dir_all, File}, io::Read, panic, @@ -30,7 +31,45 @@ pub struct BuildResult { pub assets: Option, } -pub fn build(config: &CrateConfig, _: bool, skip_assets: bool) -> Result { +/// This trait is only created for the convenient and concise way to set +/// `RUSTFLAGS` environment variable for the `subprocess::Exec`. +pub trait ExecWithRustFlagsSetter { + fn set_rust_flags(self, rust_flags: Option) -> Self; +} + +impl ExecWithRustFlagsSetter for subprocess::Exec { + /// Sets (appends to, if already set) `RUSTFLAGS` environment variable if + /// `rust_flags` is not `None`. + fn set_rust_flags(self, rust_flags: Option) -> Self { + if let Some(rust_flags) = rust_flags { + // Some `RUSTFLAGS` might be already set in the environment or provided + // by the user. They should take higher priority than the default flags. + // If no default flags are provided, then there is no point in + // redefining the environment variable with the same value, if it is + // even set. If no custom flags are set, then there is no point in + // adding the unnecessary whitespace to the command. + self.env( + "RUSTFLAGS", + if let Ok(custom_rust_flags) = env::var("RUSTFLAGS") { + rust_flags + " " + custom_rust_flags.as_str() + } else { + rust_flags + }, + ) + } else { + self + } + } +} + +/// Build client (WASM). +/// Note: `rust_flags` argument is only used for the fullstack platform. +pub fn build( + config: &CrateConfig, + _: bool, + skip_assets: bool, + rust_flags: Option, +) -> Result { // [1] Build the project with cargo, generating a wasm32-unknown-unknown target (is there a more specific, better target to leverage?) // [2] Generate the appropriate build folders // [3] Wasm-bindgen the .wasm file, and move it into the {builddir}/modules/xxxx/xxxx_bg.wasm @@ -72,6 +111,7 @@ pub fn build(config: &CrateConfig, _: bool, skip_assets: bool) -> Result Result Result, ) -> Result { log::info!("🚅 Running build [Desktop] command..."); @@ -296,6 +341,7 @@ pub fn build_desktop( let _manganis_support = ManganisSupportGuard::default(); let mut cmd = subprocess::Exec::cmd("cargo") + .set_rust_flags(rust_flags) .env("CARGO_TARGET_DIR", &config.target_dir) .cwd(&config.crate_dir) .arg("build") diff --git a/packages/cli/src/cli/build.rs b/packages/cli/src/cli/build.rs index c4e56307d..c30cb29e8 100644 --- a/packages/cli/src/cli/build.rs +++ b/packages/cli/src/cli/build.rs @@ -1,8 +1,6 @@ -use crate::assets::WebAssetConfigDropGuard; #[cfg(feature = "plugin")] use crate::plugin::PluginManager; -use crate::server::fullstack::FullstackServerEnvGuard; -use crate::server::fullstack::FullstackWebEnvGuard; +use crate::{assets::WebAssetConfigDropGuard, server::fullstack}; use dioxus_cli_config::Platform; use super::*; @@ -16,7 +14,13 @@ pub struct Build { } impl Build { - pub fn build(self, bin: Option, target_dir: Option<&std::path::Path>) -> Result<()> { + /// Note: `rust_flags` argument is only used for the fullstack platform. + pub fn build( + self, + bin: Option, + target_dir: Option<&std::path::Path>, + rust_flags: Option, + ) -> Result<()> { let mut crate_config = dioxus_cli_config::CrateConfig::new(bin)?; if let Some(target_dir) = target_dir { crate_config.target_dir = target_dir.to_path_buf(); @@ -53,16 +57,23 @@ impl Build { // let _ = PluginManager::on_build_start(&crate_config, &platform); let build_result = match platform { - Platform::Web => crate::builder::build(&crate_config, false, self.build.skip_assets)?, + Platform::Web => { + // `rust_flags` are used by fullstack's client build. + crate::builder::build(&crate_config, false, self.build.skip_assets, rust_flags)? + } Platform::Desktop => { - crate::builder::build_desktop(&crate_config, false, self.build.skip_assets)? + // Since desktop platform doesn't use `rust_flags`, this + // argument is explicitly set to `None`. + crate::builder::build_desktop(&crate_config, false, self.build.skip_assets, None)? } Platform::Fullstack => { - // Fullstack mode must be built with web configs on the desktop (server) binary as well as the web binary + // Fullstack mode must be built with web configs on the desktop + // (server) binary as well as the web binary let _config = WebAssetConfigDropGuard::new(); + let client_rust_flags = fullstack::client_rust_flags(&self.build); + let server_rust_flags = fullstack::server_rust_flags(&self.build); { let mut web_config = crate_config.clone(); - let _gaurd = FullstackWebEnvGuard::new(&self.build); let web_feature = self.build.client_feature; let features = &mut web_config.features; match features { @@ -71,7 +82,12 @@ impl Build { } None => web_config.features = Some(vec![web_feature]), }; - crate::builder::build(&web_config, false, self.build.skip_assets)?; + crate::builder::build( + &web_config, + false, + self.build.skip_assets, + Some(client_rust_flags), + )?; } { let mut desktop_config = crate_config.clone(); @@ -83,21 +99,19 @@ impl Build { } None => desktop_config.features = Some(vec![desktop_feature]), }; - let _gaurd = - FullstackServerEnvGuard::new(self.build.force_debug, self.build.release); - crate::builder::build_desktop(&desktop_config, false, self.build.skip_assets)? + crate::builder::build_desktop( + &desktop_config, + false, + self.build.skip_assets, + Some(server_rust_flags), + )? } } }; let temp = gen_page(&crate_config, build_result.assets.as_ref(), false); - let mut file = std::fs::File::create( - crate_config - .crate_dir - .join(crate_config.dioxus_config.application.out_dir.clone()) - .join("index.html"), - )?; + let mut file = std::fs::File::create(crate_config.out_dir().join("index.html"))?; file.write_all(temp.as_bytes())?; // #[cfg(feature = "plugin")] diff --git a/packages/cli/src/cli/bundle.rs b/packages/cli/src/cli/bundle.rs index 1bc3a4ff6..44c8548b3 100644 --- a/packages/cli/src/cli/bundle.rs +++ b/packages/cli/src/cli/bundle.rs @@ -84,7 +84,9 @@ impl Bundle { crate_config.set_cargo_args(self.build.cargo_args); // build the desktop app - build_desktop(&crate_config, false, false)?; + // Since the `bundle()` function is only run for the desktop platform, + // the `rust_flags` argument is set to `None`. + build_desktop(&crate_config, false, false, None)?; // copy the binary to the out dir let package = crate_config.manifest.package.as_ref().unwrap(); diff --git a/packages/cli/src/cli/clean.rs b/packages/cli/src/cli/clean.rs index 04e0cb45b..a66bac3ac 100644 --- a/packages/cli/src/cli/clean.rs +++ b/packages/cli/src/cli/clean.rs @@ -19,12 +19,12 @@ impl Clean { return custom_error!("Cargo clean failed."); } - let out_dir = crate_config.dioxus_config.application.out_dir; - if crate_config.crate_dir.join(&out_dir).is_dir() { - remove_dir_all(crate_config.crate_dir.join(&out_dir))?; + let out_dir = &crate_config.out_dir(); + if out_dir.is_dir() { + remove_dir_all(out_dir)?; } - let fullstack_out_dir = crate_config.crate_dir.join(".dioxus"); + let fullstack_out_dir = crate_config.fullstack_out_dir(); if fullstack_out_dir.is_dir() { remove_dir_all(fullstack_out_dir)?; diff --git a/packages/cli/src/cli/serve.rs b/packages/cli/src/cli/serve.rs index 5a483ec54..3ace461ef 100644 --- a/packages/cli/src/cli/serve.rs +++ b/packages/cli/src/cli/serve.rs @@ -73,9 +73,7 @@ impl Serve { ) -> anyhow::Result<()> { let serve_html = gen_page(crate_config, manifest, true); - let dist_path = crate_config - .crate_dir - .join(crate_config.dioxus_config.application.out_dir.clone()); + let dist_path = crate_config.out_dir(); if !dist_path.is_dir() { create_dir_all(&dist_path)?; } diff --git a/packages/cli/src/main.rs b/packages/cli/src/main.rs index d09dea9e4..0a57735db 100644 --- a/packages/cli/src/main.rs +++ b/packages/cli/src/main.rs @@ -99,7 +99,7 @@ async fn main() -> anyhow::Result<()> { match action { Build(opts) => opts - .build(Some(bin.clone()), None) + .build(Some(bin.clone()), None, None) .context(error_wrapper("Building project failed")), Clean(opts) => opts diff --git a/packages/cli/src/server/desktop/mod.rs b/packages/cli/src/server/desktop/mod.rs index db8f03e1e..22aeb407d 100644 --- a/packages/cli/src/server/desktop/mod.rs +++ b/packages/cli/src/server/desktop/mod.rs @@ -14,6 +14,7 @@ use dioxus_hot_reload::HotReloadMsg; use dioxus_html::HtmlCtx; use dioxus_rsx::hot_reload::*; use interprocess_docfix::local_socket::LocalSocketListener; +use std::fs::create_dir_all; use std::{ process::{Child, Command}, sync::{Arc, Mutex, RwLock}, @@ -109,6 +110,7 @@ async fn start_desktop_hot_reload(hot_reload_state: HotReloadState) -> Result<() .exec() .unwrap(); let target_dir = metadata.target_directory.as_std_path(); + let _ = create_dir_all(target_dir); // `_all` is for good measure and future-proofness. let path = target_dir.join("dioxusin"); clear_paths(&path); match LocalSocketListener::bind(path) { @@ -213,9 +215,14 @@ fn send_msg(msg: HotReloadMsg, channel: &mut impl std::io::Write) -> bool { } } -fn start_desktop(config: &CrateConfig, skip_assets: bool) -> Result<(RAIIChild, BuildResult)> { +fn start_desktop( + config: &CrateConfig, + skip_assets: bool, + rust_flags: Option, +) -> Result<(RAIIChild, BuildResult)> { // Run the desktop application - let result = crate::builder::build_desktop(config, true, skip_assets)?; + // Only used for the fullstack platform, + let result = crate::builder::build_desktop(config, true, skip_assets, rust_flags)?; match &config.executable { ExecutableType::Binary(name) @@ -242,9 +249,15 @@ pub(crate) struct DesktopPlatform { skip_assets: bool, } -impl Platform for DesktopPlatform { - fn start(config: &CrateConfig, serve: &ConfigOptsServe) -> Result { - let (child, first_build_result) = start_desktop(config, serve.skip_assets)?; +impl DesktopPlatform { + /// `rust_flags` argument is added because it is used by the + /// `DesktopPlatform`'s implementation of the `Platform::start()`. + pub fn start_with_options( + config: &CrateConfig, + serve: &ConfigOptsServe, + rust_flags: Option, + ) -> Result { + let (child, first_build_result) = start_desktop(config, serve.skip_assets, rust_flags)?; log::info!("🚀 Starting development server..."); @@ -265,14 +278,38 @@ impl Platform for DesktopPlatform { }) } - fn rebuild(&mut self, config: &CrateConfig) -> Result { + /// `rust_flags` argument is added because it is used by the + /// `DesktopPlatform`'s implementation of the `Platform::rebuild()`. + pub fn rebuild_with_options( + &mut self, + config: &CrateConfig, + rust_flags: Option, + ) -> Result { self.currently_running_child.0.kill()?; - let (child, result) = start_desktop(config, self.skip_assets)?; + let (child, result) = start_desktop(config, self.skip_assets, rust_flags)?; self.currently_running_child = child; Ok(result) } } +impl Platform for DesktopPlatform { + fn start(config: &CrateConfig, serve: &ConfigOptsServe) -> Result { + // See `start_with_options()`'s docs for the explanation why the code + // was moved there. + // Since desktop platform doesn't use `rust_flags`, this argument is + // explicitly set to `None`. + DesktopPlatform::start_with_options(config, serve, None) + } + + fn rebuild(&mut self, config: &CrateConfig) -> Result { + // See `rebuild_with_options()`'s docs for the explanation why the code + // was moved there. + // Since desktop platform doesn't use `rust_flags`, this argument is + // explicitly set to `None`. + DesktopPlatform::rebuild_with_options(self, config, None) + } +} + struct RAIIChild(Child); impl Drop for RAIIChild { diff --git a/packages/cli/src/server/fullstack/mod.rs b/packages/cli/src/server/fullstack/mod.rs index 60d685ced..1f9a4446c 100644 --- a/packages/cli/src/server/fullstack/mod.rs +++ b/packages/cli/src/server/fullstack/mod.rs @@ -1,13 +1,38 @@ use dioxus_cli_config::CrateConfig; use crate::{ - assets::WebAssetConfigDropGuard, cfg::{ConfigOptsBuild, ConfigOptsServe}, Result, }; use super::{desktop, Platform}; +static CLIENT_RUST_FLAGS: &str = "-C debuginfo=none -C strip=debuginfo"; +// 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 = "-C opt-level=2"; +static DEBUG_RUST_FLAG: &str = "-C debug-assertions"; + +fn rust_flags(build: &ConfigOptsBuild, base_flags: &str) -> String { + let mut rust_flags = base_flags.to_string(); + if !build.release { + rust_flags += " "; + rust_flags += DEBUG_RUST_FLAG; + }; + rust_flags +} + +pub fn client_rust_flags(build: &ConfigOptsBuild) -> String { + rust_flags(build, CLIENT_RUST_FLAGS) +} + +pub fn server_rust_flags(build: &ConfigOptsBuild) -> String { + rust_flags(build, SERVER_RUST_FLAGS) +} + pub async fn startup(config: CrateConfig, serve: &ConfigOptsServe) -> Result<()> { desktop::startup_with_platform::(config, serve).await } @@ -17,15 +42,29 @@ fn start_web_build_thread( serve: &ConfigOptsServe, ) -> std::thread::JoinHandle> { let serve = serve.clone(); - let target_directory = config.crate_dir.join(".dioxus").join("web"); + let target_directory = config.client_target_dir(); std::fs::create_dir_all(&target_directory).unwrap(); std::thread::spawn(move || build_web(serve, &target_directory)) } +fn make_desktop_config(config: &CrateConfig, serve: &ConfigOptsServe) -> CrateConfig { + let mut desktop_config = config.clone(); + desktop_config.target_dir = config.server_target_dir(); + 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]), + }; + desktop_config +} + struct FullstackPlatform { serve: ConfigOptsServe, desktop: desktop::DesktopPlatform, - _config: WebAssetConfigDropGuard, + server_rust_flags: String, } impl Platform for FullstackPlatform { @@ -35,17 +74,13 @@ impl Platform for FullstackPlatform { { let thread_handle = start_web_build_thread(config, serve); - 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 config = WebAssetConfigDropGuard::new(); - let desktop = desktop::DesktopPlatform::start(&desktop_config, serve)?; + let desktop_config = make_desktop_config(config, serve); + let server_rust_flags = server_rust_flags(&serve.clone().into()); + let desktop = desktop::DesktopPlatform::start_with_options( + &desktop_config, + serve, + Some(server_rust_flags.clone()), + )?; thread_handle .join() .map_err(|_| anyhow::anyhow!("Failed to join thread"))??; @@ -53,25 +88,16 @@ impl Platform for FullstackPlatform { Ok(Self { desktop, serve: serve.clone(), - _config: config, + server_rust_flags, }) } fn rebuild(&mut self, crate_config: &CrateConfig) -> Result { let thread_handle = start_web_build_thread(crate_config, &self.serve); - let result = { - 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]), - }; - let _gaurd = FullstackServerEnvGuard::new(self.serve.force_debug, self.serve.release); - self.desktop.rebuild(&desktop_config) - }; + let desktop_config = make_desktop_config(crate_config, &self.serve); + let result = self + .desktop + .rebuild_with_options(&desktop_config, Some(self.server_rust_flags.clone())); thread_handle .join() .map_err(|_| anyhow::anyhow!("Failed to join thread"))??; @@ -91,74 +117,12 @@ fn build_web(serve: ConfigOptsServe, target_directory: &std::path::Path) -> Resu }; web_config.platform = Some(dioxus_cli_config::Platform::Web); - let _gaurd = FullstackWebEnvGuard::new(&web_config); - crate::cli::build::Build { build: web_config }.build(None, Some(target_directory)) -} - -// Debug mode web builds have a very large size by default. If debug mode is not enabled, we strip some of the debug info by default -// This reduces a hello world from ~40MB to ~2MB -pub(crate) struct FullstackWebEnvGuard { - old_rustflags: Option, -} - -impl FullstackWebEnvGuard { - pub fn new(serve: &ConfigOptsBuild) -> Self { - Self { - old_rustflags: (!serve.force_debug).then(|| { - let old_rustflags = std::env::var("RUSTFLAGS").unwrap_or_default(); - let debug_assertions = if serve.release { - "" - } else { - " -C debug-assertions" - }; - - std::env::set_var( - "RUSTFLAGS", - format!( - "{old_rustflags} -C debuginfo=none -C strip=debuginfo{debug_assertions}" - ), - ); - old_rustflags - }), - } - } -} - -impl Drop for FullstackWebEnvGuard { - fn drop(&mut self) { - if let Some(old_rustflags) = self.old_rustflags.take() { - std::env::set_var("RUSTFLAGS", old_rustflags); - } - } -} - -// Debug mode web builds have a very large size by default. If debug mode is not enabled, we strip some of the debug info by default -// This reduces a hello world from ~40MB to ~2MB -pub(crate) struct FullstackServerEnvGuard { - old_rustflags: Option, -} - -impl FullstackServerEnvGuard { - pub fn new(debug: bool, release: bool) -> Self { - Self { - old_rustflags: (!debug).then(|| { - let old_rustflags = std::env::var("RUSTFLAGS").unwrap_or_default(); - let debug_assertions = if release { "" } else { " -C debug-assertions" }; - - std::env::set_var( - "RUSTFLAGS", - format!("{old_rustflags} -C opt-level=2 {debug_assertions}"), - ); - old_rustflags - }), - } - } -} - -impl Drop for FullstackServerEnvGuard { - fn drop(&mut self) { - if let Some(old_rustflags) = self.old_rustflags.take() { - std::env::set_var("RUSTFLAGS", old_rustflags); - } + crate::cli::build::Build { + build: web_config.clone(), } + .build( + None, + Some(target_directory), + Some(client_rust_flags(&web_config)), + ) } diff --git a/packages/cli/src/server/web/mod.rs b/packages/cli/src/server/web/mod.rs index 4f0daeeb1..582b81129 100644 --- a/packages/cli/src/server/web/mod.rs +++ b/packages/cli/src/server/web/mod.rs @@ -109,7 +109,9 @@ pub async fn serve( skip_assets: bool, hot_reload_state: Option, ) -> Result<()> { - let first_build_result = crate::builder::build(&config, false, skip_assets)?; + // Since web platform doesn't use `rust_flags`, this argument is explicitly + // set to `None`. + let first_build_result = crate::builder::build(&config, false, skip_assets, None)?; // generate dev-index page Serve::regen_dev_page(&config, first_build_result.assets.as_ref())?; @@ -287,14 +289,9 @@ async fn setup_router( { let body = Full::from( // TODO: Cache/memoize this. - std::fs::read_to_string( - file_service_config - .crate_dir - .join(file_service_config.out_dir()) - .join("index.html"), - ) - .ok() - .unwrap(), + std::fs::read_to_string(file_service_config.out_dir().join("index.html")) + .ok() + .unwrap(), ) .map_err(|err| match err {}) .boxed(); @@ -315,7 +312,7 @@ async fn setup_router( Ok(response) }, ) - .service(ServeDir::new(config.crate_dir.join(config.out_dir()))); + .service(ServeDir::new(config.out_dir())); // Setup websocket let mut router = Router::new().route("/_dioxus/ws", get(ws_handler)); @@ -448,7 +445,9 @@ async fn ws_handler( } fn build(config: &CrateConfig, reload_tx: &Sender<()>, skip_assets: bool) -> Result { - let result = builder::build(config, true, skip_assets)?; + // Since web platform doesn't use `rust_flags`, this argument is explicitly + // set to `None`. + let result = builder::build(config, true, skip_assets, None)?; // change the websocket reload state to true; // the page will auto-reload. if config.dioxus_config.web.watcher.reload_html {