diff --git a/examples/hot_reload.rs b/examples/hot_reload.rs index a3472829d..32de0401b 100644 --- a/examples/hot_reload.rs +++ b/examples/hot_reload.rs @@ -1,56 +1,105 @@ use dioxus::prelude::*; fn main() { - dioxus::web::launch_with_props(with_hot_reload, app, |c| c); + dioxus::desktop::launch_with_props(with_hot_reload, app, |c| c); } fn app(cx: Scope) -> Element { - let count = use_state(&cx, || 170); - let rsx_code = use_state(&cx, || None); - let re_render = cx.schedule_update(); - - cx.render(rsx! { - div { + let rsx_code = use_state(&cx, || { + r##"div { width: "100%", height: "500px", onclick: move |_| { count.modify(|count| *count + 10); }, - div { - display: "flex", - flex_direction: "row", - width: "100%", - height: "50%", - textarea { - width: "90%", - value: { - if rsx_code.get().is_none() { - let rsx_text_index: RsxTextIndex = cx.consume_context().unwrap(); - let read = rsx_text_index.read(); - rsx_code.set(Some(read.get(&__line_num).unwrap().clone())); - } - (*rsx_code.current()).clone().unwrap() - }, - oninput: move |evt| { - rsx_code.set(Some(evt.value.clone())); - }, - } - - button { - height: "100%", - width: "10%", - onclick: move |_|{ - if let Some(code) = rsx_code.get(){ - let rsx_text_index: RsxTextIndex = cx.consume_context().unwrap(); - rsx_text_index.insert(__line_num.clone(), code.clone()); - re_render(); - } - }, - "submit" - } + p { + "High-Five counter: {count}", } + div { + width: "{count}px", + height: "10px", + background_color: "red", + } + + Comp { + color: "#083289" + } + + Comp { + color: "green" + } + + { + (0..10).map(|i| { + cx.render(rsx!{p {"{i}"}}) + }) + } + }"## + .to_string() + }); + let submitted_rsx_code = use_state(&cx, || None); + + cx.render(rsx! { + div { + display: "flex", + flex_direction: "row", + width: "100%", + height: "50%", + Editable{ + current_code: submitted_rsx_code.get().clone(), + }, + + textarea { + width: "90%", + value: + rsx_code + , + oninput: move |evt| { + rsx_code.set(evt.value.clone()); + }, + } + + button { + height: "100%", + width: "10%", + onclick: move |_|{ + submitted_rsx_code.set(Some(rsx_code.get().clone())); + }, + "submit" + } + } + }) +} + +#[derive(PartialEq, Props)] +struct EditableProps { + #[props(!optional)] + current_code: Option, +} + +fn Editable(cx: Scope) -> Element { + let count = use_state(&cx, || 170); + if let Some(code) = cx.props.current_code.as_ref() { + let rsx_index: RsxTextIndex = cx.consume_context().unwrap(); + rsx_index.insert( + CodeLocation { + file: r"examples\hot_reload.rs".to_string(), + line: 95, + column: 15, + }, + code.clone(), + ); + } + cx.render(rsx! { + div { + width: "100%", + height: "500px", + onclick: move |_| { + count.modify(|count| *count + 10); + }, + p { "High-Five counter: {count}", } diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs index 0c5503a4e..0827f6abe 100644 --- a/packages/core-macro/src/lib.rs +++ b/packages/core-macro/src/lib.rs @@ -1,5 +1,5 @@ use proc_macro::TokenStream; -use quote::{quote, ToTokens}; +use quote::ToTokens; use syn::parse_macro_input; mod inlineprops; @@ -189,7 +189,7 @@ pub fn rsx(s: TokenStream) -> TokenStream { use dioxus_rsx_interperter::captuered_context::CapturedContextBuilder; let captured = CapturedContextBuilder::from_call_body(body); - quote! { + quote::quote! { { let __line_num = get_line_num(); let __rsx_text_index: RsxTextIndex = cx.consume_context().unwrap(); @@ -206,11 +206,14 @@ pub fn rsx(s: TokenStream) -> TokenStream { // clone prevents deadlock on nested rsx calls read.get(&__line_num).cloned() } { - interpert_rsx( + match interpert_rsx( __cx, &__text, #captured - ) + ){ + Ok(vnode) => vnode, + Err(err) => __cx.text(format_args!("{:?}", err)) + } } else { panic!("rsx: line number {:?} not found in rsx index", __line_num); diff --git a/packages/rsx_interperter/Cargo.toml b/packages/rsx_interperter/Cargo.toml index fd03bf717..ed0c4e810 100644 --- a/packages/rsx_interperter/Cargo.toml +++ b/packages/rsx_interperter/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" license = "MIT/Apache-2.0" [dependencies] -syn = "1.0" +syn = { version = "1.0", features = ["extra-traits"] } quote = "1.0" dioxus-rsx = { path = "../rsx", default-features = false } dioxus-ssr = { path = "../ssr" } diff --git a/packages/rsx_interperter/src/error.rs b/packages/rsx_interperter/src/error.rs new file mode 100644 index 000000000..cf3eca7cc --- /dev/null +++ b/packages/rsx_interperter/src/error.rs @@ -0,0 +1,13 @@ +#[derive(Debug)] +pub enum Error { + ParseError(syn::Error), + RecompileRequiredError(RecompileReason), +} + +#[derive(Debug)] +pub enum RecompileReason { + CapturedVariable(String), + CapturedExpression(String), + CapturedComponent(String), + CapturedListener(String), +} diff --git a/packages/rsx_interperter/src/interperter.rs b/packages/rsx_interperter/src/interperter.rs index 743cce8f5..0ddd3b887 100644 --- a/packages/rsx_interperter/src/interperter.rs +++ b/packages/rsx_interperter/src/interperter.rs @@ -7,6 +7,7 @@ use syn::{parse2, parse_str, Expr}; use crate::attributes::attrbute_to_static_str; use crate::captuered_context::{CapturedContext, IfmtArgs}; use crate::elements::element_to_static_str; +use crate::error::{Error, RecompileReason}; #[derive(Debug)] enum Segment { @@ -88,25 +89,25 @@ pub fn build<'a>( rsx: CallBody, mut ctx: CapturedContext<'a>, factory: &NodeFactory<'a>, -) -> VNode<'a> { +) -> Result, Error> { let children_built = factory.bump().alloc(Vec::new()); for child in rsx.roots { - children_built.push(build_node(child, &mut ctx, factory)); + children_built.push(build_node(child, &mut ctx, factory)?); } - factory.fragment_from_iter(children_built.iter()) + Ok(factory.fragment_from_iter(children_built.iter())) } fn build_node<'a>( node: BodyNode, ctx: &mut CapturedContext<'a>, factory: &NodeFactory<'a>, -) -> Option> { +) -> Result, Error> { let bump = factory.bump(); match node { BodyNode::Text(text) => { let ifmt: InterperedIfmt = text.value().parse().unwrap(); let text = bump.alloc(ifmt.resolve(&ctx.captured)); - Some(factory.text(format_args!("{}", text))) + Ok(factory.text(format_args!("{}", text))) } BodyNode::Element(el) => { let attributes: &mut Vec = bump.alloc(Vec::new()); @@ -132,8 +133,6 @@ fn build_node<'a>( is_volatile: false, namespace, }); - } else { - return None; } } @@ -165,7 +164,11 @@ fn build_node<'a>( }); } } else { - panic!("could not resolve expression {:?}", value); + return Err(Error::RecompileRequiredError( + RecompileReason::CapturedExpression( + value.into_token_stream().to_string(), + ), + )); } } _ => (), @@ -174,7 +177,7 @@ fn build_node<'a>( let children = bump.alloc(Vec::new()); for child in el.children { let node = build_node(child, ctx, factory); - if let Some(node) = node { + if let Ok(node) = node { children.push(node); } } @@ -182,20 +185,23 @@ fn build_node<'a>( for attr in el.attributes { match attr.attr { ElementAttr::EventTokens { .. } => { - let expr: Expr = parse2(attr.to_token_stream()).unwrap(); - if let Some(idx) = ctx - .listeners - .iter() - .position(|(code, _)| parse_str::(*code).unwrap() == expr) - { + let expr: Expr = + parse2(attr.to_token_stream()).map_err(|err| Error::ParseError(err))?; + if let Some(idx) = ctx.listeners.iter().position(|(code, _)| { + if let Ok(parsed) = parse_str::(*code) { + parsed == expr + } else { + false + } + }) { let (_, listener) = ctx.listeners.remove(idx); listeners.push(listener) } else { - panic!( - "could not resolve listener {:?} \n in: {:#?}", - expr.to_token_stream().to_string(), - ctx.listeners.iter().map(|(s, _)| s).collect::>() - ); + return Err(Error::RecompileRequiredError( + RecompileReason::CapturedListener( + expr.to_token_stream().to_string(), + ), + )); } } _ => (), @@ -204,7 +210,7 @@ fn build_node<'a>( let tag = bump.alloc(el.name.to_string()); if let Some((tag, ns)) = element_to_static_str(tag) { match el.key { - None => Some(factory.raw_element( + None => Ok(factory.raw_element( tag, ns, listeners, @@ -216,7 +222,7 @@ fn build_node<'a>( let ifmt: InterperedIfmt = lit.value().parse().unwrap(); let key = bump.alloc(ifmt.resolve(&ctx.captured)); - Some(factory.raw_element( + Ok(factory.raw_element( tag, ns, listeners, @@ -227,36 +233,44 @@ fn build_node<'a>( } } } else { - None + Err(Error::ParseError(syn::Error::new( + el.name.span(), + "unknown element", + ))) } } BodyNode::Component(comp) => { - let expr: Expr = parse2(comp.to_token_stream()).unwrap(); - if let Some(idx) = ctx - .components - .iter() - .position(|(code, _)| parse_str::(*code).unwrap() == expr) - { + let expr: Expr = + parse2(comp.to_token_stream()).map_err(|err| Error::ParseError(err))?; + if let Some(idx) = ctx.components.iter().position(|(code, _)| { + if let Ok(parsed) = parse_str::(*code) { + parsed == expr + } else { + false + } + }) { let (_, vnode) = ctx.components.remove(idx); - Some(vnode) + Ok(vnode) } else { - panic!("could not resolve component {:?}", comp.name); + Err(Error::RecompileRequiredError( + RecompileReason::CapturedComponent(comp.name.to_token_stream().to_string()), + )) } } BodyNode::RawExpr(iterator) => { - if let Some(idx) = ctx - .iterators - .iter() - .position(|(code, _)| parse_str::(*code).unwrap() == iterator) - { + if let Some(idx) = ctx.iterators.iter().position(|(code, _)| { + if let Ok(parsed) = parse_str::(*code) { + parsed == iterator + } else { + false + } + }) { let (_, vnode) = ctx.iterators.remove(idx); - Some(vnode) + Ok(vnode) } else { - panic!( - "could not resolve iterator {} \n fron {:?}", - iterator.to_token_stream(), - ctx.iterators.iter().map(|(s, _)| s).collect::>() - ); + Err(Error::RecompileRequiredError( + RecompileReason::CapturedExpression(iterator.to_token_stream().to_string()), + )) } } } diff --git a/packages/rsx_interperter/src/lib.rs b/packages/rsx_interperter/src/lib.rs index 708d21edb..adad78490 100644 --- a/packages/rsx_interperter/src/lib.rs +++ b/packages/rsx_interperter/src/lib.rs @@ -1,5 +1,6 @@ use dioxus_core::{Component, Element, LazyNodes, Scope, VNode}; use dioxus_hooks::*; +use error::Error; use interperter::build; use std::collections::HashMap; use std::panic::Location; @@ -10,6 +11,7 @@ use syn::parse_str; mod attributes; pub mod captuered_context; mod elements; +mod error; mod interperter; #[derive(Debug, Clone, Hash, PartialEq, Eq)] @@ -35,8 +37,12 @@ pub fn interpert_rsx<'a, 'b>( factory: dioxus_core::NodeFactory<'a>, text: &str, context: captuered_context::CapturedContext<'a>, -) -> VNode<'a> { - build(parse_str(text).unwrap(), context, &factory) +) -> Result, Error> { + build( + parse_str(text).map_err(|err| Error::ParseError(err))?, + context, + &factory, + ) } #[track_caller] diff --git a/src/lib.rs b/src/lib.rs index a05077d68..246ec8e96 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -62,6 +62,6 @@ pub mod prelude { #[cfg(feature = "hot_reload")] pub use dioxus_rsx_interperter::{ captuered_context::{CapturedContext, IfmtArgs}, - get_line_num, interpert_rsx, with_hot_reload, RsxTextIndex, + get_line_num, interpert_rsx, with_hot_reload, CodeLocation, RsxTextIndex, }; }