From 2131e5658bc4679a8429ed28ab087a199d290b8f Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Sat, 10 Dec 2022 14:09:59 -0600 Subject: [PATCH] add hot reloading context trait --- packages/rsx/src/component.rs | 5 +- packages/rsx/src/hot_reloading_context.rs | 19 ++++ packages/rsx/src/lib.rs | 117 ++++++++++++++++------ packages/rsx/src/node.rs | 7 +- 4 files changed, 116 insertions(+), 32 deletions(-) create mode 100644 packages/rsx/src/hot_reloading_context.rs diff --git a/packages/rsx/src/component.rs b/packages/rsx/src/component.rs index 2dddb1533..9d3e75f25 100644 --- a/packages/rsx/src/component.rs +++ b/packages/rsx/src/component.rs @@ -11,6 +11,8 @@ //! - [ ] Keys //! - [ ] Properties spreading with with `..` syntax +use std::marker::PhantomData; + use super::*; use proc_macro2::TokenStream as TokenStream2; @@ -165,8 +167,9 @@ impl ToTokens for Component { } if !self.children.is_empty() { - let renderer = TemplateRenderer { + let renderer: TemplateRenderer = TemplateRenderer { roots: &self.children, + phantom: PhantomData, }; toks.append_all(quote! { diff --git a/packages/rsx/src/hot_reloading_context.rs b/packages/rsx/src/hot_reloading_context.rs new file mode 100644 index 000000000..c38101093 --- /dev/null +++ b/packages/rsx/src/hot_reloading_context.rs @@ -0,0 +1,19 @@ +pub trait HotReloadingContext { + fn map_attribute( + element_name_rust: &str, + attribute_name_rust: &str, + ) -> Option<(&'static str, Option<&'static str>)>; + fn map_element(element_name_rust: &str) -> Option<(&'static str, Option<&'static str>)>; +} + +pub struct Empty; + +impl HotReloadingContext for Empty { + fn map_attribute(_: &str, _: &str) -> Option<(&'static str, Option<&'static str>)> { + None + } + + fn map_element(_: &str) -> Option<(&'static str, Option<&'static str>)> { + None + } +} diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index 65d48f18a..81babed6c 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -15,6 +15,7 @@ mod errors; mod component; mod element; +mod hot_reloading_context; mod ifmt; mod node; @@ -22,6 +23,7 @@ mod node; pub use component::*; use dioxus_core::{Template, TemplateAttribute, TemplateNode}; pub use element::*; +use hot_reloading_context::{Empty, HotReloadingContext}; pub use ifmt::*; pub use node::*; @@ -35,24 +37,29 @@ use syn::{ /// Fundametnally, every CallBody is a template #[derive(Default)] -pub struct CallBody { +pub struct CallBody { pub roots: Vec, // set this after pub inline_cx: bool, + + phantom: std::marker::PhantomData, } -impl CallBody { +impl CallBody { /// This function intentionally leaks memory to create a static template. /// Keeping the template static allows us to simplify the core of dioxus and leaking memory in dev mode is less of an issue. /// the previous_location is the location of the previous template at the time the template was originally compiled. pub fn leak_template(&self, previous_location: &'static str) -> Template { - let mut renderer = TemplateRenderer { roots: &self.roots }; + let mut renderer: TemplateRenderer = TemplateRenderer { + roots: &self.roots, + phantom: std::marker::PhantomData, + }; renderer.leak_template(previous_location) } } -impl Parse for CallBody { +impl Parse for CallBody { fn parse(input: ParseStream) -> Result { let mut roots = Vec::new(); @@ -69,14 +76,18 @@ impl Parse for CallBody { Ok(Self { roots, inline_cx: false, + phantom: std::marker::PhantomData, }) } } /// Serialize the same way, regardless of flavor -impl ToTokens for CallBody { +impl ToTokens for CallBody { fn to_tokens(&self, out_tokens: &mut TokenStream2) { - let body = TemplateRenderer { roots: &self.roots }; + let body: TemplateRenderer = TemplateRenderer { + roots: &self.roots, + phantom: std::marker::PhantomData, + }; if self.inline_cx { out_tokens.append_all(quote! { @@ -95,13 +106,14 @@ impl ToTokens for CallBody { } } -pub struct TemplateRenderer<'a> { +pub struct TemplateRenderer<'a, Ctx: HotReloadingContext = Empty> { pub roots: &'a [BodyNode], + phantom: std::marker::PhantomData, } -impl<'a> TemplateRenderer<'a> { +impl<'a, Ctx: HotReloadingContext> TemplateRenderer<'a, Ctx> { fn leak_template(&mut self, previous_location: &'static str) -> Template<'static> { - let mut context = DynamicContext::default(); + let mut context: DynamicContext = DynamicContext::default(); let roots: Vec<_> = self .roots @@ -138,9 +150,9 @@ impl<'a> TemplateRenderer<'a> { } } -impl<'a> ToTokens for TemplateRenderer<'a> { +impl<'a, Ctx: HotReloadingContext> ToTokens for TemplateRenderer<'a, Ctx> { fn to_tokens(&self, out_tokens: &mut TokenStream2) { - let mut context = DynamicContext::default(); + let mut context: DynamicContext = DynamicContext::default(); let key = match self.roots.get(0) { Some(BodyNode::Element(el)) if self.roots.len() == 1 => el.key.clone(), @@ -199,17 +211,31 @@ impl<'a> ToTokens for TemplateRenderer<'a> { } // As we print out the dynamic nodes, we want to keep track of them in a linear fashion // We'll use the size of the vecs to determine the index of the dynamic node in the final -#[derive(Default)] -pub struct DynamicContext<'a> { +pub struct DynamicContext<'a, Ctx: HotReloadingContext> { dynamic_nodes: Vec<&'a BodyNode>, dynamic_attributes: Vec<&'a ElementAttrNamed>, current_path: Vec, node_paths: Vec>, attr_paths: Vec>, + + phantom: std::marker::PhantomData, } -impl<'a> DynamicContext<'a> { +impl<'a, Ctx: HotReloadingContext> Default for DynamicContext<'a, Ctx> { + fn default() -> Self { + Self { + dynamic_nodes: Vec::new(), + dynamic_attributes: Vec::new(), + current_path: Vec::new(), + node_paths: Vec::new(), + attr_paths: Vec::new(), + phantom: std::marker::PhantomData, + } + } +} + +impl<'a, Ctx: HotReloadingContext> DynamicContext<'a, Ctx> { fn leak_node(&mut self, root: &'a BodyNode) -> TemplateNode<'static> { match root { BodyNode::Element(el) => { @@ -222,15 +248,24 @@ impl<'a> DynamicContext<'a> { // [0, 2] // [0, 2, 1] + let element_name_rust = el.name.to_string(); + let static_attrs: Vec<_> = el .attributes .iter() .map(|attr| match &attr.attr { - ElementAttr::AttrText { name: _, value } if value.is_static() => { + ElementAttr::AttrText { name, value } if value.is_static() => { let value = value.source.as_ref().unwrap(); + let attribute_name_rust = name.to_string(); + let (name, namespace) = + Ctx::map_attribute(&element_name_rust, &attribute_name_rust) + .unwrap_or(( + Box::leak(attribute_name_rust.into_boxed_str()), + None, + )); TemplateAttribute::Static { - name: "todo", - namespace: None, + name, + namespace, value: Box::leak(value.value().into_boxed_str()), // name: dioxus_elements::#el_name::#name.0, // namespace: dioxus_elements::#el_name::#name.1, @@ -277,15 +312,11 @@ impl<'a> DynamicContext<'a> { }) .collect(); - // TemplateNode::Element { - // tag: dioxus_elements::#el_name::TAG_NAME, - // namespace: dioxus_elements::#el_name::NAME_SPACE, - // attrs: &[ #attrs ], - // children: &[ #children ], - // } + let (tag, namespace) = Ctx::map_element(&element_name_rust) + .unwrap_or((Box::leak(element_name_rust.into_boxed_str()), None)); TemplateNode::Element { - tag: "todo", - namespace: None, + tag, + namespace, attrs: Box::leak(static_attrs.into_boxed_slice()), children: Box::leak(children.into_boxed_slice()), } @@ -420,7 +451,7 @@ impl<'a> DynamicContext<'a> { #[test] fn template() { let input = quote! { - div { + svg { width: 100, height: "100px", "width2": 100, @@ -431,7 +462,33 @@ fn template() { (0..10).map(|i| rsx!{"{i}"}) } }; - let call_body: CallBody = syn::parse2(input).unwrap(); + + struct Mock; + + impl HotReloadingContext for Mock { + fn map_attribute( + element_name_rust: &str, + attribute_name_rust: &str, + ) -> Option<(&'static str, Option<&'static str>)> { + match element_name_rust { + "svg" => match attribute_name_rust { + "width" => Some(("width", Some("style"))), + "height" => Some(("height", Some("style"))), + _ => None, + }, + _ => None, + } + } + + fn map_element(element_name_rust: &str) -> Option<(&'static str, Option<&'static str>)> { + match element_name_rust { + "svg" => Some(("svg", Some("svg"))), + _ => None, + } + } + } + + let call_body: CallBody = syn::parse2(input).unwrap(); let template = call_body.leak_template("testing"); @@ -442,13 +499,13 @@ fn template() { Template { name: "testing", roots: &[TemplateNode::Element { - tag: "div", - namespace: None, + tag: "svg", + namespace: Some("svg"), attrs: &[ TemplateAttribute::Dynamic { id: 0 }, TemplateAttribute::Static { name: "height", - namespace: None, + namespace: Some("style"), value: "100px", }, TemplateAttribute::Dynamic { id: 1 }, diff --git a/packages/rsx/src/node.rs b/packages/rsx/src/node.rs index cb604654e..4950c857c 100644 --- a/packages/rsx/src/node.rs +++ b/packages/rsx/src/node.rs @@ -1,3 +1,5 @@ +use std::marker::PhantomData; + use super::*; use proc_macro2::{Span, TokenStream as TokenStream2}; @@ -141,7 +143,10 @@ impl ToTokens for BodyNode { pat, expr, body, .. } = exp; - let renderer = TemplateRenderer { roots: body }; + let renderer: TemplateRenderer = TemplateRenderer { + roots: body, + phantom: PhantomData, + }; tokens.append_all(quote! { __cx.make_node(