Files
dioxus/packages/hot-reload/src/lib.rs
T

166 lines
6.9 KiB
Rust

use std::{
io::{BufRead, BufReader, Write},
path::PathBuf,
str::FromStr,
sync::{Arc, Mutex},
};
use dioxus_core::Template;
use dioxus_html::HtmlCtx;
use dioxus_rsx::hot_reload::{FileMap, UpdateResult};
use interprocess::local_socket::{LocalSocketListener, LocalSocketStream};
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
/// Initialize the hot reloading listener on the given path
pub fn init(root_path: &'static str, listening_paths: &'static [&'static str]) {
if let Ok(crate_dir) = PathBuf::from_str(root_path) {
let temp_file = std::env::temp_dir().join("@dioxusin");
let channels = Arc::new(Mutex::new(Vec::new()));
let file_map = Arc::new(Mutex::new(FileMap::<HtmlCtx>::new(crate_dir.clone())));
if let Ok(local_socket_stream) = LocalSocketListener::bind(temp_file.as_path()) {
// listen for connections
std::thread::spawn({
let file_map = file_map.clone();
let channels = channels.clone();
move || {
for connection in local_socket_stream.incoming() {
if let Ok(mut connection) = connection {
// 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_template(template, &mut connection) {
continue;
}
}
channels.lock().unwrap().push(connection);
println!("Connected to hot reloading 🚀");
}
}
}
});
// watch for changes
std::thread::spawn(move || {
let mut last_update_time = chrono::Local::now().timestamp();
let (tx, rx) = std::sync::mpsc::channel();
let mut watcher = RecommendedWatcher::new(tx, notify::Config::default()).unwrap();
for path in listening_paths {
if let Ok(path) = PathBuf::from_str(path) {
let examples_path = crate_dir.join(path);
let _ = watcher.watch(&examples_path, RecursiveMode::Recursive);
}
}
for evt in rx {
// Give time for the change to take effect before reading the file
std::thread::sleep(std::time::Duration::from_millis(100));
if chrono::Local::now().timestamp() > last_update_time {
if let Ok(evt) = evt {
let mut channels = channels.lock().unwrap();
for path in &evt.paths {
// skip non rust files
if path.extension().and_then(|p| p.to_str()) != Some("rs") {
continue;
}
// find changes to the rsx in the file
match file_map
.lock()
.unwrap()
.update_rsx(&path, crate_dir.as_path())
{
UpdateResult::UpdatedRsx(msgs) => {
for msg in msgs {
let mut i = 0;
while i < channels.len() {
let channel = &mut channels[i];
if send_template(msg, channel) {
i += 1;
} else {
channels.remove(i);
}
}
}
}
UpdateResult::NeedsRebuild => {
println!("Rebuild needed... shutting down hot reloading");
return;
}
}
}
}
last_update_time = chrono::Local::now().timestamp();
}
}
});
}
}
}
fn send_template(template: Template<'static>, channel: &mut impl Write) -> bool {
if let Ok(msg) = serde_json::to_string(&template) {
if channel.write_all(msg.as_bytes()).is_err() {
return false;
}
if channel.write_all(&[b'\n']).is_err() {
return false;
}
true
} else {
false
}
}
/// Connect to the hot reloading listener. The callback provided will be called every time a template change is detected
pub fn connect(mut f: impl FnMut(Template<'static>) + Send + 'static) {
std::thread::spawn(move || {
let temp_file = std::env::temp_dir().join("@dioxusin");
if let Ok(socket) = LocalSocketStream::connect(temp_file.as_path()) {
let mut buf_reader = BufReader::new(socket);
loop {
let mut buf = String::new();
match buf_reader.read_line(&mut buf) {
Ok(_) => {
let template: Template<'static> =
serde_json::from_str(Box::leak(buf.into_boxed_str())).unwrap();
f(template);
}
Err(err) => {
if err.kind() != std::io::ErrorKind::WouldBlock {
break;
}
}
}
}
}
});
}
/// Start the hot reloading server
///
/// Pass any number of paths to listen for changes on relative to the crate root as strings.
/// If no paths are passed, it will listen on the src and examples folders.
#[macro_export]
macro_rules! hot_reload_init_inner {
() => {
dioxus_hot_reload::init(core::env!("CARGO_MANIFEST_DIR"), &["src", "examples"])
};
($($paths: literal),*) => {
dioxus_hot_reload::init(core::env!("CARGO_MANIFEST_DIR"), &[$($paths),*])
};
}
pub use hot_reload_init_inner as hot_reload_init;