diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index f7b8744d6..37a089f37 100644 --- a/packages/core/src/create.rs +++ b/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 diff --git a/packages/core/src/factory.rs b/packages/core/src/factory.rs index 695a6ccaf..09d316029 100644 --- a/packages/core/src/factory.rs +++ b/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>>( diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index a8c737858..c5f77e03d 100644 --- a/packages/core/src/lib.rs +++ b/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, }; } diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 3a6819042..e6da66e18 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -34,8 +34,8 @@ pub enum Mutation<'a> { }, ReplacePlaceholder { - path: &'static [u8], m: usize, + path: &'static [u8], }, AssignId { diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 4e54c8d4b..106c01761 100644 --- a/packages/core/src/nodes.rs +++ b/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], @@ -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(&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 { + 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, - 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> { diff --git a/packages/core/src/virtualdom.rs b/packages/core/src/virtualdom.rs index d5e6c11dc..d0d8e1fcc 100644 --- a/packages/core/src/virtualdom.rs +++ b/packages/core/src/virtualdom.rs @@ -15,7 +15,7 @@ use slab::Slab; use std::collections::{BTreeSet, HashMap}; pub struct VirtualDom { - pub(crate) templates: HashMap, + pub(crate) templates: HashMap>, pub(crate) elements: Slab, pub(crate) scopes: Slab, pub(crate) scope_stack: Vec, @@ -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 { diff --git a/packages/dioxus/tests/rsx_syntax.rs b/packages/dioxus/tests/rsx_syntax.rs index d217db22a..321d2aacf 100644 --- a/packages/dioxus/tests/rsx_syntax.rs +++ b/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); diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index 2f0dc5df9..cda8e363b 100644 --- a/packages/rsx/src/lib.rs +++ b/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, + + // 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, AttrLocation<'a>>, + current_path: Vec, + } + + #[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 - // ) - // }) + 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::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 ] - }; - - ::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 + }) + }) + } } } diff --git a/packages/ssr/src/lib.rs b/packages/ssr/src/lib.rs index 2f9e88992..bdb4f6767 100644 --- a/packages/ssr/src/lib.rs +++ b/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, "")?; - } + // match &node { + // VNode::Text(text) => { + // if *last_node_was_text { + // write!(f, "")?; + // } - if self.cfg.indent { - for _ in 0..il { - write!(f, " ")?; - } - } + // if self.cfg.indent { + // for _ in 0..il { + // write!(f, " ")?; + // } + // } - *last_node_was_text = true; + // *last_node_was_text = true; - write!(f, "{}", text.text)? - } - VNode::Element(el) => { - *last_node_was_text = false; + // write!(f, "{}", text.text)? + // } + // VNode::Element(el) => { + // *last_node_was_text = false; - if self.cfg.indent { - for _ in 0..il { - write!(f, " ")?; - } - } + // if self.cfg.indent { + // for _ in 0..il { + // write!(f, " ")?; + // } + // } - write!(f, "<{}", el.tag)?; + // write!(f, "<{}", el.tag)?; - let inner_html = render_attributes(el.attributes.iter(), f)?; + // let inner_html = render_attributes(el.attributes.iter(), f)?; - match self.cfg.newline { - true => writeln!(f, ">")?, - false => write!(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 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, " ")?; - } - } + // 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, "")?; - } - _ => { - 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(); + // 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, "")?; + // } + // _ => { + // 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!() - } - } + // 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(()) } diff --git a/packages/ssr/src/template.rs b/packages/ssr/src/template.rs new file mode 100644 index 000000000..3e229298c --- /dev/null +++ b/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, Rc>>, +} + +struct StringCache { + segments: Vec, +} + +#[derive(Default)] +struct StringChain { + segments: Vec, +} + +#[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 { + 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, + 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)); +} diff --git a/packages/ssr/tests/caches.rs b/packages/ssr/tests/caches.rs new file mode 100644 index 000000000..e69de29bb