fix(serve): fixed long rebuilds with dx serve

This commit is contained in:
Andrew Voynov committed 2024-02-06 00:27:45 +03:00
1 parent 417cea7569
commit ea9622a75c
9 files changed
+213 -125

No files matched your search

+16
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 {