Prechádzať zdrojové kódy

better error handling

Evan Almloff 3 rokov pred
rodič
commit
2183ecf3fb

+ 87 - 38
examples/hot_reload.rs

@@ -1,55 +1,104 @@
 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);
             },
 
+            p {
+                "High-Five counter: {count}",
+            }
+
             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"
-                }
+                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<String>,
+}
+
+fn Editable(cx: Scope<EditableProps>) -> 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}",

+ 7 - 4
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);

+ 1 - 1
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" }

+ 13 - 0
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),
+}

+ 56 - 42
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<VNode<'a>, 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<VNode<'a>> {
+) -> Result<VNode<'a>, 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<Attribute> = 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::<Expr>(*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::<Expr>(*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::<Vec<_>>()
-                            );
+                            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::<Expr>(*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::<Expr>(*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::<Expr>(*code).unwrap() == iterator)
-            {
+            if let Some(idx) = ctx.iterators.iter().position(|(code, _)| {
+                if let Ok(parsed) = parse_str::<Expr>(*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::<Vec<_>>()
-                );
+                Err(Error::RecompileRequiredError(
+                    RecompileReason::CapturedExpression(iterator.to_token_stream().to_string()),
+                ))
             }
         }
     }

+ 8 - 2
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<VNode<'a>, Error> {
+    build(
+        parse_str(text).map_err(|err| Error::ParseError(err))?,
+        context,
+        &factory,
+    )
 }
 
 #[track_caller]

+ 1 - 1
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,
     };
 }