diff --git a/packages/cli/src/serve/mod.rs b/packages/cli/src/serve/mod.rs index 176e192cdd..36008540d9 100644 --- a/packages/cli/src/serve/mod.rs +++ b/packages/cli/src/serve/mod.rs @@ -173,30 +173,44 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &TraceController) -> Resu return Err(err); } } - BuilderUpdate::BuildReady { bundle } => match bundle.mode { - BuildMode::Thin { ref cache, .. } => { - if let Err(err) = - builder.hotpatch(&bundle, id, cache, &mut devserver).await - { - tracing::error!("Failed to hot-patch app: {err}"); - - if let Some(_patching) = - err.downcast_ref::() + BuilderUpdate::BuildReady { bundle } => { + match bundle.mode { + BuildMode::Thin { ref cache, .. } => { + if let Err(err) = + builder.hotpatch(&bundle, id, cache, &mut devserver).await { - tracing::info!("Starting full rebuild: {err}"); - builder.full_rebuild().await; - devserver.send_reload_start().await; - devserver.start_build().await; + tracing::error!("Failed to hot-patch app: {err}"); + + if let Some(_patching) = + err.downcast_ref::() + { + tracing::info!("Starting full rebuild: {err}"); + builder.full_rebuild().await; + devserver.send_reload_start().await; + devserver.start_build().await; + } } } + BuildMode::Base { .. } | BuildMode::Fat => { + _ = builder + .open(&bundle, &mut devserver) + .await + .inspect_err(|e| tracing::error!("Failed to open app: {}", e)); + } } - BuildMode::Base { .. } | BuildMode::Fat => { - _ = builder - .open(&bundle, &mut devserver) - .await - .inspect_err(|e| tracing::error!("Failed to open app: {}", e)); + + // Process any file changes that were queued while the build was in progress. + // This handles tools like stylance, tailwind, or sass that generate files + // in response to source changes - those changes would otherwise be lost. + let pending = builder.take_pending_file_changes(); + if !pending.is_empty() { + tracing::debug!( + "Processing {} pending file changes after build", + pending.len() + ); + builder.handle_file_change(&pending, &mut devserver).await; } - }, + } BuilderUpdate::StdoutReceived { msg } => { screen.push_stdio(bundle_format, msg, tracing::Level::INFO); } diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index 4d06ae7131..cc08901c78 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -81,6 +81,9 @@ pub(crate) struct AppServer { // Additional plugin-type tools pub(crate) tw_watcher: tokio::task::JoinHandle>, + + // File changes that arrived while a build was in progress, to be processed after build completes + pub(crate) pending_file_changes: Vec, } pub(crate) struct CachedFile { @@ -209,6 +212,7 @@ impl AppServer { tw_watcher, server_args, client_args, + pending_file_changes: Vec::new(), }; // Only register the hot-reload stuff if we're watching the filesystem @@ -240,6 +244,12 @@ impl AppServer { } } + /// Take any pending file changes that were queued while a build was in progress. + /// Returns the files and clears the pending list. + pub(crate) fn take_pending_file_changes(&mut self) -> Vec { + std::mem::take(&mut self.pending_file_changes) + } + pub(crate) async fn rebuild_ssg(&mut self, devserver: &WebServer) { if self.client.stage != BuildStage::Success { return; @@ -348,10 +358,14 @@ impl AppServer { self.client.stage, BuildStage::Failed | BuildStage::Aborted | BuildStage::Success ) { + // Queue file changes that arrive during a build, so we can process them after the build completes. + // This prevents losing changes from tools like stylance, tailwind, or sass that generate files + // in response to source changes. tracing::debug!( - "Ignoring file change: client is not ready to receive hotreloads. Files: {:#?}", + "Queueing file change: client is not ready to receive hotreloads. Files: {:#?}", files ); + self.pending_file_changes.extend(files.iter().cloned()); return; }