From 49b3ef83d33932bb8ee0e15f128f737994ef902a Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 9 Jun 2022 09:37:26 -0500 Subject: [PATCH 01/21] create websocket to update rsx and report errors back --- Cargo.toml | 5 + src/hot_reload/mod.rs | 614 ++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 3 + src/server/mod.rs | 174 ++++++++++-- 4 files changed, 770 insertions(+), 26 deletions(-) create mode 100644 src/hot_reload/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 21e2dad7e..4acd41463 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,11 @@ flate2 = "1.0.22" tar = "0.4.38" tower = "0.4.12" +# hot reload +syn = "1.0" +dioxus-rsx-interpreter = { path = "../dioxus/packages/rsx_interpreter" } +proc-macro2 = { version = "1.0", features = ["span-locations"] } + [[bin]] path = "src/main.rs" name = "dioxus" diff --git a/src/hot_reload/mod.rs b/src/hot_reload/mod.rs new file mode 100644 index 000000000..d70cda29e --- /dev/null +++ b/src/hot_reload/mod.rs @@ -0,0 +1,614 @@ +use dioxus_rsx::{BodyNode, CallBody}; +use proc_macro2::TokenStream; +use syn::{File, Macro}; + +pub enum DiffResult { + CodeChanged, + RsxChanged(Vec<(Macro, TokenStream)>), +} + +pub fn find_rsx(new: &File, old: &File) -> DiffResult { + let mut rsx_calls = Vec::new(); + if new.items.len() != old.items.len() { + return DiffResult::CodeChanged; + } + for (new, old) in new.items.iter().zip(old.items.iter()) { + if find_rsx_item(new, old, &mut rsx_calls) { + return DiffResult::CodeChanged; + } + } + DiffResult::RsxChanged(rsx_calls) +} + +fn find_rsx_item( + new: &syn::Item, + old: &syn::Item, + rsx_calls: &mut Vec<(Macro, TokenStream)>, +) -> bool { + match (new, old) { + (syn::Item::Const(new_item), syn::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 + || new_item.const_token != old_item.const_token + || new_item.ident != old_item.ident + || new_item.colon_token != old_item.colon_token + || new_item.ty != old_item.ty + || new_item.eq_token != old_item.eq_token + || new_item.semi_token != old_item.semi_token + } + (syn::Item::Enum(new_item), syn::Item::Enum(old_item)) => { + if new_item.variants.len() != old_item.variants.len() { + return true; + } + for (new_varient, old_varient) in new_item.variants.iter().zip(old_item.variants.iter()) + { + match (&new_varient.discriminant, &old_varient.discriminant) { + (Some((new_eq, new_expr)), Some((old_eq, old_expr))) => { + if find_rsx_expr(new_expr, old_expr, rsx_calls) || new_eq != old_eq { + return true; + } + } + (None, None) => (), + _ => return true, + } + if new_varient.attrs != old_varient.attrs + || new_varient.ident != old_varient.ident + || new_varient.fields != old_varient.fields + { + return true; + } + } + new_item.attrs != old_item.attrs + || new_item.vis != old_item.vis + || new_item.enum_token != old_item.enum_token + || new_item.ident != old_item.ident + || new_item.generics != old_item.generics + || new_item.brace_token != old_item.brace_token + } + (syn::Item::ExternCrate(new_item), syn::Item::ExternCrate(old_item)) => { + old_item != new_item + } + (syn::Item::Fn(new_item), syn::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)) => { + if new_item.items.len() != old_item.items.len() { + return true; + } + for (new_item, old_item) in new_item.items.iter().zip(old_item.items.iter()) { + if match (new_item, old_item) { + (syn::ImplItem::Const(new_item), syn::ImplItem::Const(old_item)) => { + find_rsx_expr(&new_item.expr, &old_item.expr, rsx_calls) + } + (syn::ImplItem::Method(new_item), syn::ImplItem::Method(old_item)) => { + find_rsx_block(&new_item.block, &old_item.block, rsx_calls) + } + (syn::ImplItem::Type(new_item), syn::ImplItem::Type(old_item)) => { + old_item != new_item + } + (syn::ImplItem::Macro(new_item), syn::ImplItem::Macro(old_item)) => { + old_item != new_item + } + _ => true, + } { + return true; + } + } + new_item.attrs != old_item.attrs + || new_item.defaultness != old_item.defaultness + || new_item.unsafety != old_item.unsafety + || new_item.impl_token != old_item.impl_token + || new_item.generics != old_item.generics + || new_item.trait_ != old_item.trait_ + || new_item.self_ty != old_item.self_ty + || new_item.brace_token != old_item.brace_token + } + (syn::Item::Macro(new_item), syn::Item::Macro(old_item)) => old_item != new_item, + (syn::Item::Macro2(new_item), syn::Item::Macro2(old_item)) => old_item != new_item, + (syn::Item::Mod(new_item), syn::Item::Mod(old_item)) => { + match (&new_item.content, &old_item.content) { + (Some((_, new_items)), Some((_, old_items))) => { + if new_items.len() != old_items.len() { + return true; + } + for (new_item, old_item) in new_items.iter().zip(old_items.iter()) { + if find_rsx_item(new_item, old_item, rsx_calls) { + return true; + } + } + new_item.attrs != old_item.attrs + || new_item.vis != old_item.vis + || new_item.mod_token != old_item.mod_token + || new_item.ident != old_item.ident + || new_item.semi != old_item.semi + } + (None, None) => { + new_item.attrs != old_item.attrs + || new_item.vis != old_item.vis + || new_item.mod_token != old_item.mod_token + || new_item.ident != old_item.ident + || new_item.semi != old_item.semi + } + _ => true, + } + } + (syn::Item::Static(new_item), syn::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 + || new_item.static_token != old_item.static_token + || new_item.mutability != old_item.mutability + || new_item.ident != old_item.ident + || new_item.colon_token != old_item.colon_token + || new_item.ty != old_item.ty + || new_item.eq_token != old_item.eq_token + || new_item.semi_token != old_item.semi_token + } + (syn::Item::Struct(new_item), syn::Item::Struct(old_item)) => old_item != new_item, + (syn::Item::Trait(new_item), syn::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, + _ => return true, + } +} + +fn find_rsx_trait( + new_item: &syn::ItemTrait, + old_item: &syn::ItemTrait, + rsx_calls: &mut Vec<(Macro, TokenStream)>, +) -> bool { + if new_item.items.len() != old_item.items.len() { + return true; + } + for (new_item, old_item) in new_item.items.iter().zip(old_item.items.iter()) { + if match (new_item, old_item) { + (syn::TraitItem::Const(new_item), syn::TraitItem::Const(old_item)) => { + if let (Some((_, new_expr)), Some((_, old_expr))) = + (&new_item.default, &old_item.default) + { + find_rsx_expr(new_expr, old_expr, rsx_calls) + } else { + true + } + } + (syn::TraitItem::Method(new_item), syn::TraitItem::Method(old_item)) => { + if let (Some(new_block), Some(old_block)) = (&new_item.default, &old_item.default) { + find_rsx_block(new_block, old_block, rsx_calls) + } else { + true + } + } + (syn::TraitItem::Type(new_item), syn::TraitItem::Type(old_item)) => { + old_item != new_item + } + (syn::TraitItem::Macro(new_item), syn::TraitItem::Macro(old_item)) => { + old_item != new_item + } + _ => true, + } { + return true; + } + } + new_item.attrs != old_item.attrs + || new_item.vis != old_item.vis + || new_item.unsafety != old_item.unsafety + || new_item.auto_token != old_item.auto_token + || new_item.ident != old_item.ident + || new_item.generics != old_item.generics + || new_item.colon_token != old_item.colon_token + || new_item.supertraits != old_item.supertraits + || new_item.brace_token != old_item.brace_token +} + +fn find_rsx_block( + new_block: &syn::Block, + old_block: &syn::Block, + rsx_calls: &mut Vec<(Macro, TokenStream)>, +) -> bool { + 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 +} + +fn find_rsx_stmt( + new_stmt: &syn::Stmt, + old_stmt: &syn::Stmt, + rsx_calls: &mut Vec<(Macro, TokenStream)>, +) -> bool { + match (new_stmt, old_stmt) { + (syn::Stmt::Local(new_local), syn::Stmt::Local(old_local)) => { + (match (&new_local.init, &old_local.init) { + (Some((new_eq, new_expr)), Some((old_eq, old_expr))) => { + find_rsx_expr(new_expr, old_expr, rsx_calls) || new_eq != old_eq + } + (None, None) => false, + _ => true, + } || new_local.attrs != old_local.attrs + || new_local.let_token != old_local.let_token + || new_local.pat != old_local.pat + || new_local.semi_token != old_local.semi_token) + } + (syn::Stmt::Item(new_item), syn::Stmt::Item(old_item)) => { + find_rsx_item(new_item, old_item, rsx_calls) + } + (syn::Stmt::Expr(new_expr), syn::Stmt::Expr(old_expr)) => { + find_rsx_expr(new_expr, old_expr, rsx_calls) + } + (syn::Stmt::Semi(new_expr, new_semi), syn::Stmt::Semi(old_expr, old_semi)) => { + find_rsx_expr(new_expr, old_expr, rsx_calls) || new_semi != old_semi + } + _ => true, + } +} + +fn find_rsx_expr( + new_expr: &syn::Expr, + old_expr: &syn::Expr, + rsx_calls: &mut Vec<(Macro, TokenStream)>, +) -> bool { + match (new_expr, old_expr) { + (syn::Expr::Array(new_expr), syn::Expr::Array(old_expr)) => { + if new_expr.elems.len() != old_expr.elems.len() { + return true; + } + 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; + } + } + new_expr.attrs != old_expr.attrs || new_expr.bracket_token != old_expr.bracket_token + } + (syn::Expr::Assign(new_expr), syn::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::AssignOp(new_expr), syn::Expr::AssignOp(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::Async(new_expr), syn::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)) => { + 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)) => { + 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)) => { + 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::Box(new_expr), syn::Expr::Box(old_expr)) => { + find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls) + || new_expr.attrs != old_expr.attrs + || new_expr.box_token != old_expr.box_token + } + (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, + } + } + (syn::Expr::Call(new_expr), syn::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; + } + for (new_arg, old_arg) in new_expr.args.iter().zip(old_expr.args.iter()) { + if find_rsx_expr(new_arg, old_arg, rsx_calls) { + return true; + } + } + new_expr.attrs != old_expr.attrs || new_expr.paren_token != old_expr.paren_token + } + (syn::Expr::Cast(new_expr), syn::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)) => { + find_rsx_expr(&new_expr.body, &old_expr.body, rsx_calls) + || new_expr.attrs != old_expr.attrs + || new_expr.movability != old_expr.movability + || new_expr.asyncness != old_expr.asyncness + || new_expr.capture != old_expr.capture + || new_expr.or1_token != old_expr.or1_token + || new_expr.inputs != old_expr.inputs + || new_expr.or2_token != old_expr.or2_token + || new_expr.output != old_expr.output + } + (syn::Expr::Continue(new_expr), syn::Expr::Continue(old_expr)) => old_expr != new_expr, + (syn::Expr::Field(new_expr), syn::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)) => { + 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 + || new_expr.label != old_expr.label + || new_expr.for_token != old_expr.for_token + || new_expr.pat != old_expr.pat + || new_expr.in_token != old_expr.in_token + } + (syn::Expr::Group(new_expr), syn::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)) => { + 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) + { + return true; + } + match (&new_expr.else_branch, &old_expr.else_branch) { + (Some((new_tok, new_else)), Some((old_tok, old_else))) => { + find_rsx_expr(new_else, old_else, rsx_calls) + || new_expr.attrs != old_expr.attrs + || new_expr.if_token != old_expr.if_token + || new_expr.cond != old_expr.cond + || new_tok != old_tok + } + (None, None) => { + new_expr.attrs != old_expr.attrs + || new_expr.if_token != old_expr.if_token + || new_expr.cond != old_expr.cond + } + _ => true, + } + } + (syn::Expr::Index(new_expr), syn::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::Let(new_expr), syn::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)) => { + 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)) => { + let new_mac = &new_expr.mac; + let old_mac = &old_expr.mac; + if new_mac.path.get_ident().map(|ident| ident.to_string()) == Some("rsx".to_string()) + && old_mac.path.get_ident().map(|ident| ident.to_string()) + == Some("rsx".to_string()) + { + rsx_calls.push((old_mac.clone(), new_mac.tokens.clone())); + false + } else { + new_expr != old_expr + } + } + (syn::Expr::Match(new_expr), syn::Expr::Match(old_expr)) => { + if find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls) { + return true; + } + for (new_arm, old_arm) in new_expr.arms.iter().zip(old_expr.arms.iter()) { + match (&new_arm.guard, &old_arm.guard) { + (Some((new_tok, new_expr)), Some((old_tok, old_expr))) => { + if find_rsx_expr(new_expr, old_expr, rsx_calls) || new_tok != old_tok { + return true; + } + } + (None, None) => (), + _ => return true, + } + if find_rsx_expr(&new_arm.body, &old_arm.body, rsx_calls) + || new_arm.attrs != old_arm.attrs + || new_arm.pat != old_arm.pat + || new_arm.fat_arrow_token != old_arm.fat_arrow_token + || new_arm.comma != old_arm.comma + { + return true; + } + } + new_expr.attrs != old_expr.attrs + || 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)) => { + if find_rsx_expr(&new_expr.receiver, &old_expr.receiver, rsx_calls) { + return true; + } + for (new_arg, old_arg) in new_expr.args.iter().zip(old_expr.args.iter()) { + if find_rsx_expr(new_arg, old_arg, rsx_calls) { + return true; + } + } + new_expr.attrs != old_expr.attrs + || new_expr.dot_token != old_expr.dot_token + || new_expr.method != old_expr.method + || new_expr.turbofish != old_expr.turbofish + || new_expr.paren_token != old_expr.paren_token + } + (syn::Expr::Paren(new_expr), syn::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)) => { + match (&new_expr.from, &old_expr.from) { + (Some(new_expr), Some(old_expr)) => { + if find_rsx_expr(new_expr, old_expr, rsx_calls) { + return true; + } + } + (None, None) => (), + _ => return true, + } + match (&new_expr.to, &old_expr.to) { + (Some(new_inner), Some(old_inner)) => { + find_rsx_expr(new_inner, old_inner, rsx_calls) + || new_expr.attrs != old_expr.attrs + || new_expr.limits != old_expr.limits + } + (None, None) => { + new_expr.attrs != old_expr.attrs || new_expr.limits != old_expr.limits + } + _ => true, + } + } + (syn::Expr::Reference(new_expr), syn::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)) => { + 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)) => { + 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.return_token != old_expr.return_token + } + (None, None) => { + new_expr.attrs != old_expr.attrs + || new_expr.return_token != old_expr.return_token + } + _ => true, + } + } + (syn::Expr::Struct(new_expr), syn::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) { + return true; + } + } + (None, None) => (), + _ => return true, + } + for (new_field, old_field) in new_expr.fields.iter().zip(old_expr.fields.iter()) { + if find_rsx_expr(&new_field.expr, &old_field.expr, rsx_calls) + || new_field.attrs != old_field.attrs + || new_field.member != old_field.member + || new_field.colon_token != old_field.colon_token + { + return true; + } + } + new_expr.attrs != old_expr.attrs + || new_expr.path != old_expr.path + || 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)) => { + 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)) => { + 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)) => { + 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; + } + } + new_expr.attrs != old_expr.attrs || new_expr.paren_token != old_expr.paren_token + } + (syn::Expr::Type(new_expr), syn::Expr::Type(old_expr)) => { + find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls) + || new_expr.attrs != old_expr.attrs + || new_expr.colon_token != old_expr.colon_token + || new_expr.ty != old_expr.ty + } + (syn::Expr::Unary(new_expr), syn::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)) => { + 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)) => { + 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, + } + } + (syn::Expr::Verbatim(_), syn::Expr::Verbatim(_)) => false, + _ => true, + } +} diff --git a/src/lib.rs b/src/lib.rs index 8cd4b1f2d..722b03a06 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,3 +18,6 @@ pub use error::*; pub mod logging; pub use logging::*; + +pub mod hot_reload; +pub use hot_reload::*; diff --git a/src/server/mod.rs b/src/server/mod.rs index 1df13ca6a..230136e54 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -7,18 +7,28 @@ use axum::{ Router, }; use notify::{RecommendedWatcher, Watcher}; +use std::{fs::File, io::Read}; +use syn::__private::ToTokens; use std::{path::PathBuf, sync::Arc}; use tower::ServiceBuilder; use tower_http::services::fs::{ServeDir, ServeFileSystemResponseBody}; -use crate::{builder, serve::Serve, CrateConfig, Result}; +use crate::{builder, find_rsx, serve::Serve, CrateConfig, Result}; use tokio::sync::broadcast; -struct WsRelodState { +use std::collections::HashMap; + +use dioxus_rsx_interpreter::{error::RecompileReason, CodeLocation, SetRsxMessage}; + +struct WsReloadState { update: broadcast::Sender, } +struct HotReloadState { + messages: broadcast::Sender, +} + pub async fn startup(config: CrateConfig) -> Result<()> { log::info!("🚀 Starting development server..."); @@ -26,35 +36,20 @@ pub async fn startup(config: CrateConfig) -> Result<()> { let (reload_tx, _) = broadcast::channel(100); - let ws_reload_state = Arc::new(WsRelodState { + let ws_reload_state = Arc::new(WsReloadState { update: reload_tx.clone(), }); + let hot_reload_tx = broadcast::channel(100).0; + let hot_reload_state = Arc::new(HotReloadState { + messages: hot_reload_tx.clone(), + }); + let mut last_update_time = chrono::Local::now().timestamp(); // file watcher: check file change let watcher_conf = config.clone(); - let mut watcher = RecommendedWatcher::new(move |_: notify::Result| { - if chrono::Local::now().timestamp() > last_update_time { - log::info!("Start to rebuild project..."); - if builder::build(&watcher_conf).is_ok() { - // change the websocket reload state to true; - // the page will auto-reload. - if watcher_conf - .dioxus_config - .web - .watcher - .reload_html - .unwrap_or(false) - { - let _ = Serve::regen_dev_page(&watcher_conf); - } - let _ = reload_tx.send("reload".into()); - last_update_time = chrono::Local::now().timestamp(); - } - } - }) - .unwrap(); + let mut old_files_parsed: HashMap = HashMap::new(); let allow_watch_path = config .dioxus_config .web @@ -63,6 +58,92 @@ pub async fn startup(config: CrateConfig) -> Result<()> { .clone() .unwrap_or_else(|| vec![PathBuf::from("src")]); + let crate_dir = config.crate_dir.clone(); + + let mut watcher = RecommendedWatcher::new(move |evt: notify::Result| { + if let Ok(evt) = evt { + if let notify::EventKind::Modify(_) = evt.kind { + for path in evt.paths { + log::info!("File changed: {}", path.display()); + let mut file = File::open(path.clone()).unwrap(); + let mut src = String::new(); + file.read_to_string(&mut src).expect("Unable to read file"); + if src.is_empty() { + continue; + } + if let Ok(syntax) = syn::parse_file(&src) { + if let Some(old_str) = old_files_parsed.get(path.to_str().unwrap()) { + if let Ok(old) = syn::parse_file(&old_str) { + match find_rsx(&syntax, &old) { + crate::DiffResult::CodeChanged => { + log::info!("reload required"); + if chrono::Local::now().timestamp() > last_update_time { + log::info!("Start to rebuild project..."); + if builder::build(&watcher_conf).is_ok() { + // change the websocket reload state to true; + // the page will auto-reload. + if watcher_conf + .dioxus_config + .web + .watcher + .reload_html + .unwrap_or(false) + { + let _ = Serve::regen_dev_page(&watcher_conf); + } + let _ = reload_tx.send("reload".into()); + old_files_parsed.insert( + path.to_str().unwrap().to_string(), + src, + ); + last_update_time = chrono::Local::now().timestamp(); + } + } + } + crate::DiffResult::RsxChanged(changed) => { + for (old, new) in changed.into_iter() { + if let Some(hr) = old + .to_token_stream() + .into_iter() + .map(|tree| { + let location = tree.span(); + let start = location.start(); + CodeLocation { + file: path + .strip_prefix(&crate_dir) + .unwrap() + .display() + .to_string(), + line: start.line as u32, + column: start.column as u32 + 1, + } + }) + .min_by(|cl1, cl2| { + cl1.line + .cmp(&cl2.line) + .then(cl1.column.cmp(&cl2.column)) + }) + { + let rsx = new.to_string(); + let _ = hot_reload_tx.send(SetRsxMessage { + location: hr, + new_text: rsx, + }); + } + } + } + } + } + } else { + old_files_parsed.insert(path.to_str().unwrap().to_string(), src); + } + } + } + } + } + }) + .unwrap(); + for sub_path in allow_watch_path { watcher .watch( @@ -115,6 +196,7 @@ pub async fn startup(config: CrateConfig) -> Result<()> { let router = Router::new() .route("/_dioxus/ws", get(ws_handler)) + .route("/_dioxus/hot_reload", get(hot_reload_handler)) .fallback( get_service(file_service).handle_error(|error: std::io::Error| async move { ( @@ -125,7 +207,12 @@ pub async fn startup(config: CrateConfig) -> Result<()> { ); axum::Server::bind(&format!("0.0.0.0:{}", port).parse().unwrap()) - .serve(router.layer(Extension(ws_reload_state)).into_make_service()) + .serve( + router + .layer(Extension(ws_reload_state)) + .layer(Extension(hot_reload_state)) + .into_make_service(), + ) .await?; Ok(()) @@ -134,7 +221,7 @@ pub async fn startup(config: CrateConfig) -> Result<()> { async fn ws_handler( ws: WebSocketUpgrade, _: Option>, - Extension(state): Extension>, + Extension(state): Extension>, ) -> impl IntoResponse { ws.on_upgrade(|mut socket| async move { let mut rx = state.update.subscribe(); @@ -157,3 +244,38 @@ async fn ws_handler( reload_watcher.await.unwrap(); }) } + +async fn hot_reload_handler( + ws: WebSocketUpgrade, + _: Option>, + Extension(state): Extension>, +) -> impl IntoResponse { + ws.on_upgrade(|mut socket| async move { + println!("🔥 Hot Reload WebSocket is connected."); + let mut rx = state.messages.subscribe(); + loop { + let read_set_rsx = rx.recv(); + let read_err = socket.recv(); + tokio::select! { + err = read_err => { + if let Some(Ok(Message::Text(err))) = err { + let error: RecompileReason = serde_json::from_str(&err).unwrap(); + log::error!("{:?}", error); + }; + }, + set_rsx = read_set_rsx => { + if let Ok(rsx) = set_rsx{ + if socket + .send(Message::Text(serde_json::to_string(&rsx).unwrap())) + .await + .is_err() + { + break; + }; + // println!("{:?}", rsx); + } + } + }; + } + }) +} From 3ccd65b582b8320b24c8921829b777084a6d2e1e Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 9 Jun 2022 10:35:18 -0500 Subject: [PATCH 02/21] remove unused import --- src/hot_reload/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hot_reload/mod.rs b/src/hot_reload/mod.rs index d70cda29e..3aced9567 100644 --- a/src/hot_reload/mod.rs +++ b/src/hot_reload/mod.rs @@ -1,4 +1,3 @@ -use dioxus_rsx::{BodyNode, CallBody}; use proc_macro2::TokenStream; use syn::{File, Macro}; From 61e9f56816495ad18c367cc72038409e60382ddc Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 9 Jun 2022 11:37:48 -0500 Subject: [PATCH 03/21] automaticly reload when rsx requires it --- src/server/mod.rs | 59 +++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/src/server/mod.rs b/src/server/mod.rs index 230136e54..c4ab60385 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -7,7 +7,7 @@ use axum::{ Router, }; use notify::{RecommendedWatcher, Watcher}; -use std::{fs::File, io::Read}; +use std::{fs::File, io::Read, sync::Mutex}; use syn::__private::ToTokens; use std::{path::PathBuf, sync::Arc}; @@ -23,10 +23,13 @@ use dioxus_rsx_interpreter::{error::RecompileReason, CodeLocation, SetRsxMessage struct WsReloadState { update: broadcast::Sender, + last_file_rebuild: Arc>>, + watcher_config: CrateConfig, } struct HotReloadState { messages: broadcast::Sender, + update: broadcast::Sender, } pub async fn startup(config: CrateConfig) -> Result<()> { @@ -36,20 +39,23 @@ pub async fn startup(config: CrateConfig) -> Result<()> { let (reload_tx, _) = broadcast::channel(100); + let last_file_rebuild = Arc::new(Mutex::new(HashMap::new())); + let ws_reload_state = Arc::new(WsReloadState { update: reload_tx.clone(), + last_file_rebuild: last_file_rebuild.clone(), + watcher_config: config.clone(), }); let hot_reload_tx = broadcast::channel(100).0; let hot_reload_state = Arc::new(HotReloadState { messages: hot_reload_tx.clone(), + update: reload_tx.clone(), }); let mut last_update_time = chrono::Local::now().timestamp(); // file watcher: check file change - let watcher_conf = config.clone(); - let mut old_files_parsed: HashMap = HashMap::new(); let allow_watch_path = config .dioxus_config .web @@ -64,7 +70,6 @@ pub async fn startup(config: CrateConfig) -> Result<()> { if let Ok(evt) = evt { if let notify::EventKind::Modify(_) = evt.kind { for path in evt.paths { - log::info!("File changed: {}", path.display()); let mut file = File::open(path.clone()).unwrap(); let mut src = String::new(); file.read_to_string(&mut src).expect("Unable to read file"); @@ -72,35 +77,19 @@ pub async fn startup(config: CrateConfig) -> Result<()> { continue; } if let Ok(syntax) = syn::parse_file(&src) { - if let Some(old_str) = old_files_parsed.get(path.to_str().unwrap()) { + let mut last_file_rebuild = last_file_rebuild.lock().unwrap(); + if let Some(old_str) = last_file_rebuild.get(path.to_str().unwrap()) { if let Ok(old) = syn::parse_file(&old_str) { match find_rsx(&syntax, &old) { crate::DiffResult::CodeChanged => { log::info!("reload required"); if chrono::Local::now().timestamp() > last_update_time { - log::info!("Start to rebuild project..."); - if builder::build(&watcher_conf).is_ok() { - // change the websocket reload state to true; - // the page will auto-reload. - if watcher_conf - .dioxus_config - .web - .watcher - .reload_html - .unwrap_or(false) - { - let _ = Serve::regen_dev_page(&watcher_conf); - } - let _ = reload_tx.send("reload".into()); - old_files_parsed.insert( - path.to_str().unwrap().to_string(), - src, - ); - last_update_time = chrono::Local::now().timestamp(); - } + let _ = reload_tx.send("reload".into()); + last_update_time = chrono::Local::now().timestamp(); } } crate::DiffResult::RsxChanged(changed) => { + log::info!("reloading rsx"); for (old, new) in changed.into_iter() { if let Some(hr) = old .to_token_stream() @@ -135,7 +124,7 @@ pub async fn startup(config: CrateConfig) -> Result<()> { } } } else { - old_files_parsed.insert(path.to_str().unwrap().to_string(), src); + last_file_rebuild.insert(path.to_str().unwrap().to_string(), src); } } } @@ -229,6 +218,22 @@ async fn ws_handler( loop { let v = rx.recv().await.unwrap(); if v == "reload" { + log::info!("Start to rebuild project..."); + if builder::build(&state.watcher_config).is_ok() { + // change the websocket reload state to true; + // the page will auto-reload. + if state + .watcher_config + .dioxus_config + .web + .watcher + .reload_html + .unwrap_or(false) + { + let _ = Serve::regen_dev_page(&state.watcher_config); + } + *state.last_file_rebuild.lock().unwrap() = HashMap::new(); + } // ignore the error if socket .send(Message::Text(String::from("reload"))) @@ -261,6 +266,7 @@ async fn hot_reload_handler( if let Some(Ok(Message::Text(err))) = err { let error: RecompileReason = serde_json::from_str(&err).unwrap(); log::error!("{:?}", error); + state.update.send("reload".to_string()).unwrap(); }; }, set_rsx = read_set_rsx => { @@ -272,7 +278,6 @@ async fn hot_reload_handler( { break; }; - // println!("{:?}", rsx); } } }; From f19958d766c5584c89df6d617170e87f275591bd Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 9 Jun 2022 11:58:09 -0500 Subject: [PATCH 04/21] move into feature --- Cargo.toml | 10 +-- src/lib.rs | 2 +- src/server/mod.rs | 159 +++++++++++++++++++++++++++------------------- 3 files changed, 100 insertions(+), 71 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4acd41463..770fd7b1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,10 +46,12 @@ flate2 = "1.0.22" tar = "0.4.38" tower = "0.4.12" -# hot reload -syn = "1.0" -dioxus-rsx-interpreter = { path = "../dioxus/packages/rsx_interpreter" } -proc-macro2 = { version = "1.0", features = ["span-locations"] } +syn = { version = "1.0", optional = true } +dioxus-rsx-interpreter = { path = "../dioxus/packages/rsx_interpreter", optional = true } +proc-macro2 = { version = "1.0", features = ["span-locations"], optional = true } + +[features] +hot_reload = ["dioxus-rsx-interpreter", "proc-macro2", "syn"] [[bin]] path = "src/main.rs" diff --git a/src/lib.rs b/src/lib.rs index 722b03a06..683b7251a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,5 +19,5 @@ pub use error::*; pub mod logging; pub use logging::*; +#[cfg(feature = "hot_reload")] pub mod hot_reload; -pub use hot_reload::*; diff --git a/src/server/mod.rs b/src/server/mod.rs index c4ab60385..18d50fc57 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -7,26 +7,34 @@ use axum::{ Router, }; use notify::{RecommendedWatcher, Watcher}; -use std::{fs::File, io::Read, sync::Mutex}; -use syn::__private::ToTokens; +use std::{fs::File, io::Read}; use std::{path::PathBuf, sync::Arc}; use tower::ServiceBuilder; use tower_http::services::fs::{ServeDir, ServeFileSystemResponseBody}; -use crate::{builder, find_rsx, serve::Serve, CrateConfig, Result}; +use crate::{builder, serve::Serve, CrateConfig, Result}; use tokio::sync::broadcast; -use std::collections::HashMap; - -use dioxus_rsx_interpreter::{error::RecompileReason, CodeLocation, SetRsxMessage}; +#[cfg(feature = "hot_reload")] +mod hot_reload_improts { + pub use crate::hot_reload::{find_rsx, DiffResult}; + pub use dioxus_rsx_interpreter::{error::RecompileReason, CodeLocation, SetRsxMessage}; + pub use std::collections::HashMap; + pub use std::sync::Mutex; + pub use syn::__private::ToTokens; +} +#[cfg(feature = "hot_reload")] +use hot_reload_improts::*; struct WsReloadState { update: broadcast::Sender, + #[cfg(feature = "hot_reload")] last_file_rebuild: Arc>>, watcher_config: CrateConfig, } +#[cfg(feature = "hot_reload")] struct HotReloadState { messages: broadcast::Sender, update: broadcast::Sender, @@ -39,19 +47,24 @@ pub async fn startup(config: CrateConfig) -> Result<()> { let (reload_tx, _) = broadcast::channel(100); + #[cfg(feature = "hot_reload")] let last_file_rebuild = Arc::new(Mutex::new(HashMap::new())); - - let ws_reload_state = Arc::new(WsReloadState { - update: reload_tx.clone(), - last_file_rebuild: last_file_rebuild.clone(), - watcher_config: config.clone(), - }); - + #[cfg(feature = "hot_reload")] let hot_reload_tx = broadcast::channel(100).0; + #[cfg(feature = "hot_reload")] let hot_reload_state = Arc::new(HotReloadState { messages: hot_reload_tx.clone(), update: reload_tx.clone(), }); + #[cfg(feature = "hot_reload")] + let crate_dir = config.crate_dir.clone(); + + let ws_reload_state = Arc::new(WsReloadState { + update: reload_tx.clone(), + #[cfg(feature = "hot_reload")] + last_file_rebuild: last_file_rebuild.clone(), + watcher_config: config.clone(), + }); let mut last_update_time = chrono::Local::now().timestamp(); @@ -64,8 +77,6 @@ pub async fn startup(config: CrateConfig) -> Result<()> { .clone() .unwrap_or_else(|| vec![PathBuf::from("src")]); - let crate_dir = config.crate_dir.clone(); - let mut watcher = RecommendedWatcher::new(move |evt: notify::Result| { if let Ok(evt) = evt { if let notify::EventKind::Modify(_) = evt.kind { @@ -76,55 +87,66 @@ pub async fn startup(config: CrateConfig) -> Result<()> { if src.is_empty() { continue; } - 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.get(path.to_str().unwrap()) { - if let Ok(old) = syn::parse_file(&old_str) { - match find_rsx(&syntax, &old) { - crate::DiffResult::CodeChanged => { - log::info!("reload required"); - if chrono::Local::now().timestamp() > last_update_time { - let _ = reload_tx.send("reload".into()); - last_update_time = chrono::Local::now().timestamp(); + #[cfg(feature = "hot_reload")] + { + 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.get(path.to_str().unwrap()) { + if let Ok(old) = syn::parse_file(&old_str) { + match find_rsx(&syntax, &old) { + DiffResult::CodeChanged => { + log::info!("reload required"); + if chrono::Local::now().timestamp() > last_update_time { + let _ = reload_tx.send("reload".into()); + last_update_time = chrono::Local::now().timestamp(); + } } - } - crate::DiffResult::RsxChanged(changed) => { - log::info!("reloading rsx"); - for (old, new) in changed.into_iter() { - if let Some(hr) = old - .to_token_stream() - .into_iter() - .map(|tree| { - let location = tree.span(); - let start = location.start(); - CodeLocation { - file: path - .strip_prefix(&crate_dir) - .unwrap() - .display() - .to_string(), - line: start.line as u32, - column: start.column as u32 + 1, - } - }) - .min_by(|cl1, cl2| { - cl1.line - .cmp(&cl2.line) - .then(cl1.column.cmp(&cl2.column)) - }) - { - let rsx = new.to_string(); - let _ = hot_reload_tx.send(SetRsxMessage { - location: hr, - new_text: rsx, - }); + DiffResult::RsxChanged(changed) => { + log::info!("reloading rsx"); + for (old, new) in changed.into_iter() { + if let Some(hr) = old + .to_token_stream() + .into_iter() + .map(|tree| { + let location = tree.span(); + let start = location.start(); + CodeLocation { + file: path + .strip_prefix(&crate_dir) + .unwrap() + .display() + .to_string(), + line: start.line as u32, + column: start.column as u32 + 1, + } + }) + .min_by(|cl1, cl2| { + cl1.line + .cmp(&cl2.line) + .then(cl1.column.cmp(&cl2.column)) + }) + { + let rsx = new.to_string(); + let _ = hot_reload_tx.send(SetRsxMessage { + location: hr, + new_text: rsx, + }); + } } } } } + } else { + last_file_rebuild.insert(path.to_str().unwrap().to_string(), src); } - } else { - last_file_rebuild.insert(path.to_str().unwrap().to_string(), src); + } + } + #[cfg(not(feature = "hot_reload"))] + { + log::info!("reload required"); + if chrono::Local::now().timestamp() > last_update_time { + let _ = reload_tx.send("reload".into()); + last_update_time = chrono::Local::now().timestamp(); } } } @@ -185,7 +207,6 @@ pub async fn startup(config: CrateConfig) -> Result<()> { let router = Router::new() .route("/_dioxus/ws", get(ws_handler)) - .route("/_dioxus/hot_reload", get(hot_reload_handler)) .fallback( get_service(file_service).handle_error(|error: std::io::Error| async move { ( @@ -194,14 +215,16 @@ pub async fn startup(config: CrateConfig) -> Result<()> { ) }), ); + #[cfg(feature = "hot_reload")] + let router = router.route("/_dioxus/hot_reload", get(hot_reload_handler)); + + let router = router.layer(Extension(ws_reload_state)); + + #[cfg(feature = "hot_reload")] + let router = router.layer(Extension(hot_reload_state)); axum::Server::bind(&format!("0.0.0.0:{}", port).parse().unwrap()) - .serve( - router - .layer(Extension(ws_reload_state)) - .layer(Extension(hot_reload_state)) - .into_make_service(), - ) + .serve(router.into_make_service()) .await?; Ok(()) @@ -232,7 +255,10 @@ async fn ws_handler( { let _ = Serve::regen_dev_page(&state.watcher_config); } - *state.last_file_rebuild.lock().unwrap() = HashMap::new(); + #[cfg(feature = "hot_reload")] + { + *state.last_file_rebuild.lock().unwrap() = HashMap::new(); + } } // ignore the error if socket @@ -250,6 +276,7 @@ async fn ws_handler( }) } +#[cfg(feature = "hot_reload")] async fn hot_reload_handler( ws: WebSocketUpgrade, _: Option>, From f06920a37ec12fafbff53d766c14b89fd8b21c8a Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 9 Jun 2022 12:38:59 -0500 Subject: [PATCH 05/21] search directory .rs files to diff automaticly --- src/server/mod.rs | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/src/server/mod.rs b/src/server/mod.rs index 18d50fc57..8670debb1 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -7,7 +7,10 @@ use axum::{ Router, }; use notify::{RecommendedWatcher, Watcher}; -use std::{fs::File, io::Read}; +use std::{ + fs::{self, File}, + io::{self, Read}, +}; use std::{path::PathBuf, sync::Arc}; use tower::ServiceBuilder; @@ -50,6 +53,8 @@ pub async fn startup(config: CrateConfig) -> Result<()> { #[cfg(feature = "hot_reload")] let last_file_rebuild = Arc::new(Mutex::new(HashMap::new())); #[cfg(feature = "hot_reload")] + find_rs_files(&config.crate_dir, &mut *last_file_rebuild.lock().unwrap()).unwrap(); + #[cfg(feature = "hot_reload")] let hot_reload_tx = broadcast::channel(100).0; #[cfg(feature = "hot_reload")] let hot_reload_state = Arc::new(HotReloadState { @@ -215,6 +220,7 @@ pub async fn startup(config: CrateConfig) -> Result<()> { ) }), ); + #[cfg(feature = "hot_reload")] let router = router.route("/_dioxus/hot_reload", get(hot_reload_handler)); @@ -257,7 +263,9 @@ async fn ws_handler( } #[cfg(feature = "hot_reload")] { - *state.last_file_rebuild.lock().unwrap() = HashMap::new(); + let mut write = state.last_file_rebuild.lock().unwrap(); + *write = HashMap::new(); + find_rs_files(&state.watcher_config.crate_dir, &mut *write).unwrap(); } } // ignore the error @@ -311,3 +319,23 @@ async fn hot_reload_handler( } }) } + +#[cfg(feature = "hot_reload")] +fn find_rs_files(root: &PathBuf, files: &mut HashMap) -> io::Result<()> { + for entry in fs::read_dir(root)? { + if let Ok(entry) = entry { + let path = entry.path(); + if path.is_dir() { + find_rs_files(&path, files)?; + } else { + if path.extension().map(|s| s.to_str()).flatten() == Some("rs") { + let mut file = File::open(path.clone()).unwrap(); + let mut src = String::new(); + file.read_to_string(&mut src).expect("Unable to read file"); + files.insert(path.display().to_string(), src); + } + } + } + } + Ok(()) +} From aa9f5a1a425c0e07e234ac8e594d041b53fb79f4 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 9 Jun 2022 12:44:07 -0500 Subject: [PATCH 06/21] clean up imports --- src/server/mod.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/server/mod.rs b/src/server/mod.rs index 8670debb1..9c0c86fd6 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -7,10 +7,7 @@ use axum::{ Router, }; use notify::{RecommendedWatcher, Watcher}; -use std::{ - fs::{self, File}, - io::{self, Read}, -}; +use std::{fs::File, io::Read}; use std::{path::PathBuf, sync::Arc}; use tower::ServiceBuilder; @@ -25,6 +22,7 @@ mod hot_reload_improts { pub use dioxus_rsx_interpreter::{error::RecompileReason, CodeLocation, SetRsxMessage}; pub use std::collections::HashMap; pub use std::sync::Mutex; + pub use std::{fs, io}; pub use syn::__private::ToTokens; } #[cfg(feature = "hot_reload")] From 1d29f66cfc40cbd33065a193cbc013694cfebf0f Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 9 Jun 2022 13:00:28 -0500 Subject: [PATCH 07/21] use log::info instead of print --- src/server/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/mod.rs b/src/server/mod.rs index 9c0c86fd6..95026d768 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -289,7 +289,7 @@ async fn hot_reload_handler( Extension(state): Extension>, ) -> impl IntoResponse { ws.on_upgrade(|mut socket| async move { - println!("🔥 Hot Reload WebSocket is connected."); + log::info!("🔥 Hot Reload WebSocket connected"); let mut rx = state.messages.subscribe(); loop { let read_set_rsx = rx.recv(); From 0c4028ad5270281e0893dd8e919fdebb3407b44c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 10 Jun 2022 19:18:02 -0500 Subject: [PATCH 08/21] close websocket on disconnect --- src/server/mod.rs | 54 ++++++++++++++++++++++++++++------------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/src/server/mod.rs b/src/server/mod.rs index 95026d768..3506112f2 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -291,30 +291,40 @@ async fn hot_reload_handler( ws.on_upgrade(|mut socket| async move { log::info!("🔥 Hot Reload WebSocket connected"); let mut rx = state.messages.subscribe(); - loop { - let read_set_rsx = rx.recv(); - let read_err = socket.recv(); - tokio::select! { - err = read_err => { - if let Some(Ok(Message::Text(err))) = err { - let error: RecompileReason = serde_json::from_str(&err).unwrap(); - log::error!("{:?}", error); - state.update.send("reload".to_string()).unwrap(); - }; - }, - set_rsx = read_set_rsx => { - if let Ok(rsx) = set_rsx{ - if socket - .send(Message::Text(serde_json::to_string(&rsx).unwrap())) - .await - .is_err() - { + let hot_reload_handle = tokio::spawn(async move { + loop { + let read_set_rsx = rx.recv(); + let read_err = socket.recv(); + tokio::select! { + err = read_err => { + if let Some(Ok(err)) = err { + if let Message::Text(err) = err { + let error: RecompileReason = serde_json::from_str(&err).unwrap(); + log::error!("{:?}", error); + if state.update.send("reload".to_string()).is_err() { + break; + } + } + } else { break; - }; + } + }, + set_rsx = read_set_rsx => { + if let Ok(rsx) = set_rsx { + if socket + .send(Message::Text(serde_json::to_string(&rsx).unwrap())) + .await + .is_err() + { + break; + }; + } } - } - }; - } + }; + } + }); + + hot_reload_handle.await.unwrap(); }) } From b3db5469c36b143d024b6b828cebcef261740510 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 13 Jun 2022 15:33:49 -0500 Subject: [PATCH 09/21] ignore files that have not been changed recently when checking for changes initially --- src/hot_reload/mod.rs | 4 +- src/server/mod.rs | 206 +++++++++++++++++++++++++++++------------- 2 files changed, 145 insertions(+), 65 deletions(-) diff --git a/src/hot_reload/mod.rs b/src/hot_reload/mod.rs index 3aced9567..8297fedb9 100644 --- a/src/hot_reload/mod.rs +++ b/src/hot_reload/mod.rs @@ -426,7 +426,9 @@ fn find_rsx_expr( && old_mac.path.get_ident().map(|ident| ident.to_string()) == Some("rsx".to_string()) { - rsx_calls.push((old_mac.clone(), new_mac.tokens.clone())); + if old_mac != new_mac { + rsx_calls.push((old_mac.clone(), new_mac.tokens.clone())); + } false } else { new_expr != old_expr diff --git a/src/server/mod.rs b/src/server/mod.rs index 3506112f2..a0bdf24b0 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -7,7 +7,6 @@ use axum::{ Router, }; use notify::{RecommendedWatcher, Watcher}; -use std::{fs::File, io::Read}; use std::{path::PathBuf, sync::Arc}; use tower::ServiceBuilder; @@ -20,9 +19,12 @@ use tokio::sync::broadcast; mod hot_reload_improts { pub use crate::hot_reload::{find_rsx, DiffResult}; pub use dioxus_rsx_interpreter::{error::RecompileReason, CodeLocation, SetRsxMessage}; + pub use proc_macro2::TokenStream; pub use std::collections::HashMap; pub use std::sync::Mutex; - pub use std::{fs, io}; + pub use std::time::SystemTime; + pub use std::{fs, io, path::Path}; + pub use std::{fs::File, io::Read}; pub use syn::__private::ToTokens; } #[cfg(feature = "hot_reload")] @@ -31,7 +33,7 @@ use hot_reload_improts::*; struct WsReloadState { update: broadcast::Sender, #[cfg(feature = "hot_reload")] - last_file_rebuild: Arc>>, + last_file_rebuild: Arc>, watcher_config: CrateConfig, } @@ -39,6 +41,47 @@ struct WsReloadState { struct HotReloadState { messages: broadcast::Sender, update: broadcast::Sender, + last_file_rebuild: Arc>, + watcher_config: CrateConfig, +} + +#[cfg(feature = "hot_reload")] +struct FileMap { + map: HashMap, + last_updated_time: std::time::SystemTime, +} + +#[cfg(feature = "hot_reload")] +impl FileMap { + fn new(path: PathBuf) -> Self { + fn find_rs_files(root: PathBuf) -> io::Result> { + let mut files = HashMap::new(); + if root.is_dir() { + let mut handles = Vec::new(); + for entry in fs::read_dir(root)? { + if let Ok(entry) = entry { + let path = entry.path(); + handles.push(std::thread::spawn(move || find_rs_files(path))); + } + } + for handle in handles { + files.extend(handle.join().unwrap()?); + } + } else { + if root.extension().map(|s| s.to_str()).flatten() == Some("rs") { + let mut file = File::open(root.clone()).unwrap(); + let mut src = String::new(); + file.read_to_string(&mut src).expect("Unable to read file"); + files.insert(root, src); + } + } + Ok(files) + } + Self { + last_updated_time: SystemTime::now(), + map: find_rs_files(path).unwrap(), + } + } } pub async fn startup(config: CrateConfig) -> Result<()> { @@ -49,15 +92,15 @@ pub async fn startup(config: CrateConfig) -> Result<()> { let (reload_tx, _) = broadcast::channel(100); #[cfg(feature = "hot_reload")] - let last_file_rebuild = Arc::new(Mutex::new(HashMap::new())); - #[cfg(feature = "hot_reload")] - find_rs_files(&config.crate_dir, &mut *last_file_rebuild.lock().unwrap()).unwrap(); + let last_file_rebuild = Arc::new(Mutex::new(FileMap::new(config.crate_dir.clone()))); #[cfg(feature = "hot_reload")] let hot_reload_tx = broadcast::channel(100).0; #[cfg(feature = "hot_reload")] let hot_reload_state = Arc::new(HotReloadState { messages: hot_reload_tx.clone(), update: reload_tx.clone(), + last_file_rebuild: last_file_rebuild.clone(), + watcher_config: config.clone(), }); #[cfg(feature = "hot_reload")] let crate_dir = config.crate_dir.clone(); @@ -83,18 +126,21 @@ pub async fn startup(config: CrateConfig) -> Result<()> { let mut watcher = RecommendedWatcher::new(move |evt: notify::Result| { if let Ok(evt) = evt { if let notify::EventKind::Modify(_) = evt.kind { - for path in evt.paths { - let mut file = File::open(path.clone()).unwrap(); - let mut src = String::new(); - file.read_to_string(&mut src).expect("Unable to read file"); - if src.is_empty() { - continue; - } - #[cfg(feature = "hot_reload")] - { + #[cfg(feature = "hot_reload")] + { + for path in evt.paths { + let mut file = File::open(path.clone()).unwrap(); + if path.extension().map(|p| p.to_str()).flatten() != Some("rs") { + continue; + } + let mut src = String::new(); + file.read_to_string(&mut src).expect("Unable to read file"); + if src.is_empty() { + continue; + } 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.get(path.to_str().unwrap()) { + if let Some(old_str) = last_file_rebuild.map.get(&path) { if let Ok(old) = syn::parse_file(&old_str) { match find_rsx(&syntax, &old) { DiffResult::CodeChanged => { @@ -107,28 +153,13 @@ pub async fn startup(config: CrateConfig) -> Result<()> { DiffResult::RsxChanged(changed) => { log::info!("reloading rsx"); for (old, new) in changed.into_iter() { - if let Some(hr) = old - .to_token_stream() - .into_iter() - .map(|tree| { - let location = tree.span(); - let start = location.start(); - CodeLocation { - file: path - .strip_prefix(&crate_dir) - .unwrap() - .display() - .to_string(), - line: start.line as u32, - column: start.column as u32 + 1, - } - }) - .min_by(|cl1, cl2| { - cl1.line - .cmp(&cl2.line) - .then(cl1.column.cmp(&cl2.column)) - }) - { + if let Some(hr) = get_min_location( + &path + .strip_prefix(&crate_dir) + .unwrap() + .to_path_buf(), + old.to_token_stream(), + ) { let rsx = new.to_string(); let _ = hot_reload_tx.send(SetRsxMessage { location: hr, @@ -140,17 +171,17 @@ pub async fn startup(config: CrateConfig) -> Result<()> { } } } else { - last_file_rebuild.insert(path.to_str().unwrap().to_string(), src); + *last_file_rebuild = FileMap::new(crate_dir.clone()); } } } - #[cfg(not(feature = "hot_reload"))] - { - log::info!("reload required"); - if chrono::Local::now().timestamp() > last_update_time { - let _ = reload_tx.send("reload".into()); - last_update_time = chrono::Local::now().timestamp(); - } + } + #[cfg(not(feature = "hot_reload"))] + { + log::info!("reload required"); + if chrono::Local::now().timestamp() > last_update_time { + let _ = reload_tx.send("reload".into()); + last_update_time = chrono::Local::now().timestamp(); } } } @@ -262,8 +293,7 @@ async fn ws_handler( #[cfg(feature = "hot_reload")] { let mut write = state.last_file_rebuild.lock().unwrap(); - *write = HashMap::new(); - find_rs_files(&state.watcher_config.crate_dir, &mut *write).unwrap(); + *write = FileMap::new(state.watcher_config.crate_dir.clone()); } } // ignore the error @@ -290,6 +320,59 @@ async fn hot_reload_handler( ) -> impl IntoResponse { ws.on_upgrade(|mut socket| async move { log::info!("🔥 Hot Reload WebSocket connected"); + { + log::info!("Searching files for changes since last run..."); + // update any files that changed before the websocket connected. + let mut messages = Vec::new(); + + { + 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 = String::new(); + file.read_to_string(&mut new).expect("Unable to read file"); + if let Ok(new) = syn::parse_file(&new) { + if let Ok(old) = syn::parse_file(&v) { + if let DiffResult::RsxChanged(changed) = find_rsx(&new, &old) { + for (old, new) in changed.into_iter() { + if let Some(hr) = get_min_location( + k.strip_prefix(&state.watcher_config.crate_dir).unwrap(), + old.to_token_stream(), + ) { + let rsx = new.to_string(); + let msg = SetRsxMessage { + location: hr, + new_text: rsx, + }; + messages.push(msg); + } + } + } + } + } + } + } + + for msg in &messages { + if socket + .send(Message::Text(serde_json::to_string(msg).unwrap())) + .await + .is_err() + { + return; + } + } + log::info!("Updated page"); + } + let mut rx = state.messages.subscribe(); let hot_reload_handle = tokio::spawn(async move { loop { @@ -329,21 +412,16 @@ async fn hot_reload_handler( } #[cfg(feature = "hot_reload")] -fn find_rs_files(root: &PathBuf, files: &mut HashMap) -> io::Result<()> { - for entry in fs::read_dir(root)? { - if let Ok(entry) = entry { - let path = entry.path(); - if path.is_dir() { - find_rs_files(&path, files)?; - } else { - if path.extension().map(|s| s.to_str()).flatten() == Some("rs") { - let mut file = File::open(path.clone()).unwrap(); - let mut src = String::new(); - file.read_to_string(&mut src).expect("Unable to read file"); - files.insert(path.display().to_string(), src); - } +fn get_min_location(path: &Path, ts: TokenStream) -> Option { + ts.into_iter() + .map(|tree| { + let location = tree.span(); + let start = location.start(); + CodeLocation { + file: path.display().to_string(), + line: start.line as u32, + column: start.column as u32 + 1, } - } - } - Ok(()) + }) + .min_by(|cl1, cl2| cl1.line.cmp(&cl2.line).then(cl1.column.cmp(&cl2.column))) } From 712d258496415f839b389da8163d5634a36e15f1 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 14 Jun 2022 07:31:37 -0500 Subject: [PATCH 10/21] clean up config statements --- src/hot_reload/mod.rs | 4 +- src/server/hot_reload.rs | 174 ++++++++++++++++++ src/server/mod.rs | 383 +++++++++++++++------------------------ 3 files changed, 321 insertions(+), 240 deletions(-) create mode 100644 src/server/hot_reload.rs diff --git a/src/hot_reload/mod.rs b/src/hot_reload/mod.rs index 8297fedb9..3aced9567 100644 --- a/src/hot_reload/mod.rs +++ b/src/hot_reload/mod.rs @@ -426,9 +426,7 @@ fn find_rsx_expr( && old_mac.path.get_ident().map(|ident| ident.to_string()) == Some("rsx".to_string()) { - if old_mac != new_mac { - rsx_calls.push((old_mac.clone(), new_mac.tokens.clone())); - } + rsx_calls.push((old_mac.clone(), new_mac.tokens.clone())); false } else { new_expr != old_expr diff --git a/src/server/hot_reload.rs b/src/server/hot_reload.rs new file mode 100644 index 000000000..661d249e6 --- /dev/null +++ b/src/server/hot_reload.rs @@ -0,0 +1,174 @@ +use axum::{ + extract::{ws::Message, Extension, TypedHeader, WebSocketUpgrade}, + response::IntoResponse, +}; + +use std::{path::PathBuf, sync::Arc}; + +pub use crate::hot_reload::{find_rsx, DiffResult}; +use crate::CrateConfig; +pub use dioxus_rsx_interpreter::{error::RecompileReason, CodeLocation, SetRsxMessage}; +pub use proc_macro2::TokenStream; +pub use std::collections::HashMap; +pub use std::sync::Mutex; +pub use std::time::SystemTime; +pub use std::{fs, io, path::Path}; +pub use std::{fs::File, io::Read}; +pub use syn::__private::ToTokens; +use tokio::sync::broadcast; + +pub struct HotReloadState { + pub messages: broadcast::Sender, + pub update: broadcast::Sender, + pub last_file_rebuild: Arc>, + pub watcher_config: CrateConfig, +} + +pub struct FileMap { + pub map: HashMap, + pub last_updated_time: std::time::SystemTime, +} + +impl FileMap { + pub fn new(path: PathBuf) -> Self { + fn find_rs_files(root: PathBuf) -> io::Result> { + let mut files = HashMap::new(); + if root.is_dir() { + let mut handles = Vec::new(); + for entry in fs::read_dir(root)? { + if let Ok(entry) = entry { + let path = entry.path(); + handles.push(std::thread::spawn(move || find_rs_files(path))); + } + } + for handle in handles { + files.extend(handle.join().unwrap()?); + } + } else { + if root.extension().map(|s| s.to_str()).flatten() == Some("rs") { + let mut file = File::open(root.clone()).unwrap(); + let mut src = String::new(); + file.read_to_string(&mut src).expect("Unable to read file"); + files.insert(root, src); + } + } + Ok(files) + } + Self { + last_updated_time: SystemTime::now(), + map: find_rs_files(path).unwrap(), + } + } +} + +pub async fn hot_reload_handler( + ws: WebSocketUpgrade, + _: Option>, + Extension(state): Extension>, +) -> impl IntoResponse { + ws.on_upgrade(|mut socket| async move { + log::info!("🔥 Hot Reload WebSocket connected"); + { + log::info!("Searching files for changes since last run..."); + // update any files that changed before the websocket connected. + let mut messages = Vec::new(); + + { + 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 = String::new(); + file.read_to_string(&mut new).expect("Unable to read file"); + if let Ok(new) = syn::parse_file(&new) { + if let Ok(old) = syn::parse_file(&v) { + if let DiffResult::RsxChanged(changed) = find_rsx(&new, &old) { + for (old, new) in changed.into_iter() { + if let Some(hr) = get_min_location( + k.strip_prefix(&state.watcher_config.crate_dir).unwrap(), + old.to_token_stream(), + ) { + let rsx = new.to_string(); + let msg = SetRsxMessage { + location: hr, + new_text: rsx, + }; + messages.push(msg); + } + } + } + } + } + } + } + + for msg in &messages { + if socket + .send(Message::Text(serde_json::to_string(msg).unwrap())) + .await + .is_err() + { + return; + } + } + log::info!("Updated page"); + } + + let mut rx = state.messages.subscribe(); + let hot_reload_handle = tokio::spawn(async move { + loop { + let read_set_rsx = rx.recv(); + let read_err = socket.recv(); + tokio::select! { + err = read_err => { + if let Some(Ok(err)) = err { + if let Message::Text(err) = err { + let error: RecompileReason = serde_json::from_str(&err).unwrap(); + log::error!("{:?}", error); + if state.update.send("reload".to_string()).is_err() { + break; + } + } + } else { + break; + } + }, + set_rsx = read_set_rsx => { + if let Ok(rsx) = set_rsx { + if socket + .send(Message::Text(serde_json::to_string(&rsx).unwrap())) + .await + .is_err() + { + break; + }; + } + } + }; + } + }); + + hot_reload_handle.await.unwrap(); + }) +} + +pub fn get_min_location(path: &Path, ts: TokenStream) -> Option { + ts.into_iter() + .map(|tree| { + let location = tree.span(); + let start = location.start(); + CodeLocation { + file: path.display().to_string(), + line: start.line as u32, + column: start.column as u32 + 1, + } + }) + .min_by(|cl1, cl2| cl1.line.cmp(&cl2.line).then(cl1.column.cmp(&cl2.column))) +} diff --git a/src/server/mod.rs b/src/server/mod.rs index a0bdf24b0..262dbf17d 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -16,19 +16,9 @@ use crate::{builder, serve::Serve, CrateConfig, Result}; use tokio::sync::broadcast; #[cfg(feature = "hot_reload")] -mod hot_reload_improts { - pub use crate::hot_reload::{find_rsx, DiffResult}; - pub use dioxus_rsx_interpreter::{error::RecompileReason, CodeLocation, SetRsxMessage}; - pub use proc_macro2::TokenStream; - pub use std::collections::HashMap; - pub use std::sync::Mutex; - pub use std::time::SystemTime; - pub use std::{fs, io, path::Path}; - pub use std::{fs::File, io::Read}; - pub use syn::__private::ToTokens; -} +mod hot_reload; #[cfg(feature = "hot_reload")] -use hot_reload_improts::*; +use hot_reload::*; struct WsReloadState { update: broadcast::Sender, @@ -38,76 +28,24 @@ struct WsReloadState { } #[cfg(feature = "hot_reload")] -struct HotReloadState { - messages: broadcast::Sender, - update: broadcast::Sender, - last_file_rebuild: Arc>, - watcher_config: CrateConfig, -} - -#[cfg(feature = "hot_reload")] -struct FileMap { - map: HashMap, - last_updated_time: std::time::SystemTime, -} - -#[cfg(feature = "hot_reload")] -impl FileMap { - fn new(path: PathBuf) -> Self { - fn find_rs_files(root: PathBuf) -> io::Result> { - let mut files = HashMap::new(); - if root.is_dir() { - let mut handles = Vec::new(); - for entry in fs::read_dir(root)? { - if let Ok(entry) = entry { - let path = entry.path(); - handles.push(std::thread::spawn(move || find_rs_files(path))); - } - } - for handle in handles { - files.extend(handle.join().unwrap()?); - } - } else { - if root.extension().map(|s| s.to_str()).flatten() == Some("rs") { - let mut file = File::open(root.clone()).unwrap(); - let mut src = String::new(); - file.read_to_string(&mut src).expect("Unable to read file"); - files.insert(root, src); - } - } - Ok(files) - } - Self { - last_updated_time: SystemTime::now(), - map: find_rs_files(path).unwrap(), - } - } -} - pub async fn startup(config: CrateConfig) -> Result<()> { log::info!("🚀 Starting development server..."); let dist_path = config.out_dir.clone(); - let (reload_tx, _) = broadcast::channel(100); - - #[cfg(feature = "hot_reload")] let last_file_rebuild = Arc::new(Mutex::new(FileMap::new(config.crate_dir.clone()))); - #[cfg(feature = "hot_reload")] let hot_reload_tx = broadcast::channel(100).0; - #[cfg(feature = "hot_reload")] let hot_reload_state = Arc::new(HotReloadState { messages: hot_reload_tx.clone(), update: reload_tx.clone(), last_file_rebuild: last_file_rebuild.clone(), watcher_config: config.clone(), }); - #[cfg(feature = "hot_reload")] - let crate_dir = config.crate_dir.clone(); + let crate_dir = config.crate_dir.clone(); let ws_reload_state = Arc::new(WsReloadState { update: reload_tx.clone(), - #[cfg(feature = "hot_reload")] + last_file_rebuild: last_file_rebuild.clone(), watcher_config: config.clone(), }); @@ -125,63 +63,49 @@ pub async fn startup(config: CrateConfig) -> Result<()> { let mut watcher = RecommendedWatcher::new(move |evt: notify::Result| { if let Ok(evt) = evt { - if let notify::EventKind::Modify(_) = evt.kind { - #[cfg(feature = "hot_reload")] - { - for path in evt.paths { - let mut file = File::open(path.clone()).unwrap(); - if path.extension().map(|p| p.to_str()).flatten() != Some("rs") { - continue; - } - let mut src = String::new(); - file.read_to_string(&mut src).expect("Unable to read file"); - if src.is_empty() { - continue; - } - 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) { - match find_rsx(&syntax, &old) { - DiffResult::CodeChanged => { - log::info!("reload required"); - if chrono::Local::now().timestamp() > last_update_time { - let _ = reload_tx.send("reload".into()); - last_update_time = chrono::Local::now().timestamp(); - } - } - DiffResult::RsxChanged(changed) => { - log::info!("reloading rsx"); - for (old, new) in changed.into_iter() { - if let Some(hr) = get_min_location( - &path - .strip_prefix(&crate_dir) - .unwrap() - .to_path_buf(), - old.to_token_stream(), - ) { - let rsx = new.to_string(); - let _ = hot_reload_tx.send(SetRsxMessage { - location: hr, - new_text: rsx, - }); - } - } + for path in evt.paths { + let mut file = File::open(path.clone()).unwrap(); + if path.extension().map(|p| p.to_str()).flatten() != Some("rs") { + continue; + } + let mut src = String::new(); + file.read_to_string(&mut src).expect("Unable to read file"); + if src.is_empty() { + continue; + } + // 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) { + match find_rsx(&syntax, &old) { + DiffResult::CodeChanged => { + log::info!("reload required"); + if chrono::Local::now().timestamp() > last_update_time { + let _ = reload_tx.send("reload".into()); + last_update_time = chrono::Local::now().timestamp(); + } + } + DiffResult::RsxChanged(changed) => { + log::info!("reloading rsx"); + for (old, new) in changed.into_iter() { + if let Some(hr) = get_min_location( + &path.strip_prefix(&crate_dir).unwrap().to_path_buf(), + old.to_token_stream(), + ) { + let rsx = new.to_string(); + let _ = hot_reload_tx.send(SetRsxMessage { + location: hr, + new_text: rsx, + }); } } } - } else { - *last_file_rebuild = FileMap::new(crate_dir.clone()); } } - } - } - #[cfg(not(feature = "hot_reload"))] - { - log::info!("reload required"); - if chrono::Local::now().timestamp() > last_update_time { - let _ = reload_tx.send("reload".into()); - last_update_time = chrono::Local::now().timestamp(); + } else { + // if this is a new file, rebuild the project + *last_file_rebuild = FileMap::new(crate_dir.clone()); } } } @@ -250,13 +174,112 @@ pub async fn startup(config: CrateConfig) -> Result<()> { }), ); - #[cfg(feature = "hot_reload")] - let router = router.route("/_dioxus/hot_reload", get(hot_reload_handler)); + let router = router + .route("/_dioxus/hot_reload", get(hot_reload_handler)) + .layer(Extension(ws_reload_state)) + .layer(Extension(hot_reload_state)); - let router = router.layer(Extension(ws_reload_state)); + axum::Server::bind(&format!("0.0.0.0:{}", port).parse().unwrap()) + .serve(router.into_make_service()) + .await?; - #[cfg(feature = "hot_reload")] - let router = router.layer(Extension(hot_reload_state)); + Ok(()) +} + +#[cfg(not(feature = "hot_reload"))] +pub async fn startup(config: CrateConfig) -> Result<()> { + log::info!("🚀 Starting development server..."); + + let dist_path = config.out_dir.clone(); + + let (reload_tx, _) = broadcast::channel(100); + + let ws_reload_state = Arc::new(WsReloadState { + update: reload_tx.clone(), + watcher_config: config.clone(), + }); + + let mut last_update_time = chrono::Local::now().timestamp(); + + // file watcher: check file change + let allow_watch_path = config + .dioxus_config + .web + .watcher + .watch_path + .clone() + .unwrap_or_else(|| vec![PathBuf::from("src")]); + + let mut watcher = RecommendedWatcher::new(move |_: notify::Result| { + log::info!("reload required"); + if chrono::Local::now().timestamp() > last_update_time { + let _ = reload_tx.send("reload".into()); + last_update_time = chrono::Local::now().timestamp(); + } + }) + .unwrap(); + + for sub_path in allow_watch_path { + watcher + .watch( + &config.crate_dir.join(sub_path), + notify::RecursiveMode::Recursive, + ) + .unwrap(); + } + + // start serve dev-server at 0.0.0.0:8080 + let port = "8080"; + log::info!("📡 Dev-Server is started at: http://127.0.0.1:{}/", port); + + let file_service_config = config.clone(); + let file_service = ServiceBuilder::new() + .and_then( + |response: Response| async move { + let response = if file_service_config + .dioxus_config + .web + .watcher + .index_on_404 + .unwrap_or(false) + && response.status() == StatusCode::NOT_FOUND + { + let body = Full::from( + // TODO: Cache/memoize this. + std::fs::read_to_string( + file_service_config + .crate_dir + .join(file_service_config.out_dir) + .join("index.html"), + ) + .ok() + .unwrap(), + ) + .map_err(|err| match err {}) + .boxed(); + Response::builder() + .status(StatusCode::OK) + .body(body) + .unwrap() + } else { + response.map(|body| body.boxed()) + }; + Ok(response) + }, + ) + .service(ServeDir::new((&config.crate_dir).join(&dist_path))); + + let router = Router::new() + .route("/_dioxus/ws", get(ws_handler)) + .fallback( + get_service(file_service).handle_error(|error: std::io::Error| async move { + ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Unhandled internal error: {}", error), + ) + }), + ) + .layer(Extension(ws_reload_state)); axum::Server::bind(&format!("0.0.0.0:{}", port).parse().unwrap()) .serve(router.into_make_service()) @@ -311,117 +334,3 @@ async fn ws_handler( reload_watcher.await.unwrap(); }) } - -#[cfg(feature = "hot_reload")] -async fn hot_reload_handler( - ws: WebSocketUpgrade, - _: Option>, - Extension(state): Extension>, -) -> impl IntoResponse { - ws.on_upgrade(|mut socket| async move { - log::info!("🔥 Hot Reload WebSocket connected"); - { - log::info!("Searching files for changes since last run..."); - // update any files that changed before the websocket connected. - let mut messages = Vec::new(); - - { - 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 = String::new(); - file.read_to_string(&mut new).expect("Unable to read file"); - if let Ok(new) = syn::parse_file(&new) { - if let Ok(old) = syn::parse_file(&v) { - if let DiffResult::RsxChanged(changed) = find_rsx(&new, &old) { - for (old, new) in changed.into_iter() { - if let Some(hr) = get_min_location( - k.strip_prefix(&state.watcher_config.crate_dir).unwrap(), - old.to_token_stream(), - ) { - let rsx = new.to_string(); - let msg = SetRsxMessage { - location: hr, - new_text: rsx, - }; - messages.push(msg); - } - } - } - } - } - } - } - - for msg in &messages { - if socket - .send(Message::Text(serde_json::to_string(msg).unwrap())) - .await - .is_err() - { - return; - } - } - log::info!("Updated page"); - } - - let mut rx = state.messages.subscribe(); - let hot_reload_handle = tokio::spawn(async move { - loop { - let read_set_rsx = rx.recv(); - let read_err = socket.recv(); - tokio::select! { - err = read_err => { - if let Some(Ok(err)) = err { - if let Message::Text(err) = err { - let error: RecompileReason = serde_json::from_str(&err).unwrap(); - log::error!("{:?}", error); - if state.update.send("reload".to_string()).is_err() { - break; - } - } - } else { - break; - } - }, - set_rsx = read_set_rsx => { - if let Ok(rsx) = set_rsx { - if socket - .send(Message::Text(serde_json::to_string(&rsx).unwrap())) - .await - .is_err() - { - break; - }; - } - } - }; - } - }); - - hot_reload_handle.await.unwrap(); - }) -} - -#[cfg(feature = "hot_reload")] -fn get_min_location(path: &Path, ts: TokenStream) -> Option { - ts.into_iter() - .map(|tree| { - let location = tree.span(); - let start = location.start(); - CodeLocation { - file: path.display().to_string(), - line: start.line as u32, - column: start.column as u32 + 1, - } - }) - .min_by(|cl1, cl2| cl1.line.cmp(&cl2.line).then(cl1.column.cmp(&cl2.column))) -} From fb7ea5915b303fe79682a299bbb6bdc3907e2e82 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Tue, 14 Jun 2022 09:47:16 -0500 Subject: [PATCH 11/21] use cli flag instead of feature flag --- Cargo.toml | 8 +++----- src/cli/cfg.rs | 5 +++++ src/cli/serve/mod.rs | 4 +++- src/config.rs | 8 ++++++++ src/lib.rs | 1 - src/server/mod.rs | 26 +++++++++++++++----------- 6 files changed, 34 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 770fd7b1d..7f1223520 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,12 +46,10 @@ flate2 = "1.0.22" tar = "0.4.38" tower = "0.4.12" -syn = { version = "1.0", optional = true } -dioxus-rsx-interpreter = { path = "../dioxus/packages/rsx_interpreter", optional = true } -proc-macro2 = { version = "1.0", features = ["span-locations"], optional = true } +syn = { version = "1.0" } +dioxus-rsx-interpreter = { path = "../dioxus/packages/rsx_interpreter" } +proc-macro2 = { version = "1.0", features = ["span-locations"] } -[features] -hot_reload = ["dioxus-rsx-interpreter", "proc-macro2", "syn"] [[bin]] path = "src/main.rs" diff --git a/src/cli/cfg.rs b/src/cli/cfg.rs index f5ee45b87..3f284cbbf 100644 --- a/src/cli/cfg.rs +++ b/src/cli/cfg.rs @@ -39,6 +39,11 @@ pub struct ConfigOptsServe { /// Build platform: support Web & Desktop [default: "default_platform"] #[clap(long)] pub platform: Option, + + /// Build with hot reloading rsx [default: false] + #[clap(long)] + #[serde(default)] + pub hot_reload: bool, } /// Ensure the given value for `--public-url` is formatted correctly. diff --git a/src/cli/serve/mod.rs b/src/cli/serve/mod.rs index 85bd92c74..391e646d4 100644 --- a/src/cli/serve/mod.rs +++ b/src/cli/serve/mod.rs @@ -18,7 +18,9 @@ impl Serve { let mut crate_config = crate::CrateConfig::new()?; // change the relase state. - crate_config.with_release(self.serve.release); + crate_config + .with_release(self.serve.release) + .with_hot_reload(self.serve.hot_reload); if self.serve.example.is_some() { crate_config.as_example(self.serve.example.unwrap()); diff --git a/src/config.rs b/src/config.rs index 783c72ccd..aa2a03440 100644 --- a/src/config.rs +++ b/src/config.rs @@ -114,6 +114,7 @@ pub struct CrateConfig { pub executable: ExecutableType, pub dioxus_config: DioxusConfig, pub release: bool, + pub hot_reload: bool, } #[derive(Debug, Clone)] @@ -162,6 +163,7 @@ impl CrateConfig { let executable = ExecutableType::Binary(output_filename); let release = false; + let hot_reload = false; Ok(Self { out_dir, @@ -173,6 +175,7 @@ impl CrateConfig { executable, release, dioxus_config, + hot_reload, }) } @@ -186,6 +189,11 @@ impl CrateConfig { self } + pub fn with_hot_reload(&mut self, hot_reload: bool) -> &mut Self { + self.hot_reload = hot_reload; + self + } + // pub fn with_build_options(&mut self, options: &BuildOptions) { // if let Some(name) = &options.example { // self.as_example(name.clone()); diff --git a/src/lib.rs b/src/lib.rs index 683b7251a..6d009c18c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,5 +19,4 @@ pub use error::*; pub mod logging; pub use logging::*; -#[cfg(feature = "hot_reload")] pub mod hot_reload; diff --git a/src/server/mod.rs b/src/server/mod.rs index 262dbf17d..8a98fd9d9 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -15,20 +15,24 @@ use tower_http::services::fs::{ServeDir, ServeFileSystemResponseBody}; use crate::{builder, serve::Serve, CrateConfig, Result}; use tokio::sync::broadcast; -#[cfg(feature = "hot_reload")] mod hot_reload; -#[cfg(feature = "hot_reload")] use hot_reload::*; struct WsReloadState { update: broadcast::Sender, - #[cfg(feature = "hot_reload")] - last_file_rebuild: Arc>, + last_file_rebuild: Option>>, watcher_config: CrateConfig, } -#[cfg(feature = "hot_reload")] pub async fn startup(config: CrateConfig) -> Result<()> { + if config.hot_reload { + startup_hot_reload(config).await + } else { + startup_default(config).await + } +} + +pub async fn startup_hot_reload(config: CrateConfig) -> Result<()> { log::info!("🚀 Starting development server..."); let dist_path = config.out_dir.clone(); @@ -46,7 +50,7 @@ pub async fn startup(config: CrateConfig) -> Result<()> { let ws_reload_state = Arc::new(WsReloadState { update: reload_tx.clone(), - last_file_rebuild: last_file_rebuild.clone(), + last_file_rebuild: Some(last_file_rebuild.clone()), watcher_config: config.clone(), }); @@ -186,8 +190,7 @@ pub async fn startup(config: CrateConfig) -> Result<()> { Ok(()) } -#[cfg(not(feature = "hot_reload"))] -pub async fn startup(config: CrateConfig) -> Result<()> { +pub async fn startup_default(config: CrateConfig) -> Result<()> { log::info!("🚀 Starting development server..."); let dist_path = config.out_dir.clone(); @@ -196,6 +199,8 @@ pub async fn startup(config: CrateConfig) -> Result<()> { let ws_reload_state = Arc::new(WsReloadState { update: reload_tx.clone(), + + last_file_rebuild: None, watcher_config: config.clone(), }); @@ -313,9 +318,8 @@ async fn ws_handler( { let _ = Serve::regen_dev_page(&state.watcher_config); } - #[cfg(feature = "hot_reload")] - { - let mut write = state.last_file_rebuild.lock().unwrap(); + if let Some(file_map) = &state.last_file_rebuild { + let mut write = file_map.lock().unwrap(); *write = FileMap::new(state.watcher_config.crate_dir.clone()); } } From b0f987aa106994e95af8e75765ce12321684821b Mon Sep 17 00:00:00 2001 From: Demonthos <66571940+Demonthos@users.noreply.github.com> Date: Wed, 15 Jun 2022 12:55:25 -0500 Subject: [PATCH 12/21] accept all interperter errors --- src/server/hot_reload.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/hot_reload.rs b/src/server/hot_reload.rs index 661d249e6..a9f51abe7 100644 --- a/src/server/hot_reload.rs +++ b/src/server/hot_reload.rs @@ -7,7 +7,7 @@ use std::{path::PathBuf, sync::Arc}; pub use crate::hot_reload::{find_rsx, DiffResult}; use crate::CrateConfig; -pub use dioxus_rsx_interpreter::{error::RecompileReason, CodeLocation, SetRsxMessage}; +pub use dioxus_rsx_interpreter::{error::Error, CodeLocation, SetRsxMessage}; pub use proc_macro2::TokenStream; pub use std::collections::HashMap; pub use std::sync::Mutex; @@ -130,7 +130,7 @@ pub async fn hot_reload_handler( err = read_err => { if let Some(Ok(err)) = err { if let Message::Text(err) = err { - let error: RecompileReason = serde_json::from_str(&err).unwrap(); + let error: Error = serde_json::from_str(&err).unwrap(); log::error!("{:?}", error); if state.update.send("reload".to_string()).is_err() { break; From 8ff50df3a5dc14b90b15fd84122a12436326144a Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 15 Jun 2022 13:29:35 -0500 Subject: [PATCH 13/21] preserve whitespace in rsx --- src/server/hot_reload.rs | 37 ++++++++++++++++--------------------- src/server/mod.rs | 29 ++++++++++++++++++++++------- 2 files changed, 38 insertions(+), 28 deletions(-) diff --git a/src/server/hot_reload.rs b/src/server/hot_reload.rs index a9f51abe7..f79a7a50e 100644 --- a/src/server/hot_reload.rs +++ b/src/server/hot_reload.rs @@ -15,6 +15,7 @@ pub use std::time::SystemTime; pub use std::{fs, io, path::Path}; pub use std::{fs::File, io::Read}; pub use syn::__private::ToTokens; +use syn::spanned::Spanned; use tokio::sync::broadcast; pub struct HotReloadState { @@ -91,17 +92,16 @@ pub async fn hot_reload_handler( if let Ok(old) = syn::parse_file(&v) { if let DiffResult::RsxChanged(changed) = find_rsx(&new, &old) { for (old, new) in changed.into_iter() { - if let Some(hr) = get_min_location( + let hr = get_location( k.strip_prefix(&state.watcher_config.crate_dir).unwrap(), old.to_token_stream(), - ) { - let rsx = new.to_string(); - let msg = SetRsxMessage { - location: hr, - new_text: rsx, - }; - messages.push(msg); - } + ); + let rsx = new.to_string(); + let msg = SetRsxMessage { + location: hr, + new_text: rsx, + }; + messages.push(msg); } } } @@ -159,16 +159,11 @@ pub async fn hot_reload_handler( }) } -pub fn get_min_location(path: &Path, ts: TokenStream) -> Option { - ts.into_iter() - .map(|tree| { - let location = tree.span(); - let start = location.start(); - CodeLocation { - file: path.display().to_string(), - line: start.line as u32, - column: start.column as u32 + 1, - } - }) - .min_by(|cl1, cl2| cl1.line.cmp(&cl2.line).then(cl1.column.cmp(&cl2.column))) +pub fn get_location(path: &Path, ts: TokenStream) -> CodeLocation { + let span = ts.span().start(); + CodeLocation { + file: path.display().to_string(), + line: span.line as u32, + column: span.column as u32 + 1, + } } diff --git a/src/server/mod.rs b/src/server/mod.rs index 8a98fd9d9..3397beac6 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -7,6 +7,7 @@ use axum::{ Router, }; use notify::{RecommendedWatcher, Watcher}; +use syn::spanned::Spanned; use std::{path::PathBuf, sync::Arc}; use tower::ServiceBuilder; @@ -93,16 +94,30 @@ pub async fn startup_hot_reload(config: CrateConfig) -> Result<()> { DiffResult::RsxChanged(changed) => { log::info!("reloading rsx"); for (old, new) in changed.into_iter() { - if let Some(hr) = get_min_location( + let hr = get_location( &path.strip_prefix(&crate_dir).unwrap().to_path_buf(), old.to_token_stream(), - ) { - let rsx = new.to_string(); - let _ = hot_reload_tx.send(SetRsxMessage { - location: hr, - new_text: rsx, - }); + ); + // get the original source code to preserve whitespace + let span = new.span(); + let start = span.start(); + let end = span.end(); + let mut lines: Vec<_> = src + .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() { + *last = last.split_at(end.column).0; + } + let rsx = lines.join("\n"); + let _ = hot_reload_tx.send(SetRsxMessage { + location: hr, + new_text: rsx, + }); } } } From edc31dfc031025a6a7893da39fe3278fa053d2c7 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Wed, 15 Jun 2022 13:58:24 -0500 Subject: [PATCH 14/21] better error messages --- src/server/hot_reload.rs | 45 +++++++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/src/server/hot_reload.rs b/src/server/hot_reload.rs index f79a7a50e..2011b8958 100644 --- a/src/server/hot_reload.rs +++ b/src/server/hot_reload.rs @@ -86,22 +86,36 @@ pub async fn hot_reload_handler( } } } - let mut new = String::new(); - file.read_to_string(&mut new).expect("Unable to read file"); - if let Ok(new) = syn::parse_file(&new) { - if let Ok(old) = syn::parse_file(&v) { - if let DiffResult::RsxChanged(changed) = find_rsx(&new, &old) { + 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( k.strip_prefix(&state.watcher_config.crate_dir).unwrap(), old.to_token_stream(), ); - let rsx = new.to_string(); - let msg = SetRsxMessage { + // 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() { + *last = last.split_at(end.column).0; + } + let rsx = lines.join("\n"); + messages.push(SetRsxMessage { location: hr, new_text: rsx, - }; - messages.push(msg); + }); } } } @@ -131,9 +145,16 @@ pub async fn hot_reload_handler( if let Some(Ok(err)) = err { if let Message::Text(err) = err { let error: Error = serde_json::from_str(&err).unwrap(); - log::error!("{:?}", error); - if state.update.send("reload".to_string()).is_err() { - break; + match error{ + Error::ParseError(parse_error) => { + log::error!("parse error:\n--> at {}:{}:{}\n\t{:?}", parse_error.location.file, parse_error.location.line, parse_error.location.column, parse_error.message); + }, + Error::RecompileRequiredError(reason) => { + log::info!("{:?}", reason); + if state.update.send("reload".to_string()).is_err() { + break; + } + } } } } else { From 8e33b8e02711cc4108f4d05d2cf95d647ad697d7 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 16 Jun 2022 09:11:02 -0500 Subject: [PATCH 15/21] improve speed when reloading the site --- src/server/hot_reload.rs | 91 +++++++++++++++++++++-------- src/server/mod.rs | 120 +++++++++++++++++++++------------------ 2 files changed, 132 insertions(+), 79 deletions(-) diff --git a/src/server/hot_reload.rs b/src/server/hot_reload.rs index 2011b8958..14f9ffbc6 100644 --- a/src/server/hot_reload.rs +++ b/src/server/hot_reload.rs @@ -2,12 +2,13 @@ use axum::{ extract::{ws::Message, Extension, TypedHeader, WebSocketUpgrade}, response::IntoResponse, }; +use dioxus_rsx_interpreter::SetRsxMessage; use std::{path::PathBuf, sync::Arc}; pub use crate::hot_reload::{find_rsx, DiffResult}; use crate::CrateConfig; -pub use dioxus_rsx_interpreter::{error::Error, CodeLocation, SetRsxMessage}; +pub use dioxus_rsx_interpreter::{error::Error, CodeLocation, SetManyRsxMessage}; pub use proc_macro2::TokenStream; pub use std::collections::HashMap; pub use std::sync::Mutex; @@ -19,7 +20,7 @@ use syn::spanned::Spanned; use tokio::sync::broadcast; pub struct HotReloadState { - pub messages: broadcast::Sender, + pub messages: broadcast::Sender, pub update: broadcast::Sender, pub last_file_rebuild: Arc>, pub watcher_config: CrateConfig, @@ -32,32 +33,76 @@ pub struct FileMap { 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> { let mut files = HashMap::new(); if root.is_dir() { - let mut handles = Vec::new(); for entry in fs::read_dir(root)? { if let Ok(entry) = entry { let path = entry.path(); - handles.push(std::thread::spawn(move || find_rs_files(path))); + files.extend(find_rs_files(path)?); } } - for handle in handles { - files.extend(handle.join().unwrap()?); - } } else { if root.extension().map(|s| s.to_str()).flatten() == Some("rs") { - let mut file = File::open(root.clone()).unwrap(); - let mut src = String::new(); - file.read_to_string(&mut src).expect("Unable to read file"); - files.insert(root, src); + 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); + } } } Ok(files) } - Self { - last_updated_time: SystemTime::now(), + + let last_updated_time = SystemTime::now(); + let result = Self { + last_updated_time, map: find_rs_files(path).unwrap(), + }; + log::info!("Files updated"); + result + } + + pub fn update(&mut self, path: PathBuf) { + log::info!("Updating files that changed since last compile..."); + self.last_updated_time = SystemTime::now(); + self.update_inner(path); + log::info!("Files updated"); + } + + fn update_inner(&mut self, path: PathBuf) { + if path.is_dir() { + if let Ok(files) = fs::read_dir(path) { + for entry in files { + if let Ok(entry) = entry { + if let Ok(md) = entry.metadata() { + if let Ok(time) = md.modified() { + if time < self.last_updated_time { + return; + } + } + } + let path = entry.path(); + self.update(path); + } + } + } + } else { + if path.extension().map(|s| s.to_str()).flatten() == Some("rs") { + if let Ok(mut file) = File::open(path.clone()) { + if let Ok(md) = file.metadata() { + if let Ok(time) = md.modified() { + if time < self.last_updated_time { + return; + } + } + } + let mut src = String::new(); + file.read_to_string(&mut src).expect("Unable to read file"); + self.map.insert(path, src); + } + } } } } @@ -70,7 +115,6 @@ pub async fn hot_reload_handler( ws.on_upgrade(|mut socket| async move { log::info!("🔥 Hot Reload WebSocket connected"); { - log::info!("Searching files for changes since last run..."); // update any files that changed before the websocket connected. let mut messages = Vec::new(); @@ -123,16 +167,14 @@ pub async fn hot_reload_handler( } } - for msg in &messages { - if socket - .send(Message::Text(serde_json::to_string(msg).unwrap())) - .await - .is_err() - { - return; - } + let msg = SetManyRsxMessage(messages); + if socket + .send(Message::Text(serde_json::to_string(&msg).unwrap())) + .await + .is_err() + { + return; } - log::info!("Updated page"); } let mut rx = state.messages.subscribe(); @@ -149,8 +191,7 @@ pub async fn hot_reload_handler( Error::ParseError(parse_error) => { log::error!("parse error:\n--> at {}:{}:{}\n\t{:?}", parse_error.location.file, parse_error.location.line, parse_error.location.column, parse_error.message); }, - Error::RecompileRequiredError(reason) => { - log::info!("{:?}", reason); + Error::RecompileRequiredError(_) => { if state.update.send("reload".to_string()).is_err() { break; } diff --git a/src/server/mod.rs b/src/server/mod.rs index 3397beac6..19791fd79 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -6,6 +6,7 @@ use axum::{ routing::{get, get_service}, Router, }; +use dioxus_rsx_interpreter::SetRsxMessage; use notify::{RecommendedWatcher, Watcher}; use syn::spanned::Spanned; @@ -67,67 +68,75 @@ pub async fn startup_hot_reload(config: CrateConfig) -> Result<()> { .unwrap_or_else(|| vec![PathBuf::from("src")]); let mut watcher = RecommendedWatcher::new(move |evt: notify::Result| { - if let Ok(evt) = evt { - for path in evt.paths { - let mut file = File::open(path.clone()).unwrap(); - if path.extension().map(|p| p.to_str()).flatten() != Some("rs") { - continue; - } - let mut src = String::new(); - file.read_to_string(&mut src).expect("Unable to read file"); - if src.is_empty() { - continue; - } - // 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) { - match find_rsx(&syntax, &old) { - DiffResult::CodeChanged => { - log::info!("reload required"); - if chrono::Local::now().timestamp() > last_update_time { - let _ = reload_tx.send("reload".into()); - last_update_time = chrono::Local::now().timestamp(); + if chrono::Local::now().timestamp() > last_update_time { + if let Ok(evt) = evt { + let mut messages = Vec::new(); + for path in evt.paths { + let mut file = File::open(path.clone()).unwrap(); + if path.extension().map(|p| p.to_str()).flatten() != Some("rs") { + continue; + } + let mut src = String::new(); + file.read_to_string(&mut src).expect("Unable to read file"); + if src.is_empty() { + continue; + } + // 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) { + match find_rsx(&syntax, &old) { + DiffResult::CodeChanged => { + log::info!("reload required"); + let _ = reload_tx.send("reload time".into()); + break; } - } - DiffResult::RsxChanged(changed) => { - log::info!("reloading rsx"); - for (old, new) in changed.into_iter() { - let hr = get_location( - &path.strip_prefix(&crate_dir).unwrap().to_path_buf(), - 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<_> = src - .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; + DiffResult::RsxChanged(changed) => { + log::info!("reloading rsx"); + for (old, new) in changed.into_iter() { + let hr = get_location( + &path + .strip_prefix(&crate_dir) + .unwrap() + .to_path_buf(), + 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<_> = src + .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() { + *last = last.split_at(end.column).0; + } + let rsx = lines.join("\n"); + messages.push(SetRsxMessage { + location: hr, + new_text: rsx, + }); } - if let Some(last) = lines.last_mut() { - *last = last.split_at(end.column).0; - } - let rsx = lines.join("\n"); - let _ = hot_reload_tx.send(SetRsxMessage { - location: hr, - new_text: rsx, - }); } } } + } else { + // if this is a new file, rebuild the project + *last_file_rebuild = FileMap::new(crate_dir.clone()); } - } else { - // if this is a new file, rebuild the project - *last_file_rebuild = FileMap::new(crate_dir.clone()); } } + if !messages.is_empty() { + let _ = hot_reload_tx.send(SetManyRsxMessage(messages)); + } } + last_update_time = chrono::Local::now().timestamp(); } }) .unwrap(); @@ -313,7 +322,7 @@ async fn ws_handler( _: Option>, Extension(state): Extension>, ) -> impl IntoResponse { - ws.on_upgrade(|mut socket| async move { + ws.max_send_queue(1).on_upgrade(|mut socket| async move { let mut rx = state.update.subscribe(); let reload_watcher = tokio::spawn(async move { loop { @@ -335,7 +344,7 @@ async fn ws_handler( } if let Some(file_map) = &state.last_file_rebuild { let mut write = file_map.lock().unwrap(); - *write = FileMap::new(state.watcher_config.crate_dir.clone()); + write.update(state.watcher_config.crate_dir.clone()); } } // ignore the error @@ -346,6 +355,9 @@ async fn ws_handler( { break; } + + // flush the errors after recompling + rx = rx.resubscribe(); } } }); From 22e40c6bc1f35f0a3c9a6ec9d225f7c4fe029fff Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 16 Jun 2022 10:21:28 -0500 Subject: [PATCH 16/21] removed debugging messages --- src/server/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/mod.rs b/src/server/mod.rs index 19791fd79..bf9299022 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -89,7 +89,7 @@ pub async fn startup_hot_reload(config: CrateConfig) -> Result<()> { match find_rsx(&syntax, &old) { DiffResult::CodeChanged => { log::info!("reload required"); - let _ = reload_tx.send("reload time".into()); + let _ = reload_tx.send("reload".into()); break; } DiffResult::RsxChanged(changed) => { From a79046f36a39a20cc4e43f8e3d83df46e23b4fa5 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 16 Jun 2022 13:13:06 -0500 Subject: [PATCH 17/21] use build manager instead of rebuilding in the ws handler --- src/server/hot_reload.rs | 53 +++--------------- src/server/mod.rs | 115 ++++++++++++++++++++++----------------- 2 files changed, 72 insertions(+), 96 deletions(-) diff --git a/src/server/hot_reload.rs b/src/server/hot_reload.rs index 14f9ffbc6..6a2a58ba0 100644 --- a/src/server/hot_reload.rs +++ b/src/server/hot_reload.rs @@ -6,6 +6,7 @@ use dioxus_rsx_interpreter::SetRsxMessage; use std::{path::PathBuf, sync::Arc}; +use super::BuildManager; pub use crate::hot_reload::{find_rsx, DiffResult}; use crate::CrateConfig; pub use dioxus_rsx_interpreter::{error::Error, CodeLocation, SetManyRsxMessage}; @@ -21,7 +22,7 @@ use tokio::sync::broadcast; pub struct HotReloadState { pub messages: broadcast::Sender, - pub update: broadcast::Sender, + pub build_manager: Arc, pub last_file_rebuild: Arc>, pub watcher_config: CrateConfig, } @@ -63,48 +64,6 @@ impl FileMap { log::info!("Files updated"); result } - - pub fn update(&mut self, path: PathBuf) { - log::info!("Updating files that changed since last compile..."); - self.last_updated_time = SystemTime::now(); - self.update_inner(path); - log::info!("Files updated"); - } - - fn update_inner(&mut self, path: PathBuf) { - if path.is_dir() { - if let Ok(files) = fs::read_dir(path) { - for entry in files { - if let Ok(entry) = entry { - if let Ok(md) = entry.metadata() { - if let Ok(time) = md.modified() { - if time < self.last_updated_time { - return; - } - } - } - let path = entry.path(); - self.update(path); - } - } - } - } else { - if path.extension().map(|s| s.to_str()).flatten() == Some("rs") { - if let Ok(mut file) = File::open(path.clone()) { - if let Ok(md) = file.metadata() { - if let Ok(time) = md.modified() { - if time < self.last_updated_time { - return; - } - } - } - let mut src = String::new(); - file.read_to_string(&mut src).expect("Unable to read file"); - self.map.insert(path, src); - } - } - } - } } pub async fn hot_reload_handler( @@ -115,10 +74,11 @@ pub async fn hot_reload_handler( ws.on_upgrade(|mut socket| async move { log::info!("🔥 Hot Reload WebSocket connected"); { - // update any files that changed before the websocket connected. + // update any rsx calls that changed before the websocket connected. let mut messages = Vec::new(); { + println!("Finding updates since last reload..."); let handle = state.last_file_rebuild.lock().unwrap(); let update_time = handle.last_updated_time.clone(); for (k, v) in handle.map.iter() { @@ -165,6 +125,7 @@ pub async fn hot_reload_handler( } } } + println!("finished"); } let msg = SetManyRsxMessage(messages); @@ -192,8 +153,8 @@ pub async fn hot_reload_handler( log::error!("parse error:\n--> at {}:{}:{}\n\t{:?}", parse_error.location.file, parse_error.location.line, parse_error.location.column, parse_error.message); }, Error::RecompileRequiredError(_) => { - if state.update.send("reload".to_string()).is_err() { - break; + if let Err(err) = state.build_manager.build(){ + log::error!("{}", err); } } } diff --git a/src/server/mod.rs b/src/server/mod.rs index bf9299022..5742c86ee 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -20,18 +20,43 @@ use tokio::sync::broadcast; mod hot_reload; use hot_reload::*; +pub struct BuildManager { + config: CrateConfig, + reload_tx: broadcast::Sender<()>, +} + +impl BuildManager { + fn build(&self) -> Result<()> { + log::info!("Start to rebuild project..."); + builder::build(&self.config)?; + // change the websocket reload state to true; + // the page will auto-reload. + if self + .config + .dioxus_config + .web + .watcher + .reload_html + .unwrap_or(false) + { + let _ = Serve::regen_dev_page(&self.config); + } + let _ = self.reload_tx.send(()); + Ok(()) + } +} + struct WsReloadState { - update: broadcast::Sender, - last_file_rebuild: Option>>, - watcher_config: CrateConfig, + update: broadcast::Sender<()>, } pub async fn startup(config: CrateConfig) -> Result<()> { if config.hot_reload { - startup_hot_reload(config).await + startup_hot_reload(config).await? } else { - startup_default(config).await + startup_default(config).await? } + Ok(()) } pub async fn startup_hot_reload(config: CrateConfig) -> Result<()> { @@ -40,10 +65,14 @@ pub async fn startup_hot_reload(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 build_manager = Arc::new(BuildManager { + config: config.clone(), + reload_tx: reload_tx.clone(), + }); let hot_reload_tx = broadcast::channel(100).0; let hot_reload_state = Arc::new(HotReloadState { messages: hot_reload_tx.clone(), - update: reload_tx.clone(), + build_manager: build_manager.clone(), last_file_rebuild: last_file_rebuild.clone(), watcher_config: config.clone(), }); @@ -51,9 +80,6 @@ pub async fn startup_hot_reload(config: CrateConfig) -> Result<()> { let crate_dir = config.crate_dir.clone(); let ws_reload_state = Arc::new(WsReloadState { update: reload_tx.clone(), - - last_file_rebuild: Some(last_file_rebuild.clone()), - watcher_config: config.clone(), }); let mut last_update_time = chrono::Local::now().timestamp(); @@ -71,6 +97,7 @@ pub async fn startup_hot_reload(config: CrateConfig) -> Result<()> { if chrono::Local::now().timestamp() > last_update_time { if let Ok(evt) = evt { let mut messages = Vec::new(); + let mut needs_rebuild = false; for path in evt.paths { let mut file = File::open(path.clone()).unwrap(); if path.extension().map(|p| p.to_str()).flatten() != Some("rs") { @@ -88,9 +115,8 @@ pub async fn startup_hot_reload(config: CrateConfig) -> Result<()> { if let Ok(old) = syn::parse_file(&old_str) { match find_rsx(&syntax, &old) { DiffResult::CodeChanged => { - log::info!("reload required"); - let _ = reload_tx.send("reload".into()); - break; + needs_rebuild = true; + last_file_rebuild.map.insert(path, src); } DiffResult::RsxChanged(changed) => { log::info!("reloading rsx"); @@ -132,6 +158,12 @@ pub async fn startup_hot_reload(config: CrateConfig) -> Result<()> { } } } + if needs_rebuild { + log::info!("reload required"); + if let Err(err) = build_manager.build() { + log::error!("{}", err); + } + } if !messages.is_empty() { let _ = hot_reload_tx.send(SetManyRsxMessage(messages)); } @@ -221,11 +253,13 @@ pub async fn startup_default(config: CrateConfig) -> Result<()> { let (reload_tx, _) = broadcast::channel(100); + let build_manager = BuildManager { + config: config.clone(), + reload_tx: reload_tx.clone(), + }; + let ws_reload_state = Arc::new(WsReloadState { update: reload_tx.clone(), - - last_file_rebuild: None, - watcher_config: config.clone(), }); let mut last_update_time = chrono::Local::now().timestamp(); @@ -242,8 +276,10 @@ pub async fn startup_default(config: CrateConfig) -> Result<()> { let mut watcher = RecommendedWatcher::new(move |_: notify::Result| { log::info!("reload required"); if chrono::Local::now().timestamp() > last_update_time { - let _ = reload_tx.send("reload".into()); - last_update_time = chrono::Local::now().timestamp(); + match build_manager.build() { + Ok(_) => last_update_time = chrono::Local::now().timestamp(), + Err(e) => log::error!("{}", e), + } } }) .unwrap(); @@ -322,43 +358,22 @@ async fn ws_handler( _: Option>, Extension(state): Extension>, ) -> impl IntoResponse { - ws.max_send_queue(1).on_upgrade(|mut socket| async move { + ws.on_upgrade(|mut socket| async move { let mut rx = state.update.subscribe(); let reload_watcher = tokio::spawn(async move { loop { - let v = rx.recv().await.unwrap(); - if v == "reload" { - log::info!("Start to rebuild project..."); - if builder::build(&state.watcher_config).is_ok() { - // change the websocket reload state to true; - // the page will auto-reload. - if state - .watcher_config - .dioxus_config - .web - .watcher - .reload_html - .unwrap_or(false) - { - let _ = Serve::regen_dev_page(&state.watcher_config); - } - if let Some(file_map) = &state.last_file_rebuild { - let mut write = file_map.lock().unwrap(); - write.update(state.watcher_config.crate_dir.clone()); - } - } - // ignore the error - if socket - .send(Message::Text(String::from("reload"))) - .await - .is_err() - { - break; - } - - // flush the errors after recompling - rx = rx.resubscribe(); + rx.recv().await.unwrap(); + // ignore the error + if socket + .send(Message::Text(String::from("reload"))) + .await + .is_err() + { + break; } + + // flush the errors after recompling + rx = rx.resubscribe(); } }); From 815bdcdf0032a37518ee1611d9b0cd08bd2c79f3 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 16 Jun 2022 13:23:45 -0500 Subject: [PATCH 18/21] clean up logs --- src/server/hot_reload.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/hot_reload.rs b/src/server/hot_reload.rs index 6a2a58ba0..a0eb43e08 100644 --- a/src/server/hot_reload.rs +++ b/src/server/hot_reload.rs @@ -78,7 +78,7 @@ pub async fn hot_reload_handler( let mut messages = Vec::new(); { - println!("Finding updates since last reload..."); + 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() { @@ -125,7 +125,7 @@ pub async fn hot_reload_handler( } } } - println!("finished"); + log::info!("finished"); } let msg = SetManyRsxMessage(messages); From 2908e53d1b55d6d5664d1b27046d53966d42bd5e Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 17 Jun 2022 14:50:05 -0500 Subject: [PATCH 19/21] add delay before reading the changed file --- src/server/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/server/mod.rs b/src/server/mod.rs index 1eb159ae5..16401aa85 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -95,6 +95,8 @@ pub async fn startup_hot_reload(config: CrateConfig) -> Result<()> { let mut watcher = RecommendedWatcher::new(move |evt: notify::Result| { 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)); if let Ok(evt) = evt { let mut messages = Vec::new(); let mut needs_rebuild = false; @@ -105,9 +107,6 @@ pub async fn startup_hot_reload(config: CrateConfig) -> Result<()> { } let mut src = String::new(); file.read_to_string(&mut src).expect("Unable to read file"); - if src.is_empty() { - continue; - } // 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(); From 7025bd43aab4e98c8fe1b5907f51c19117efc814 Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Mon, 20 Jun 2022 15:44:14 -0500 Subject: [PATCH 20/21] use line!, col!, and row! instead of panic location --- src/server/hot_reload.rs | 2 +- src/server/mod.rs | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/server/hot_reload.rs b/src/server/hot_reload.rs index a0eb43e08..7c9a6957e 100644 --- a/src/server/hot_reload.rs +++ b/src/server/hot_reload.rs @@ -97,7 +97,7 @@ pub async fn hot_reload_handler( if let DiffResult::RsxChanged(changed) = find_rsx(&new_file, &old_file) { for (old, new) in changed.into_iter() { let hr = get_location( - k.strip_prefix(&state.watcher_config.crate_dir).unwrap(), + k, old.to_token_stream(), ); // get the original source code to preserve whitespace diff --git a/src/server/mod.rs b/src/server/mod.rs index 16401aa85..8cdd76373 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -121,10 +121,7 @@ pub async fn startup_hot_reload(config: CrateConfig) -> Result<()> { log::info!("reloading rsx"); for (old, new) in changed.into_iter() { let hr = get_location( - &path - .strip_prefix(&crate_dir) - .unwrap() - .to_path_buf(), + &path.to_path_buf(), old.to_token_stream(), ); // get the original source code to preserve whitespace From f8014f307b7ae0ac53a9415e4849d35b4a286fc3 Mon Sep 17 00:00:00 2001 From: Demonthos <66571940+Demonthos@users.noreply.github.com> Date: Tue, 21 Jun 2022 10:19:42 -0500 Subject: [PATCH 21/21] depend on dioxus master until next release --- Cargo.toml | 2 +- src/server/hot_reload.rs | 4 ++-- src/server/mod.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f5afe80db..59a840d77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,7 @@ zip = "0.6.2" tower = "0.4.12" syn = { version = "1.0" } -dioxus-rsx-interpreter = { path = "../dioxus/packages/rsx_interpreter" } +dioxus = { git = "https://github.com/dioxuslabs/dioxus/", features = ["hot_reload"] } proc-macro2 = { version = "1.0", features = ["span-locations"] } diff --git a/src/server/hot_reload.rs b/src/server/hot_reload.rs index 7c9a6957e..ea9c64fc7 100644 --- a/src/server/hot_reload.rs +++ b/src/server/hot_reload.rs @@ -2,14 +2,14 @@ use axum::{ extract::{ws::Message, Extension, TypedHeader, WebSocketUpgrade}, response::IntoResponse, }; -use dioxus_rsx_interpreter::SetRsxMessage; +use dioxus::rsx_interpreter::SetRsxMessage; use std::{path::PathBuf, sync::Arc}; use super::BuildManager; pub use crate::hot_reload::{find_rsx, DiffResult}; use crate::CrateConfig; -pub use dioxus_rsx_interpreter::{error::Error, CodeLocation, SetManyRsxMessage}; +pub use dioxus::rsx_interpreter::{error::Error, CodeLocation, SetManyRsxMessage}; pub use proc_macro2::TokenStream; pub use std::collections::HashMap; pub use std::sync::Mutex; diff --git a/src/server/mod.rs b/src/server/mod.rs index 8cdd76373..387355ee9 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -6,7 +6,7 @@ use axum::{ routing::{get, get_service}, Router, }; -use dioxus_rsx_interpreter::SetRsxMessage; +use dioxus::rsx_interpreter::SetRsxMessage; use notify::{RecommendedWatcher, Watcher}; use syn::spanned::Spanned;