use dioxus_core::{ OwnedAttributeValue, OwnedPathSeg, OwnedTraverse, TemplateAttributeValue, TemplateNodeId, TextTemplate, TextTemplateSegment, UpdateOp, }; use proc_macro2::TokenStream; use quote::TokenStreamExt; use quote::{quote, ToTokens}; use syn::{Expr, Ident, LitStr}; #[cfg(any(feature = "hot-reload", debug_assertions))] pub fn try_parse_template( rsx: &str, location: OwnedCodeLocation, previous_template: Option, ) -> Result<(OwnedTemplate, DynamicTemplateContextBuilder), Error> { use crate::CallBody; let call_body: CallBody = syn::parse_str(rsx).map_err(|e| Error::ParseError(ParseError::new(e, location.clone())))?; let mut template_builder = TemplateBuilder::from_roots_always(call_body.roots); if let Some(prev) = previous_template { template_builder = template_builder .try_switch_dynamic_context(prev) .ok_or_else(|| { Error::RecompileRequiredError(RecompileReason::Variable( "dynamic context updated".to_string(), )) })?; } let dyn_ctx = template_builder.dynamic_context.clone(); Ok((template_builder.try_into_owned(&location)?, dyn_ctx)) } #[cfg(any(feature = "hot-reload", debug_assertions))] use hot_reload_imports::*; #[cfg(any(feature = "hot-reload", debug_assertions))] mod hot_reload_imports { pub use crate::{ attributes::attrbute_to_static_str, elements::element_to_static_str, error::{Error, ParseError, RecompileReason}, }; pub use dioxus_core::prelude::OwnedTemplate; pub use dioxus_core::{ AttributeDiscription, OwnedAttributeValue, OwnedCodeLocation, OwnedDynamicNodeMapping, OwnedTemplateNode, Template, TemplateAttribute, TemplateAttributeValue, TemplateElement, TemplateNodeId, TemplateNodeType, TextTemplate, TextTemplateSegment, }; pub use std::collections::HashMap; } use crate::{BodyNode, ElementAttr, FormattedSegment, Segment}; struct TemplateElementBuilder { tag: Ident, attributes: Vec, children: Vec, listeners: Vec, locally_static: bool, } #[cfg(any(feature = "hot-reload", debug_assertions))] type OwndedTemplateElement = TemplateElement< Vec>, OwnedAttributeValue, Vec, Vec, >; impl TemplateElementBuilder { #[cfg(any(feature = "hot-reload", debug_assertions))] fn try_into_owned(self, location: &OwnedCodeLocation) -> Result { let Self { tag, attributes, children, listeners, .. } = self; let (element_tag, element_ns) = element_to_static_str(&tag.to_string()).ok_or_else(|| { Error::ParseError(ParseError::new( syn::Error::new(tag.span(), format!("unknown element: {}", tag)), location.clone(), )) })?; let mut owned_attributes = Vec::new(); for a in attributes { owned_attributes.push(a.try_into_owned(location, element_tag, element_ns)?); } Ok(TemplateElement::new( element_tag, element_ns, owned_attributes, children, listeners, )) } } impl ToTokens for TemplateElementBuilder { fn to_tokens(&self, tokens: &mut TokenStream) { let Self { tag, attributes, children, listeners, .. } = self; let children = children.iter().map(|id| { let raw = id.0; quote! {TemplateNodeId(#raw)} }); tokens.append_all(quote! { TemplateElement::new( dioxus_elements::#tag::TAG_NAME, dioxus_elements::#tag::NAME_SPACE, &[#(#attributes),*], &[#(#children),*], &[#(#listeners),*], ) }) } } enum AttributeName { Ident(Ident), Str(LitStr), } struct TemplateAttributeBuilder { element_tag: Ident, name: AttributeName, value: TemplateAttributeValue, } impl TemplateAttributeBuilder { #[cfg(any(feature = "hot-reload", debug_assertions))] fn try_into_owned( self, location: &OwnedCodeLocation, element_tag: &'static str, element_ns: Option<&'static str>, ) -> Result, Error> { let Self { name, value, .. } = self; let (name, span, literal) = match name { AttributeName::Ident(name) => (name.to_string(), name.span(), false), AttributeName::Str(name) => (name.value(), name.span(), true), }; let (name, namespace, volatile) = attrbute_to_static_str(&name, element_tag, element_ns) .ok_or_else(|| { if literal { // literals will be captured when a full recompile is triggered Error::RecompileRequiredError(RecompileReason::Attribute(name.to_string())) } else { Error::ParseError(ParseError::new( syn::Error::new(span, format!("unknown attribute: {}", name)), location.clone(), )) } })?; let attribute = AttributeDiscription { name, namespace, volatile, }; Ok(TemplateAttribute { value, attribute }) } } impl ToTokens for TemplateAttributeBuilder { fn to_tokens(&self, tokens: &mut TokenStream) { let Self { element_tag, name, value, } = self; let value = match value { TemplateAttributeValue::Static(val) => { let val = match val { OwnedAttributeValue::Text(txt) => quote! {StaticAttributeValue::Text(#txt)}, OwnedAttributeValue::Float32(f) => quote! {StaticAttributeValue::Float32(#f)}, OwnedAttributeValue::Float64(f) => quote! {StaticAttributeValue::Float64(#f)}, OwnedAttributeValue::Int32(i) => quote! {StaticAttributeValue::Int32(#i)}, OwnedAttributeValue::Int64(i) => quote! {StaticAttributeValue::Int64(#i)}, OwnedAttributeValue::Uint32(u) => quote! {StaticAttributeValue::Uint32(#u)}, OwnedAttributeValue::Uint64(u) => quote! {StaticAttributeValue::Uint64(#u)}, OwnedAttributeValue::Bool(b) => quote! {StaticAttributeValue::Bool(#b)}, OwnedAttributeValue::Vec3Float(f1, f2, f3) => { quote! {StaticAttributeValue::Vec3Float(#f1, #f2, #f3)} } OwnedAttributeValue::Vec3Int(f1, f2, f3) => { quote! {StaticAttributeValue::Vec3Int(#f1, #f2, #f3)} } OwnedAttributeValue::Vec3Uint(f1, f2, f3) => { quote! {StaticAttributeValue::Vec3Uint(#f1, #f2, #f3)} } OwnedAttributeValue::Vec4Float(f1, f2, f3, f4) => { quote! {StaticAttributeValue::Vec4Float(#f1, #f2, #f3, #f4)} } OwnedAttributeValue::Vec4Int(f1, f2, f3, f4) => { quote! {StaticAttributeValue::Vec4Int(#f1, #f2, #f3, #f4)} } OwnedAttributeValue::Vec4Uint(f1, f2, f3, f4) => { quote! {StaticAttributeValue::Vec4Uint(#f1, #f2, #f3, #f4)} } OwnedAttributeValue::Bytes(b) => { quote! {StaticAttributeValue::Bytes(&[#(#b),*])} } OwnedAttributeValue::Any(_) => todo!(), }; quote! {TemplateAttributeValue::Static(#val)} } TemplateAttributeValue::Dynamic(idx) => quote! {TemplateAttributeValue::Dynamic(#idx)}, }; match name { AttributeName::Ident(name) => tokens.append_all(quote! { TemplateAttribute{ attribute: dioxus_elements::#element_tag::#name, value: #value, } }), AttributeName::Str(lit) => tokens.append_all(quote! { TemplateAttribute{ attribute: dioxus::prelude::AttributeDiscription{ name: #lit, namespace: None, volatile: false }, value: #value, } }), } } } enum TemplateNodeTypeBuilder { Element(TemplateElementBuilder), Text(TextTemplate>, String>), DynamicNode(usize), } #[cfg(any(feature = "hot-reload", debug_assertions))] type OwnedTemplateNodeType = TemplateNodeType< Vec>, OwnedAttributeValue, Vec, Vec, Vec>, String, >; impl TemplateNodeTypeBuilder { #[cfg(any(feature = "hot-reload", debug_assertions))] fn try_into_owned(self, location: &OwnedCodeLocation) -> Result { match self { TemplateNodeTypeBuilder::Element(el) => { Ok(TemplateNodeType::Element(el.try_into_owned(location)?)) } TemplateNodeTypeBuilder::Text(txt) => Ok(TemplateNodeType::Text(txt)), TemplateNodeTypeBuilder::DynamicNode(idx) => Ok(TemplateNodeType::DynamicNode(idx)), } } } impl ToTokens for TemplateNodeTypeBuilder { fn to_tokens(&self, tokens: &mut TokenStream) { match self { TemplateNodeTypeBuilder::Element(el) => tokens.append_all(quote! { TemplateNodeType::Element(#el) }), TemplateNodeTypeBuilder::Text(txt) => { let mut length = 0; let segments = txt.segments.iter().map(|seg| match seg { TextTemplateSegment::Static(s) => { length += s.len(); quote!(TextTemplateSegment::Static(#s)) } TextTemplateSegment::Dynamic(idx) => quote!(TextTemplateSegment::Dynamic(#idx)), }); tokens.append_all(quote! { TemplateNodeType::Text(TextTemplate::new(&[#(#segments),*], #length)) }); } TemplateNodeTypeBuilder::DynamicNode(idx) => tokens.append_all(quote! { TemplateNodeType::DynamicNode(#idx) }), } } } struct TemplateNodeBuilder { id: TemplateNodeId, depth: usize, parent: Option, node_type: TemplateNodeTypeBuilder, fully_static: bool, } impl TemplateNodeBuilder { #[cfg(any(feature = "hot-reload", debug_assertions))] fn try_into_owned(self, location: &OwnedCodeLocation) -> Result { let TemplateNodeBuilder { id, node_type, parent, depth, .. } = self; let node_type = node_type.try_into_owned(location)?; Ok(OwnedTemplateNode { id, node_type, parent, depth, }) } fn to_tokens(&self, tokens: &mut TokenStream) { let Self { id, node_type, parent, depth, .. } = self; let raw_id = id.0; let parent = match parent { Some(id) => { let id = id.0; quote! {Some(TemplateNodeId(#id))} } None => quote! {None}, }; tokens.append_all(quote! { TemplateNode { id: TemplateNodeId(#raw_id), node_type: #node_type, parent: #parent, depth: #depth, } }) } } #[derive(Default)] pub struct TemplateBuilder { nodes: Vec, root_nodes: Vec, dynamic_context: DynamicTemplateContextBuilder, } impl TemplateBuilder { /// Create a template builder from nodes if it would improve performance to do so. pub fn from_roots(roots: Vec) -> Option { let mut builder = Self::default(); for (i, root) in roots.into_iter().enumerate() { let id = builder.build_node(root, None, vec![i], 0); builder.root_nodes.push(id); } // only build a template if there is at least one static node if builder .nodes .iter() .all(|r| matches!(&r.node_type, TemplateNodeTypeBuilder::DynamicNode(_))) { None } else { Some(builder) } } /// Create a template builder from nodes regardless of performance. #[cfg(any(feature = "hot-reload", debug_assertions))] fn from_roots_always(roots: Vec) -> Self { let mut builder = Self::default(); for (i, root) in roots.into_iter().enumerate() { let id = builder.build_node(root, None, vec![i], 0); builder.root_nodes.push(id); } builder } fn build_node( &mut self, node: BodyNode, parent: Option, path: Vec, depth: usize, ) -> TemplateNodeId { let id = TemplateNodeId(self.nodes.len()); match node { BodyNode::Element(el) => { let mut locally_static = true; let mut attributes = Vec::new(); let mut listeners = Vec::new(); for attr in el.attributes { match attr.attr { ElementAttr::AttrText { name, value } => { if let Some(static_value) = value.to_static() { attributes.push(TemplateAttributeBuilder { element_tag: el.name.clone(), name: AttributeName::Ident(name), value: TemplateAttributeValue::Static( OwnedAttributeValue::Text(static_value), ), }) } else { locally_static = false; attributes.push(TemplateAttributeBuilder { element_tag: el.name.clone(), name: AttributeName::Ident(name), value: TemplateAttributeValue::Dynamic( self.dynamic_context.add_attr(quote!(#value)), ), }) } } ElementAttr::CustomAttrText { name, value } => { if let Some(static_value) = value.to_static() { attributes.push(TemplateAttributeBuilder { element_tag: el.name.clone(), name: AttributeName::Str(name), value: TemplateAttributeValue::Static( OwnedAttributeValue::Text(static_value), ), }) } else { locally_static = false; attributes.push(TemplateAttributeBuilder { element_tag: el.name.clone(), name: AttributeName::Str(name), value: TemplateAttributeValue::Dynamic( self.dynamic_context.add_attr(quote!(#value)), ), }) } } ElementAttr::AttrExpression { name, value } => { locally_static = false; attributes.push(TemplateAttributeBuilder { element_tag: el.name.clone(), name: AttributeName::Ident(name), value: TemplateAttributeValue::Dynamic( self.dynamic_context.add_attr(quote!(#value)), ), }) } ElementAttr::CustomAttrExpression { name, value } => { locally_static = false; attributes.push(TemplateAttributeBuilder { element_tag: el.name.clone(), name: AttributeName::Str(name), value: TemplateAttributeValue::Dynamic( self.dynamic_context.add_attr(quote!(#value)), ), }) } ElementAttr::EventTokens { name, tokens } => { locally_static = false; listeners.push(self.dynamic_context.add_listener(name, tokens)) } } } if let Some(key) = el.key { self.dynamic_context.add_key(quote!( dioxus::core::exports::bumpalo::format!(in __bump, "{}", #key) .into_bump_str() )); } self.nodes.push(TemplateNodeBuilder { id, node_type: TemplateNodeTypeBuilder::Element(TemplateElementBuilder { tag: el.name, attributes, children: Vec::new(), listeners, locally_static, }), parent, depth, fully_static: false, }); let children: Vec<_> = el .children .into_iter() .enumerate() .map(|(i, child)| { let mut new_path = path.clone(); new_path.push(i); self.build_node(child, Some(id), new_path, depth + 1) }) .collect(); let children_fully_static = children.iter().all(|c| self.nodes[c.0].fully_static); let parent = &mut self.nodes[id.0]; parent.fully_static = locally_static && children_fully_static; if let TemplateNodeTypeBuilder::Element(element) = &mut parent.node_type { element.children = children; } } BodyNode::Component(comp) => { self.nodes.push(TemplateNodeBuilder { id, node_type: TemplateNodeTypeBuilder::DynamicNode( self.dynamic_context.add_node(BodyNode::Component(comp)), ), parent, depth, fully_static: false, }); } BodyNode::Text(txt) => { let mut segments = Vec::new(); let mut length = 0; let mut fully_static = true; for segment in txt.segments { segments.push(match segment { Segment::Literal(lit) => { length += lit.len(); TextTemplateSegment::Static(lit) } Segment::Formatted(fmted) => { fully_static = false; TextTemplateSegment::Dynamic(self.dynamic_context.add_text(fmted)) } }) } self.nodes.push(TemplateNodeBuilder { id, node_type: TemplateNodeTypeBuilder::Text(TextTemplate::new(segments, length)), parent, depth, fully_static, }); } BodyNode::RawExpr(expr) => { self.nodes.push(TemplateNodeBuilder { id, node_type: TemplateNodeTypeBuilder::DynamicNode( self.dynamic_context.add_node(BodyNode::RawExpr(expr)), ), parent, depth, fully_static: false, }); } BodyNode::ForLoop(expr) => { self.nodes.push(TemplateNodeBuilder { id, node_type: TemplateNodeTypeBuilder::DynamicNode( self.dynamic_context.add_node(BodyNode::ForLoop(expr)), ), }); } BodyNode::IfChain(expr) => { self.nodes.push(TemplateNodeBuilder { id, node_type: TemplateNodeTypeBuilder::DynamicNode( self.dynamic_context.add_node(BodyNode::IfChain(expr)), ), }); } } id } #[cfg(any(feature = "hot-reload", debug_assertions))] pub fn try_switch_dynamic_context( mut self, dynamic_context: DynamicTemplateContextBuilder, ) -> Option { let attribute_mapping: HashMap = dynamic_context .attributes .iter() .enumerate() .map(|(i, ts)| (ts.to_string(), i)) .collect(); let text_mapping: HashMap = dynamic_context .text .iter() .enumerate() .map(|(i, ts)| (ts.to_token_stream().to_string(), i)) .collect(); let listener_mapping: HashMap<(String, Expr), usize> = dynamic_context .listeners .iter() .enumerate() .map(|(i, ts)| (ts.clone(), i)) .collect(); let node_mapping: HashMap = dynamic_context .nodes .iter() .enumerate() .map(|(i, ts)| (ts.to_token_stream().to_string(), i)) .collect(); for node in &mut self.nodes { match &mut node.node_type { TemplateNodeTypeBuilder::Element(element) => { for listener in &mut element.listeners { *listener = *listener_mapping.get(&self.dynamic_context.listeners[*listener])?; } for attribute in &mut element.attributes { if let TemplateAttributeValue::Dynamic(idx) = &mut attribute.value { *idx = *attribute_mapping .get(&self.dynamic_context.attributes[*idx].to_string())?; } } } TemplateNodeTypeBuilder::Text(txt) => { for seg in &mut txt.segments { if let TextTemplateSegment::Dynamic(idx) = seg { *idx = *text_mapping.get( &self.dynamic_context.text[*idx] .to_token_stream() .to_string(), )?; } } } TemplateNodeTypeBuilder::DynamicNode(idx) => { *idx = *node_mapping.get( &self.dynamic_context.nodes[*idx] .to_token_stream() .to_string(), )?; } } } self.dynamic_context = dynamic_context; Some(self) } #[cfg(any(feature = "hot-reload", debug_assertions))] pub fn try_into_owned(self, location: &OwnedCodeLocation) -> Result { let mut nodes = Vec::new(); let dynamic_mapping = self.dynamic_mapping(&nodes); let dynamic_path = self.dynamic_path(); for node in self.nodes { nodes.push(node.try_into_owned(location)?); } Ok(OwnedTemplate { nodes, root_nodes: self.root_nodes, dynamic_mapping, dynamic_path, }) } #[cfg(any(feature = "hot-reload", debug_assertions))] pub fn dynamic_mapping( &self, resolved_nodes: &Vec, ) -> OwnedDynamicNodeMapping { let dynamic_context = &self.dynamic_context; let mut node_mapping = vec![None; dynamic_context.nodes.len()]; let nodes = &self.nodes; for n in nodes { if let TemplateNodeTypeBuilder::DynamicNode(idx) = &n.node_type { node_mapping[*idx] = Some(n.id) } } let mut text_mapping = vec![Vec::new(); dynamic_context.text.len()]; for n in nodes { if let TemplateNodeTypeBuilder::Text(txt) = &n.node_type { for seg in &txt.segments { match seg { TextTemplateSegment::Static(_) => (), TextTemplateSegment::Dynamic(idx) => text_mapping[*idx].push(n.id), } } } } let mut attribute_mapping = vec![Vec::new(); dynamic_context.attributes.len()]; for n in nodes { if let TemplateNodeTypeBuilder::Element(el) = &n.node_type { for (i, attr) in el.attributes.iter().enumerate() { match attr.value { TemplateAttributeValue::Static(_) => (), TemplateAttributeValue::Dynamic(idx) => { attribute_mapping[idx].push((n.id, i)); } } } } } let mut listener_mapping = Vec::new(); for n in nodes { if let TemplateNodeTypeBuilder::Element(el) = &n.node_type { if !el.listeners.is_empty() { listener_mapping.push(n.id); } } } let mut volatile_mapping = Vec::new(); for n in resolved_nodes { if let TemplateNodeType::Element(el) = &n.node_type { for (i, attr) in el.attributes.iter().enumerate() { if attr.attribute.volatile { volatile_mapping.push((n.id, i)); } } } } OwnedDynamicNodeMapping::new( node_mapping, text_mapping, attribute_mapping, volatile_mapping, listener_mapping, ) } fn dynamic_path(&self) -> Option { let mut last_seg: Option = None; let mut nodes_to_insert_after = Vec::new(); // iterating from the last root to the first for root in self.root_nodes.iter().rev() { let root_node = &self.nodes[root.0]; if let TemplateNodeTypeBuilder::DynamicNode(_) = root_node.node_type { match &mut last_seg { // if there has been no static nodes, we can append the child to the parent node None => nodes_to_insert_after.push(*root), // otherwise we insert the child before the last static node Some(seg) => { seg.ops.push(UpdateOp::InsertBefore(*root)); } } } else if let Some(mut new) = self.construct_path_segment(*root) { if let Some(last) = last_seg.take() { match new.traverse { OwnedTraverse::Halt => { new.traverse = OwnedTraverse::NextSibling(Box::new(last)); } OwnedTraverse::FirstChild(b) => { new.traverse = OwnedTraverse::Both(Box::new((*b, last))); } _ => unreachable!(), } } else { for node in nodes_to_insert_after.drain(..) { new.ops.push(UpdateOp::InsertAfter(node)); } } last_seg = Some(new); } else if let Some(last) = last_seg.take() { last_seg = Some(OwnedPathSeg { ops: Vec::new(), traverse: OwnedTraverse::NextSibling(Box::new(last)), }); } } last_seg } fn construct_path_segment(&self, node_id: TemplateNodeId) -> Option { let n = &self.nodes[node_id.0]; if n.fully_static { return None; } match &n.node_type { TemplateNodeTypeBuilder::Element(el) => { let mut last_seg: Option = None; let mut children_to_append = Vec::new(); // iterating from the last child to the first for child in el.children.iter().rev() { let child_node = &self.nodes[child.0]; if let TemplateNodeTypeBuilder::DynamicNode(_) = child_node.node_type { match &mut last_seg { // if there has been no static nodes, we can append the child to the parent node None => children_to_append.push(*child), // otherwise we insert the child before the last static node Some(seg) => { seg.ops.push(UpdateOp::InsertBefore(*child)); } } } else if let Some(mut new) = self.construct_path_segment(*child) { if let Some(last) = last_seg.take() { match new.traverse { OwnedTraverse::Halt => { new.traverse = OwnedTraverse::NextSibling(Box::new(last)); } OwnedTraverse::FirstChild(b) => { new.traverse = OwnedTraverse::Both(Box::new((*b, last))); } _ => unreachable!(), } } last_seg = Some(new); } else if let Some(last) = last_seg.take() { last_seg = Some(OwnedPathSeg { ops: Vec::new(), traverse: OwnedTraverse::NextSibling(Box::new(last)), }); } } let mut ops = Vec::new(); if !el.locally_static || n.parent.is_none() { ops.push(UpdateOp::StoreNode(node_id)); } for child in children_to_append.into_iter().rev() { ops.push(UpdateOp::AppendChild(child)); } Some(OwnedPathSeg { ops, traverse: match last_seg { Some(last) => OwnedTraverse::FirstChild(Box::new(last)), None => OwnedTraverse::Halt, }, }) } TemplateNodeTypeBuilder::Text(_) => Some(OwnedPathSeg { ops: vec![UpdateOp::StoreNode(n.id)], traverse: dioxus_core::OwnedTraverse::Halt, }), TemplateNodeTypeBuilder::DynamicNode(_) => unreachable!( "constructing path segment for dynamic nodes is handled in the parent node" ), } } } impl ToTokens for TemplateBuilder { fn to_tokens(&self, tokens: &mut TokenStream) { let Self { nodes, root_nodes, dynamic_context, } = self; let mut node_mapping = vec![None; dynamic_context.nodes.len()]; for n in nodes { if let TemplateNodeTypeBuilder::DynamicNode(idx) = &n.node_type { node_mapping[*idx] = Some(n.id); } } let mut text_mapping = vec![Vec::new(); dynamic_context.text.len()]; for n in nodes { if let TemplateNodeTypeBuilder::Text(txt) = &n.node_type { for seg in &txt.segments { match seg { TextTemplateSegment::Static(_) => (), TextTemplateSegment::Dynamic(idx) => text_mapping[*idx].push(n.id), } } } } let mut attribute_mapping = vec![Vec::new(); dynamic_context.attributes.len()]; for n in nodes { if let TemplateNodeTypeBuilder::Element(el) = &n.node_type { for (i, attr) in el.attributes.iter().enumerate() { match attr.value { TemplateAttributeValue::Static(_) => (), TemplateAttributeValue::Dynamic(idx) => { attribute_mapping[idx].push((n.id, i)); } } } } } let mut listener_mapping = Vec::new(); for n in nodes { if let TemplateNodeTypeBuilder::Element(el) = &n.node_type { if !el.listeners.is_empty() { listener_mapping.push(n.id); } } } let root_nodes = root_nodes.iter().map(|id| { let raw = id.0; quote! { TemplateNodeId(#raw) } }); let node_mapping_quoted = node_mapping.iter().map(|op| match op { Some(id) => { let raw_id = id.0; quote! {Some(TemplateNodeId(#raw_id))} } None => quote! {None}, }); let text_mapping_quoted = text_mapping.iter().map(|inner| { let raw = inner.iter().map(|id| id.0); quote! {&[#(TemplateNodeId(#raw)),*]} }); let attribute_mapping_quoted = attribute_mapping.iter().map(|inner| { let raw = inner.iter().map(|(id, _)| id.0); let indecies = inner.iter().map(|(_, idx)| idx); quote! {&[#((TemplateNodeId(#raw), #indecies)),*]} }); let listener_mapping_quoted = listener_mapping.iter().map(|id| { let raw = id.0; quote! {TemplateNodeId(#raw)} }); let mut nodes_quoted = TokenStream::new(); for n in nodes { n.to_tokens(&mut nodes_quoted); quote! {,}.to_tokens(&mut nodes_quoted); } let dynamic_path = match self.dynamic_path() { Some(seg) => { let seg = quote_owned_segment(seg); quote! {Some(#seg)} } None => quote! {None}, }; let quoted = quote! { { const __NODES: dioxus::prelude::StaticTemplateNodes = &[#nodes_quoted]; const __TEXT_MAPPING: &'static [&'static [dioxus::prelude::TemplateNodeId]] = &[#(#text_mapping_quoted),*]; const __ATTRIBUTE_MAPPING: &'static [&'static [(dioxus::prelude::TemplateNodeId, usize)]] = &[#(#attribute_mapping_quoted),*]; const __ROOT_NODES: &'static [dioxus::prelude::TemplateNodeId] = &[#(#root_nodes),*]; const __NODE_MAPPING: &'static [Option] = &[#(#node_mapping_quoted),*]; const __NODES_WITH_LISTENERS: &'static [dioxus::prelude::TemplateNodeId] = &[#(#listener_mapping_quoted),*]; static __VOLITALE_MAPPING_INNER: dioxus::core::exports::once_cell::sync::Lazy> = dioxus::core::exports::once_cell::sync::Lazy::new(||{ // check each property to see if it is volatile let mut volatile = Vec::new(); for n in __NODES { if let TemplateNodeType::Element(el) = &n.node_type { for (i, attr) in el.attributes.iter().enumerate() { if attr.attribute.volatile { volatile.push((n.id, i)); } } } } volatile }); static __VOLITALE_MAPPING: &'static dioxus::core::exports::once_cell::sync::Lazy> = &__VOLITALE_MAPPING_INNER; static __STATIC_VOLITALE_MAPPING: dioxus::prelude::LazyStaticVec<(dioxus::prelude::TemplateNodeId, usize)> = LazyStaticVec(__VOLITALE_MAPPING); static __TEMPLATE: dioxus::prelude::Template = Template::Static(&StaticTemplate { nodes: __NODES, root_nodes: __ROOT_NODES, dynamic_mapping: StaticDynamicNodeMapping::new(__NODE_MAPPING, __TEXT_MAPPING, __ATTRIBUTE_MAPPING, __STATIC_VOLITALE_MAPPING, __NODES_WITH_LISTENERS), dynamic_path: #dynamic_path, }); let __bump = __cx.bump(); __cx.template_ref(dioxus::prelude::TemplateId(get_line_num!()), __TEMPLATE.clone(), #dynamic_context) } }; tokens.append_all(quoted) } } #[derive(Default, Clone, Debug)] pub struct DynamicTemplateContextBuilder { nodes: Vec, text: Vec, attributes: Vec, listeners: Vec<(String, Expr)>, key: Option, } impl DynamicTemplateContextBuilder { fn add_node(&mut self, node: BodyNode) -> usize { let node_id = self.nodes.len(); self.nodes.push(node); node_id } fn add_text(&mut self, text: FormattedSegment) -> usize { let text_id = self.text.len(); self.text.push(text); text_id } fn add_attr(&mut self, attr: TokenStream) -> usize { let attr_id = self.attributes.len(); self.attributes.push(attr); attr_id } fn add_listener(&mut self, name: Ident, listener: Expr) -> usize { let listener_id = self.listeners.len(); self.listeners.push((name.to_string(), listener)); listener_id } fn add_key(&mut self, key: TokenStream) { self.key = Some(key); } } impl ToTokens for DynamicTemplateContextBuilder { fn to_tokens(&self, tokens: &mut TokenStream) { let nodes = &self.nodes; let text = &self.text; let attributes = &self.attributes; let listeners_names = self .listeners .iter() .map(|(n, _)| syn::parse_str::(n).expect(n)); let listeners_exprs = self.listeners.iter().map(|(_, e)| e); let key = match &self.key { Some(k) => quote!(Some(#k)), None => quote!(None), }; tokens.append_all(quote! { TemplateContext { nodes: __cx.bump().alloc([#(#nodes),*]), text_segments: __cx.bump().alloc([#(&*dioxus::core::exports::bumpalo::format!(in __bump, "{}", #text).into_bump_str()),*]), attributes: __cx.bump().alloc([#({#attributes}.into_value(__cx.bump())),*]), listeners: __cx.bump().alloc([#(dioxus_elements::on::#listeners_names(__cx, #listeners_exprs)),*]), key: #key, } }) } } fn quote_owned_segment(seg: OwnedPathSeg) -> proc_macro2::TokenStream { let OwnedPathSeg { ops, traverse } = seg; let ops = ops .into_iter() .map(|op| match op { UpdateOp::StoreNode(id) => { let id = quote_template_node_id(id); quote!(UpdateOp::StoreNode(#id)) } UpdateOp::InsertBefore(id) => { let id = quote_template_node_id(id); quote!(UpdateOp::InsertBefore(#id)) } UpdateOp::InsertAfter(id) => { let id = quote_template_node_id(id); quote!(UpdateOp::InsertAfter(#id)) } UpdateOp::AppendChild(id) => { let id = quote_template_node_id(id); quote!(UpdateOp::AppendChild(#id)) } }) .collect::>(); let traverse = quote_owned_traverse(traverse); quote! { StaticPathSeg { ops: &[#(#ops),*], traverse: #traverse, } } } fn quote_owned_traverse(traverse: OwnedTraverse) -> proc_macro2::TokenStream { match traverse { OwnedTraverse::Halt => { quote! {StaticTraverse::Halt} } OwnedTraverse::FirstChild(seg) => { let seg = quote_owned_segment(*seg); quote! {StaticTraverse::FirstChild(&#seg)} } OwnedTraverse::NextSibling(seg) => { let seg = quote_owned_segment(*seg); quote! {StaticTraverse::NextSibling(&#seg)} } OwnedTraverse::Both(b) => { let (child, sibling) = *b; let child = quote_owned_segment(child); let sibling = quote_owned_segment(sibling); quote! {StaticTraverse::Both(&(#child, #sibling))} } } } fn quote_template_node_id(id: TemplateNodeId) -> proc_macro2::TokenStream { let raw = id.0; quote! { TemplateNodeId(#raw) } }