From 04bc635ac5b898a9c57372df47a15a48beb80800 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 15 Nov 2022 23:22:41 -0800 Subject: [PATCH] wip: prep to fix bug in create --- examples/custom_element.rs | 14 +- examples/fermi.rs | 2 +- examples/rsx_compile_fail.rs | 3 +- examples/simple_list.rs | 28 +- examples/ssr.rs | 5 +- packages/core/src.old/mutations.rs | 409 ++++++++++++++++++++++++ packages/core/src/create.rs | 30 +- packages/core/src/factory.rs | 6 +- packages/core/src/nodes.rs | 3 + packages/desktop/src/controller.rs | 2 + packages/desktop/src/protocol.rs | 2 +- packages/interpreter/src/interpreter.js | 42 ++- packages/rsx/src/lib.rs | 9 +- packages/ssr/src/helpers.rs | 41 ++- 14 files changed, 539 insertions(+), 57 deletions(-) diff --git a/examples/custom_element.rs b/examples/custom_element.rs index 1c870e157..92c61ef86 100644 --- a/examples/custom_element.rs +++ b/examples/custom_element.rs @@ -16,15 +16,17 @@ fn main() { } fn app(cx: Scope) -> Element { - let nf = NodeFactory::new(&cx); + // let nf = NodeFactory::new(&cx); - let mut attrs = dioxus::core::exports::bumpalo::collections::Vec::new_in(nf.bump()); + // let mut attrs = dioxus::core::exports::bumpalo::collections::Vec::new_in(nf.bump()); - attrs.push(nf.attr("client-id", format_args!("abc123"), None, false)); + // attrs.push(nf.attr("client-id", format_args!("abc123"), None, false)); - attrs.push(nf.attr("name", format_args!("bob"), None, false)); + // attrs.push(nf.attr("name", format_args!("bob"), None, false)); - attrs.push(nf.attr("age", format_args!("47"), None, false)); + // attrs.push(nf.attr("age", format_args!("47"), None, false)); - Some(nf.raw_element("my-element", None, &[], attrs.into_bump_slice(), &[], None)) + // Some(nf.raw_element("my-element", None, &[], attrs.into_bump_slice(), &[], None)) + + todo!() } diff --git a/examples/fermi.rs b/examples/fermi.rs index 06bc38fc5..de5fb6d68 100644 --- a/examples/fermi.rs +++ b/examples/fermi.rs @@ -15,7 +15,7 @@ fn app(cx: Scope) -> Element { cx.render(rsx! { div { "hello {name}!" } Child {} - ChildWithRef{} + ChildWithRef {} }) } diff --git a/examples/rsx_compile_fail.rs b/examples/rsx_compile_fail.rs index 2859dc53e..75b740df9 100644 --- a/examples/rsx_compile_fail.rs +++ b/examples/rsx_compile_fail.rs @@ -2,12 +2,13 @@ //! It also proves that lifetimes work properly, especially when used with use_ref use dioxus::prelude::*; +use dioxus_ssr::config::SsrConfig; fn main() { let mut vdom = VirtualDom::new(example); vdom.rebuild(); - let out = dioxus_ssr::render_vdom_cfg(&vdom, |c| c.newline(true).indent(true)); + let out = dioxus_ssr::render_vdom_cfg(&vdom, SsrConfig::default().newline(true).indent(true)); println!("{}", out); } diff --git a/examples/simple_list.rs b/examples/simple_list.rs index 035244903..385e8525a 100644 --- a/examples/simple_list.rs +++ b/examples/simple_list.rs @@ -6,15 +6,23 @@ fn main() { fn app(cx: Scope) -> Element { cx.render(rsx!( - // Use Map directly to lazily pull elements - (0..10).map(|f| rsx! { "{f}" }), - // Collect into an intermediate collection if necessary, and call into_iter - ["a", "b", "c"] - .into_iter() - .map(|f| rsx! { "{f}" }) - .collect::>() - .into_iter(), - // Use optionals - Some(rsx! { "Some" }), + div { id: "123123123", + // Use Map directly to lazily pull elements + // (0..3).map(|f| rsx! { "{f}" }), + // Collect into an intermediate collection if necessary, and call into_iter + ["a", "b", "c", "x", "y", "z"] + .into_iter() + .map(|f| rsx! { "{f}" }) + .collect::>() + .into_iter(), + + ["d", "e", "f"] + .into_iter() + .map(|f| rsx! { "{f}" }) + .collect::>() + .into_iter(), + // Use optionals + // Some(rsx! { "Some" }), + } )) } diff --git a/examples/ssr.rs b/examples/ssr.rs index 7d3e5b615..e1e5a3ad8 100644 --- a/examples/ssr.rs +++ b/examples/ssr.rs @@ -5,6 +5,7 @@ use std::fmt::Write; use dioxus::prelude::*; +use dioxus_ssr::config::SsrConfig; fn main() { // We can render VirtualDoms @@ -25,14 +26,14 @@ fn main() { // We can configure the SSR rendering to add ids for rehydration println!( "{}", - dioxus_ssr::render_vdom_cfg(&vdom, |c| c.pre_render(true)) + dioxus_ssr::render_vdom_cfg(&vdom, SsrConfig::default().pre_render(true)) ); // We can even render as a writer let mut file = String::new(); let _ = file.write_fmt(format_args!( "{}", - dioxus_ssr::TextRenderer::from_vdom(&vdom, Default::default()) + dioxus_ssr::SsrRender::default().render_vdom(&vdom) )); println!("{}", file); } diff --git a/packages/core/src.old/mutations.rs b/packages/core/src.old/mutations.rs index 1bd88ad65..0ddbd398b 100644 --- a/packages/core/src.old/mutations.rs +++ b/packages/core/src.old/mutations.rs @@ -96,3 +96,412 @@ set_node(el, [1,2,3,4]) set_attribute("class", "foo") append_child(1) */ + + +//! Instructions returned by the VirtualDOM on how to modify the Real DOM. +//! +//! This module contains an internal API to generate these instructions. +//! +//! Beware that changing code in this module will break compatibility with +//! interpreters for these types of DomEdits. + +use crate::innerlude::*; +use std::{any::Any, fmt::Debug}; + +/// ## Mutations +/// +/// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also +/// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has +/// applied the edits. +/// +/// Mutations are the only link between the RealDOM and the VirtualDOM. +pub struct Mutations<'a> { + /// The list of edits that need to be applied for the RealDOM to match the VirtualDOM. + pub edits: Vec>, + + /// The list of Scopes that were diffed, created, and removed during the Diff process. + pub dirty_scopes: FxHashSet, + + /// The list of nodes to connect to the RealDOM. + pub refs: Vec>, +} + +impl Debug for Mutations<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Mutations") + .field("edits", &self.edits) + .field("noderefs", &self.refs) + .finish() + } +} + +/// A `DomEdit` represents a serialized form of the VirtualDom's trait-based API. This allows streaming edits across the +/// network or through FFI boundaries. +#[derive(Debug, PartialEq)] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type") +)] +pub enum DomEdit<'bump> { + /// Pop the topmost node from our stack and append them to the node + /// at the top of the stack. + AppendChildren { + /// The parent to append nodes to. + root: Option, + + /// The ids of the children to append. + children: Vec, + }, + + /// Replace a given (single) node with a handful of nodes currently on the stack. + ReplaceWith { + /// The ID of the node to be replaced. + root: Option, + + /// The ids of the nodes to replace the root with. + nodes: Vec, + }, + + /// Insert a number of nodes after a given node. + InsertAfter { + /// The ID of the node to insert after. + root: Option, + + /// The ids of the nodes to insert after the target node. + nodes: Vec, + }, + + /// Insert a number of nodes before a given node. + InsertBefore { + /// The ID of the node to insert before. + root: Option, + + /// The ids of the nodes to insert before the target node. + nodes: Vec, + }, + + /// Remove a particular node from the DOM + Remove { + /// The ID of the node to remove. + root: Option, + }, + + /// Create a new purely-text node + CreateTextNode { + /// The ID the new node should have. + root: Option, + + /// The textcontent of the node + text: &'bump str, + }, + + /// Create a new purely-element node + CreateElement { + /// The ID the new node should have. + root: Option, + + /// The tagname of the node + tag: &'bump str, + + /// The number of children nodes that will follow this message. + children: u32, + }, + + /// Create a new purely-comment node with a given namespace + CreateElementNs { + /// The ID the new node should have. + root: Option, + + /// The namespace of the node + tag: &'bump str, + + /// The namespace of the node (like `SVG`) + ns: &'static str, + + /// The number of children nodes that will follow this message. + children: u32, + }, + + /// Create a new placeholder node. + /// In most implementations, this will either be a hidden div or a comment node. + CreatePlaceholder { + /// The ID the new node should have. + root: Option, + }, + + /// Create a new Event Listener. + NewEventListener { + /// The name of the event to listen for. + event_name: &'static str, + + /// The ID of the node to attach the listener to. + scope: ScopeId, + + /// The ID of the node to attach the listener to. + root: Option, + }, + + /// Remove an existing Event Listener. + RemoveEventListener { + /// The ID of the node to remove. + root: Option, + + /// The name of the event to remove. + event: &'static str, + }, + + /// Set the textcontent of a node. + SetText { + /// The ID of the node to set the textcontent of. + root: Option, + + /// The textcontent of the node + text: &'bump str, + }, + + /// Set the value of a node's attribute. + SetAttribute { + /// The ID of the node to set the attribute of. + root: Option, + + /// The name of the attribute to set. + field: &'static str, + + /// The value of the attribute. + value: AttributeValue<'bump>, + + // value: &'bump str, + /// The (optional) namespace of the attribute. + /// For instance, "style" is in the "style" namespace. + ns: Option<&'bump str>, + }, + + /// Remove an attribute from a node. + RemoveAttribute { + /// The ID of the node to remove. + root: Option, + + /// The name of the attribute to remove. + name: &'static str, + + /// The namespace of the attribute. + ns: Option<&'bump str>, + }, + + /// Clones a node. + CloneNode { + /// The ID of the node to clone. + id: Option, + + /// The ID of the new node. + new_id: u64, + }, + + /// Clones the children of a node. (allows cloning fragments) + CloneNodeChildren { + /// The ID of the node to clone. + id: Option, + + /// The ID of the new node. + new_ids: Vec, + }, + + /// Navigates to the last node to the first child of the current node. + FirstChild {}, + + /// Navigates to the last node to the last child of the current node. + NextSibling {}, + + /// Navigates to the last node to the parent of the current node. + ParentNode {}, + + /// Stores the last node with a new id. + StoreWithId { + /// The ID of the node to store. + id: u64, + }, + + /// Manually set the last node. + SetLastNode { + /// The ID to set the last node to. + id: u64, + }, +} + +use rustc_hash::FxHashSet; +use DomEdit::*; + +#[allow(unused)] +impl<'a> Mutations<'a> { + pub(crate) fn new() -> Self { + Self { + edits: Vec::new(), + refs: Vec::new(), + dirty_scopes: Default::default(), + } + } + + pub(crate) fn replace_with(&mut self, root: Option, nodes: Vec) { + self.edits.push(ReplaceWith { nodes, root }); + } + + pub(crate) fn insert_after(&mut self, root: Option, nodes: Vec) { + self.edits.push(InsertAfter { nodes, root }); + } + + pub(crate) fn insert_before(&mut self, root: Option, nodes: Vec) { + self.edits.push(InsertBefore { nodes, root }); + } + + pub(crate) fn append_children(&mut self, root: Option, children: Vec) { + self.edits.push(AppendChildren { root, children }); + } + + // Remove Nodes from the dom + pub(crate) fn remove(&mut self, id: Option) { + self.edits.push(Remove { root: id }); + } + + // Create + pub(crate) fn create_text_node(&mut self, text: &'a str, id: Option) { + self.edits.push(CreateTextNode { text, root: id }); + } + + pub(crate) fn create_element( + &mut self, + tag: &'static str, + ns: Option<&'static str>, + id: Option, + children: u32, + ) { + match ns { + Some(ns) => self.edits.push(CreateElementNs { + root: id, + ns, + tag, + children, + }), + None => self.edits.push(CreateElement { + root: id, + tag, + children, + }), + } + } + + // placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom + pub(crate) fn create_placeholder(&mut self, id: Option) { + self.edits.push(CreatePlaceholder { root: id }); + } + + // events + pub(crate) fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId) { + let Listener { + event, + mounted_node, + .. + } = listener; + + let element_id = Some(mounted_node.get().unwrap().into()); + + self.edits.push(NewEventListener { + scope, + event_name: event, + root: element_id, + }); + } + + pub(crate) fn remove_event_listener(&mut self, event: &'static str, root: Option) { + self.edits.push(RemoveEventListener { event, root }); + } + + // modify + pub(crate) fn set_text(&mut self, text: &'a str, root: Option) { + self.edits.push(SetText { text, root }); + } + + pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: Option) { + let Attribute { + value, attribute, .. + } = attribute; + + self.edits.push(SetAttribute { + field: attribute.name, + value: value.clone(), + ns: attribute.namespace, + root, + }); + } + + pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: Option) { + let Attribute { attribute, .. } = attribute; + + self.edits.push(RemoveAttribute { + name: attribute.name, + ns: attribute.namespace, + root, + }); + } + + pub(crate) fn mark_dirty_scope(&mut self, scope: ScopeId) { + self.dirty_scopes.insert(scope); + } + + pub(crate) fn clone_node(&mut self, id: Option, new_id: u64) { + self.edits.push(CloneNode { id, new_id }); + } + + pub(crate) fn clone_node_children(&mut self, id: Option, new_ids: Vec) { + self.edits.push(CloneNodeChildren { id, new_ids }); + } + + pub(crate) fn first_child(&mut self) { + self.edits.push(FirstChild {}); + } + + pub(crate) fn next_sibling(&mut self) { + self.edits.push(NextSibling {}); + } + + pub(crate) fn parent_node(&mut self) { + self.edits.push(ParentNode {}); + } + + pub(crate) fn store_with_id(&mut self, id: u64) { + self.edits.push(StoreWithId { id }); + } + + pub(crate) fn set_last_node(&mut self, id: u64) { + self.edits.push(SetLastNode { id }); + } +} + +// refs are only assigned once +pub struct NodeRefMutation<'a> { + pub element: &'a mut Option>>, + pub element_id: ElementId, +} + +impl<'a> std::fmt::Debug for NodeRefMutation<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("NodeRefMutation") + .field("element_id", &self.element_id) + .finish() + } +} + +impl<'a> NodeRefMutation<'a> { + pub fn downcast_ref(&self) -> Option<&T> { + self.element + .as_ref() + .and_then(|f| f.get()) + .and_then(|f| f.downcast_ref::()) + } + pub fn downcast_mut(&mut self) -> Option<&mut T> { + self.element + .as_mut() + .and_then(|f| f.get_mut()) + .and_then(|f| f.downcast_mut::()) + } +} diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index ac936bb8f..fbea50c05 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -49,20 +49,19 @@ impl VirtualDom { let mut dynamic_nodes = template.template.node_paths.iter().enumerate().peekable(); let cur_scope = self.scope_stack.last().copied().unwrap(); + println!("creating template: {:#?}", template); + let mut on_stack = 0; for (root_idx, root) in template.template.roots.iter().enumerate() { - on_stack += match root { - TemplateNode::Element { .. } - | TemplateNode::Text(_) - | TemplateNode::DynamicText { .. } => { - mutations.push(LoadTemplate { - name: template.template.id, - index: root_idx, - }); - 1 - } + mutations.push(LoadTemplate { + name: template.template.id, + index: root_idx, + }); - TemplateNode::Dynamic(id) => { + on_stack += match root { + TemplateNode::Element { .. } | TemplateNode::Text(_) => 1, + + TemplateNode::DynamicText(id) | TemplateNode::Dynamic(id) => { self.create_dynamic_node(mutations, template, &template.dynamic_nodes[*id], *id) } }; @@ -268,9 +267,12 @@ impl VirtualDom { } } - DynamicNode::Fragment { nodes, .. } => nodes - .iter() - .fold(0, |acc, child| acc + self.create(mutations, child)), + DynamicNode::Fragment { nodes, .. } => { + // + nodes + .iter() + .fold(0, |acc, child| acc + self.create(mutations, child)) + } DynamicNode::Placeholder(_) => { let id = self.next_element(template); diff --git a/packages/core/src/factory.rs b/packages/core/src/factory.rs index 3344ac1f3..717f73a3c 100644 --- a/packages/core/src/factory.rs +++ b/packages/core/src/factory.rs @@ -160,8 +160,10 @@ impl<'a, 'b> IntoDynNode<'a> for VNode<'a> { impl<'a, 'b, T: IntoDynNode<'a>> IntoDynNode<'a> for Option { fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> { - // DynamicNode::Fragment { nodes: cx., inner: () } - todo!() + match self { + Some(val) => val.into_vnode(_cx), + None => DynamicNode::Placeholder(Default::default()), + } } } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 71be193a2..7f87fe776 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -9,6 +9,7 @@ use std::{ pub type TemplateId = &'static str; /// A reference to a template along with any context needed to hydrate it +#[derive(Debug)] pub struct VNode<'a> { // The ID assigned for the root of this template pub node_id: Cell, @@ -106,6 +107,7 @@ pub enum TemplateNode<'a> { DynamicText(usize), } +#[derive(Debug)] pub enum DynamicNode<'a> { Component { name: &'static str, @@ -137,6 +139,7 @@ pub enum TemplateAttribute<'a> { Dynamic(usize), } +#[derive(Debug)] pub struct Attribute<'a> { pub name: &'a str, pub value: AttributeValue<'a>, diff --git a/packages/desktop/src/controller.rs b/packages/desktop/src/controller.rs index d69ee5a1c..fb6d352fc 100644 --- a/packages/desktop/src/controller.rs +++ b/packages/desktop/src/controller.rs @@ -50,6 +50,8 @@ impl DesktopController { let edits = dom.rebuild(); + println!("got muts: {:#?}", edits); + { let mut queue = edit_queue.lock().unwrap(); queue.push(serde_json::to_string(&edits.template_mutations).unwrap()); diff --git a/packages/desktop/src/protocol.rs b/packages/desktop/src/protocol.rs index b8e5cc341..cc084b403 100644 --- a/packages/desktop/src/protocol.rs +++ b/packages/desktop/src/protocol.rs @@ -7,7 +7,7 @@ use wry::{ const MODULE_LOADER: &str = r#" "#; diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 34ac09ad9..b4540b20f 100644 --- a/packages/interpreter/src/interpreter.js +++ b/packages/interpreter/src/interpreter.js @@ -1,7 +1,9 @@ export function main() { let root = window.document.getElementById("main"); + console.log("loading!"); if (root != null) { window.interpreter = new Interpreter(root); + console.log("properly loaded!"); window.ipc.postMessage(serializeIpcMessage("initialize")); } } @@ -62,13 +64,15 @@ class ListenerMap { export class Interpreter { constructor(root) { + console.log("interpreter created", root); this.root = root; - this.stack = [root]; this.listeners = new ListenerMap(root); + this.nodes = [root]; + this.stack = [root]; this.handlers = {}; this.lastNodeWasText = false; - this.nodes = [root]; this.templates = {}; + console.log(this); } top() { return this.stack[this.stack.length - 1]; @@ -208,14 +212,11 @@ export class Interpreter { } } handleEdits(edits) { - console.log("handling edits", edits); + console.log("handling edits", edits, this.stack.length); for (let edit of edits) { this.handleEdit(edit); - console.log(this.stack); } - - console.log(this.stack); } AssignId(path, id) { this.nodes[id] = this.LoadChild(path); @@ -235,16 +236,20 @@ export class Interpreter { node.textContent = value; this.nodes[id] = node; } + ReplacePlaceholder(path, m) { + let els = this.stack.splice(this.stack.length - m); + let node = this.LoadChild(path); + node.replaceWith(...els); + } LoadTemplate(name, index) { console.log("loading template", name, index); let node = this.templates[name][index].cloneNode(true); this.stack.push(node); } SaveTemplate(name, m) { - this.templates[name] = this.stack.splice(-m); + this.templates[name] = this.stack.splice(this.stack.length - m); } handleEdit(edit) { - console.log("handling edit", edit); switch (edit.type) { case "AppendChildren": this.AppendChildren(edit.m); @@ -270,6 +275,9 @@ export class Interpreter { case "ReplaceWith": this.ReplaceWith(edit.id, edit.m); break; + case "ReplacePlaceholder": + this.ReplacePlaceholder(edit.path, edit.m); + break; case "InsertAfter": this.InsertAfter(edit.id, edit.n); break; @@ -288,6 +296,15 @@ export class Interpreter { case "CreateElementNs": this.CreateElementNs(edit.tag, edit.id, edit.ns); break; + case "SetText": + this.SetText(edit.id, edit.text); + break; + case "SetAttribute": + this.SetAttribute(edit.id, edit.name, edit.value, edit.ns); + break; + case "RemoveAttribute": + this.RemoveAttribute(edit.id, edit.name, edit.ns); + break; case "RemoveEventListener": this.RemoveEventListener(edit.id, edit.event_name); break; @@ -385,15 +402,6 @@ export class Interpreter { }; this.NewEventListener(edit.event_name, edit.id, handler, event_bubbles(edit.event_name)); - break; - case "SetText": - this.SetText(edit.id, edit.text); - break; - case "SetAttribute": - this.SetAttribute(edit.id, edit.field, edit.value, edit.ns); - break; - case "RemoveAttribute": - this.RemoveAttribute(edit.id, edit.name, edit.ns); break; } } diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index 6d417bdfd..09c6bd6c8 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -245,11 +245,16 @@ impl<'a> DynamicContext<'a> { quote! { ::dioxus::core::TemplateNode::Text(#text) } } - BodyNode::RawExpr(_) | BodyNode::Text(_) | BodyNode::Component(_) => { + BodyNode::Text(_) | BodyNode::RawExpr(_) | BodyNode::Component(_) => { let ct = self.dynamic_nodes.len(); self.dynamic_nodes.push(root); self.node_paths.push(self.current_path.clone()); - quote! { ::dioxus::core::TemplateNode::Dynamic(#ct) } + + if let BodyNode::Text(_) = root { + quote! { ::dioxus::core::TemplateNode::DynamicText(#ct) } + } else { + quote! { ::dioxus::core::TemplateNode::Dynamic(#ct) } + } } } } diff --git a/packages/ssr/src/helpers.rs b/packages/ssr/src/helpers.rs index b9e9ee653..ed8449ea0 100644 --- a/packages/ssr/src/helpers.rs +++ b/packages/ssr/src/helpers.rs @@ -1,6 +1,8 @@ use std::fmt::Write; -use dioxus_core::VirtualDom; +use dioxus_core::{LazyNodes, ScopeId, VirtualDom}; + +use crate::config::SsrConfig; pub fn pre_render(dom: &VirtualDom) -> String { todo!() @@ -9,3 +11,40 @@ pub fn pre_render(dom: &VirtualDom) -> String { pub fn pre_render_to(dom: &VirtualDom, write: impl Write) { todo!() } + +pub fn render_vdom(dom: &VirtualDom) -> String { + todo!() + // format!("{:}", TextRenderer::from_vdom(dom, SsrConfig::default())) +} + +pub fn pre_render_vdom(dom: &VirtualDom) -> String { + todo!() + // format!( + // "{:}", + // TextRenderer::from_vdom(dom, SsrConfig::default().pre_render(true)) + // ) +} + +pub fn render_vdom_cfg(dom: &VirtualDom, cfg: SsrConfig) -> String { + todo!() + // format!( + // "{:}", + // TextRenderer::from_vdom(dom, cfg(SsrConfig::default())) + // ) +} + +pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option { + todo!() + // Some(format!( + // "{:}", + // TextRenderer { + // cfg: SsrConfig::default(), + // root: vdom.get_scope(scope).unwrap().root_node(), + // vdom: Some(vdom), + // } + // )) +} + +pub fn render_lazy<'a, 'b>(f: LazyNodes<'a, 'b>) -> String { + todo!() +}