Browse Source

feat: keys

Jonathan Kelley 2 years ago
parent
commit
584504feb7

+ 18 - 14
packages/core/src/create.rs

@@ -1,7 +1,7 @@
 use crate::mutations::Mutation;
 use crate::mutations::Mutation::*;
 use crate::nodes::VNode;
-use crate::nodes::{DynamicNode, DynamicNodeKind, TemplateNode};
+use crate::nodes::{DynamicNode, TemplateNode};
 use crate::virtualdom::VirtualDom;
 use crate::{AttributeValue, TemplateAttribute};
 
@@ -30,8 +30,8 @@ impl VirtualDom {
 
         // Walk the roots backwards, creating nodes and assigning IDs
         // todo: adjust dynamic nodes to be in the order of roots and then leaves (ie BFS)
-        let mut dynamic_attrs = template.dynamic_attrs.iter().peekable();
-        let mut dynamic_nodes = template.dynamic_nodes.iter().peekable();
+        let mut dynamic_attrs = template.template.attr_paths.iter().enumerate().peekable();
+        let mut dynamic_nodes = template.template.node_paths.iter().enumerate().peekable();
 
         let mut on_stack = 0;
         for (root_idx, root) in template.template.roots.iter().enumerate() {
@@ -46,7 +46,7 @@ impl VirtualDom {
                 }
 
                 TemplateNode::Dynamic(id) => {
-                    self.create_dynamic_node(mutations, template, &template.dynamic_nodes[*id])
+                    self.create_dynamic_node(mutations, template, &template.dynamic_nodes[*id], *id)
                 }
 
                 TemplateNode::DynamicText { .. } => 1,
@@ -56,9 +56,11 @@ impl VirtualDom {
 
             // we're on top of a node that has a dynamic attribute for a descendant
             // Set that attribute now before the stack gets in a weird state
-            while let Some(attr) = dynamic_attrs.next_if(|a| a.path[0] == root_idx as u8) {
+            while let Some((idx, path)) = dynamic_attrs.next_if(|(_, p)| p[0] == root_idx as u8) {
+                let attr = &template.dynamic_attrs[idx];
+
                 if cur_route.is_none() {
-                    cur_route = Some((self.next_element(template), &attr.path[1..]));
+                    cur_route = Some((self.next_element(template), &path[1..]));
                 }
 
                 // Attach all the elementIDs to the nodes with dynamic content
@@ -91,11 +93,12 @@ impl VirtualDom {
             }
 
             // We're on top of a node that has a dynamic child for a descendant
-            while let Some(node) = dynamic_nodes.next_if(|f| f.path[0] == root_idx as u8) {
-                let m = self.create_dynamic_node(mutations, template, node);
+            while let Some((idx, path)) = dynamic_nodes.next_if(|(_, p)| p[0] == root_idx as u8) {
+                let node = &template.dynamic_nodes[idx];
+                let m = self.create_dynamic_node(mutations, template, node, idx);
                 mutations.push(ReplacePlaceholder {
                     m,
-                    path: &node.path[1..],
+                    path: &path[1..],
                 });
             }
         }
@@ -151,21 +154,22 @@ impl VirtualDom {
         mutations: &mut Vec<Mutation<'a>>,
         template: &'a VNode<'a>,
         node: &'a DynamicNode<'a>,
+        idx: usize,
     ) -> usize {
-        match &node.kind {
-            DynamicNodeKind::Text { id, value } => {
+        match &node {
+            DynamicNode::Text { id, value } => {
                 let new_id = self.next_element(template);
                 id.set(new_id);
                 mutations.push(HydrateText {
                     id: new_id,
-                    path: &node.path[1..],
+                    path: &template.template.node_paths[idx][1..],
                     value,
                 });
 
                 1
             }
 
-            DynamicNodeKind::Component { props, .. } => {
+            DynamicNode::Component { props, .. } => {
                 let id = self.new_scope(*props);
 
                 let template = self.run_scope(id);
@@ -180,7 +184,7 @@ impl VirtualDom {
                 created
             }
 
-            DynamicNodeKind::Fragment { children } => {
+            DynamicNode::Fragment { children } => {
                 //
                 children
                     .iter()

+ 5 - 3
packages/core/src/events.rs

@@ -115,14 +115,16 @@ impl VirtualDom {
 
         while let Some((raw_parent, dyn_index)) = index {
             let parent = unsafe { &mut *raw_parent };
-            let path = parent.dynamic_nodes[dyn_index].path;
+            let path = parent.template.node_paths[dyn_index];
 
             listeners.extend(
                 parent
                     .dynamic_attrs
                     .iter()
-                    .filter(|attr| is_path_ascendant(attr.path, path))
-                    .filter(|attr| attr.name == event.name),
+                    .enumerate()
+                    .filter(|(idx, attr)| is_path_ascendant(parent.template.node_paths[*idx], path))
+                    .filter(|(idx, attr)| attr.name == event.name)
+                    .map(|(_, attr)| attr),
             );
 
             index = parent.parent;

+ 21 - 26
packages/core/src/factory.rs

@@ -5,25 +5,24 @@ use bumpalo::Bump;
 use crate::{
     any_props::{AnyProps, VComponentProps},
     arena::ElementId,
-    innerlude::{DynamicNode, DynamicNodeKind},
+    innerlude::DynamicNode,
     Attribute, AttributeValue, Element, LazyNodes, Properties, Scope, ScopeState, VNode,
 };
 
 impl ScopeState {
     /// Create some text that's allocated along with the other vnodes
-    ///
     pub fn text<'a>(&'a self, args: Arguments) -> DynamicNode<'a> {
         let (text, _) = self.raw_text(args);
-
-        DynamicNode {
-            kind: DynamicNodeKind::Text {
-                id: Cell::new(ElementId(0)),
-                value: text,
-            },
-            path: &[0],
+        DynamicNode::Text {
+            id: Cell::new(ElementId(0)),
+            value: text,
         }
     }
 
+    pub fn raw_text_inline<'a>(&'a self, args: Arguments) -> &'a str {
+        self.raw_text(args).0
+    }
+
     pub fn raw_text<'a>(&'a self, args: Arguments) -> (&'a str, bool) {
         match args.as_str() {
             Some(static_str) => (static_str, true),
@@ -46,11 +45,8 @@ impl ScopeState {
             bump_vec.push(item.into_dynamic_node(self));
         }
 
-        DynamicNode {
-            path: &[0, 0],
-            kind: crate::innerlude::DynamicNodeKind::Fragment {
-                children: bump_vec.into_bump_slice(),
-            },
+        DynamicNode::Fragment {
+            children: bump_vec.into_bump_slice(),
         }
     }
 
@@ -60,14 +56,14 @@ impl ScopeState {
         name: &'static str,
         val: impl IntoAttributeValue<'a>,
         namespace: Option<&'static str>,
-        is_volatile: bool,
+        volatile: bool,
     ) -> Attribute<'a> {
         Attribute {
             name,
             namespace,
-            mounted_element: Cell::new(ElementId(0)),
-            path: &[0],
+            volatile,
             value: val.into_value(self.bump()),
+            mounted_element: Cell::new(ElementId(0)),
         }
     }
 
@@ -94,13 +90,10 @@ impl ScopeState {
         //     self.scope.items.borrow_mut().borrowed_props.push(vcomp);
         // }
 
-        DynamicNode {
-            path: &[0],
-            kind: DynamicNodeKind::Component {
-                name: fn_name,
-                can_memoize: P::IS_STATIC,
-                props: detached_dyn,
-            },
+        DynamicNode::Component {
+            name: fn_name,
+            can_memoize: P::IS_STATIC,
+            props: detached_dyn,
         }
     }
 }
@@ -116,17 +109,19 @@ impl<'a, 'b> IntoVnode<'a> for LazyNodes<'a, 'b> {
 }
 
 impl<'a, 'b> IntoVnode<'a> for VNode<'a> {
-    fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a> {
+    fn into_dynamic_node(self, _cx: &'a ScopeState) -> VNode<'a> {
         self
     }
 }
+
 impl<'a, 'b> IntoVnode<'a> for &'a VNode<'a> {
-    fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a> {
+    fn into_dynamic_node(self, _cx: &'a ScopeState) -> VNode<'a> {
         VNode {
             node_id: self.node_id.clone(),
             parent: self.parent,
             template: self.template,
             root_ids: self.root_ids,
+            key: self.key,
             dynamic_nodes: self.dynamic_nodes,
             dynamic_attrs: self.dynamic_attrs,
         }

+ 5 - 9
packages/core/src/garbage.rs

@@ -1,8 +1,4 @@
-use crate::{
-    nodes::{DynamicNodeKind, VNode},
-    scopes::ScopeId,
-    virtualdom::VirtualDom,
-};
+use crate::{nodes::VNode, scopes::ScopeId, virtualdom::VirtualDom, DynamicNode};
 
 impl VirtualDom {
     pub fn drop_scope(&mut self, id: ScopeId) {
@@ -16,14 +12,14 @@ impl VirtualDom {
 
     pub fn drop_template<'a>(&'a mut self, template: &'a VNode<'a>) {
         for node in template.dynamic_nodes.iter() {
-            match &node.kind {
-                DynamicNodeKind::Text { id, .. } => {}
+            match node {
+                DynamicNode::Text { id, .. } => {}
 
-                DynamicNodeKind::Component { .. } => {
+                DynamicNode::Component { .. } => {
                     todo!()
                 }
 
-                DynamicNodeKind::Fragment { children } => {}
+                DynamicNode::Fragment { children } => {}
             }
         }
     }

+ 3 - 4
packages/core/src/lib.rs

@@ -72,7 +72,6 @@ pub use crate::innerlude::{
     Attribute,
     AttributeValue,
     DynamicNode,
-    DynamicNodeKind,
     Element,
     EventPriority,
     LazyNodes,
@@ -98,9 +97,9 @@ pub use crate::innerlude::{
 /// This includes types like [`Scope`], [`Element`], and [`Component`].
 pub mod prelude {
     pub use crate::innerlude::{
-        fc_to_builder, Attribute, DynamicNode, DynamicNodeKind, Element, EventPriority, LazyNodes,
-        NodeFactory, Properties, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute,
-        TemplateNode, UiEvent, VNode, VirtualDom,
+        fc_to_builder, Attribute, DynamicNode, Element, EventPriority, LazyNodes, NodeFactory,
+        Properties, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute, TemplateNode,
+        UiEvent, VNode, VirtualDom,
     };
 }
 

+ 7 - 11
packages/core/src/nodes.rs

@@ -12,6 +12,8 @@ pub struct VNode<'a> {
     // The ID assigned for the root of this template
     pub node_id: Cell<ElementId>,
 
+    pub key: Option<&'a str>,
+
     // When rendered, this template will be linked to its parent manually
     pub parent: Option<(*mut VNode<'static>, usize)>,
 
@@ -28,6 +30,8 @@ pub struct VNode<'a> {
 pub struct Template<'a> {
     pub id: &'a str,
     pub roots: &'a [TemplateNode<'a>],
+    pub node_paths: &'a [&'a [u8]],
+    pub attr_paths: &'a [&'a [u8]],
 }
 
 impl<'a> std::hash::Hash for Template<'a> {
@@ -68,12 +72,7 @@ pub enum TemplateNode<'a> {
     DynamicText(usize),
 }
 
-pub struct DynamicNode<'a> {
-    pub path: &'static [u8],
-    pub kind: DynamicNodeKind<'a>,
-}
-
-pub enum DynamicNodeKind<'a> {
+pub enum DynamicNode<'a> {
     // Anything declared in component form
     // IE in caps or with underscores
     Component {
@@ -102,10 +101,7 @@ pub enum TemplateAttribute<'a> {
         namespace: Option<&'static str>,
         volatile: bool,
     },
-    Dynamic {
-        name: &'static str,
-        index: usize,
-    },
+    Dynamic(usize),
 }
 
 pub struct Attribute<'a> {
@@ -113,7 +109,7 @@ pub struct Attribute<'a> {
     pub value: AttributeValue<'a>,
     pub namespace: Option<&'static str>,
     pub mounted_element: Cell<ElementId>,
-    pub path: &'static [u8],
+    pub volatile: bool,
 }
 
 pub enum AttributeValue<'a> {

+ 3 - 3
packages/dioxus/tests/rsx_syntax.rs

@@ -5,11 +5,10 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element {
     let var = 123;
 
     cx.render(rsx! {
-        div {
+        div { key: "12345",
             class: "asd",
             class: "{asd}",
             onclick: move |_| {},
-
             div { "{var}" }
             div {
                 h1 { "var" }
@@ -25,8 +24,9 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element {
 }
 
 fn basic_template(cx: Scope) -> Element {
+    let val = 123;
     cx.render(rsx! {
-        div {
+        div { class: "{val}", class: "{val}", class: "{val}", class: "{val}",
             (0..2).map(|i| rsx! { div { "asd {i}" } })
             basic_child { }
         }

+ 12 - 0
packages/rsx/src/component.rs

@@ -55,6 +55,18 @@ impl Component {
 
         Ok(())
     }
+
+    pub fn key(&self) -> Option<&IfmtInput> {
+        match self
+            .fields
+            .iter()
+            .find(|f| f.name.to_string() == "key")
+            .map(|f| &f.content)
+        {
+            Some(ContentField::Formatted(fmt)) => Some(fmt),
+            _ => None,
+        }
+    }
 }
 
 impl Parse for Component {

+ 33 - 15
packages/rsx/src/lib.rs

@@ -99,6 +99,18 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
             dynamic_nodes: vec![],
             dynamic_attributes: vec![],
             current_path: vec![],
+            attr_paths: vec![],
+            node_paths: vec![],
+        };
+
+        let key = match self.roots.get(0) {
+            Some(BodyNode::Element(el)) if self.roots.len() == 1 => el.key.clone(),
+            Some(BodyNode::Component(comp)) if self.roots.len() == 1 => comp.key().cloned(),
+            _ => None,
+        };
+        let key_tokens = match key {
+            Some(tok) => quote! { Some( __cx.raw_text_inline(#tok) ) },
+            None => quote! { None },
         };
 
         let root_printer = self.roots.iter().enumerate().map(|(idx, root)| {
@@ -112,15 +124,27 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
         let roots = quote! { #( #root_printer ),* };
         let node_printer = &context.dynamic_nodes;
         let dyn_attr_printer = &context.dynamic_attributes;
+        let node_paths = context
+            .node_paths
+            .iter()
+            .map(|items| quote!(&[#(#items),*]));
+
+        let attr_paths = context
+            .attr_paths
+            .iter()
+            .map(|items| quote!(&[#(#items),*]));
 
         out_tokens.append_all(quote! {
             static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {
                 id: ::dioxus::core::get_line_num!(),
-                roots: &[ #roots ]
+                roots: &[ #roots ],
+                node_paths: &[ #(#node_paths),* ],
+                attr_paths: &[ #(#attr_paths),* ],
             };
             ::dioxus::core::VNode {
                 node_id: Default::default(),
                 parent: None,
+                key: #key_tokens,
                 template: TEMPLATE,
                 root_ids: __cx.bump().alloc([]),
                 dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]),
@@ -135,6 +159,9 @@ pub struct DynamicContext<'a> {
     dynamic_nodes: Vec<&'a BodyNode>,
     dynamic_attributes: Vec<&'a ElementAttrNamed>,
     current_path: Vec<u8>,
+
+    node_paths: Vec<Vec<u8>>,
+    attr_paths: Vec<Vec<u8>>,
 }
 
 impl<'a> DynamicContext<'a> {
@@ -180,22 +207,12 @@ impl<'a> DynamicContext<'a> {
                     ElementAttr::AttrExpression { .. }
                     | ElementAttr::AttrText { .. }
                     | ElementAttr::CustomAttrText { .. }
-                    | ElementAttr::CustomAttrExpression { .. } => {
-                        let ct = self.dynamic_attributes.len();
-                        self.dynamic_attributes.push(attr);
-                        Some(quote! { ::dioxus::core::TemplateAttribute::Dynamic {
-                            name: "asd",
-                            index: #ct
-                        } })
-                    }
-
-                    ElementAttr::EventTokens { .. } => {
+                    | ElementAttr::CustomAttrExpression { .. }
+                    | ElementAttr::EventTokens { .. } => {
                         let ct = self.dynamic_attributes.len();
                         self.dynamic_attributes.push(attr);
-                        Some(quote! { ::dioxus::core::TemplateAttribute::Dynamic {
-                            name: "asd",
-                            index: #ct
-                        } })
+                        self.attr_paths.push(self.current_path.clone());
+                        Some(quote! { ::dioxus::core::TemplateAttribute::Dynamic(#ct) })
                     }
                 });
 
@@ -228,6 +245,7 @@ impl<'a> DynamicContext<'a> {
             BodyNode::RawExpr(_) | BodyNode::Text(_) | BodyNode::Component(_) => {
                 let ct = self.dynamic_nodes.len();
                 self.dynamic_nodes.push(root);
+                self.node_paths.push(self.current_path.clone());
                 quote! { ::dioxus::core::TemplateNode::Dynamic(#ct) }
             }
         }

+ 6 - 9
packages/ssr/src/template.rs

@@ -72,7 +72,7 @@ impl StringCache {
                         TemplateAttribute::Static { name, value, .. } => {
                             write!(chain, " {}=\"{}\"", name, value)?;
                         }
-                        TemplateAttribute::Dynamic { index, .. } => {
+                        TemplateAttribute::Dynamic(index) => {
                             chain.segments.push(Segment::Attr(*index))
                         }
                     }
@@ -126,18 +126,18 @@ impl SsrRender {
                         _ => {}
                     };
                 }
-                Segment::Node(idx) => match &template.dynamic_nodes[*idx].kind {
-                    DynamicNodeKind::Text { value, .. } => {
+                Segment::Node(idx) => match &template.dynamic_nodes[*idx] {
+                    DynamicNode::Text { value, .. } => {
                         // todo: escape the text
                         write!(buf, "{}", value)?
                     }
-                    DynamicNodeKind::Fragment { children } => {
+                    DynamicNode::Fragment { children } => {
                         for child in *children {
                             self.render_template(buf, child)?;
                         }
                         //
                     }
-                    DynamicNodeKind::Component { .. } => {
+                    DynamicNode::Component { .. } => {
                         //
                     }
                 },
@@ -194,10 +194,7 @@ fn children_processes_properly() {
         render! {
             div {
                 ChildWithChildren {
-                    p {
-                        "{d}"
-                        "hii"
-                    }
+                    p { "{d}" "hii" }
                 }
             }
         }