From 982b96074a331ffbd7d7f7fd661a450313c9947e Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 13 Mar 2024 13:07:15 -0700 Subject: [PATCH] Add basic hotreload test samples --- Cargo.lock | 34 ++- packages/cli/Cargo.toml | 1 + packages/cli/src/main.rs | 5 +- packages/cli/src/server/mod.rs | 240 +++++++++++------- .../rsx/src/hot_reload/hot_reload_diff.rs | 190 +++++++------- .../src/hot_reload/hot_reloading_file_map.rs | 37 +-- packages/rsx/src/hot_reload/mod.rs | 2 + packages/rsx/tests/hotreloads.rs | 19 ++ packages/rsx/tests/valid_samples/new.expr.rsx | 17 ++ packages/rsx/tests/valid_samples/old.expr.rsx | 17 ++ packages/web/src/hot_reload.rs | 40 ++- 11 files changed, 373 insertions(+), 229 deletions(-) create mode 100644 packages/rsx/tests/hotreloads.rs create mode 100644 packages/rsx/tests/valid_samples/new.expr.rsx create mode 100644 packages/rsx/tests/valid_samples/old.expr.rsx diff --git a/Cargo.lock b/Cargo.lock index 3d3267e18..0e544ae13 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1184,7 +1184,7 @@ dependencies = [ "clap 4.4.18", "console", "dialoguer", - "env_logger", + "env_logger 0.10.2", "fs-err", "git2", "gix-config", @@ -2192,7 +2192,7 @@ dependencies = [ "dioxus-ssr", "dioxus-tui", "dioxus-web", - "env_logger", + "env_logger 0.10.2", "futures-util", "rand 0.8.5", "serde", @@ -2250,6 +2250,7 @@ dependencies = [ "dioxus-html", "dioxus-rsx", "dirs", + "env_logger 0.11.3", "fern", "flate2", "fs_extra", @@ -2994,6 +2995,16 @@ dependencies = [ "syn 2.0.51", ] +[[package]] +name = "env_filter" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea" +dependencies = [ + "log", + "regex", +] + [[package]] name = "env_logger" version = "0.10.2" @@ -3007,6 +3018,19 @@ dependencies = [ "termcolor", ] +[[package]] +name = "env_logger" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b35839ba51819680ba087cd351788c9a3c476841207e0b8cee0b04722343b9" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "humantime", + "log", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -5592,9 +5616,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" dependencies = [ "value-bag", ] @@ -7063,7 +7087,7 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" dependencies = [ - "env_logger", + "env_logger 0.10.2", "log", ] diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index fe4e0a8fe..10853647b 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -93,6 +93,7 @@ dioxus-hot-reload = { workspace = true } interprocess = { workspace = true } # interprocess-docfix = { version = "1.2.2" } ignore = "0.4.22" +env_logger = "0.11.3" [features] default = [] diff --git a/packages/cli/src/main.rs b/packages/cli/src/main.rs index 87f9e32e7..4943e6e3b 100644 --- a/packages/cli/src/main.rs +++ b/packages/cli/src/main.rs @@ -11,7 +11,10 @@ use Commands::*; async fn main() -> anyhow::Result<()> { let args = Cli::parse(); - set_up_logging(); + #[cfg(debug_assertions)] + env_logger::init(); + + // set_up_logging(); match args.action { Translate(opts) => opts diff --git a/packages/cli/src/server/mod.rs b/packages/cli/src/server/mod.rs index 21256bd0d..0c958e885 100644 --- a/packages/cli/src/server/mod.rs +++ b/packages/cli/src/server/mod.rs @@ -9,6 +9,7 @@ use dioxus_rsx::hot_reload::*; use fs_extra::{dir::CopyOptions, file}; use notify::{RecommendedWatcher, Watcher}; use std::{ + ops::ControlFlow, path::PathBuf, sync::{Arc, Mutex}, }; @@ -22,7 +23,10 @@ pub mod web; #[derive(Clone)] pub struct HotReloadState { + /// Pending hotreload updates to be sent to all connected clients pub messages: broadcast::Sender, + + /// The file map that tracks the state of the projecta pub file_map: Arc>>, } @@ -99,8 +103,8 @@ fn watch_event( return; } - // By default we want to opt into a full rebuild, but hotreloading will actually set this force us - let mut needs_full_rebuild = true; + // By default we want to not do a full rebuild, and instead let the hot reload system invalidate it + let mut needs_full_rebuild = false; if let Some(hot_reload) = &hot_reload { hotreload_files(hot_reload, &mut needs_full_rebuild, &event, &config); @@ -152,83 +156,23 @@ fn hotreload_files( let mut rsx_file_map = hot_reload.file_map.lock().unwrap(); let mut messages: Vec = Vec::new(); - // In hot reload mode, we only need to rebuild if non-rsx code is changed - *needs_full_rebuild = false; - for path in &event.paths { - // for various assets that might be linked in, we just try to hotreloading them forcefully - // That is, unless they appear in an include! macro, in which case we need to a full rebuild.... - let Some(ext) = path.extension().and_then(|v| v.to_str()) else { - continue; - }; + // Attempt to hotreload this file + let is_potentially_reloadable = hotreload_file( + path, + config, + &rsx_file_map, + &mut messages, + needs_full_rebuild, + ); - // Workaround for notify and vscode-like editor: - // when edit & save a file in vscode, there will be two notifications, - // the first one is a file with empty content. - // filter the empty file notification to avoid false rebuild during hot-reload - if let Ok(metadata) = fs::metadata(path) { - if metadata.len() == 0 { - continue; - } + // If the file was not hotreloaded, continue + if is_potentially_reloadable.is_none() { + println!("Not hotreloaded: {:?}", path); + continue; } - match ext { - // Attempt hot reload - "rs" => {} - - // Anything with a .file is also ignored - _ if path.file_stem().is_none() || ext.ends_with("~") => {} - - // Anything else is a maybe important file that needs to be rebuilt - _ => { - // If it happens to be a file in the asset directory, there's a chance we can hotreload it. - // Only css is currently supported for hotreload - if ext == "css" { - let asset_dir = config - .crate_dir - .join(&config.dioxus_config.application.asset_dir); - - if path.starts_with(&asset_dir) { - let local_path: PathBuf = path - .file_name() - .unwrap() - .to_str() - .unwrap() - .to_string() - .parse() - .unwrap(); - - println!( - "maybe tracking asset: {:?}, {:#?}", - local_path, - rsx_file_map.tracked_assets() - ); - - if let Some(f) = rsx_file_map.is_tracking_asset(&local_path) { - println!( - "Hot reloading asset - it's tracked by the rsx!: {:?}", - local_path - ); - - // copy the asset over tothe output directory - let output_dir = config.out_dir(); - fs_extra::copy_items( - &[path], - output_dir, - &CopyOptions::new().overwrite(true), - ) - .unwrap(); - - messages.push(HotReloadMsg::UpdateAsset(local_path)); - continue; - } - } - } - - *needs_full_rebuild = true; - } - }; - + // If the file was hotreloaded, update the file map in place match rsx_file_map.update_rsx(path, &config.crate_dir) { Ok(UpdateResult::UpdatedRsx(msgs)) => { println!("Updated: {:?}", msgs); @@ -237,14 +181,15 @@ fn hotreload_files( msgs.into_iter() .map(|msg| HotReloadMsg::UpdateTemplate(msg)), ); - *needs_full_rebuild = false; } + Ok(UpdateResult::NeedsRebuild) => { + println!("Needs rebuild: {:?}", path); *needs_full_rebuild = true; } - Err(err) => { - log::error!("{}", err); - } + + Err(err) => log::error!("{}", err), + // Err(err) => log::error!("{}", err), } } @@ -273,6 +218,96 @@ fn hotreload_files( } } +fn hotreload_file( + path: &PathBuf, + config: &CrateConfig, + rsx_file_map: &std::sync::MutexGuard<'_, FileMap>, + messages: &mut Vec, + needs_full_rebuild: &mut bool, +) -> Option<()> { + // for various assets that might be linked in, we just try to hotreloading them forcefully + // That is, unless they appear in an include! macro, in which case we need to a full rebuild.... + let ext = path.extension().and_then(|v| v.to_str())?; + + // Workaround for notify and vscode-like editor: + // when edit & save a file in vscode, there will be two notifications, + // the first one is a file with empty content. + // filter the empty file notification to avoid false rebuild during hot-reload + if let Ok(metadata) = fs::metadata(path) { + if metadata.len() == 0 { + return None; + } + } + + // If the extension is a backup file, or a hidden file, ignore it completely (no rebuilds) + if is_backup_file(path) { + println!("Ignoring backup file: {:?}", path); + return None; + } + + // Attempt to hotreload css in the asset directory + // Currently no other assets are hotreloaded, but in theory we could hotreload pngs/jpegs, etc + // + // All potential hotreloadable mime types: + // "bin" |"css" | "csv" | "html" | "ico" | "js" | "json" | "jsonld" | "mjs" | "rtf" | "svg" | "mp4" + if ext == "css" { + let asset_dir = config + .crate_dir + .join(&config.dioxus_config.application.asset_dir); + + // Only if the CSS is in the asset directory, and we're tracking it, do we hotreload it + // Otherwise, we need to do a full rebuild since the user might be doing an include_str! on it + if attempt_css_reload(path, asset_dir, rsx_file_map, config, messages).is_none() { + *needs_full_rebuild = true; + } + + return None; + } + + // If the file is not rsx or css and we've already not needed a full rebuild, return + if ext != "rs" && ext != "css" { + *needs_full_rebuild = true; + return None; + } + + Some(()) +} + +fn attempt_css_reload( + path: &PathBuf, + asset_dir: PathBuf, + rsx_file_map: &std::sync::MutexGuard<'_, FileMap>, + config: &CrateConfig, + messages: &mut Vec, +) -> Option<()> { + // If the path is not in the asset directory, return + if !path.starts_with(&asset_dir) { + return None; + } + + // Get the local path of the asset (ie var.css or some_dir/var.css as long as the dir is under the asset dir) + let local_path = local_path_of_asset(path)?; + + // Make sure we're actually tracking this asset... + _ = rsx_file_map.is_tracking_asset(&local_path)?; + + // copy the asset over to the output directory + // todo this whole css hotreloading shouldbe less hacky and more robust + _ = fs_extra::copy_items( + &[path], + config.out_dir(), + &CopyOptions::new().overwrite(true), + ); + + messages.push(HotReloadMsg::UpdateAsset(local_path)); + + Some(()) +} + +fn local_path_of_asset(path: &PathBuf) -> Option { + path.file_name()?.to_str()?.to_string().parse().ok() +} + pub(crate) trait Platform { fn start(config: &CrateConfig, serve: &ConfigOptsServe) -> Result where @@ -280,15 +315,38 @@ pub(crate) trait Platform { fn rebuild(&mut self, config: &CrateConfig) -> Result; } -// Some("bin") => "application/octet-stream", -// Some("css") => "text/css", -// Some("csv") => "text/csv", -// Some("html") => "text/html", -// Some("ico") => "image/vnd.microsoft.icon", -// Some("js") => "text/javascript", -// Some("json") => "application/json", -// Some("jsonld") => "application/ld+json", -// Some("mjs") => "text/javascript", -// Some("rtf") => "application/rtf", -// Some("svg") => "image/svg+xml", -// Some("mp4") => "video/mp4", +fn is_backup_file(path: &PathBuf) -> bool { + // If there's a tilde at the end of the file, it's a backup file + if let Some(name) = path.file_name() { + if let Some(name) = name.to_str() { + if name.ends_with('~') { + return true; + } + } + } + + // if the file is hidden, it's a backup file + if let Some(name) = path.file_name() { + if let Some(name) = name.to_str() { + if name.starts_with('.') { + return true; + } + } + } + + false +} + +#[test] +fn test_is_backup_file() { + assert!(is_backup_file(&PathBuf::from("examples/test.rs~"))); + assert!(is_backup_file(&PathBuf::from("examples/.back"))); + assert!(is_backup_file(&PathBuf::from("test.rs~"))); + assert!(is_backup_file(&PathBuf::from(".back"))); + + assert!(!is_backup_file(&PathBuf::from("val.rs"))); + assert!(!is_backup_file(&PathBuf::from( + "/Users/jonkelley/Development/Tinkering/basic_05_example/src/lib.rs" + ))); + assert!(!is_backup_file(&PathBuf::from("exmaples/val.rs"))); +} diff --git a/packages/rsx/src/hot_reload/hot_reload_diff.rs b/packages/rsx/src/hot_reload/hot_reload_diff.rs index 7d1a8ebb7..58e681687 100644 --- a/packages/rsx/src/hot_reload/hot_reload_diff.rs +++ b/packages/rsx/src/hot_reload/hot_reload_diff.rs @@ -1,10 +1,11 @@ use proc_macro2::TokenStream; use quote::ToTokens; -use syn::{File, Macro}; +use syn::{Expr, File, Item, Macro}; +#[derive(Debug)] pub enum DiffResult { /// Non-rsx was changed in the file - CodeChanged, + CodeChanged(NotreloadableReason), /// Rsx was changed in the file /// @@ -12,6 +13,13 @@ pub enum DiffResult { RsxChanged { rsx_calls: Vec }, } +#[derive(Debug)] +pub enum NotreloadableReason { + RootMismatch, + + RsxMismatch, +} + #[derive(Debug)] pub struct ChangedRsx { /// The macro that was changed @@ -22,8 +30,9 @@ pub struct ChangedRsx { } /// Find any rsx calls in the given file and return a list of all the rsx calls that have changed. -pub fn find_rsx(new: &File, old: &File) -> DiffResult { +pub fn diff_rsx(new: &File, old: &File) -> DiffResult { let mut rsx_calls = Vec::new(); + if new.items.len() != old.items.len() { tracing::trace!( "found not hot reload-able change {:#?} != {:#?}", @@ -36,7 +45,7 @@ pub fn find_rsx(new: &File, old: &File) -> DiffResult { .map(|i| i.to_token_stream().to_string()) .collect::>() ); - return DiffResult::CodeChanged; + return DiffResult::CodeChanged(NotreloadableReason::RootMismatch); } for (new, old) in new.items.iter().zip(old.items.iter()) { @@ -46,7 +55,8 @@ pub fn find_rsx(new: &File, old: &File) -> DiffResult { new.to_token_stream().to_string(), old.to_token_stream().to_string() ); - return DiffResult::CodeChanged; + + return DiffResult::CodeChanged(NotreloadableReason::RsxMismatch); } } @@ -54,9 +64,9 @@ pub fn find_rsx(new: &File, old: &File) -> DiffResult { DiffResult::RsxChanged { rsx_calls } } -fn find_rsx_item(new: &syn::Item, old: &syn::Item, rsx_calls: &mut Vec) -> bool { +fn find_rsx_item(new: &Item, old: &Item, rsx_calls: &mut Vec) -> bool { match (new, old) { - (syn::Item::Const(new_item), syn::Item::Const(old_item)) => { + (Item::Const(new_item), Item::Const(old_item)) => { find_rsx_expr(&new_item.expr, &old_item.expr, rsx_calls) || new_item.attrs != old_item.attrs || new_item.vis != old_item.vis @@ -67,7 +77,7 @@ fn find_rsx_item(new: &syn::Item, old: &syn::Item, rsx_calls: &mut Vec { + (Item::Enum(new_item), Item::Enum(old_item)) => { if new_item.variants.len() != old_item.variants.len() { return true; } @@ -96,17 +106,15 @@ fn find_rsx_item(new: &syn::Item, old: &syn::Item, rsx_calls: &mut Vec { - old_item != new_item - } - (syn::Item::Fn(new_item), syn::Item::Fn(old_item)) => { + (Item::ExternCrate(new_item), Item::ExternCrate(old_item)) => old_item != new_item, + (Item::Fn(new_item), Item::Fn(old_item)) => { find_rsx_block(&new_item.block, &old_item.block, rsx_calls) || new_item.attrs != old_item.attrs || new_item.vis != old_item.vis || new_item.sig != old_item.sig } - (syn::Item::ForeignMod(new_item), syn::Item::ForeignMod(old_item)) => old_item != new_item, - (syn::Item::Impl(new_item), syn::Item::Impl(old_item)) => { + (Item::ForeignMod(new_item), Item::ForeignMod(old_item)) => old_item != new_item, + (Item::Impl(new_item), Item::Impl(old_item)) => { if new_item.items.len() != old_item.items.len() { return true; } @@ -141,13 +149,13 @@ fn find_rsx_item(new: &syn::Item, old: &syn::Item, rsx_calls: &mut Vec { + (Item::Macro(new_item), Item::Macro(old_item)) => { find_rsx_macro(&new_item.mac, &old_item.mac, rsx_calls) || new_item.attrs != old_item.attrs || new_item.semi_token != old_item.semi_token || new_item.ident != old_item.ident } - (syn::Item::Mod(new_item), syn::Item::Mod(old_item)) => { + (Item::Mod(new_item), Item::Mod(old_item)) => { match (&new_item.content, &old_item.content) { (Some((_, new_items)), Some((_, old_items))) => { if new_items.len() != old_items.len() { @@ -174,7 +182,7 @@ fn find_rsx_item(new: &syn::Item, old: &syn::Item, rsx_calls: &mut Vec true, } } - (syn::Item::Static(new_item), syn::Item::Static(old_item)) => { + (Item::Static(new_item), Item::Static(old_item)) => { find_rsx_expr(&new_item.expr, &old_item.expr, rsx_calls) || new_item.attrs != old_item.attrs || new_item.vis != old_item.vis @@ -186,15 +194,16 @@ fn find_rsx_item(new: &syn::Item, old: &syn::Item, rsx_calls: &mut Vec old_item != new_item, - (syn::Item::Trait(new_item), syn::Item::Trait(old_item)) => { + (Item::Struct(new_item), Item::Struct(old_item)) => old_item != new_item, + (Item::Trait(new_item), Item::Trait(old_item)) => { find_rsx_trait(new_item, old_item, rsx_calls) } - (syn::Item::TraitAlias(new_item), syn::Item::TraitAlias(old_item)) => old_item != new_item, - (syn::Item::Type(new_item), syn::Item::Type(old_item)) => old_item != new_item, - (syn::Item::Union(new_item), syn::Item::Union(old_item)) => old_item != new_item, - (syn::Item::Use(new_item), syn::Item::Use(old_item)) => old_item != new_item, - (syn::Item::Verbatim(_), syn::Item::Verbatim(_)) => false, + (Item::TraitAlias(new_item), Item::TraitAlias(old_item)) => old_item != new_item, + (Item::Type(new_item), Item::Type(old_item)) => old_item != new_item, + (Item::Union(new_item), Item::Union(old_item)) => old_item != new_item, + (Item::Use(new_item), Item::Use(old_item)) => old_item != new_item, + (Item::Verbatim(_), Item::Verbatim(_)) => false, + _ => true, } } @@ -241,6 +250,7 @@ fn find_rsx_trait( return true; } } + new_item.attrs != old_item.attrs || new_item.vis != old_item.vis || new_item.unsafety != old_item.unsafety @@ -260,11 +270,13 @@ fn find_rsx_block( if new_block.stmts.len() != old_block.stmts.len() { return true; } + for (new_stmt, old_stmt) in new_block.stmts.iter().zip(old_block.stmts.iter()) { if find_rsx_stmt(new_stmt, old_stmt, rsx_calls) { return true; } } + new_block.brace_token != old_block.brace_token } @@ -302,13 +314,9 @@ fn find_rsx_stmt( } } -fn find_rsx_expr( - new_expr: &syn::Expr, - old_expr: &syn::Expr, - rsx_calls: &mut Vec, -) -> bool { +fn find_rsx_expr(new_expr: &Expr, old_expr: &Expr, rsx_calls: &mut Vec) -> bool { match (new_expr, old_expr) { - (syn::Expr::Array(new_expr), syn::Expr::Array(old_expr)) => { + (Expr::Array(new_expr), Expr::Array(old_expr)) => { if new_expr.elems.len() != old_expr.elems.len() { return true; } @@ -319,52 +327,50 @@ fn find_rsx_expr( } new_expr.attrs != old_expr.attrs || new_expr.bracket_token != old_expr.bracket_token } - (syn::Expr::Assign(new_expr), syn::Expr::Assign(old_expr)) => { + (Expr::Assign(new_expr), Expr::Assign(old_expr)) => { find_rsx_expr(&new_expr.left, &old_expr.left, rsx_calls) || find_rsx_expr(&new_expr.right, &old_expr.right, rsx_calls) || new_expr.attrs != old_expr.attrs || new_expr.eq_token != old_expr.eq_token } - (syn::Expr::Async(new_expr), syn::Expr::Async(old_expr)) => { + (Expr::Async(new_expr), Expr::Async(old_expr)) => { find_rsx_block(&new_expr.block, &old_expr.block, rsx_calls) || new_expr.attrs != old_expr.attrs || new_expr.async_token != old_expr.async_token || new_expr.capture != old_expr.capture } - (syn::Expr::Await(new_expr), syn::Expr::Await(old_expr)) => { + (Expr::Await(new_expr), Expr::Await(old_expr)) => { find_rsx_expr(&new_expr.base, &old_expr.base, rsx_calls) || new_expr.attrs != old_expr.attrs || new_expr.dot_token != old_expr.dot_token || new_expr.await_token != old_expr.await_token } - (syn::Expr::Binary(new_expr), syn::Expr::Binary(old_expr)) => { + (Expr::Binary(new_expr), Expr::Binary(old_expr)) => { find_rsx_expr(&new_expr.left, &old_expr.left, rsx_calls) || find_rsx_expr(&new_expr.right, &old_expr.right, rsx_calls) || new_expr.attrs != old_expr.attrs || new_expr.op != old_expr.op } - (syn::Expr::Block(new_expr), syn::Expr::Block(old_expr)) => { + (Expr::Block(new_expr), Expr::Block(old_expr)) => { find_rsx_block(&new_expr.block, &old_expr.block, rsx_calls) || new_expr.attrs != old_expr.attrs || new_expr.label != old_expr.label } - (syn::Expr::Break(new_expr), syn::Expr::Break(old_expr)) => { - match (&new_expr.expr, &old_expr.expr) { - (Some(new_inner), Some(old_inner)) => { - find_rsx_expr(new_inner, old_inner, rsx_calls) - || new_expr.attrs != old_expr.attrs - || new_expr.break_token != old_expr.break_token - || new_expr.label != old_expr.label - } - (None, None) => { - new_expr.attrs != old_expr.attrs - || new_expr.break_token != old_expr.break_token - || new_expr.label != old_expr.label - } - _ => true, + (Expr::Break(new_expr), Expr::Break(old_expr)) => match (&new_expr.expr, &old_expr.expr) { + (Some(new_inner), Some(old_inner)) => { + find_rsx_expr(new_inner, old_inner, rsx_calls) + || new_expr.attrs != old_expr.attrs + || new_expr.break_token != old_expr.break_token + || new_expr.label != old_expr.label } - } - (syn::Expr::Call(new_expr), syn::Expr::Call(old_expr)) => { + (None, None) => { + new_expr.attrs != old_expr.attrs + || new_expr.break_token != old_expr.break_token + || new_expr.label != old_expr.label + } + _ => true, + }, + (Expr::Call(new_expr), Expr::Call(old_expr)) => { find_rsx_expr(&new_expr.func, &old_expr.func, rsx_calls); if new_expr.args.len() != old_expr.args.len() { return true; @@ -376,13 +382,13 @@ fn find_rsx_expr( } new_expr.attrs != old_expr.attrs || new_expr.paren_token != old_expr.paren_token } - (syn::Expr::Cast(new_expr), syn::Expr::Cast(old_expr)) => { + (Expr::Cast(new_expr), Expr::Cast(old_expr)) => { find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls) || new_expr.attrs != old_expr.attrs || new_expr.as_token != old_expr.as_token || new_expr.ty != old_expr.ty } - (syn::Expr::Closure(new_expr), syn::Expr::Closure(old_expr)) => { + (Expr::Closure(new_expr), Expr::Closure(old_expr)) => { find_rsx_expr(&new_expr.body, &old_expr.body, rsx_calls) || new_expr.attrs != old_expr.attrs || new_expr.movability != old_expr.movability @@ -393,19 +399,19 @@ fn find_rsx_expr( || new_expr.or2_token != old_expr.or2_token || new_expr.output != old_expr.output } - (syn::Expr::Const(new_expr), syn::Expr::Const(old_expr)) => { + (Expr::Const(new_expr), Expr::Const(old_expr)) => { find_rsx_block(&new_expr.block, &old_expr.block, rsx_calls) || new_expr.attrs != old_expr.attrs || new_expr.const_token != old_expr.const_token } - (syn::Expr::Continue(new_expr), syn::Expr::Continue(old_expr)) => old_expr != new_expr, - (syn::Expr::Field(new_expr), syn::Expr::Field(old_expr)) => { + (Expr::Continue(new_expr), Expr::Continue(old_expr)) => old_expr != new_expr, + (Expr::Field(new_expr), Expr::Field(old_expr)) => { find_rsx_expr(&new_expr.base, &old_expr.base, rsx_calls) || new_expr.attrs != old_expr.attrs || new_expr.dot_token != old_expr.dot_token || new_expr.member != old_expr.member } - (syn::Expr::ForLoop(new_expr), syn::Expr::ForLoop(old_expr)) => { + (Expr::ForLoop(new_expr), Expr::ForLoop(old_expr)) => { find_rsx_block(&new_expr.body, &old_expr.body, rsx_calls) || find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls) || new_expr.attrs != old_expr.attrs @@ -414,10 +420,10 @@ fn find_rsx_expr( || new_expr.pat != old_expr.pat || new_expr.in_token != old_expr.in_token } - (syn::Expr::Group(new_expr), syn::Expr::Group(old_expr)) => { + (Expr::Group(new_expr), Expr::Group(old_expr)) => { find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls) } - (syn::Expr::If(new_expr), syn::Expr::If(old_expr)) => { + (Expr::If(new_expr), Expr::If(old_expr)) => { if find_rsx_expr(&new_expr.cond, &old_expr.cond, rsx_calls) || find_rsx_block(&new_expr.then_branch, &old_expr.then_branch, rsx_calls) { @@ -439,32 +445,32 @@ fn find_rsx_expr( _ => true, } } - (syn::Expr::Index(new_expr), syn::Expr::Index(old_expr)) => { + (Expr::Index(new_expr), Expr::Index(old_expr)) => { find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls) || find_rsx_expr(&new_expr.index, &old_expr.index, rsx_calls) || new_expr.attrs != old_expr.attrs || new_expr.bracket_token != old_expr.bracket_token } - (syn::Expr::Infer(new_expr), syn::Expr::Infer(old_expr)) => new_expr != old_expr, - (syn::Expr::Let(new_expr), syn::Expr::Let(old_expr)) => { + (Expr::Infer(new_expr), Expr::Infer(old_expr)) => new_expr != old_expr, + (Expr::Let(new_expr), Expr::Let(old_expr)) => { find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls) || new_expr.attrs != old_expr.attrs || new_expr.let_token != old_expr.let_token || new_expr.pat != old_expr.pat || new_expr.eq_token != old_expr.eq_token } - (syn::Expr::Lit(new_expr), syn::Expr::Lit(old_expr)) => old_expr != new_expr, - (syn::Expr::Loop(new_expr), syn::Expr::Loop(old_expr)) => { + (Expr::Lit(new_expr), Expr::Lit(old_expr)) => old_expr != new_expr, + (Expr::Loop(new_expr), Expr::Loop(old_expr)) => { find_rsx_block(&new_expr.body, &old_expr.body, rsx_calls) || new_expr.attrs != old_expr.attrs || new_expr.label != old_expr.label || new_expr.loop_token != old_expr.loop_token } - (syn::Expr::Macro(new_expr), syn::Expr::Macro(old_expr)) => { + (Expr::Macro(new_expr), Expr::Macro(old_expr)) => { find_rsx_macro(&new_expr.mac, &old_expr.mac, rsx_calls) || new_expr.attrs != old_expr.attrs } - (syn::Expr::Match(new_expr), syn::Expr::Match(old_expr)) => { + (Expr::Match(new_expr), Expr::Match(old_expr)) => { if find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls) { return true; } @@ -491,7 +497,7 @@ fn find_rsx_expr( || new_expr.match_token != old_expr.match_token || new_expr.brace_token != old_expr.brace_token } - (syn::Expr::MethodCall(new_expr), syn::Expr::MethodCall(old_expr)) => { + (Expr::MethodCall(new_expr), Expr::MethodCall(old_expr)) => { if find_rsx_expr(&new_expr.receiver, &old_expr.receiver, rsx_calls) { return true; } @@ -506,13 +512,13 @@ fn find_rsx_expr( || new_expr.turbofish != old_expr.turbofish || new_expr.paren_token != old_expr.paren_token } - (syn::Expr::Paren(new_expr), syn::Expr::Paren(old_expr)) => { + (Expr::Paren(new_expr), Expr::Paren(old_expr)) => { find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls) || new_expr.attrs != old_expr.attrs || new_expr.paren_token != old_expr.paren_token } - (syn::Expr::Path(new_expr), syn::Expr::Path(old_expr)) => old_expr != new_expr, - (syn::Expr::Range(new_expr), syn::Expr::Range(old_expr)) => { + (Expr::Path(new_expr), Expr::Path(old_expr)) => old_expr != new_expr, + (Expr::Range(new_expr), Expr::Range(old_expr)) => { match (&new_expr.start, &old_expr.start) { (Some(new_expr), Some(old_expr)) => { if find_rsx_expr(new_expr, old_expr, rsx_calls) { @@ -534,20 +540,20 @@ fn find_rsx_expr( _ => true, } } - (syn::Expr::Reference(new_expr), syn::Expr::Reference(old_expr)) => { + (Expr::Reference(new_expr), Expr::Reference(old_expr)) => { find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls) || new_expr.attrs != old_expr.attrs || new_expr.and_token != old_expr.and_token || new_expr.mutability != old_expr.mutability } - (syn::Expr::Repeat(new_expr), syn::Expr::Repeat(old_expr)) => { + (Expr::Repeat(new_expr), Expr::Repeat(old_expr)) => { find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls) || find_rsx_expr(&new_expr.len, &old_expr.len, rsx_calls) || new_expr.attrs != old_expr.attrs || new_expr.bracket_token != old_expr.bracket_token || new_expr.semi_token != old_expr.semi_token } - (syn::Expr::Return(new_expr), syn::Expr::Return(old_expr)) => { + (Expr::Return(new_expr), Expr::Return(old_expr)) => { match (&new_expr.expr, &old_expr.expr) { (Some(new_inner), Some(old_inner)) => { find_rsx_expr(new_inner, old_inner, rsx_calls) @@ -561,7 +567,7 @@ fn find_rsx_expr( _ => true, } } - (syn::Expr::Struct(new_expr), syn::Expr::Struct(old_expr)) => { + (Expr::Struct(new_expr), Expr::Struct(old_expr)) => { match (&new_expr.rest, &old_expr.rest) { (Some(new_expr), Some(old_expr)) => { if find_rsx_expr(new_expr, old_expr, rsx_calls) { @@ -585,17 +591,17 @@ fn find_rsx_expr( || new_expr.brace_token != old_expr.brace_token || new_expr.dot2_token != old_expr.dot2_token } - (syn::Expr::Try(new_expr), syn::Expr::Try(old_expr)) => { + (Expr::Try(new_expr), Expr::Try(old_expr)) => { find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls) || new_expr.attrs != old_expr.attrs || new_expr.question_token != old_expr.question_token } - (syn::Expr::TryBlock(new_expr), syn::Expr::TryBlock(old_expr)) => { + (Expr::TryBlock(new_expr), Expr::TryBlock(old_expr)) => { find_rsx_block(&new_expr.block, &old_expr.block, rsx_calls) || new_expr.attrs != old_expr.attrs || new_expr.try_token != old_expr.try_token } - (syn::Expr::Tuple(new_expr), syn::Expr::Tuple(old_expr)) => { + (Expr::Tuple(new_expr), Expr::Tuple(old_expr)) => { for (new_el, old_el) in new_expr.elems.iter().zip(old_expr.elems.iter()) { if find_rsx_expr(new_el, old_el, rsx_calls) { return true; @@ -603,37 +609,35 @@ fn find_rsx_expr( } new_expr.attrs != old_expr.attrs || new_expr.paren_token != old_expr.paren_token } - (syn::Expr::Unary(new_expr), syn::Expr::Unary(old_expr)) => { + (Expr::Unary(new_expr), Expr::Unary(old_expr)) => { find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls) || new_expr.attrs != old_expr.attrs || new_expr.op != old_expr.op } - (syn::Expr::Unsafe(new_expr), syn::Expr::Unsafe(old_expr)) => { + (Expr::Unsafe(new_expr), Expr::Unsafe(old_expr)) => { find_rsx_block(&new_expr.block, &old_expr.block, rsx_calls) || new_expr.attrs != old_expr.attrs || new_expr.unsafe_token != old_expr.unsafe_token } - (syn::Expr::While(new_expr), syn::Expr::While(old_expr)) => { + (Expr::While(new_expr), Expr::While(old_expr)) => { find_rsx_expr(&new_expr.cond, &old_expr.cond, rsx_calls) || find_rsx_block(&new_expr.body, &old_expr.body, rsx_calls) || new_expr.attrs != old_expr.attrs || new_expr.label != old_expr.label || new_expr.while_token != old_expr.while_token } - (syn::Expr::Yield(new_expr), syn::Expr::Yield(old_expr)) => { - match (&new_expr.expr, &old_expr.expr) { - (Some(new_inner), Some(old_inner)) => { - find_rsx_expr(new_inner, old_inner, rsx_calls) - || new_expr.attrs != old_expr.attrs - || new_expr.yield_token != old_expr.yield_token - } - (None, None) => { - new_expr.attrs != old_expr.attrs || new_expr.yield_token != old_expr.yield_token - } - _ => true, + (Expr::Yield(new_expr), Expr::Yield(old_expr)) => match (&new_expr.expr, &old_expr.expr) { + (Some(new_inner), Some(old_inner)) => { + find_rsx_expr(new_inner, old_inner, rsx_calls) + || new_expr.attrs != old_expr.attrs + || new_expr.yield_token != old_expr.yield_token } - } - (syn::Expr::Verbatim(stream), syn::Expr::Verbatim(stream2)) => { + (None, None) => { + new_expr.attrs != old_expr.attrs || new_expr.yield_token != old_expr.yield_token + } + _ => true, + }, + (Expr::Verbatim(stream), Expr::Verbatim(stream2)) => { stream.to_string() != stream2.to_string() } _ => true, diff --git a/packages/rsx/src/hot_reload/hot_reloading_file_map.rs b/packages/rsx/src/hot_reload/hot_reloading_file_map.rs index 37ef95279..c40191cfd 100644 --- a/packages/rsx/src/hot_reload/hot_reloading_file_map.rs +++ b/packages/rsx/src/hot_reload/hot_reloading_file_map.rs @@ -20,7 +20,7 @@ pub use syn::__private::ToTokens; use syn::spanned::Spanned; use super::{ - hot_reload_diff::{find_rsx, DiffResult}, + hot_reload_diff::{diff_rsx, DiffResult}, ChangedRsx, }; @@ -118,7 +118,7 @@ impl FileMap { // And collect out its errors instead of giving up to a full rebuild let old = syn::parse_file(&*old_cached.raw).map_err(|_e| HotreloadError::Parse)?; - let instances = match find_rsx(&syntax, &old) { + let instances = match diff_rsx(&syntax, &old) { // If the changes were just some rsx, we can just update the template // // However... if the changes involved code in the rsx itself, this should actually be a CodeChanged @@ -128,7 +128,8 @@ impl FileMap { // If the changes were some code, we should insert the file into the map and rebuild // todo: not sure we even need to put the cached file into the map, but whatever - DiffResult::CodeChanged => { + DiffResult::CodeChanged(_) => { + println!("code changed"); let cached_file = CachedSynFile { raw: src.clone(), path: file_path.to_path_buf(), @@ -282,22 +283,22 @@ impl FileMap { fn child_in_workspace(&mut self, crate_dir: &Path) -> io::Result> { if let Some(in_workspace) = self.in_workspace.get(crate_dir) { - Ok(in_workspace.clone()) - } else { - let mut cmd = Cmd::new(); - let manafest_path = crate_dir.join("Cargo.toml"); - cmd.manifest_path(&manafest_path); - let cmd: MetadataCommand = cmd.into(); - let metadata = cmd - .exec() - .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; - - let in_workspace = metadata.workspace_root != crate_dir; - let workspace_path = in_workspace.then(|| metadata.workspace_root.into()); - self.in_workspace - .insert(crate_dir.to_path_buf(), workspace_path.clone()); - Ok(workspace_path) + return Ok(in_workspace.clone()); } + + let mut cmd = Cmd::new(); + let manafest_path = crate_dir.join("Cargo.toml"); + cmd.manifest_path(&manafest_path); + let cmd: MetadataCommand = cmd.into(); + let metadata = cmd + .exec() + .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?; + + let in_workspace = metadata.workspace_root != crate_dir; + let workspace_path = in_workspace.then(|| metadata.workspace_root.into()); + self.in_workspace + .insert(crate_dir.to_path_buf(), workspace_path.clone()); + Ok(workspace_path) } } diff --git a/packages/rsx/src/hot_reload/mod.rs b/packages/rsx/src/hot_reload/mod.rs index b42ed454d..869164454 100644 --- a/packages/rsx/src/hot_reload/mod.rs +++ b/packages/rsx/src/hot_reload/mod.rs @@ -1,6 +1,8 @@ mod hot_reload_diff; pub use hot_reload_diff::*; + mod hot_reloading_context; pub use hot_reloading_context::*; + mod hot_reloading_file_map; pub use hot_reloading_file_map::*; diff --git a/packages/rsx/tests/hotreloads.rs b/packages/rsx/tests/hotreloads.rs new file mode 100644 index 000000000..58e31606f --- /dev/null +++ b/packages/rsx/tests/hotreloads.rs @@ -0,0 +1,19 @@ +use dioxus_rsx::hot_reload::diff_rsx; +use syn::File; + +fn load_files(old: &str, new: &str) -> (File, File) { + let old = syn::parse_file(old).unwrap(); + let new = syn::parse_file(new).unwrap(); + (old, new) +} + +#[test] +fn hotreloads() { + let (old, new) = load_files( + include_str!("./valid_samples/old.expr.rsx"), + include_str!("./valid_samples/new.expr.rsx"), + ); + + let res = diff_rsx(&new, &old); + dbg!(res); +} diff --git a/packages/rsx/tests/valid_samples/new.expr.rsx b/packages/rsx/tests/valid_samples/new.expr.rsx new file mode 100644 index 000000000..eb0d04b59 --- /dev/null +++ b/packages/rsx/tests/valid_samples/new.expr.rsx @@ -0,0 +1,17 @@ +use dioxus::prelude::*; + +pub fn CoolChild() -> Element { + let head_ = rsx! { + div { + div { "asasddasdasd" } + div { "asasdd1asaassdd23asasddasd" } + div { "aasdsdsaasdsddasd" } + } + }; + + rsx! { + div { + {head_} + } + } +} diff --git a/packages/rsx/tests/valid_samples/old.expr.rsx b/packages/rsx/tests/valid_samples/old.expr.rsx new file mode 100644 index 000000000..c356ac638 --- /dev/null +++ b/packages/rsx/tests/valid_samples/old.expr.rsx @@ -0,0 +1,17 @@ +use dioxus::prelude::*; + +pub fn CoolChild() -> Element { + let head_ = rsx! { + div { + div { "asasddasdasd" } + div { "asasdd1asaassdd23asasddasd" } + // div { "aasdsdsaasdsddasd" } + } + }; + + rsx! { + div { + {head_} + } + } +} diff --git a/packages/web/src/hot_reload.rs b/packages/web/src/hot_reload.rs index 5c3d0340e..aeb889a17 100644 --- a/packages/web/src/hot_reload.rs +++ b/packages/web/src/hot_reload.rs @@ -40,29 +40,27 @@ pub(crate) fn init() -> UnboundedReceiver