From c5ac7698fadf349f995bed8b269b8b46d368473c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 19 Dec 2022 20:48:35 -0600 Subject: [PATCH] fix hot reloading --- Cargo.lock | 4 +- Cargo.toml | 6 +- src/server/hot_reload.rs | 184 ++++++++++++++++++++------------------- src/server/mod.rs | 118 +++++++------------------ 4 files changed, 132 insertions(+), 180 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3694c673b..dd19a70d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2374,9 +2374,9 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fe39d9fbb0ebf5eb2c7cb7e2a47e4f462fad1379f1166b8ae49ad9eae89a7ca" +checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 442d8d8dc..95eb14b93 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -73,9 +73,9 @@ mlua = { version = "0.8.1", features = [ ] } ctrlc = "3.2.3" # dioxus-rsx = "0.0.1" -dioxus-rsx = { path = "/home/evan/Desktop/github/dioxus/packages/rsx" } -dioxus-html = { path = "/home/evan/Desktop/github/dioxus/packages/html" } -dioxus-core = { path = "/home/evan/Desktop/github/dioxus/packages/core" } +dioxus-rsx = { path = "c:/users/Desktop/github/dioxus/packages/rsx" } +dioxus-html = { path = "c:/users//Desktop/github/dioxus/packages/html" } +dioxus-core = { path = "c:/users//Desktop/github/dioxus/packages/core" } [[bin]] path = "src/main.rs" diff --git a/src/server/hot_reload.rs b/src/server/hot_reload.rs index 134b6b3e1..53a0d83e9 100644 --- a/src/server/hot_reload.rs +++ b/src/server/hot_reload.rs @@ -2,6 +2,7 @@ use axum::{ extract::{ws::Message, Extension, TypedHeader, WebSocketUpgrade}, response::IntoResponse, }; +use dioxus_rsx::CallBody; // use dioxus_rsx::try_parse_template; use std::{path::PathBuf, sync::Arc}; @@ -10,6 +11,7 @@ use super::BuildManager; pub use crate::hot_reload::{find_rsx, DiffResult}; use crate::CrateConfig; use dioxus_core::Template; +use dioxus_html::HtmlCtx; pub use proc_macro2::TokenStream; pub use std::collections::HashMap; pub use std::sync::Mutex; @@ -20,45 +22,101 @@ pub use syn::__private::ToTokens; use syn::spanned::Spanned; use tokio::sync::broadcast; +pub(crate) enum UpdateResult { + UpdatedRsx(Vec>), + NeedsRebuild, +} + +pub(crate) fn update_rsx( + path: &Path, + crate_dir: &Path, + src: String, + file_map: &mut FileMap, +) -> UpdateResult { + if let Ok(syntax) = syn::parse_file(&src) { + if let Some((old_src, template_slot)) = file_map.map.get_mut(path) { + if let Ok(old) = syn::parse_file(old_src) { + match find_rsx(&syntax, &old) { + DiffResult::CodeChanged => { + file_map.map.insert(path.to_path_buf(), (src, None)); + } + DiffResult::RsxChanged(changed) => { + log::info!("🪁 reloading rsx"); + let mut messages: Vec> = Vec::new(); + for (old, new) in changed.into_iter() { + let old_start = old.span().start(); + + if let (Ok(old_call_body), Ok(new_call_body)) = ( + syn::parse2::>(old.tokens), + syn::parse2::>(new), + ) { + if let Ok(file) = path.strip_prefix(crate_dir) { + let line = old_start.line; + let column = old_start.column + 1; + let location = file.display().to_string() + + ":" + + &line.to_string() + + ":" + + &column.to_string(); + + if let Some(template) = new_call_body.update_template( + Some(old_call_body), + Box::leak(location.into_boxed_str()), + ) { + *template_slot = Some(template); + messages.push(template); + } else { + return UpdateResult::NeedsRebuild; + } + } + } + } + return UpdateResult::UpdatedRsx(messages); + } + } + } + } else { + // if this is a new file, rebuild the project + *file_map = FileMap::new(crate_dir.to_path_buf()); + } + } + UpdateResult::NeedsRebuild +} + pub struct HotReloadState { pub messages: broadcast::Sender>, pub build_manager: Arc, - pub last_file_rebuild: Arc>, + pub file_map: Arc>, pub watcher_config: CrateConfig, } pub struct FileMap { - pub map: HashMap, - pub last_updated_time: std::time::SystemTime, + pub map: HashMap>)>, } impl FileMap { pub fn new(path: PathBuf) -> Self { log::info!("🔮 Searching files for changes since last compile..."); - fn find_rs_files(root: PathBuf) -> io::Result> { + fn find_rs_files( + root: PathBuf, + ) -> io::Result>)>> { let mut files = HashMap::new(); if root.is_dir() { - for entry in fs::read_dir(root)? { - if let Ok(entry) = entry { - let path = entry.path(); - files.extend(find_rs_files(path)?); - } + for entry in (fs::read_dir(root)?).flatten() { + let path = entry.path(); + files.extend(find_rs_files(path)?); } - } else { - if root.extension().map(|s| s.to_str()).flatten() == Some("rs") { - if let Ok(mut file) = File::open(root.clone()) { - let mut src = String::new(); - file.read_to_string(&mut src).expect("Unable to read file"); - files.insert(root, src); - } + } else if root.extension().and_then(|s| s.to_str()) == Some("rs") { + if let Ok(mut file) = File::open(root.clone()) { + let mut src = String::new(); + file.read_to_string(&mut src).expect("Unable to read file"); + files.insert(root, (src, None)); } } Ok(files) } - let last_updated_time = SystemTime::now(); let result = Self { - last_updated_time, map: find_rs_files(path).unwrap(), }; // log::info!("Files updated"); @@ -75,82 +133,28 @@ pub async fn hot_reload_handler( log::info!("🔥 Hot Reload WebSocket connected"); { // update any rsx calls that changed before the websocket connected. - // let mut messages = Vec::new(); - { log::info!("🔮 Finding updates since last compile..."); - let handle = state.last_file_rebuild.lock().unwrap(); - let update_time = handle.last_updated_time.clone(); - for (k, v) in handle.map.iter() { - let mut file = File::open(k).unwrap(); - if let Ok(md) = file.metadata() { - if let Ok(time) = md.modified() { - if time < update_time { - continue; - } - } - } - let mut new_str = String::new(); - file.read_to_string(&mut new_str) - .expect("Unable to read file"); - if let Ok(new_file) = syn::parse_file(&new_str) { - if let Ok(old_file) = syn::parse_file(&v) { - if let DiffResult::RsxChanged(changed) = find_rsx(&new_file, &old_file) - { - for (old, new) in changed.into_iter() { - // let hr = get_location( - // &state.watcher_config.crate_dir, - // k, - // old.to_token_stream(), - // ); - // get the original source code to preserve whitespace - let span = new.span(); - let start = span.start(); - let end = span.end(); - let mut lines: Vec<_> = new_str - .lines() - .skip(start.line - 1) - .take(end.line - start.line + 1) - .collect(); - if let Some(first) = lines.first_mut() { - *first = first.split_at(start.column).1; - } - if let Some(last) = lines.last_mut() { - // if there is only one line the start index of last line will be the start of the rsx!, not the start of the line - if start.line == end.line { - *last = last.split_at(end.column - start.column).0; - } else { - *last = last.split_at(end.column).0; - } - } - let rsx = lines.join("\n"); - - // let old_dyn_ctx = try_parse_template( - // &format!("{}", old.tokens), - // hr.to_owned(), - // None, - // ) - // .map(|(_, old_dyn_ctx)| old_dyn_ctx); - // if let Ok((template, _)) = - // try_parse_template(&rsx, hr.to_owned(), old_dyn_ctx.ok()) - // { - // // messages.push(SetTemplateMsg(TemplateId(hr), template)); - // } - } - } - } + let templates: Vec<_> = { + state + .file_map + .lock() + .unwrap() + .map + .values() + .filter_map(|(_, template_slot)| *template_slot) + .collect() + }; + for template in templates { + if socket + .send(Message::Text(serde_json::to_string(&template).unwrap())) + .await + .is_err() + { + return; } } } - // for msg in messages { - // if socket - // .send(Message::Text(serde_json::to_string(&msg).unwrap())) - // .await - // .is_err() - // { - // return; - // } - // } log::info!("finished"); } @@ -158,11 +162,13 @@ pub async fn hot_reload_handler( let hot_reload_handle = tokio::spawn(async move { loop { if let Ok(rsx) = rx.recv().await { + println!("sending"); if socket .send(Message::Text(serde_json::to_string(&rsx).unwrap())) .await .is_err() { + println!("error sending"); break; }; } diff --git a/src/server/mod.rs b/src/server/mod.rs index 0b01b3fd5..da49c4dc9 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -79,7 +79,7 @@ pub async fn startup_hot_reload(port: u16, config: CrateConfig) -> Result<()> { let dist_path = config.out_dir.clone(); let (reload_tx, _) = broadcast::channel(100); - let last_file_rebuild = Arc::new(Mutex::new(FileMap::new(config.crate_dir.clone()))); + let file_map = Arc::new(Mutex::new(FileMap::new(config.crate_dir.clone()))); let build_manager = Arc::new(BuildManager { config: config.clone(), reload_tx: reload_tx.clone(), @@ -88,7 +88,7 @@ pub async fn startup_hot_reload(port: u16, config: CrateConfig) -> Result<()> { let hot_reload_state = Arc::new(HotReloadState { messages: hot_reload_tx.clone(), build_manager: build_manager.clone(), - last_file_rebuild: last_file_rebuild.clone(), + file_map: file_map.clone(), watcher_config: config.clone(), }); @@ -97,8 +97,6 @@ pub async fn startup_hot_reload(port: u16, config: CrateConfig) -> Result<()> { update: reload_tx.clone(), }); - let mut last_update_time = chrono::Local::now().timestamp(); - // file watcher: check file change let allow_watch_path = config .dioxus_config @@ -109,100 +107,50 @@ pub async fn startup_hot_reload(port: u16, config: CrateConfig) -> Result<()> { .unwrap_or_else(|| vec![PathBuf::from("src")]); let watcher_config = config.clone(); + let mut last_update_time = chrono::Local::now().timestamp(); + let mut watcher = RecommendedWatcher::new( move |evt: notify::Result| { let config = watcher_config.clone(); + // 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 { - // Give time for the change to take effect before reading the file - std::thread::sleep(std::time::Duration::from_millis(100)); - let mut updated = false; if let Ok(evt) = evt { + println!("{:?}", evt); let mut messages: Vec> = Vec::new(); - let mut needs_rebuild = false; for path in evt.paths.clone() { - if path.extension().map(|p| p.to_str()).flatten() != Some("rs") { + if path.extension().and_then(|p| p.to_str()) != Some("rs") { continue; } let mut file = File::open(path.clone()).unwrap(); let mut src = String::new(); file.read_to_string(&mut src).expect("Unable to read file"); // find changes to the rsx in the file - if let Ok(syntax) = syn::parse_file(&src) { - let mut last_file_rebuild = last_file_rebuild.lock().unwrap(); - if let Some(old_str) = last_file_rebuild.map.get(&path) { - if let Ok(old) = syn::parse_file(&old_str) { - updated = true; - match find_rsx(&syntax, &old) { - DiffResult::CodeChanged => { - needs_rebuild = true; - last_file_rebuild.map.insert(path, src); - } - DiffResult::RsxChanged(changed) => { - log::info!("🪁 reloading rsx"); - for (old, new) in changed.into_iter() { - let old_start = old.span().start(); + let mut map = file_map.lock().unwrap(); - if let (Ok(old_call_body), Ok(new_call_body)) = ( - syn::parse2::(old.tokens), - syn::parse2::(new), - ) { - let spndbg = format!( - "{:?}", - old_call_body.roots[0].span() - ); - let root_col = - spndbg[9..].split("..").next().unwrap(); - if let Ok(file) = path.strip_prefix(&crate_dir) - { - let line = old_start.line; - let column = old_start.column; - let location = file.display().to_string() - + ":" - + &line.to_string() - + ":" - + &column.to_string() - + ":" - + root_col; - - if let Some(template) = new_call_body - .update_template( - Some(old_call_body), - Box::leak( - location.into_boxed_str(), - ), - ) - { - messages.push(template); - } else { - needs_rebuild = true; - } - } - } - } - } + match update_rsx(&path, &crate_dir, src, &mut map) { + UpdateResult::UpdatedRsx(msgs) => { + println!("{msgs:#?}"); + messages.extend(msgs); + } + UpdateResult::NeedsRebuild => { + match build_manager.rebuild() { + Ok(res) => { + print_console_info( + port, + &config, + PrettierOptions { + changed: evt.paths, + warnings: res.warnings, + elapsed_time: res.elapsed_time, + }, + ); + } + Err(err) => { + log::error!("{}", err); } } - } else { - // if this is a new file, rebuild the project - *last_file_rebuild = FileMap::new(crate_dir.clone()); - } - } - } - if needs_rebuild { - match build_manager.rebuild() { - Ok(res) => { - print_console_info( - port, - &config, - PrettierOptions { - changed: evt.paths, - warnings: res.warnings, - elapsed_time: res.elapsed_time, - }, - ); - } - Err(err) => { - log::error!("{}", err); + return; } } } @@ -210,9 +158,7 @@ pub async fn startup_hot_reload(port: u16, config: CrateConfig) -> Result<()> { let _ = hot_reload_tx.send(msg); } } - if updated { - last_update_time = chrono::Local::now().timestamp(); - } + last_update_time = chrono::Local::now().timestamp(); } }, notify::Config::default(), @@ -274,7 +220,7 @@ pub async fn startup_hot_reload(port: u16, config: CrateConfig) -> Result<()> { Ok(response) }, ) - .service(ServeDir::new((&config.crate_dir).join(&dist_path))); + .service(ServeDir::new(config.crate_dir.join(&dist_path))); let router = Router::new() .route("/_dioxus/ws", get(ws_handler))