Kaynağa Gözat

fix(serve): fixed long rebuilds with `dx serve`

Andrew Voynov 1 yıl önce
ebeveyn
işleme
ea9622a75c

+ 16 - 0
packages/cli-config/src/config.rs

@@ -448,6 +448,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

+ 47 - 1
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<AssetManifest>,
 }
 
-pub fn build(config: &CrateConfig, _: bool, skip_assets: bool) -> Result<BuildResult> {
+/// 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<String>) -> 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<String>) -> 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<String>,
+) -> Result<BuildResult> {
     // [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<BuildRe
     }
 
     let cmd = subprocess::Exec::cmd("cargo")
+        .set_rust_flags(rust_flags)
         .env("CARGO_TARGET_DIR", target_dir)
         .cwd(crate_dir)
         .arg("build")
@@ -79,6 +119,8 @@ pub fn build(config: &CrateConfig, _: bool, skip_assets: bool) -> Result<BuildRe
         .arg("wasm32-unknown-unknown")
         .arg("--message-format=json");
 
+    // TODO: make the initial variable mutable to simplify all the expressions
+    // below. Look inside the `build_desktop()` as an example.
     let cmd = if config.release {
         cmd.arg("--release")
     } else {
@@ -283,10 +325,13 @@ pub fn build(config: &CrateConfig, _: bool, skip_assets: bool) -> Result<BuildRe
     })
 }
 
+/// Note: `rust_flags` argument is only used for the fullstack platform
+/// (server).
 pub fn build_desktop(
     config: &CrateConfig,
     _is_serve: bool,
     skip_assets: bool,
+    rust_flags: Option<String>,
 ) -> Result<BuildResult> {
     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")

+ 31 - 12
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<PathBuf>, target_dir: Option<&std::path::Path>) -> Result<()> {
+    /// Note: `rust_flags` argument is only used for the fullstack platform.
+    pub fn build(
+        self,
+        bin: Option<PathBuf>,
+        target_dir: Option<&std::path::Path>,
+        rust_flags: Option<String>,
+    ) -> 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,9 +99,12 @@ 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),
+                    )?
                 }
             }
         };

+ 3 - 1
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();

+ 4 - 4
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.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 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)?;

+ 1 - 1
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

+ 44 - 7
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<String>,
+) -> 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<Self> {
-        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<String>,
+    ) -> Result<Self> {
+        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<BuildResult> {
+    /// `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<String>,
+    ) -> Result<BuildResult> {
         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<Self> {
+        // 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<BuildResult> {
+        // 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 {

+ 61 - 97
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::<FullstackPlatform>(config, serve).await
 }
@@ -17,15 +42,29 @@ fn start_web_build_thread(
     serve: &ConfigOptsServe,
 ) -> std::thread::JoinHandle<Result<()>> {
     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<crate::BuildResult> {
         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<String>,
-}
-
-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<String>,
-}
-
-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)),
+    )
 }

+ 6 - 2
packages/cli/src/server/web/mod.rs

@@ -109,7 +109,9 @@ pub async fn serve(
     skip_assets: bool,
     hot_reload_state: Option<HotReloadState>,
 ) -> 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())?;
@@ -447,7 +449,9 @@ async fn ws_handler(
 }
 
 fn build(config: &CrateConfig, reload_tx: &Sender<()>, skip_assets: bool) -> Result<BuildResult> {
-    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 {