diff --git a/packages/rsx-hotreload/src/diff.rs b/packages/rsx-hotreload/src/diff.rs index 194b26bfd..a89c67742 100644 --- a/packages/rsx-hotreload/src/diff.rs +++ b/packages/rsx-hotreload/src/diff.rs @@ -122,7 +122,10 @@ impl HotReloadResult { new: &TemplateBody, name: String, ) -> Option { - let full_rebuild_state = LastBuildState::new(full_rebuild_state, name); + // Normalize both the full rebuild state and the new state for rendering + let full_rebuild_state = full_rebuild_state.normalized(); + let new = new.normalized(); + let full_rebuild_state = LastBuildState::new(&full_rebuild_state, name); let mut s = Self { full_rebuild_state, templates: Default::default(), @@ -131,7 +134,7 @@ impl HotReloadResult { literal_component_properties: Default::default(), }; - s.hotreload_body::(new)?; + s.hotreload_body::(&new)?; Some(s) } diff --git a/packages/rsx-hotreload/tests/hotreload_pattern.rs b/packages/rsx-hotreload/tests/hotreload_pattern.rs index fb5dd873e..4e920abeb 100644 --- a/packages/rsx-hotreload/tests/hotreload_pattern.rs +++ b/packages/rsx-hotreload/tests/hotreload_pattern.rs @@ -467,6 +467,32 @@ fn invalid_cases() { assert!(added.is_none()); } +#[test] +fn invalid_empty_rsx() { + let old_template = quote! { + div { + for item in vec![1, 2, 3, 4] { + div { "asasddasdasd" } + div { "123" } + } + for item in vec![4, 5, 6] { + span { "asasddasdasd" } + span { "123" } + } + } + }; + + // empty out the whole rsx block + let new_template = quote! {}; + + let location = "file:line:col:0"; + + let old_template: CallBody = syn::parse2(old_template).unwrap(); + let new_template: CallBody = syn::parse2(new_template).unwrap(); + + assert!(hotreload_callbody::(&old_template, &new_template).is_none()); +} + #[test] fn attributes_reload() { let old = quote! { diff --git a/packages/rsx/src/template_body.rs b/packages/rsx/src/template_body.rs index 991b44cd0..5b860ecdb 100644 --- a/packages/rsx/src/template_body.rs +++ b/packages/rsx/src/template_body.rs @@ -91,6 +91,7 @@ impl Parse for TemplateBody { myself .diagnostics .extend(children.diagnostics.into_diagnostics()); + Ok(myself) } } @@ -99,51 +100,39 @@ impl Parse for TemplateBody { /// This is because the parsing phase filled in all the additional metadata we need impl ToTokens for TemplateBody { fn to_tokens(&self, tokens: &mut TokenStream2) { - // If the nodes are completely empty, insert a placeholder node - // Core expects at least one node in the template to make it easier to replace - if self.is_empty() { - // Create an empty template body with a placeholder and diagnostics + the template index from the original - let empty = Self::new(vec![BodyNode::RawExpr(parse_quote! {()})]); - let default = Self { - diagnostics: self.diagnostics.clone(), - template_idx: self.template_idx.clone(), - ..empty - }; - // And then render the default template body - default.to_tokens(tokens); - return; - } + // First normalize the template body for rendering + let node = self.normalized(); // If we have an implicit key, then we need to write its tokens - let key_tokens = match self.implicit_key() { + let key_tokens = match node.implicit_key() { Some(tok) => quote! { Some( #tok.to_string() ) }, None => quote! { None }, }; - let roots = self.quote_roots(); + let roots = node.quote_roots(); // Print paths is easy - just print the paths - let node_paths = self.node_paths.iter().map(|it| quote!(&[#(#it),*])); - let attr_paths = self.attr_paths.iter().map(|(it, _)| quote!(&[#(#it),*])); + let node_paths = node.node_paths.iter().map(|it| quote!(&[#(#it),*])); + let attr_paths = node.attr_paths.iter().map(|(it, _)| quote!(&[#(#it),*])); // For printing dynamic nodes, we rely on the ToTokens impl // Elements have a weird ToTokens - they actually are the entrypoint for Template creation - let dynamic_nodes: Vec<_> = self.dynamic_nodes().collect(); + let dynamic_nodes: Vec<_> = node.dynamic_nodes().collect(); let dynamic_nodes_len = dynamic_nodes.len(); // We could add a ToTokens for Attribute but since we use that for both components and elements // They actually need to be different, so we just localize that here - let dyn_attr_printer: Vec<_> = self + let dyn_attr_printer: Vec<_> = node .dynamic_attributes() .map(|attr| attr.rendered_as_dynamic_attr()) .collect(); let dynamic_attr_len = dyn_attr_printer.len(); - let dynamic_text = self.dynamic_text_segments.iter(); + let dynamic_text = node.dynamic_text_segments.iter(); - let diagnostics = &self.diagnostics; - let index = self.template_idx.get(); - let hot_reload_mapping = self.hot_reload_mapping(); + let diagnostics = &node.diagnostics; + let index = node.template_idx.get(); + let hot_reload_mapping = node.hot_reload_mapping(); tokens.append_all(quote! { dioxus_core::Element::Ok({ @@ -255,6 +244,23 @@ impl TemplateBody { body } + /// Normalize the Template body for rendering. If the body is completely empty, insert a placeholder node + pub fn normalized(&self) -> Self { + // If the nodes are completely empty, insert a placeholder node + // Core expects at least one node in the template to make it easier to replace + if self.is_empty() { + // Create an empty template body with a placeholder and diagnostics + the template index from the original + let empty = Self::new(vec![BodyNode::RawExpr(parse_quote! {()})]); + let default = Self { + diagnostics: self.diagnostics.clone(), + template_idx: self.template_idx.clone(), + ..empty + }; + return default; + } + self.clone() + } + pub fn is_empty(&self) -> bool { self.roots.is_empty() }