use super::build::build_proj;
use crate::internal_prelude::*;
use crate::{
compile::{self},
config::Project,
service,
signal::{Interrupt, Outcome, Product, ProductSet, ReloadSignal, ServerRestart},
};
use leptos_hot_reload::ViewMacros;
use std::sync::Arc;
use tokio::sync::broadcast::error::RecvError;
use tokio::try_join;
pub async fn watch(proj: &Arc<Project>) -> Result<()> {
build_proj(proj).await?;
if Interrupt::is_shutdown_requested().await {
return Ok(());
}
if proj.hot_reload && proj.release {
log::warn!("warning: Hot reloading does not currently work in --release mode.");
}
let view_macros = if proj.hot_reload {
let view_macros = ViewMacros::new();
view_macros
.update_from_paths(&proj.lib.src_paths)
.wrap_anyhow_err("Couldn't update view-macro watch")?;
Some(view_macros)
} else {
None
};
service::notify::spawn(proj, view_macros).await?;
service::serve::spawn(proj).await;
service::reload::spawn(proj).await;
let res = run_loop(proj).await;
if res.is_err() {
Interrupt::request_shutdown().await;
}
res
}
pub async fn run_loop(proj: &Arc<Project>) -> Result<()> {
let mut int = Interrupt::subscribe_any();
loop {
debug!("Watch waiting for changes");
let int = int.recv().await;
if matches!(int, Err(RecvError::Closed)) {
return Err(RecvError::Closed).dot();
}
if Interrupt::is_shutdown_requested().await {
debug!("Shutting down");
return Ok(());
}
runner(proj).await?;
}
}
pub async fn runner(proj: &Arc<Project>) -> Result<()> {
let changes = Interrupt::get_source_changes().await;
let server_hdl = compile::server(proj, &changes).await;
let front_hdl = compile::front(proj, &changes).await;
let assets_hdl = compile::assets(proj, &changes).await;
let style_hdl = compile::style(proj, &changes).await;
let (server, front, assets, style) = try_join!(server_hdl, front_hdl, assets_hdl, style_hdl)?;
let outcomes = vec![server?, front?, assets?, style?];
let interrupted = outcomes.iter().any(|outcome| *outcome == Outcome::Stopped);
if interrupted {
info!("Build interrupted. Restarting.");
return Ok(());
}
let failed = outcomes.iter().any(|outcome| *outcome == Outcome::Failed);
if failed {
warn!("Build failed");
Interrupt::clear_source_changes().await;
return Ok(());
}
let set = ProductSet::from(outcomes);
if set.is_empty() {
trace!("Build step done with no changes");
} else {
trace!("Build step done with changes: {set}");
}
if set.contains(&Product::Server) {
ServerRestart::send();
info!("Watch updated {set}. Server restarting")
} else if set.only_style() {
ReloadSignal::send_style();
info!("Watch updated style")
} else if set.contains_any(&[Product::Front, Product::Assets]) {
ReloadSignal::send_full();
info!("Watch updated {set}")
}
Interrupt::clear_source_changes().await;
Ok(())
}