Explorar o código

feat: components pass thru children

Jonathan Kelley %!s(int64=2) %!d(string=hai) anos
pai
achega
c6a6c3d0f8

+ 3 - 24
packages/core-macro/src/lib.rs

@@ -39,19 +39,6 @@ pub fn rsx(s: TokenStream) -> TokenStream {
     }
 }
 
-/// A version of the rsx! macro that does not use templates. Used for testing diffing
-#[proc_macro]
-pub fn rsx_without_templates(s: TokenStream) -> TokenStream {
-    match syn::parse::<rsx::CallBody>(s) {
-        Err(err) => err.to_compile_error().into(),
-        Ok(body) => {
-            let mut tokens = proc_macro2::TokenStream::new();
-            body.to_tokens_without_template(&mut tokens);
-            tokens.into()
-        }
-    }
-}
-
 /// The render! macro makes it easy for developers to write jsx-style markup in their components.
 ///
 /// The render macro automatically renders rsx - making it unhygenic.
@@ -64,18 +51,10 @@ pub fn rsx_without_templates(s: TokenStream) -> TokenStream {
 pub fn render(s: TokenStream) -> TokenStream {
     match syn::parse::<rsx::CallBody>(s) {
         Err(err) => err.to_compile_error().into(),
-        Ok(body) => {
-            let mut inner = proc_macro2::TokenStream::new();
-            body.to_tokens_without_lazynodes(&mut inner);
-            quote::quote! {
-                {
-                    let __cx = NodeFactory::new(&cx.scope);
-                    Some(#inner)
-                }
-            }
+        Ok(mut body) => {
+            body.inline_cx = true;
+            body.into_token_stream().into()
         }
-        .into_token_stream()
-        .into(),
     }
 }
 

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

@@ -3,7 +3,7 @@ use crate::mutations::Mutation::*;
 use crate::nodes::VNode;
 use crate::nodes::{DynamicNode, DynamicNodeKind, TemplateNode};
 use crate::virtualdom::VirtualDom;
-use crate::TemplateAttribute;
+use crate::{AttributeValue, TemplateAttribute};
 
 impl VirtualDom {
     /// Create this template and write its mutations
@@ -52,25 +52,41 @@ impl VirtualDom {
                 TemplateNode::DynamicText { .. } => 1,
             };
 
+            let mut cur_route = None;
+
             // 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(loc) = dynamic_attrs.next_if(|a| a.path[0] == root_idx as u8) {
+            while let Some(attr) = dynamic_attrs.next_if(|a| a.path[0] == root_idx as u8) {
+                if cur_route.is_none() {
+                    cur_route = Some((self.next_element(template), &attr.path[1..]));
+                }
+
                 // Attach all the elementIDs to the nodes with dynamic content
-                let id = self.next_element(template);
+                let (id, path) = cur_route.unwrap();
+
+                mutations.push(AssignId { path, id });
+                attr.mounted_element.set(id);
+
+                match attr.value {
+                    AttributeValue::Text(value) => {
+                        mutations.push(SetAttribute {
+                            name: attr.name,
+                            value,
+                            id,
+                        });
+                    }
 
-                mutations.push(AssignId {
-                    path: &loc.path[1..],
-                    id,
-                });
+                    AttributeValue::Listener(_) => {
+                        //
+                    }
 
-                loc.mounted_element.set(id);
+                    AttributeValue::Float(_) => todo!(),
+                    AttributeValue::Int(_) => todo!(),
+                    AttributeValue::Bool(_) => todo!(),
+                    AttributeValue::Any(_) => todo!(),
 
-                for attr in loc.attrs.iter() {
-                    mutations.push(SetAttribute {
-                        name: attr.name,
-                        value: attr.value,
-                        id,
-                    });
+                    // Optional attributes
+                    AttributeValue::None => todo!(),
                 }
             }
 
@@ -94,6 +110,7 @@ impl VirtualDom {
         node: &'a TemplateNode<'static>,
     ) {
         match *node {
+            // Todo: create the children's template
             TemplateNode::Dynamic(_) => mutations.push(CreatePlaceholder),
             TemplateNode::Text(value) => mutations.push(CreateText { value }),
             TemplateNode::DynamicText { .. } => mutations.push(CreateText {

+ 8 - 7
packages/core/src/events.rs

@@ -1,6 +1,6 @@
 use std::{any::Any, cell::Cell};
 
-use crate::{arena::ElementId, nodes::Listener, scopes::ScopeId, virtualdom::VirtualDom};
+use crate::{arena::ElementId, scopes::ScopeId, virtualdom::VirtualDom, Attribute, AttributeValue};
 
 /// User Events are events that are shuttled from the renderer into the [`VirtualDom`] through the scheduler channel.
 ///
@@ -106,12 +106,12 @@ impl VirtualDom {
 
         let location = unsafe { &mut *path.template }
             .dynamic_attrs
-            .iter_mut()
+            .iter()
             .position(|attr| attr.mounted_element.get() == event.element)?;
 
         let mut index = Some((path.template, location));
 
-        let mut listeners = Vec::<&mut Listener>::new();
+        let mut listeners = Vec::<&Attribute>::new();
 
         while let Some((raw_parent, dyn_index)) = index {
             let parent = unsafe { &mut *raw_parent };
@@ -120,17 +120,18 @@ impl VirtualDom {
             listeners.extend(
                 parent
                     .dynamic_attrs
-                    .iter_mut()
+                    .iter()
                     .filter(|attr| is_path_ascendant(attr.path, path))
-                    .map(|f| f.listeners.iter_mut().filter(|f| f.name == event.name))
-                    .flatten(),
+                    .filter(|attr| attr.name == event.name),
             );
 
             index = parent.parent;
         }
 
         for listener in listeners {
-            (listener.callback)(&event.event);
+            if let AttributeValue::Listener(listener) = &listener.value {
+                (listener.borrow_mut())(&event.event)
+            }
         }
 
         Some(())

+ 107 - 1
packages/core/src/factory.rs

@@ -1,9 +1,12 @@
 use std::{cell::Cell, fmt::Arguments};
 
+use bumpalo::Bump;
+
 use crate::{
+    any_props::{AnyProps, VComponentProps},
     arena::ElementId,
     innerlude::{DynamicNode, DynamicNodeKind},
-    LazyNodes, ScopeState, VNode,
+    Attribute, AttributeValue, Element, LazyNodes, Properties, Scope, ScopeState, VNode,
 };
 
 impl ScopeState {
@@ -50,6 +53,56 @@ impl ScopeState {
             },
         }
     }
+
+    /// Create a new [`Attribute`]
+    pub fn attr<'a>(
+        &'a self,
+        name: &'static str,
+        val: impl IntoAttributeValue<'a>,
+        namespace: Option<&'static str>,
+        is_volatile: bool,
+    ) -> Attribute<'a> {
+        Attribute {
+            name,
+            namespace,
+            mounted_element: Cell::new(ElementId(0)),
+            path: &[0],
+            value: val.into_value(self.bump()),
+        }
+    }
+
+    /// Create a new [`VNode::Component`]
+    pub fn component<'a, P>(
+        &'a self,
+        component: fn(Scope<'a, P>) -> Element,
+        props: P,
+        fn_name: &'static str,
+    ) -> DynamicNode<'a>
+    where
+        P: Properties + 'a,
+    {
+        let props = self.bump().alloc(props);
+        let detached = unsafe { std::mem::transmute(component) };
+        let vcomp = VComponentProps::new(detached, P::memoize, props);
+        let as_dyn = self.bump().alloc(vcomp) as &mut dyn AnyProps;
+        let detached_dyn: *mut dyn AnyProps = unsafe { std::mem::transmute(as_dyn) };
+
+        // todo: clean up borrowed props
+        // if !P::IS_STATIC {
+        //     let vcomp = &*vcomp;
+        //     let vcomp = unsafe { std::mem::transmute(vcomp) };
+        //     self.scope.items.borrow_mut().borrowed_props.push(vcomp);
+        // }
+
+        DynamicNode {
+            path: &[],
+            kind: DynamicNodeKind::Component {
+                name: fn_name,
+                can_memoize: P::IS_STATIC,
+                props: detached_dyn,
+            },
+        }
+    }
 }
 
 pub trait IntoVnode<'a, A = ()> {
@@ -61,3 +114,56 @@ impl<'a, 'b> IntoVnode<'a> for LazyNodes<'a, 'b> {
         self.call(cx)
     }
 }
+
+impl<'a, 'b> IntoVnode<'a> for 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> {
+        VNode {
+            node_id: self.node_id.clone(),
+            parent: self.parent,
+            template: self.template,
+            root_ids: self.root_ids,
+            dynamic_nodes: self.dynamic_nodes,
+            dynamic_attrs: self.dynamic_attrs,
+        }
+    }
+}
+
+/// A value that can be converted into an attribute value
+pub trait IntoAttributeValue<'a> {
+    /// Convert into an attribute value
+    fn into_value(self, bump: &'a Bump) -> AttributeValue<'a>;
+}
+
+impl<'a> IntoAttributeValue<'a> for &'a str {
+    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
+        AttributeValue::Text(self)
+    }
+}
+impl<'a> IntoAttributeValue<'a> for f32 {
+    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
+        AttributeValue::Float(self)
+    }
+}
+impl<'a> IntoAttributeValue<'a> for i32 {
+    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
+        AttributeValue::Int(self)
+    }
+}
+impl<'a> IntoAttributeValue<'a> for bool {
+    fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
+        AttributeValue::Bool(self)
+    }
+}
+impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
+    fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
+        use bumpalo::core_alloc::fmt::Write;
+        let mut str_buf = bumpalo::collections::String::new_in(bump);
+        str_buf.write_fmt(self).unwrap();
+        AttributeValue::Text(str_buf.into_bump_str())
+    }
+}

+ 8 - 6
packages/core/src/lib.rs

@@ -12,6 +12,7 @@ mod garbage;
 mod lazynodes;
 mod mutations;
 mod nodes;
+mod properties;
 mod scope_arena;
 mod scopes;
 mod virtualdom;
@@ -20,10 +21,10 @@ pub(crate) mod innerlude {
     pub use crate::element::Element;
     pub use crate::events::*;
     pub use crate::future_container::*;
+    pub use crate::lazynodes::*;
     pub use crate::mutations::*;
     pub use crate::nodes::*;
-    // pub use crate::properties::*;
-    pub use crate::lazynodes::*;
+    pub use crate::properties::*;
     pub use crate::scopes::*;
     pub use crate::virtualdom::*;
 
@@ -66,6 +67,7 @@ pub(crate) mod innerlude {
 }
 
 pub use crate::innerlude::{
+    fc_to_builder,
     // AnyAttributeValue, AnyEvent, Attribute, AttributeValue, Component, Element, ElementId,
     Attribute,
     AttributeValue,
@@ -74,8 +76,8 @@ pub use crate::innerlude::{
     Element,
     EventPriority,
     LazyNodes,
-    Listener,
     NodeFactory,
+    Properties,
     Scope,
     ScopeId,
     ScopeState,
@@ -96,9 +98,9 @@ pub use crate::innerlude::{
 /// This includes types like [`Scope`], [`Element`], and [`Component`].
 pub mod prelude {
     pub use crate::innerlude::{
-        Attribute, DynamicNode, DynamicNodeKind, Element, EventPriority, LazyNodes, Listener,
-        NodeFactory, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute, TemplateNode,
-        UiEvent, VNode, VirtualDom,
+        fc_to_builder, Attribute, DynamicNode, DynamicNodeKind, Element, EventPriority, LazyNodes,
+        NodeFactory, Properties, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute,
+        TemplateNode, UiEvent, VNode, VirtualDom,
     };
 }
 

+ 1 - 1
packages/core/src/mutations.rs

@@ -7,7 +7,7 @@ pub struct Renderer<'a> {
 #[derive(Debug)]
 pub enum Mutation<'a> {
     SetAttribute {
-        name: &'static str,
+        name: &'a str,
         value: &'a str,
         id: ElementId,
     },

+ 21 - 20
packages/core/src/nodes.rs

@@ -1,5 +1,9 @@
 use crate::{any_props::AnyProps, arena::ElementId};
-use std::{any::Any, cell::Cell, hash::Hasher};
+use std::{
+    any::Any,
+    cell::{Cell, RefCell},
+    hash::Hasher,
+};
 
 pub type TemplateId = &'static str;
 
@@ -8,17 +12,16 @@ pub struct VNode<'a> {
     // The ID assigned for the root of this template
     pub node_id: Cell<ElementId>,
 
-    // When rendered, this template will be linked to its parent
+    // When rendered, this template will be linked to its parent manually
     pub parent: Option<(*mut VNode<'static>, usize)>,
 
     pub template: Template<'static>,
 
     pub root_ids: &'a [Cell<ElementId>],
 
-    /// All the dynamic nodes for a template
-    pub dynamic_nodes: &'a mut [DynamicNode<'a>],
+    pub dynamic_nodes: &'a [DynamicNode<'a>],
 
-    pub dynamic_attrs: &'a mut [AttributeLocation<'a>],
+    pub dynamic_attrs: &'a [Attribute<'a>],
 }
 
 #[derive(Debug, Clone, Copy)]
@@ -75,6 +78,7 @@ pub enum DynamicNodeKind<'a> {
     // IE in caps or with underscores
     Component {
         name: &'static str,
+        can_memoize: bool,
         props: *mut dyn AnyProps,
     },
 
@@ -104,18 +108,12 @@ pub enum TemplateAttribute<'a> {
     },
 }
 
-pub struct AttributeLocation<'a> {
-    pub mounted_element: Cell<ElementId>,
-    pub attrs: &'a mut [Attribute<'a>],
-    pub listeners: &'a mut [Listener<'a>],
-    pub path: &'static [u8],
-}
-
-#[derive(Debug)]
 pub struct Attribute<'a> {
-    pub name: &'static str,
-    pub value: &'a str,
+    pub name: &'a str,
+    pub value: AttributeValue<'a>,
     pub namespace: Option<&'static str>,
+    pub mounted_element: Cell<ElementId>,
+    pub path: &'static [u8],
 }
 
 pub enum AttributeValue<'a> {
@@ -123,7 +121,15 @@ pub enum AttributeValue<'a> {
     Float(f32),
     Int(i32),
     Bool(bool),
+    Listener(RefCell<&'a mut dyn FnMut(&dyn Any)>),
     Any(&'a dyn AnyValue),
+    None,
+}
+
+impl<'a> AttributeValue<'a> {
+    fn is_listener(&self) -> bool {
+        matches!(self, AttributeValue::Listener(_))
+    }
 }
 
 pub trait AnyValue {
@@ -142,11 +148,6 @@ where
     }
 }
 
-pub struct Listener<'a> {
-    pub name: &'static str,
-    pub callback: &'a mut dyn FnMut(&dyn Any),
-}
-
 #[test]
 fn what_are_the_sizes() {
     dbg!(std::mem::size_of::<VNode>());

+ 170 - 0
packages/core/src/properties.rs

@@ -0,0 +1,170 @@
+use crate::innerlude::*;
+
+pub struct FragmentProps<'a>(Element<'a>);
+pub struct FragmentBuilder<'a, const BUILT: bool>(Element<'a>);
+impl<'a> FragmentBuilder<'a, false> {
+    pub fn children(self, children: Element<'a>) -> FragmentBuilder<'a, true> {
+        FragmentBuilder(children)
+    }
+}
+impl<'a, const A: bool> FragmentBuilder<'a, A> {
+    pub fn build(self) -> FragmentProps<'a> {
+        FragmentProps(self.0)
+    }
+}
+
+/// Access the children elements passed into the component
+///
+/// This enables patterns where a component is passed children from its parent.
+///
+/// ## Details
+///
+/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions
+/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure
+/// on the props that takes Context.
+///
+/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other
+/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's
+/// props are valid for the static lifetime.
+///
+/// ## Example
+///
+/// ```rust, ignore
+/// fn App(cx: Scope) -> Element {
+///     cx.render(rsx!{
+///         CustomCard {
+///             h1 {}
+///             p {}
+///         }
+///     })
+/// }
+///
+/// #[derive(PartialEq, Props)]
+/// struct CardProps {
+///     children: Element
+/// }
+///
+/// fn CustomCard(cx: Scope<CardProps>) -> Element {
+///     cx.render(rsx!{
+///         div {
+///             h1 {"Title card"}
+///             {cx.props.children}
+///         }
+///     })
+/// }
+/// ```
+impl<'a> Properties for FragmentProps<'a> {
+    type Builder = FragmentBuilder<'a, false>;
+    const IS_STATIC: bool = false;
+    fn builder() -> Self::Builder {
+        FragmentBuilder(None)
+    }
+    unsafe fn memoize(&self, _other: &Self) -> bool {
+        false
+    }
+}
+
+// /// Create inline fragments using Component syntax.
+// ///
+// /// ## Details
+// ///
+// /// Fragments capture a series of children without rendering extra nodes.
+// ///
+// /// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and
+// /// a key is needed to identify each item.
+// ///
+// /// ## Example
+// ///
+// /// ```rust, ignore
+// /// rsx!{
+// ///     Fragment { key: "abc" }
+// /// }
+// /// ```
+// ///
+// /// ## Usage
+// ///
+// /// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
+// /// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments.
+// ///
+// /// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
+// ///
+// /// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
+// #[allow(non_upper_case_globals, non_snake_case)]
+// pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
+//     let i = cx.props.0.as_ref().map(|f| f.decouple());
+//     cx.render(LazyNodes::new(|f| f.fragment_from_iter(i)))
+// }
+
+/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
+/// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the
+/// derive macro to implement the `Properties` trait automatically as guarantee that your memoization strategy is safe.
+///
+/// If your props are 'static, then Dioxus will require that they also be PartialEq for the derived memoize strategy. However,
+/// if your props borrow data, then the memoization strategy will simply default to "false" and the PartialEq will be ignored.
+/// This tends to be useful when props borrow something that simply cannot be compared (IE a reference to a closure);
+///
+/// By default, the memoization strategy is very conservative, but can be tuned to be more aggressive manually. However,
+/// this is only safe if the props are 'static - otherwise you might borrow references after-free.
+///
+/// We strongly suggest that any changes to memoization be done at the "PartialEq" level for 'static props. Additionally,
+/// we advise the use of smart pointers in cases where memoization is important.
+///
+/// ## Example
+///
+/// For props that are 'static:
+/// ```rust, ignore
+/// #[derive(Props, PartialEq)]
+/// struct MyProps {
+///     data: String
+/// }
+/// ```
+///
+/// For props that borrow:
+///
+/// ```rust, ignore
+/// #[derive(Props)]
+/// struct MyProps<'a >{
+///     data: &'a str
+/// }
+/// ```
+pub trait Properties: Sized {
+    /// The type of the builder for this component.
+    /// Used to create "in-progress" versions of the props.
+    type Builder;
+
+    /// An indication if these props are can be memoized automatically.
+    const IS_STATIC: bool;
+
+    /// Create a builder for this component.
+    fn builder() -> Self::Builder;
+
+    /// Memoization can only happen if the props are valid for the 'static lifetime
+    ///
+    /// # Safety
+    /// The user must know if their props are static, but if they make a mistake, UB happens
+    /// Therefore it's unsafe to memoize.
+    unsafe fn memoize(&self, other: &Self) -> bool;
+}
+
+impl Properties for () {
+    type Builder = EmptyBuilder;
+    const IS_STATIC: bool = true;
+    fn builder() -> Self::Builder {
+        EmptyBuilder {}
+    }
+    unsafe fn memoize(&self, _other: &Self) -> bool {
+        true
+    }
+}
+// We allow components to use the () generic parameter if they have no props. This impl enables the "build" method
+// that the macros use to anonymously complete prop construction.
+pub struct EmptyBuilder;
+impl EmptyBuilder {
+    pub fn build(self) {}
+}
+
+/// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern
+/// to initialize a component's props.
+pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder {
+    T::builder()
+}

+ 8 - 10
packages/dioxus/tests/rsx_syntax.rs

@@ -16,10 +16,7 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element {
                 p { "you're great!" }
                 div { background_color: "red",
                     h1 { "var" }
-                    div {
-                        b { "asd" }
-                        "not great"
-                    }
+                    div { b { "asd" } "not great" }
                 }
                 p { "you're great!" }
             }
@@ -30,16 +27,17 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element {
 fn basic_template(cx: Scope) -> Element {
     cx.render(rsx! {
         div {
-            (0..2).map(|i| rsx! {
-                div { "asd" }
-            }),
-            (0..2).map(|i| rsx! {
-                div { "asd" }
-            })
+            (0..2).map(|i| rsx! { div { "asd {i}" } })
+            basic_child { }
         }
     })
 }
 
+/// A beautiful component
+fn basic_child(cx: Scope) -> Element {
+    todo!()
+}
+
 #[test]
 fn basic_prints() {
     let mut dom = VirtualDom::new(basic_template);

+ 1 - 1
packages/html/src/events.rs

@@ -38,7 +38,7 @@ pub mod on {
                         factory: NodeFactory<'a>,
                         mut callback: impl FnMut($wrapper) + 'a,
                         // mut callback: impl FnMut(UiEvent<$data>) + 'a,
-                    ) -> Listener<'a>
+                    ) -> Attribute<'a>
                     {
                         // let bump = &factory.bump();
 

+ 11 - 22
packages/rsx/src/component.rs

@@ -117,8 +117,6 @@ impl ToTokens for Component {
         let name = &self.name;
         let prop_gen_args = &self.prop_gen_args;
 
-        let mut has_key = None;
-
         let builder = match &self.manual_props {
             Some(manual_props) => {
                 let mut toks = quote! {
@@ -126,7 +124,7 @@ impl ToTokens for Component {
                 };
                 for field in &self.fields {
                     if field.name == "key" {
-                        has_key = Some(field);
+                        // skip keys
                     } else {
                         let name = &field.name;
                         let val = &field.content;
@@ -149,18 +147,22 @@ impl ToTokens for Component {
                 };
                 for field in &self.fields {
                     match field.name.to_string().as_str() {
-                        "key" => {
-                            //
-                            has_key = Some(field);
-                        }
+                        "key" => {}
                         _ => toks.append_all(quote! {#field}),
                     }
                 }
 
                 if !self.children.is_empty() {
-                    let childs = &self.children;
+                    let renderer = TemplateRenderer {
+                        roots: &self.children,
+                    };
+
                     toks.append_all(quote! {
-                        .children(__cx.create_children([ #( #childs ),* ]))
+                        .children(
+                            Some({
+                                #renderer
+                            })
+                        )
                     });
                 }
 
@@ -171,25 +173,12 @@ impl ToTokens for Component {
             }
         };
 
-        let key_token = match has_key {
-            Some(field) => {
-                let inners = &field.content;
-                if let ContentField::Formatted(ifmt) = inners {
-                    quote! { Some(#ifmt) }
-                } else {
-                    unreachable!()
-                }
-            }
-            None => quote! { None },
-        };
-
         let fn_name = self.name.segments.last().unwrap().ident.to_string();
 
         tokens.append_all(quote! {
             __cx.component(
                 #name,
                 #builder,
-                #key_token,
                 #fn_name
             )
         })

+ 124 - 152
packages/rsx/src/lib.rs

@@ -70,118 +70,40 @@ impl Parse for CallBody {
 /// Serialize the same way, regardless of flavor
 impl ToTokens for CallBody {
     fn to_tokens(&self, out_tokens: &mut TokenStream2) {
-        // 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
-        struct DynamicContext<'a> {
-            dynamic_nodes: Vec<&'a BodyNode>,
-            dynamic_attributes: HashMap<Vec<u8>, AttrLocation<'a>>,
-            current_path: Vec<u8>,
-        }
+        let body = TemplateRenderer { roots: &self.roots };
 
-        #[derive(Default)]
-        struct AttrLocation<'a> {
-            attrs: Vec<&'a ElementAttrNamed>,
-            listeners: Vec<&'a ElementAttrNamed>,
+        if self.inline_cx {
+            out_tokens.append_all(quote! {
+                Some({
+                    let __cx = cx;
+                    #body
+                })
+            })
+        } else {
+            out_tokens.append_all(quote! {
+                ::dioxus::core::LazyNodes::new( move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
+                    #body
+                })
+            })
         }
+    }
+}
 
+pub struct TemplateRenderer<'a> {
+    pub roots: &'a [BodyNode],
+}
+
+impl<'a> ToTokens for TemplateRenderer<'a> {
+    fn to_tokens(&self, out_tokens: &mut TokenStream2) {
         let mut context = DynamicContext {
             dynamic_nodes: vec![],
-            dynamic_attributes: HashMap::new(),
+            dynamic_attributes: vec![],
             current_path: vec![],
         };
 
-        fn render_static_node<'a>(root: &'a BodyNode, cx: &mut DynamicContext<'a>) -> TokenStream2 {
-            match root {
-                BodyNode::Element(el) => {
-                    let el_name = &el.name;
-
-                    let children = el.children.iter().enumerate().map(|(idx, root)| {
-                        cx.current_path.push(idx as u8);
-                        let out = render_static_node(root, cx);
-                        cx.current_path.pop();
-                        out
-                    });
-
-                    let children = quote! { #(#children),* };
-
-                    let attrs = el.attributes.iter().filter_map(|attr| {
-                        //
-                        match &attr.attr {
-                            ElementAttr::AttrText { name, value } if value.is_static() => {
-                                let value = value.source.as_ref().unwrap();
-                                Some(quote! {
-                                    ::dioxus::core::TemplateAttribute::Static {
-                                        name: dioxus_elements::#el_name::#name.0,
-                                        namespace: dioxus_elements::#el_name::#name.1,
-                                        volatile: dioxus_elements::#el_name::#name.2,
-                                        value: #value,
-                                    }
-                                })
-                            }
-
-                            ElementAttr::CustomAttrText { name, value } if value.is_static() => {
-                                let value = value.source.as_ref().unwrap();
-                                Some(quote! {
-                                    ::dioxus::core::TemplateAttribute::Static {
-                                        name: dioxus_elements::#el_name::#name.0,
-                                        namespace: dioxus_elements::#el_name::#name.1,
-                                        volatile: dioxus_elements::#el_name::#name.2,
-                                        value: #value,
-                                    }
-                                })
-                            }
-
-                            ElementAttr::AttrExpression { .. }
-                            | ElementAttr::AttrText { .. }
-                            | ElementAttr::CustomAttrText { .. }
-                            | ElementAttr::CustomAttrExpression { .. } => {
-                                // todo!();
-                                let ct = cx.dynamic_attributes.len();
-                                // cx.dynamic_attributes.push(attr);
-                                // quote! {}
-                                // None
-                                Some(quote! { ::dioxus::core::TemplateAttribute::Dynamic {
-                                    name: "asd",
-                                    index: #ct
-                                } })
-                            }
-
-                            ElementAttr::EventTokens { .. } => {
-                                // todo!();
-                                // let ct = cx.dynamic_listeners.len();
-                                // cx.dynamic_listeners.push(attr);
-                                // quote! {}
-                                None
-                            }
-                        }
-                    });
-
-                    quote! {
-                        ::dioxus::core::TemplateNode::Element {
-                            tag: dioxus_elements::#el_name::TAG_NAME,
-                            namespace: dioxus_elements::#el_name::NAME_SPACE,
-                            attrs: &[ #(#attrs),* ],
-                            children: &[ #children ],
-                        }
-                    }
-                }
-
-                BodyNode::Text(text) if text.is_static() => {
-                    let text = text.source.as_ref().unwrap();
-                    quote! { ::dioxus::core::TemplateNode::Text(#text) }
-                }
-
-                BodyNode::RawExpr(_) | BodyNode::Component(_) | BodyNode::Text(_) => {
-                    let ct = cx.dynamic_nodes.len();
-                    cx.dynamic_nodes.push(root);
-                    quote! { ::dioxus::core::TemplateNode::Dynamic(#ct) }
-                }
-            }
-        }
-
         let root_printer = self.roots.iter().enumerate().map(|(idx, root)| {
             context.current_path.push(idx as u8);
-            let out = render_static_node(root, &mut context);
+            let out = context.render_static_node(root);
             context.current_path.pop();
             out
         });
@@ -189,8 +111,9 @@ impl ToTokens for CallBody {
         // Render and release the mutable borrow on context
         let roots = quote! { #( #root_printer ),* };
         let node_printer = &context.dynamic_nodes;
+        let dyn_attr_printer = &context.dynamic_attributes;
 
-        let body = quote! {
+        out_tokens.append_all(quote! {
             static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {
                 id: ::dioxus::core::get_line_num!(),
                 roots: &[ #roots ]
@@ -201,63 +124,112 @@ impl ToTokens for CallBody {
                 template: TEMPLATE,
                 root_ids: __cx.bump().alloc([]),
                 dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]),
-                dynamic_attrs: __cx.bump().alloc([]),
+                dynamic_attrs: __cx.bump().alloc([ #( #dyn_attr_printer ),* ]),
             }
-        };
-
-        if self.inline_cx {
-            out_tokens.append_all(quote! {
-                {
-                    let __cx = cx;
-                    #body
-                }
-            })
-        } else {
-            out_tokens.append_all(quote! {
-                ::dioxus::core::LazyNodes::new( move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
-                    #body
-                })
-            })
-        }
+        });
     }
 }
+// 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
+pub struct DynamicContext<'a> {
+    dynamic_nodes: Vec<&'a BodyNode>,
+    dynamic_attributes: Vec<&'a ElementAttrNamed>,
+    current_path: Vec<u8>,
+}
 
-impl CallBody {
-    pub fn to_tokens_without_template(&self, out_tokens: &mut TokenStream2) {
+impl<'a> DynamicContext<'a> {
+    fn render_static_node(&mut self, root: &'a BodyNode) -> TokenStream2 {
+        match root {
+            BodyNode::Element(el) => {
+                let el_name = &el.name;
+
+                // dynamic attributes
+                // [0]
+                // [0, 1]
+                // [0, 1]
+                // [0, 1]
+                // [0, 1, 2]
+                // [0, 2]
+                // [0, 2, 1]
+
+                let static_attrs = el.attributes.iter().filter_map(|attr| match &attr.attr {
+                    ElementAttr::AttrText { name, value } if value.is_static() => {
+                        let value = value.source.as_ref().unwrap();
+                        Some(quote! {
+                            ::dioxus::core::TemplateAttribute::Static {
+                                name: dioxus_elements::#el_name::#name.0,
+                                namespace: dioxus_elements::#el_name::#name.1,
+                                volatile: dioxus_elements::#el_name::#name.2,
+                                value: #value,
+                            }
+                        })
+                    }
 
-        // let children = &self.roots;
-        // let inner = if children.len() == 1 {
-        //     let inner = &self.roots[0];
-        //     quote! { #inner }
-        // } else {
-        //     quote! { __cx.fragment_root([ #(#children),* ]) }
-        // };
+                    ElementAttr::CustomAttrText { name, value } if value.is_static() => {
+                        let value = value.source.as_ref().unwrap();
+                        Some(quote! {
+                            ::dioxus::core::TemplateAttribute::Static {
+                                name: dioxus_elements::#el_name::#name.0,
+                                namespace: dioxus_elements::#el_name::#name.1,
+                                volatile: dioxus_elements::#el_name::#name.2,
+                                value: #value,
+                            }
+                        })
+                    }
 
-        // out_tokens.append_all(quote! {
-        //     LazyNodes::new(move |__cx: NodeFactory| -> VNode {
-        //         use dioxus_elements::{GlobalAttributes, SvgAttributes};
-        //         #inner
-        //     })
-        // })
-    }
+                    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
+                        } })
+                    }
 
-    pub fn to_tokens_without_lazynodes(&self, out_tokens: &mut TokenStream2) {
-        out_tokens.append_all(quote! {
-            static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {
-                id: ::dioxus::core::get_line_num!(),
-                roots: &[]
-            };
+                    ElementAttr::EventTokens { .. } => {
+                        let ct = self.dynamic_attributes.len();
+                        self.dynamic_attributes.push(attr);
+                        Some(quote! { ::dioxus::core::TemplateAttribute::Dynamic {
+                            name: "asd",
+                            index: #ct
+                        } })
+                    }
+                });
+
+                let attrs = quote! { #(#static_attrs),*};
+
+                let children = el.children.iter().enumerate().map(|(idx, root)| {
+                    self.current_path.push(idx as u8);
+                    let out = self.render_static_node(root);
+                    self.current_path.pop();
+                    out
+                });
+
+                let children = quote! { #(#children),* };
 
-            LazyNodes::new( move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
-                ::dioxus::core::VNode {
-                    node_id: Default::default(),
-                    parent: None,
-                    template: &TEMPLATE,
-                    root_ids: __cx.bump().alloc([]),
-                    dynamic_nodes: __cx.bump().alloc([]),
-                    dynamic_attrs: __cx.bump().alloc([]),
+                quote! {
+                    ::dioxus::core::TemplateNode::Element {
+                        tag: dioxus_elements::#el_name::TAG_NAME,
+                        namespace: dioxus_elements::#el_name::NAME_SPACE,
+                        attrs: &[ #attrs ],
+                        children: &[ #children ],
+                    }
                 }
-            })
-        })
+            }
+
+            BodyNode::Text(text) if text.is_static() => {
+                let text = text.source.as_ref().unwrap();
+                quote! { ::dioxus::core::TemplateNode::Text(#text) }
+            }
+
+            BodyNode::RawExpr(_) | BodyNode::Text(_) | BodyNode::Component(_) => {
+                let ct = self.dynamic_nodes.len();
+                self.dynamic_nodes.push(root);
+                quote! { ::dioxus::core::TemplateNode::Dynamic(#ct) }
+            }
+        }
     }
 }

+ 7 - 6
packages/rsx/src/node.rs

@@ -51,17 +51,18 @@ impl Parse for BodyNode {
             // - one ident
             // - followed by `{`
             // - 1st char is lowercase
+            // - no underscores (reserved for components)
             //
             // example:
             // div {}
             if let Some(ident) = path.get_ident() {
+                let el_name = ident.to_string();
+
+                let first_char = el_name.chars().next().unwrap();
+
                 if body_stream.peek(token::Brace)
-                    && ident
-                        .to_string()
-                        .chars()
-                        .next()
-                        .unwrap()
-                        .is_ascii_lowercase()
+                    && first_char.is_ascii_lowercase()
+                    && !el_name.contains('_')
                 {
                     return Ok(BodyNode::Element(stream.parse::<Element>()?));
                 }

+ 1 - 1
packages/ssr/src/lib.rs

@@ -1,7 +1,7 @@
 #![doc = include_str!("../README.md")]
 
 use std::fmt::{Display, Formatter, Write};
-mod template;
+pub mod template;
 
 use dioxus_core::exports::bumpalo;
 

+ 68 - 21
packages/ssr/src/template.rs

@@ -1,6 +1,6 @@
-use dioxus_core::prelude::*;
+use dioxus_core::{prelude::*, AttributeValue};
 use std::cell::RefCell;
-use std::collections::{HashMap, HashSet};
+use std::collections::HashMap;
 use std::fmt::Write;
 use std::rc::Rc;
 
@@ -22,7 +22,7 @@ struct StringChain {
 #[derive(Debug, Clone)]
 enum Segment {
     Attr(usize),
-    Dyn(usize),
+    Node(usize),
     PreRendered(String),
 }
 
@@ -89,8 +89,9 @@ impl StringCache {
                 cur_path.pop();
             }
             TemplateNode::Text(text) => write!(chain, "{}", text)?,
-            TemplateNode::Dynamic(idx) => chain.segments.push(Segment::Dyn(*idx)),
-            TemplateNode::DynamicText(idx) => chain.segments.push(Segment::Dyn(*idx)),
+            TemplateNode::Dynamic(idx) | TemplateNode::DynamicText(idx) => {
+                chain.segments.push(Segment::Node(*idx))
+            }
         }
 
         Ok(())
@@ -98,7 +99,7 @@ impl StringCache {
 }
 
 impl SsrRender {
-    fn render_vdom(&mut self, dom: &VirtualDom) -> String {
+    pub fn render_vdom(&mut self, dom: &VirtualDom) -> String {
         let scope = dom.base_scope();
         let root = scope.root_node();
 
@@ -119,14 +120,17 @@ impl SsrRender {
         for segment in entry.segments.iter() {
             match segment {
                 Segment::Attr(idx) => {
-                    todo!("properly implement attrs in the macro");
-                    // let loc = &template.dynamic_attrs[*idx];
-                    // for attr in loc.attrs.iter() {
-                    //     write!(buf, " {}=\"{}\"", attr.name, attr.value)?;
-                    // }
+                    let attr = &template.dynamic_attrs[*idx];
+                    match attr.value {
+                        AttributeValue::Text(value) => write!(buf, " {}=\"{}\"", attr.name, value)?,
+                        _ => {}
+                    };
                 }
-                Segment::Dyn(idx) => match &template.dynamic_nodes[*idx].kind {
-                    DynamicNodeKind::Text { value, .. } => write!(buf, "{}", value)?,
+                Segment::Node(idx) => match &template.dynamic_nodes[*idx].kind {
+                    DynamicNodeKind::Text { value, .. } => {
+                        // todo: escape the text
+                        write!(buf, "{}", value)?
+                    }
                     DynamicNodeKind::Fragment { children } => {
                         for child in *children {
                             self.render_template(buf, child)?;
@@ -152,20 +156,21 @@ fn to_string_works() {
 
     fn app(cx: Scope) -> Element {
         let dynamic = 123;
+        let dyn2 = "</diiiiiiiiv>"; // todo: escape this
 
-        cx.render(rsx! {
-            div { class: "asdasdasd", class: "asdasdasd",
+        render! {
+            div { class: "asdasdasd", class: "asdasdasd", id: "id-{dynamic}",
                 "Hello world 1 -->"
                 "{dynamic}"
                 "<-- Hello world 2"
                 div { "nest 1" }
                 div {}
                 div { "nest 2" }
-                (0..5).map(|i| rsx! {
-                    div { "finalize {i}" }
-                })
+                "{dyn2}"
+
+                (0..5).map(|i| rsx! { div { "finalize {i}" } })
             }
-        })
+        }
     }
     let mut dom = VirtualDom::new(app);
 
@@ -173,10 +178,52 @@ fn to_string_works() {
     dom.rebuild(&mut mutations);
 
     let cache = StringCache::from_template(&dom.base_scope().root_node()).unwrap();
-
     dbg!(cache.segments);
 
     let mut renderer = SsrRender::default();
-
     dbg!(renderer.render_vdom(&dom));
 }
+
+#[test]
+fn children_processes_properly() {
+    use dioxus::prelude::*;
+
+    fn app(cx: Scope) -> Element {
+        render! {
+            div {
+                "yo!"
+                "yo!"
+                "yo!"
+                "yo!"
+                "yo!"
+                Child {}
+                Child {}
+                Child {}
+                Child {}
+                ChildWithChildren {
+                    "hii"
+                }
+                (0..10).map(|f| rsx! {
+                    "div {f}"
+                })
+            }
+        }
+    }
+
+    /// Yo its the child component!
+    fn Child(cx: Scope) -> Element {
+        render! ( div { "child" } )
+    }
+
+    #[inline_props]
+    fn ChildWithChildren<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
+        render! {
+            div {
+                "div"
+                children
+            }
+        }
+    }
+
+    let mut dom = VirtualDom::new(app);
+}

+ 0 - 0
packages/ssr/tests/caches.rs → packages/ssr/tests.old/caches.rs


+ 0 - 0
packages/ssr/tests/renders.rs → packages/ssr/tests.old/renders.rs