1
0
Jonathan Kelley 2 жил өмнө
parent
commit
b6c0bce89c

+ 6 - 4
packages/core/src/create.rs

@@ -3,6 +3,7 @@ use crate::mutations::Mutation::*;
 use crate::nodes::VNode;
 use crate::nodes::{DynamicNode, DynamicNodeKind, TemplateNode};
 use crate::virtualdom::VirtualDom;
+use crate::TemplateAttribute;
 
 impl VirtualDom {
     /// Create this template and write its mutations
@@ -112,10 +113,11 @@ impl VirtualDom {
                     id,
                 });
 
-                mutations.extend(attrs.into_iter().map(|attr| SetAttribute {
-                    name: attr.name,
-                    value: attr.value,
-                    id,
+                mutations.extend(attrs.into_iter().filter_map(|attr| match attr {
+                    TemplateAttribute::Static { name, value, .. } => {
+                        Some(SetAttribute { name, value, id })
+                    }
+                    _ => None,
                 }));
 
                 children

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

@@ -1,19 +1,36 @@
-use std::fmt::Arguments;
+use std::{cell::Cell, fmt::Arguments};
 
-use crate::{innerlude::DynamicNode, LazyNodes, ScopeState, VNode};
+use crate::{
+    arena::ElementId,
+    innerlude::{DynamicNode, DynamicNodeKind},
+    LazyNodes, ScopeState, VNode,
+};
 
 impl ScopeState {
     /// Create some text that's allocated along with the other vnodes
     ///
-    pub fn text(&self, args: Arguments) -> DynamicNode {
-        // let (text, _is_static) = self.raw_text(args);
+    pub fn text<'a>(&'a self, args: Arguments) -> DynamicNode<'a> {
+        let (text, _) = self.raw_text(args);
 
-        // VNode::Text(self.bump.alloc(VText {
-        //     text,
-        //     id: Default::default(),
-        // }))
+        DynamicNode {
+            kind: DynamicNodeKind::Text {
+                id: Cell::new(ElementId(0)),
+                value: text,
+            },
+            path: &[0],
+        }
+    }
 
-        todo!()
+    pub fn raw_text<'a>(&'a self, args: Arguments) -> (&'a str, bool) {
+        match args.as_str() {
+            Some(static_str) => (static_str, true),
+            None => {
+                use bumpalo::core_alloc::fmt::Write;
+                let mut str_buf = bumpalo::collections::String::new_in(self.bump());
+                str_buf.write_fmt(args).unwrap();
+                (str_buf.into_bump_str(), false)
+            }
+        }
     }
 
     pub fn fragment_from_iter<'a, I, F: IntoVnode<'a, I>>(

+ 5 - 2
packages/core/src/lib.rs

@@ -69,6 +69,8 @@ pub use crate::innerlude::{
     // AnyAttributeValue, AnyEvent, Attribute, AttributeValue, Component, Element, ElementId,
     Attribute,
     AttributeValue,
+    DynamicNode,
+    DynamicNodeKind,
     Element,
     EventPriority,
     LazyNodes,
@@ -94,8 +96,9 @@ pub use crate::innerlude::{
 /// This includes types like [`Scope`], [`Element`], and [`Component`].
 pub mod prelude {
     pub use crate::innerlude::{
-        Attribute, Element, EventPriority, LazyNodes, Listener, NodeFactory, Scope, ScopeId,
-        ScopeState, TaskId, Template, TemplateAttribute, TemplateNode, UiEvent, VNode, VirtualDom,
+        Attribute, DynamicNode, DynamicNodeKind, Element, EventPriority, LazyNodes, Listener,
+        NodeFactory, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute, TemplateNode,
+        UiEvent, VNode, VirtualDom,
     };
 }
 

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

@@ -34,8 +34,8 @@ pub enum Mutation<'a> {
     },
 
     ReplacePlaceholder {
-        path: &'static [u8],
         m: usize,
+        path: &'static [u8],
     },
 
     AssignId {

+ 41 - 16
packages/core/src/nodes.rs

@@ -1,9 +1,5 @@
 use crate::{any_props::AnyProps, arena::ElementId};
-use std::{
-    any::{Any, TypeId},
-    cell::Cell,
-    num::NonZeroUsize,
-};
+use std::{any::Any, cell::Cell, hash::Hasher};
 
 pub type TemplateId = &'static str;
 
@@ -15,7 +11,7 @@ pub struct VNode<'a> {
     // When rendered, this template will be linked to its parent
     pub parent: Option<(*mut VNode<'static>, usize)>,
 
-    pub template: Template,
+    pub template: Template<'static>,
 
     pub root_ids: &'a [Cell<ElementId>],
 
@@ -26,15 +22,38 @@ pub struct VNode<'a> {
 }
 
 #[derive(Debug, Clone, Copy)]
-pub struct Template {
-    pub id: &'static str,
-    pub roots: &'static [TemplateNode<'static>],
+pub struct Template<'a> {
+    pub id: &'a str,
+    pub roots: &'a [TemplateNode<'a>],
+}
+
+impl<'a> std::hash::Hash for Template<'a> {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.id.hash(state);
+    }
+}
+
+impl PartialEq for Template<'_> {
+    fn eq(&self, other: &Self) -> bool {
+        self.id == other.id
+    }
+}
+
+impl Eq for Template<'_> {}
+impl PartialOrd for Template<'_> {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        self.id.partial_cmp(other.id)
+    }
+}
+impl Ord for Template<'_> {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.id.cmp(other.id)
+    }
 }
 
 /// A weird-ish variant of VNodes with way more limited types
 #[derive(Debug, Clone, Copy)]
 pub enum TemplateNode<'a> {
-    /// A simple element
     Element {
         tag: &'a str,
         namespace: Option<&'a str>,
@@ -62,7 +81,7 @@ pub enum DynamicNodeKind<'a> {
     // Comes in with string interpolation or from format_args, include_str, etc
     Text {
         id: Cell<ElementId>,
-        value: &'static str,
+        value: &'a str,
     },
 
     // Anything that's coming in as an iterator
@@ -72,11 +91,17 @@ pub enum DynamicNodeKind<'a> {
 }
 
 #[derive(Debug)]
-pub struct TemplateAttribute<'a> {
-    pub name: &'static str,
-    pub value: &'a str,
-    pub namespace: Option<&'static str>,
-    pub volatile: bool,
+pub enum TemplateAttribute<'a> {
+    Static {
+        name: &'static str,
+        value: &'a str,
+        namespace: Option<&'static str>,
+        volatile: bool,
+    },
+    Dynamic {
+        name: &'static str,
+        index: usize,
+    },
 }
 
 pub struct AttributeLocation<'a> {

+ 9 - 1
packages/core/src/virtualdom.rs

@@ -15,7 +15,7 @@ use slab::Slab;
 use std::collections::{BTreeSet, HashMap};
 
 pub struct VirtualDom {
-    pub(crate) templates: HashMap<TemplateId, Template>,
+    pub(crate) templates: HashMap<TemplateId, Template<'static>>,
     pub(crate) elements: Slab<ElementPath>,
     pub(crate) scopes: Slab<ScopeState>,
     pub(crate) scope_stack: Vec<ScopeId>,
@@ -82,6 +82,14 @@ impl VirtualDom {
     ///
     /// This is cancel safe, so if the future is dropped, you can push events into the virtualdom
     pub async fn wait_for_work(&mut self) {}
+
+    pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
+        self.scopes.get(id.0)
+    }
+
+    pub fn base_scope(&self) -> &ScopeState {
+        self.scopes.get(0).unwrap()
+    }
 }
 
 impl Drop for VirtualDom {

+ 6 - 0
packages/dioxus/tests/rsx_syntax.rs

@@ -30,6 +30,9 @@ 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" }
             })
@@ -42,7 +45,10 @@ fn basic_prints() {
     let mut dom = VirtualDom::new(basic_template);
 
     let mut edits = Vec::new();
+    dom.rebuild(&mut edits);
+    dbg!(edits);
 
+    let mut edits = Vec::new();
     dom.rebuild(&mut edits);
 
     dbg!(edits);

+ 71 - 57
packages/rsx/src/lib.rs

@@ -21,6 +21,8 @@ mod ifmt;
 mod node;
 mod template;
 
+use std::collections::HashMap;
+
 // Re-export the namespaces into each other
 pub use component::*;
 pub use element::*;
@@ -39,6 +41,9 @@ use syn::{
 #[derive(Default)]
 pub struct CallBody {
     pub roots: Vec<BodyNode>,
+
+    // set this after
+    pub inline_cx: bool,
 }
 
 impl Parse for CallBody {
@@ -55,7 +60,10 @@ impl Parse for CallBody {
             roots.push(node);
         }
 
-        Ok(Self { roots })
+        Ok(Self {
+            roots,
+            inline_cx: false,
+        })
     }
 }
 
@@ -66,14 +74,20 @@ impl ToTokens for CallBody {
         // 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: Vec<&'a ElementAttrNamed>,
-            dynamic_listeners: Vec<&'a ElementAttrNamed>,
+            dynamic_attributes: HashMap<Vec<u8>, AttrLocation<'a>>,
+            current_path: Vec<u8>,
+        }
+
+        #[derive(Default)]
+        struct AttrLocation<'a> {
+            attrs: Vec<&'a ElementAttrNamed>,
+            listeners: Vec<&'a ElementAttrNamed>,
         }
 
         let mut context = DynamicContext {
             dynamic_nodes: vec![],
-            dynamic_attributes: vec![],
-            dynamic_listeners: vec![],
+            dynamic_attributes: HashMap::new(),
+            current_path: vec![],
         };
 
         fn render_static_node<'a>(root: &'a BodyNode, cx: &mut DynamicContext<'a>) -> TokenStream2 {
@@ -81,10 +95,14 @@ impl ToTokens for CallBody {
                 BodyNode::Element(el) => {
                     let el_name = &el.name;
 
-                    let children = {
-                        let children = el.children.iter().map(|root| render_static_node(root, cx));
-                        quote! { #(#children),* }
-                    };
+                    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| {
                         //
@@ -92,7 +110,7 @@ impl ToTokens for CallBody {
                             ElementAttr::AttrText { name, value } if value.is_static() => {
                                 let value = value.source.as_ref().unwrap();
                                 Some(quote! {
-                                    ::dioxus::core::TemplateAttribute {
+                                    ::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,
@@ -104,7 +122,7 @@ impl ToTokens for CallBody {
                             ElementAttr::CustomAttrText { name, value } if value.is_static() => {
                                 let value = value.source.as_ref().unwrap();
                                 Some(quote! {
-                                    ::dioxus::core::TemplateAttribute {
+                                    ::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,
@@ -117,16 +135,21 @@ impl ToTokens for CallBody {
                             | ElementAttr::AttrText { .. }
                             | ElementAttr::CustomAttrText { .. }
                             | ElementAttr::CustomAttrExpression { .. } => {
+                                // todo!();
                                 let ct = cx.dynamic_attributes.len();
-                                cx.dynamic_attributes.push(attr);
+                                // cx.dynamic_attributes.push(attr);
                                 // quote! {}
-                                None
-                                // quote! { ::dioxus::core::TemplateAttribute::Dynamic(#ct) }
+                                // None
+                                Some(quote! { ::dioxus::core::TemplateAttribute::Dynamic {
+                                    name: "asd",
+                                    index: #ct
+                                } })
                             }
 
                             ElementAttr::EventTokens { .. } => {
-                                let ct = cx.dynamic_listeners.len();
-                                cx.dynamic_listeners.push(attr);
+                                // todo!();
+                                // let ct = cx.dynamic_listeners.len();
+                                // cx.dynamic_listeners.push(attr);
                                 // quote! {}
                                 None
                             }
@@ -156,55 +179,46 @@ impl ToTokens for CallBody {
             }
         }
 
-        let root_printer = self
-            .roots
-            .iter()
-            .map(|root| render_static_node(root, &mut context));
+        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);
+            context.current_path.pop();
+            out
+        });
 
         // Render and release the mutable borrow on context
         let roots = quote! { #( #root_printer ),* };
-
         let node_printer = &context.dynamic_nodes;
-        let attr_printer = context.dynamic_attributes.iter();
-        let listener_printer = context.dynamic_listeners.iter();
 
-        out_tokens.append_all(quote! {
-            // LazyNodes::new(move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
-            //     __cx.template_ref(
-            //         ::dioxus::core::Template {
-            //             id: ::dioxus::core::get_line_num!(),
-            //             roots: &[ #roots ]
-            //         },
-            //         __cx.bump().alloc([
-            //            #( #node_printer ),*
-            //         ]),
-            //         __cx.bump().alloc([
-            //            #( #attr_printer ),*
-            //         ]),
-            //         __cx.bump().alloc([
-            //            #( #listener_printer ),*
-            //         ]),
-            //         None
-            //     )
-            // })
-
-
-            ::dioxus::core::LazyNodes::new( move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
-                static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {
-                    id: ::dioxus::core::get_line_num!(),
-                    roots: &[ #roots ]
-                };
+        let body = quote! {
+            static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {
+                id: ::dioxus::core::get_line_num!(),
+                roots: &[ #roots ]
+            };
+            ::dioxus::core::VNode {
+                node_id: Default::default(),
+                parent: None,
+                template: TEMPLATE,
+                root_ids: __cx.bump().alloc([]),
+                dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]),
+                dynamic_attrs: __cx.bump().alloc([]),
+            }
+        };
 
-                ::dioxus::core::VNode {
-                    node_id: Default::default(),
-                    parent: None,
-                    template: TEMPLATE,
-                    root_ids: __cx.bump().alloc([]),
-                    dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]),
-                    dynamic_attrs: __cx.bump().alloc([]),
+        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
+                })
+            })
+        }
     }
 }
 

+ 97 - 99
packages/ssr/src/lib.rs

@@ -1,9 +1,10 @@
 #![doc = include_str!("../README.md")]
 
 use std::fmt::{Display, Formatter, Write};
+mod template;
 
 use dioxus_core::exports::bumpalo;
-use dioxus_core::IntoVNode;
+
 use dioxus_core::*;
 
 fn app(_cx: Scope) -> Element {
@@ -25,9 +26,7 @@ impl SsrRenderer {
 
     pub fn render_lazy<'a>(&'a mut self, f: LazyNodes<'a, '_>) -> String {
         let scope = self.vdom.base_scope();
-        let factory = NodeFactory::new(scope);
-
-        let root = f.into_vnode(factory);
+        let root = f.call(scope);
         format!(
             "{:}",
             TextRenderer {
@@ -55,8 +54,7 @@ pub fn render_lazy<'a>(f: LazyNodes<'a, '_>) -> String {
     // Therefore, we cast our local bump allocator to the right lifetime. This is okay because our usage of the bump
     // arena is *definitely* shorter than the <'a> lifetime, and we return *owned* data - not borrowed data.
     let scope = unsafe { &*scope };
-
-    let root = f.into_vnode(NodeFactory::new(scope));
+    let root = f.call(scope);
 
     let vdom = Some(&vdom);
 
@@ -148,99 +146,99 @@ impl<'a: 'c, 'c> TextRenderer<'a, '_, 'c> {
         il: u16,
         last_node_was_text: &mut bool,
     ) -> std::fmt::Result {
-        match &node {
-            VNode::Text(text) => {
-                if *last_node_was_text {
-                    write!(f, "<!--spacer-->")?;
-                }
-
-                if self.cfg.indent {
-                    for _ in 0..il {
-                        write!(f, "    ")?;
-                    }
-                }
-
-                *last_node_was_text = true;
-
-                write!(f, "{}", text.text)?
-            }
-            VNode::Element(el) => {
-                *last_node_was_text = false;
-
-                if self.cfg.indent {
-                    for _ in 0..il {
-                        write!(f, "    ")?;
-                    }
-                }
-
-                write!(f, "<{}", el.tag)?;
-
-                let inner_html = render_attributes(el.attributes.iter(), f)?;
-
-                match self.cfg.newline {
-                    true => writeln!(f, ">")?,
-                    false => write!(f, ">")?,
-                }
-
-                if let Some(inner_html) = inner_html {
-                    write!(f, "{}", inner_html)?;
-                } else {
-                    let mut last_node_was_text = false;
-                    for child in el.children {
-                        self.html_render(child, f, il + 1, &mut last_node_was_text)?;
-                    }
-                }
-
-                if self.cfg.newline {
-                    writeln!(f)?;
-                }
-                if self.cfg.indent {
-                    for _ in 0..il {
-                        write!(f, "    ")?;
-                    }
-                }
-
-                write!(f, "</{}>", el.tag)?;
-                if self.cfg.newline {
-                    writeln!(f)?;
-                }
-            }
-            VNode::Fragment(frag) => match frag.children.len() {
-                0 => {
-                    *last_node_was_text = false;
-                    if self.cfg.indent {
-                        for _ in 0..il {
-                            write!(f, "    ")?;
-                        }
-                    }
-                    write!(f, "<!--placeholder-->")?;
-                }
-                _ => {
-                    for child in frag.children {
-                        self.html_render(child, f, il + 1, last_node_was_text)?;
-                    }
-                }
-            },
-            VNode::Component(vcomp) => {
-                let idx = vcomp.scope.get().unwrap();
-
-                if let (Some(vdom), false) = (self.vdom, self.cfg.skip_components) {
-                    let new_node = vdom.get_scope(idx).unwrap().root_node();
-                    self.html_render(new_node, f, il + 1, last_node_was_text)?;
-                } else {
-                }
-            }
-            VNode::Template(t) => {
-                if let Some(vdom) = self.vdom {
-                    todo!()
-                } else {
-                    panic!("Cannot render template without vdom");
-                }
-            }
-            VNode::Placeholder(_) => {
-                todo!()
-            }
-        }
+        // match &node {
+        //     VNode::Text(text) => {
+        //         if *last_node_was_text {
+        //             write!(f, "<!--spacer-->")?;
+        //         }
+
+        //         if self.cfg.indent {
+        //             for _ in 0..il {
+        //                 write!(f, "    ")?;
+        //             }
+        //         }
+
+        //         *last_node_was_text = true;
+
+        //         write!(f, "{}", text.text)?
+        //     }
+        //     VNode::Element(el) => {
+        //         *last_node_was_text = false;
+
+        //         if self.cfg.indent {
+        //             for _ in 0..il {
+        //                 write!(f, "    ")?;
+        //             }
+        //         }
+
+        //         write!(f, "<{}", el.tag)?;
+
+        //         let inner_html = render_attributes(el.attributes.iter(), f)?;
+
+        //         match self.cfg.newline {
+        //             true => writeln!(f, ">")?,
+        //             false => write!(f, ">")?,
+        //         }
+
+        //         if let Some(inner_html) = inner_html {
+        //             write!(f, "{}", inner_html)?;
+        //         } else {
+        //             let mut last_node_was_text = false;
+        //             for child in el.children {
+        //                 self.html_render(child, f, il + 1, &mut last_node_was_text)?;
+        //             }
+        //         }
+
+        //         if self.cfg.newline {
+        //             writeln!(f)?;
+        //         }
+        //         if self.cfg.indent {
+        //             for _ in 0..il {
+        //                 write!(f, "    ")?;
+        //             }
+        //         }
+
+        //         write!(f, "</{}>", el.tag)?;
+        //         if self.cfg.newline {
+        //             writeln!(f)?;
+        //         }
+        //     }
+        //     VNode::Fragment(frag) => match frag.children.len() {
+        //         0 => {
+        //             *last_node_was_text = false;
+        //             if self.cfg.indent {
+        //                 for _ in 0..il {
+        //                     write!(f, "    ")?;
+        //                 }
+        //             }
+        //             write!(f, "<!--placeholder-->")?;
+        //         }
+        //         _ => {
+        //             for child in frag.children {
+        //                 self.html_render(child, f, il + 1, last_node_was_text)?;
+        //             }
+        //         }
+        //     },
+        //     VNode::Component(vcomp) => {
+        //         let idx = vcomp.scope.get().unwrap();
+
+        //         if let (Some(vdom), false) = (self.vdom, self.cfg.skip_components) {
+        //             let new_node = vdom.get_scope(idx).unwrap().root_node();
+        //             self.html_render(new_node, f, il + 1, last_node_was_text)?;
+        //         } else {
+        //         }
+        //     }
+        //     VNode::Template(t) => {
+        //         if let Some(vdom) = self.vdom {
+        //             todo!()
+        //         } else {
+        //             panic!("Cannot render template without vdom");
+        //         }
+        //     }
+        //     VNode::Placeholder(_) => {
+        //         todo!()
+        //     }
+        // }
         Ok(())
     }
 

+ 182 - 0
packages/ssr/src/template.rs

@@ -0,0 +1,182 @@
+use dioxus_core::prelude::*;
+use std::cell::RefCell;
+use std::collections::{HashMap, HashSet};
+use std::fmt::Write;
+use std::rc::Rc;
+
+/// A virtualdom renderer that caches the templates it has seen for faster rendering
+#[derive(Default)]
+pub struct SsrRender {
+    template_cache: RefCell<HashMap<Template<'static>, Rc<StringCache>>>,
+}
+
+struct StringCache {
+    segments: Vec<Segment>,
+}
+
+#[derive(Default)]
+struct StringChain {
+    segments: Vec<Segment>,
+}
+
+#[derive(Debug, Clone)]
+enum Segment {
+    Attr(usize),
+    Dyn(usize),
+    PreRendered(String),
+}
+
+impl std::fmt::Write for StringChain {
+    fn write_str(&mut self, s: &str) -> std::fmt::Result {
+        match self.segments.last_mut() {
+            Some(Segment::PreRendered(s2)) => s2.push_str(s),
+            _ => self.segments.push(Segment::PreRendered(s.to_string())),
+        }
+
+        Ok(())
+    }
+}
+
+impl StringCache {
+    fn from_template(template: &VNode) -> Result<Self, std::fmt::Error> {
+        let mut chain = StringChain::default();
+
+        let mut cur_path = vec![];
+
+        for (root_idx, root) in template.template.roots.iter().enumerate() {
+            Self::recurse(root, &mut cur_path, root_idx, &mut chain)?;
+        }
+
+        Ok(Self {
+            segments: chain.segments,
+        })
+    }
+
+    fn recurse(
+        root: &TemplateNode,
+        cur_path: &mut Vec<usize>,
+        root_idx: usize,
+        chain: &mut StringChain,
+    ) -> Result<(), std::fmt::Error> {
+        match root {
+            TemplateNode::Element {
+                tag,
+                attrs,
+                children,
+                ..
+            } => {
+                cur_path.push(root_idx);
+                write!(chain, "<{}", tag)?;
+                for attr in *attrs {
+                    match attr {
+                        TemplateAttribute::Static { name, value, .. } => {
+                            write!(chain, " {}=\"{}\"", name, value)?;
+                        }
+                        TemplateAttribute::Dynamic { index, .. } => {
+                            chain.segments.push(Segment::Attr(*index))
+                        }
+                    }
+                }
+                if children.len() == 0 {
+                    write!(chain, "/>")?;
+                } else {
+                    write!(chain, ">")?;
+                    for child in *children {
+                        Self::recurse(child, cur_path, root_idx, chain)?;
+                    }
+                    write!(chain, "</{}>", tag)?;
+                }
+                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)),
+        }
+
+        Ok(())
+    }
+}
+
+impl SsrRender {
+    fn render_vdom(&mut self, dom: &VirtualDom) -> String {
+        let scope = dom.base_scope();
+        let root = scope.root_node();
+
+        let mut out = String::new();
+        self.render_template(&mut out, root).unwrap();
+
+        out
+    }
+
+    fn render_template(&self, buf: &mut String, template: &VNode) -> std::fmt::Result {
+        let entry = self
+            .template_cache
+            .borrow_mut()
+            .entry(template.template)
+            .or_insert_with(|| Rc::new(StringCache::from_template(template).unwrap()))
+            .clone();
+
+        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)?;
+                    // }
+                }
+                Segment::Dyn(idx) => match &template.dynamic_nodes[*idx].kind {
+                    DynamicNodeKind::Text { value, .. } => write!(buf, "{}", value)?,
+                    DynamicNodeKind::Fragment { children } => {
+                        for child in *children {
+                            self.render_template(buf, child)?;
+                        }
+                        //
+                    }
+                    DynamicNodeKind::Component { .. } => {
+                        //
+                    }
+                },
+
+                Segment::PreRendered(text) => buf.push_str(&text),
+            }
+        }
+
+        Ok(())
+    }
+}
+
+#[test]
+fn to_string_works() {
+    use dioxus::prelude::*;
+
+    fn app(cx: Scope) -> Element {
+        let dynamic = 123;
+
+        cx.render(rsx! {
+            div { class: "asdasdasd", class: "asdasdasd",
+                "Hello world 1 -->"
+                "{dynamic}"
+                "<-- Hello world 2"
+                div { "nest 1" }
+                div {}
+                div { "nest 2" }
+                (0..5).map(|i| rsx! {
+                    div { "finalize {i}" }
+                })
+            }
+        })
+    }
+    let mut dom = VirtualDom::new(app);
+
+    let mut mutations = Vec::new();
+    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));
+}

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