Files
dioxus/packages/cli/src/server/desktop/mod.rs
T

256 lines
7.8 KiB
Rust

use crate::{
builder,
serve::Serve,
server::{
output::{print_console_info, PrettierOptions, WebServerInfo},
setup_file_watcher, setup_file_watcher_hot_reload, BuildManager,
},
BuildResult, CrateConfig, Result,
};
use axum::{
body::{Full, HttpBody},
extract::{ws::Message, Extension, TypedHeader, WebSocketUpgrade},
http::{
header::{HeaderName, HeaderValue},
Method, Response, StatusCode,
},
response::IntoResponse,
routing::{get, get_service},
Router,
};
use axum_server::tls_rustls::RustlsConfig;
use cargo_metadata::diagnostic::Diagnostic;
use dioxus_core::Template;
use dioxus_hot_reload::HotReloadMsg;
use dioxus_html::HtmlCtx;
use dioxus_rsx::hot_reload::*;
use interprocess_docfix::local_socket::LocalSocketListener;
use notify::{RecommendedWatcher, Watcher};
use std::{
net::UdpSocket,
path::PathBuf,
process::{Command, Stdio},
sync::{Arc, Mutex},
};
use tokio::sync::broadcast::{self, Sender};
use tower::ServiceBuilder;
use tower_http::services::fs::{ServeDir, ServeFileSystemResponseBody};
use tower_http::{
cors::{Any, CorsLayer},
ServiceBuilderExt,
};
#[cfg(feature = "plugin")]
use plugin::PluginManager;
struct WsReloadState {
update: broadcast::Sender<()>,
}
pub async fn startup(config: CrateConfig) -> Result<()> {
// ctrl-c shutdown checker
let _crate_config = config.clone();
let _ = ctrlc::set_handler(move || {
#[cfg(feature = "plugin")]
let _ = PluginManager::on_serve_shutdown(&_crate_config);
std::process::exit(0);
});
match config.hot_reload {
true => serve_hot_reload(config).await?,
false => serve_default(config).await?,
}
Ok(())
}
/// Start the server without hot reload
pub async fn serve_default(config: CrateConfig) -> Result<()> {
let first_build_result = start_desktop(&config)?;
log::info!("🚀 Starting development server...");
// We got to own watcher so that it exists for the duration of serve
// Otherwise full reload won't work.
let _watcher = setup_file_watcher(crate::cfg::Platform::Desktop, &config, None, None).await?;
// Print serve info
print_console_info(
&config,
PrettierOptions {
changed: vec![],
warnings: first_build_result.warnings,
elapsed_time: first_build_result.elapsed_time,
},
None,
);
Ok(())
}
/// Start the server without hot reload
/// Start dx serve with hot reload
pub async fn serve_hot_reload(config: CrateConfig) -> Result<()> {
let first_build_result = start_desktop(&config)?;
println!("🚀 Starting development server...");
// Setup hot reload
let FileMapBuildResult { map, errors } =
FileMap::<HtmlCtx>::create(config.crate_dir.clone()).unwrap();
println!("🚀 Starting development server...");
for err in errors {
log::error!("{}", err);
}
let file_map = Arc::new(Mutex::new(map));
let build_manager = Arc::new(BuildManager {
platform: crate::cfg::Platform::Web,
config: config.clone(),
reload_tx: None,
});
let (hot_reload_tx, mut hot_reload_rx) = broadcast::channel(100);
// States
// Setup file watcher
// We got to own watcher so that it exists for the duration of serve
// Otherwise hot reload won't work.
let _watcher = setup_file_watcher_hot_reload(
&config,
hot_reload_tx,
file_map.clone(),
build_manager,
None,
)
.await?;
// Print serve info
print_console_info(
&config,
PrettierOptions {
changed: vec![],
warnings: first_build_result.warnings,
elapsed_time: first_build_result.elapsed_time,
},
None,
);
#[cfg(target_os = "macos")]
{
// On unix, if you force quit the application, it can leave the file socket open
// This will cause the local socket listener to fail to open
// We check if the file socket is already open from an old session and then delete it
let paths = ["./dioxusin", "./@dioxusin"];
for path in paths {
let path = PathBuf::from(path);
if path.exists() {
let _ = std::fs::remove_file(path);
}
}
}
let channels = Arc::new(Mutex::new(Vec::new()));
match LocalSocketListener::bind("@dioxusin") {
Ok(local_socket_stream) => {
let aborted = Arc::new(Mutex::new(false));
// listen for connections
std::thread::spawn({
let file_map = file_map.clone();
let channels = channels.clone();
let aborted = aborted.clone();
let _ = local_socket_stream.set_nonblocking(true);
move || {
loop {
if let Ok(mut connection) = local_socket_stream.accept() {
// send any templates than have changed before the socket connected
let templates: Vec<_> = {
file_map
.lock()
.unwrap()
.map
.values()
.filter_map(|(_, template_slot)| *template_slot)
.collect()
};
for template in templates {
if !send_msg(
HotReloadMsg::UpdateTemplate(template),
&mut connection,
) {
continue;
}
}
channels.lock().unwrap().push(connection);
println!("Connected to hot reloading 🚀");
}
if *aborted.lock().unwrap() {
break;
}
}
}
});
// for channel in &mut *channels.lock().unwrap() {
// send_msg(HotReloadMsg::Shutdown, channel);
// }
while let Ok(template) = hot_reload_rx.recv().await {
let channels = &mut *channels.lock().unwrap();
let mut i = 0;
while i < channels.len() {
let channel = &mut channels[i];
if send_msg(HotReloadMsg::UpdateTemplate(template), channel) {
i += 1;
} else {
channels.remove(i);
}
}
}
}
Err(error) => println!("failed to connect to hot reloading\n{error}"),
}
Ok(())
}
fn send_msg(msg: HotReloadMsg, channel: &mut impl std::io::Write) -> bool {
if let Ok(msg) = serde_json::to_string(&msg) {
if channel.write_all(msg.as_bytes()).is_err() {
return false;
}
if channel.write_all(&[b'\n']).is_err() {
return false;
}
true
} else {
false
}
}
pub fn start_desktop(config: &CrateConfig) -> Result<BuildResult> {
// Run the desktop application
let result = crate::builder::build_desktop(config, true)?;
match &config.executable {
crate::ExecutableType::Binary(name)
| crate::ExecutableType::Lib(name)
| crate::ExecutableType::Example(name) => {
let mut file = config.out_dir.join(name);
if cfg!(windows) {
file.set_extension("exe");
}
Command::new(file.to_str().unwrap())
.stdout(Stdio::inherit())
.spawn()?;
}
}
Ok(result)
}