From f0655a11adddeed2e4ef5a72d600fdafd411ad6c Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Fri, 1 Jul 2022 09:01:15 -0500 Subject: [PATCH] panic on errors in hot-reload if no error handler socket is connected --- packages/desktop/src/controller.rs | 2 + .../rsx_interpreter/src/captuered_context.rs | 4 +- packages/rsx_interpreter/src/error.rs | 17 ++++ packages/rsx_interpreter/src/interperter.rs | 13 ++- packages/rsx_interpreter/src/lib.rs | 2 + packages/web/src/lib.rs | 98 ++++++++++--------- 6 files changed, 84 insertions(+), 52 deletions(-) diff --git a/packages/desktop/src/controller.rs b/packages/desktop/src/controller.rs index 6c0c7feef..717819182 100644 --- a/packages/desktop/src/controller.rs +++ b/packages/desktop/src/controller.rs @@ -86,6 +86,8 @@ impl DesktopController { (serde_json::to_string(&err).unwrap() + "\n").as_bytes(), ) .unwrap(); + } else { + panic!("{}", err); } } } diff --git a/packages/rsx_interpreter/src/captuered_context.rs b/packages/rsx_interpreter/src/captuered_context.rs index 8473771e9..665d8ed34 100644 --- a/packages/rsx_interpreter/src/captuered_context.rs +++ b/packages/rsx_interpreter/src/captuered_context.rs @@ -113,9 +113,9 @@ impl ToTokens for CapturedContextBuilder { let expr = segment.to_token_stream(); let as_string = expr.to_string(); let format_expr = if format_args.is_empty() { - "{".to_string() + format_args + "}" + "{".to_string() + &format_args + "}" } else { - "{".to_string() + ":" + format_args + "}" + "{".to_string() + ":" + &format_args + "}" }; Some(quote! { FormattedArg{ diff --git a/packages/rsx_interpreter/src/error.rs b/packages/rsx_interpreter/src/error.rs index 368d81d6f..82e5d4fb5 100644 --- a/packages/rsx_interpreter/src/error.rs +++ b/packages/rsx_interpreter/src/error.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use serde::{Deserialize, Serialize}; use crate::CodeLocation; @@ -37,3 +39,18 @@ impl ParseError { ParseError { message, location } } } + +impl Display for Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Error::ParseError(error) => write!( + f, + "parse error:\n--> at {}:{}:{}\n\t{:?}\n", + error.location.file_path, error.location.line, error.location.column, error.message + ), + Error::RecompileRequiredError(reason) => { + write!(f, "recompile required: {:?}\n", reason) + } + } + } +} diff --git a/packages/rsx_interpreter/src/interperter.rs b/packages/rsx_interpreter/src/interperter.rs index f35488c05..07dc70bac 100644 --- a/packages/rsx_interpreter/src/interperter.rs +++ b/packages/rsx_interpreter/src/interperter.rs @@ -39,7 +39,7 @@ fn resolve_ifmt(ifmt: &IfmtInput, captured: &IfmtArgs) -> Result } } } - Segment::Literal(lit) => result.push_str(lit), + Segment::Literal(lit) => result.push_str(&lit), } } Ok(result) @@ -117,12 +117,12 @@ fn build_node<'a>( ElementAttr::AttrExpression { .. } | ElementAttr::CustomAttrExpression { .. } => { - let (name, value) = match &attr.attr { + let (name, value, span) = match &attr.attr { ElementAttr::AttrExpression { name, value } => { - (name.to_string(), value) + (name.to_string(), value, name.span()) } ElementAttr::CustomAttrExpression { name, value } => { - (name.value(), value) + (name.value(), value, name.span()) } _ => unreachable!(), }; @@ -140,6 +140,11 @@ fn build_node<'a>( is_volatile: false, namespace, }); + } else { + return Err(Error::ParseError(ParseError::new( + syn::Error::new(span, format!("unknown attribute: {}", name)), + ctx.location.clone(), + ))); } } else { return Err(Error::RecompileRequiredError( diff --git a/packages/rsx_interpreter/src/lib.rs b/packages/rsx_interpreter/src/lib.rs index ff50dcdb2..b5e596865 100644 --- a/packages/rsx_interpreter/src/lib.rs +++ b/packages/rsx_interpreter/src/lib.rs @@ -150,6 +150,8 @@ impl RsxContext { fn report_error(&self, error: Error) { if let Some(handler) = &self.data.write().unwrap().error_handler { handler.handle_error(error) + } else { + panic!("no error handler set for this platform...\n{}", error); } } diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 115e70fe5..e7a4d098d 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -62,6 +62,7 @@ use dioxus_core::prelude::Component; use dioxus_core::SchedulerMsg; use dioxus_core::VirtualDom; use futures_util::FutureExt; +use web_sys::console; mod cache; mod cfg; @@ -172,50 +173,6 @@ pub fn launch_with_props( pub async fn run_with_props(root: Component, root_props: T, cfg: WebConfig) { let mut dom = VirtualDom::new_with_props(root, root_props); - for s in crate::cache::BUILTIN_INTERNED_STRINGS { - wasm_bindgen::intern(s); - } - for s in &cfg.cached_strings { - wasm_bindgen::intern(s); - } - - let tasks = dom.get_scheduler_channel(); - - let sender_callback: Rc = - Rc::new(move |event| tasks.unbounded_send(event).unwrap()); - - let should_hydrate = cfg.hydrate; - - let mut websys_dom = dom::WebsysDom::new(cfg, sender_callback); - - log::trace!("rebuilding app"); - - if should_hydrate { - // todo: we need to split rebuild and initialize into two phases - // it's a waste to produce edits just to get the vdom loaded - let _ = dom.rebuild(); - - if let Err(err) = websys_dom.rehydrate(&dom) { - log::error!( - "Rehydration failed {:?}. Rebuild DOM into element from scratch", - &err - ); - - websys_dom.root.set_text_content(None); - - // errrrr we should split rebuild into two phases - // one that initializes things and one that produces edits - let edits = dom.rebuild(); - - websys_dom.apply_edits(edits.edits); - } - } else { - let edits = dom.rebuild(); - websys_dom.apply_edits(edits.edits); - } - - let mut work_loop = ric_raf::RafLoop::new(); - #[cfg(feature = "hot-reload")] { use dioxus_rsx_interpreter::error::Error; @@ -274,12 +231,61 @@ pub async fn run_with_props(root: Component, root_props: T // forward stream to the websocket dom.base_scope().spawn_forever(async move { while let Some(err) = error_channel_receiver.next().await { - ws.send_with_str(serde_json::to_string(&err).unwrap().as_str()) - .unwrap(); + if ws.ready_state() == WebSocket::OPEN { + ws.send_with_str(serde_json::to_string(&err).unwrap().as_str()) + .unwrap(); + } else { + console::warn_1(&"WebSocket is not open, cannot send error. Run with dioxus serve --hot-reload to enable hot reloading.".into()); + panic!("{}", err); + } } }); } + for s in crate::cache::BUILTIN_INTERNED_STRINGS { + wasm_bindgen::intern(s); + } + for s in &cfg.cached_strings { + wasm_bindgen::intern(s); + } + + let tasks = dom.get_scheduler_channel(); + + let sender_callback: Rc = + Rc::new(move |event| tasks.unbounded_send(event).unwrap()); + + let should_hydrate = cfg.hydrate; + + let mut websys_dom = dom::WebsysDom::new(cfg, sender_callback); + + log::trace!("rebuilding app"); + + if should_hydrate { + // todo: we need to split rebuild and initialize into two phases + // it's a waste to produce edits just to get the vdom loaded + let _ = dom.rebuild(); + + if let Err(err) = websys_dom.rehydrate(&dom) { + log::error!( + "Rehydration failed {:?}. Rebuild DOM into element from scratch", + &err + ); + + websys_dom.root.set_text_content(None); + + // errrrr we should split rebuild into two phases + // one that initializes things and one that produces edits + let edits = dom.rebuild(); + + websys_dom.apply_edits(edits.edits); + } + } else { + let edits = dom.rebuild(); + websys_dom.apply_edits(edits.edits); + } + + let mut work_loop = ric_raf::RafLoop::new(); + loop { log::trace!("waiting for work"); // if virtualdom has nothing, wait for it to have something before requesting idle time