use crate::{
styles::{GLOW_STYLE, LINK_STYLE},
AppBuilder, BuildId, BuildMode, BuilderUpdate, BundleFormat, Result, ServeArgs,
TraceController,
};
mod ansi_buffer;
mod output;
mod proxy;
mod proxy_ws;
mod runner;
mod server;
mod update;
use anyhow::bail;
use dioxus_dx_wire_format::BuildStage;
pub(crate) use output::*;
pub(crate) use runner::*;
pub(crate) use server::*;
pub(crate) use update::*;
pub(crate) async fn serve_all(args: ServeArgs, tracer: &TraceController) -> Result<()> {
let exit_on_error = args.exit_on_error;
let mut builder = AppServer::new(args).await?;
let mut devserver = WebServer::start(&builder)?;
let mut screen = Output::start(builder.interactive).await?;
tracing::info!(
r#"-----------------------------------------------------------------
Serving your app: {binname}! 🚀
• Press {GLOW_STYLE}`ctrl+c`{GLOW_STYLE:#} to exit the server
• Press {GLOW_STYLE}`r`{GLOW_STYLE:#} to rebuild the app
• Press {GLOW_STYLE}`p`{GLOW_STYLE:#} to toggle automatic rebuilds
• Press {GLOW_STYLE}`v`{GLOW_STYLE:#} to toggle verbose logging
• Press {GLOW_STYLE}`/`{GLOW_STYLE:#} for more commands and shortcuts{extra}
----------------------------------------------------------------"#,
binname = builder.client.build.executable_name(),
extra = if builder.client.build.using_dioxus_explicitly {
format!(
"\n Learn more at {LINK_STYLE}https://dioxuslabs.com/learn/0.7/getting_started{LINK_STYLE:#}"
)
} else {
String::new()
}
);
builder.initialize();
loop {
screen.render(&builder, &devserver);
let msg = tokio::select! {
msg = builder.wait() => msg,
msg = devserver.wait() => msg,
msg = screen.wait() => msg,
msg = tracer.wait() => msg,
};
match msg {
ServeUpdate::FilesChanged { files } => {
if files.is_empty() || !builder.hot_reload {
continue;
}
builder.handle_file_change(&files, &mut devserver).await;
}
ServeUpdate::RequestRebuild => {
tracing::info!("Full rebuild: triggered manually");
builder.full_rebuild().await;
devserver.send_reload_start().await;
devserver.start_build().await;
}
ServeUpdate::NewConnection {
id,
aslr_reference,
pid,
} => {
devserver
.send_hotreload(builder.applied_hot_reload_changes(BuildId::PRIMARY))
.await;
if builder.server.is_some() {
devserver
.send_hotreload(builder.applied_hot_reload_changes(BuildId::SECONDARY))
.await;
}
builder.client_connected(id, aslr_reference, pid).await;
}
ServeUpdate::WsMessage { msg, bundle } => {
screen.push_ws_message(bundle, &msg);
}
ServeUpdate::BuilderUpdate { id, update } => {
let bundle_format = builder.get_build(id).unwrap().build.bundle;
screen.new_build_update(&update);
devserver.new_build_update(&update).await;
builder.new_build_update(&update, &devserver).await;
match update {
BuilderUpdate::Progress {
stage: BuildStage::Failed,
} => {
if exit_on_error {
bail!("Build failed for platform: {bundle_format}");
}
}
BuilderUpdate::Progress {
stage: BuildStage::Aborted,
} => {
if exit_on_error {
bail!("Build aborted for platform: {bundle_format}");
}
}
BuilderUpdate::Progress { .. } => {}
BuilderUpdate::ProfilePhase { .. } => {}
BuilderUpdate::CompilerMessage { message } => {
screen.push_cargo_log(message);
}
BuilderUpdate::BuildFailed { err } => {
tracing::error!(
"{ERROR_STYLE}Build failed{ERROR_STYLE:#}: {}",
crate::error::log_stacktrace(&err, 15),
ERROR_STYLE = crate::styles::ERROR_STYLE,
);
if exit_on_error {
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::<crate::build::PatchError>()
{
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));
}
}
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);
}
BuilderUpdate::StderrReceived { msg } => {
screen.push_stdio(bundle_format, msg, tracing::Level::ERROR);
}
BuilderUpdate::ProcessExited { status } => {
if status.success() {
tracing::info!(
r#"Application [{bundle_format}] exited gracefully.
• To restart the app, press `r` to rebuild or `o` to open
• To exit the server, press `ctrl+c`"#
);
} else {
tracing::error!(
"Application [{bundle_format}] exited with error: {status}"
);
if exit_on_error {
bail!("Application [{bundle_format}] exited with error: {status}");
}
}
}
BuilderUpdate::ProcessWaitFailed { err } => {
tracing::warn!(
"Failed to wait for process - maybe it's hung or being debugged?: {err}"
);
if exit_on_error {
return Err(err.into());
}
}
}
}
ServeUpdate::TracingLog { log } => {
screen.push_log(log);
}
ServeUpdate::OpenApp => {
if builder.use_hotpatch_engine
&& !matches!(builder.client.build.bundle, BundleFormat::Web)
{
tracing::warn!(
"Opening a native app with hotpatching enabled requires a full rebuild..."
);
builder.full_rebuild().await;
devserver.send_reload_start().await;
devserver.start_build().await;
} else if let Err(err) = builder.open_all(&devserver, true).await {
tracing::error!(
"Failed to open app: {}",
crate::error::log_stacktrace(&err, 15)
)
}
}
ServeUpdate::Redraw => {
}
ServeUpdate::ToggleShouldRebuild => {
use crate::styles::{ERROR, NOTE_STYLE};
builder.automatic_rebuilds = !builder.automatic_rebuilds;
tracing::info!(
"Automatic rebuilds are currently: {}",
if builder.automatic_rebuilds {
format!("{NOTE_STYLE}enabled{NOTE_STYLE:#}")
} else {
format!("{ERROR}disabled{ERROR:#}")
}
)
}
ServeUpdate::OpenDebugger { id } => {
builder.open_debugger(&devserver, id).await;
}
ServeUpdate::Exit { error } => {
_ = builder.shutdown().await;
_ = devserver.shutdown().await;
match error {
Some(err) => return Err(err),
None => return Ok(()),
}
}
}
}
}