浏览代码

wip: prep to fix bug in create

Jonathan Kelley 2 年之前
父节点
当前提交
04bc635ac5

+ 8 - 6
examples/custom_element.rs

@@ -16,15 +16,17 @@ fn main() {
 }
 }
 
 
 fn app(cx: Scope) -> Element {
 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!()
 }
 }

+ 1 - 1
examples/fermi.rs

@@ -15,7 +15,7 @@ fn app(cx: Scope) -> Element {
     cx.render(rsx! {
     cx.render(rsx! {
         div { "hello {name}!" }
         div { "hello {name}!" }
         Child {}
         Child {}
-        ChildWithRef{}
+        ChildWithRef {}
     })
     })
 }
 }
 
 

+ 2 - 1
examples/rsx_compile_fail.rs

@@ -2,12 +2,13 @@
 //! It also proves that lifetimes work properly, especially when used with use_ref
 //! It also proves that lifetimes work properly, especially when used with use_ref
 
 
 use dioxus::prelude::*;
 use dioxus::prelude::*;
+use dioxus_ssr::config::SsrConfig;
 
 
 fn main() {
 fn main() {
     let mut vdom = VirtualDom::new(example);
     let mut vdom = VirtualDom::new(example);
     vdom.rebuild();
     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);
     println!("{}", out);
 }
 }
 
 

+ 18 - 10
examples/simple_list.rs

@@ -6,15 +6,23 @@ fn main() {
 
 
 fn app(cx: Scope) -> Element {
 fn app(cx: Scope) -> Element {
     cx.render(rsx!(
     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::<Vec<_>>()
-            .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::<Vec<_>>()
+                .into_iter(),
+
+            ["d", "e", "f"]
+                .into_iter()
+                .map(|f| rsx! { "{f}" })
+                .collect::<Vec<_>>()
+                .into_iter(),
+            // Use optionals
+            // Some(rsx! { "Some" }),
+        }
     ))
     ))
 }
 }

+ 3 - 2
examples/ssr.rs

@@ -5,6 +5,7 @@
 use std::fmt::Write;
 use std::fmt::Write;
 
 
 use dioxus::prelude::*;
 use dioxus::prelude::*;
+use dioxus_ssr::config::SsrConfig;
 
 
 fn main() {
 fn main() {
     // We can render VirtualDoms
     // We can render VirtualDoms
@@ -25,14 +26,14 @@ fn main() {
     // We can configure the SSR rendering to add ids for rehydration
     // We can configure the SSR rendering to add ids for rehydration
     println!(
     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
     // We can even render as a writer
     let mut file = String::new();
     let mut file = String::new();
     let _ = file.write_fmt(format_args!(
     let _ = file.write_fmt(format_args!(
         "{}",
         "{}",
-        dioxus_ssr::TextRenderer::from_vdom(&vdom, Default::default())
+        dioxus_ssr::SsrRender::default().render_vdom(&vdom)
     ));
     ));
     println!("{}", file);
     println!("{}", file);
 }
 }

+ 409 - 0
packages/core/src.old/mutations.rs

@@ -96,3 +96,412 @@ set_node(el, [1,2,3,4])
 set_attribute("class", "foo")
 set_attribute("class", "foo")
 append_child(1)
 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<DomEdit<'a>>,
+
+    /// The list of Scopes that were diffed, created, and removed during the Diff process.
+    pub dirty_scopes: FxHashSet<ScopeId>,
+
+    /// The list of nodes to connect to the RealDOM.
+    pub refs: Vec<NodeRefMutation<'a>>,
+}
+
+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<u64>,
+
+        /// The ids of the children to append.
+        children: Vec<u64>,
+    },
+
+    /// 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<u64>,
+
+        /// The ids of the nodes to replace the root with.
+        nodes: Vec<u64>,
+    },
+
+    /// Insert a number of nodes after a given node.
+    InsertAfter {
+        /// The ID of the node to insert after.
+        root: Option<u64>,
+
+        /// The ids of the nodes to insert after the target node.
+        nodes: Vec<u64>,
+    },
+
+    /// Insert a number of nodes before a given node.
+    InsertBefore {
+        /// The ID of the node to insert before.
+        root: Option<u64>,
+
+        /// The ids of the nodes to insert before the target node.
+        nodes: Vec<u64>,
+    },
+
+    /// Remove a particular node from the DOM
+    Remove {
+        /// The ID of the node to remove.
+        root: Option<u64>,
+    },
+
+    /// Create a new purely-text node
+    CreateTextNode {
+        /// The ID the new node should have.
+        root: Option<u64>,
+
+        /// The textcontent of the node
+        text: &'bump str,
+    },
+
+    /// Create a new purely-element node
+    CreateElement {
+        /// The ID the new node should have.
+        root: Option<u64>,
+
+        /// 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<u64>,
+
+        /// 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<u64>,
+    },
+
+    /// 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<u64>,
+    },
+
+    /// Remove an existing Event Listener.
+    RemoveEventListener {
+        /// The ID of the node to remove.
+        root: Option<u64>,
+
+        /// 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<u64>,
+
+        /// 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<u64>,
+
+        /// 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<u64>,
+
+        /// 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<u64>,
+
+        /// 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<u64>,
+
+        /// The ID of the new node.
+        new_ids: Vec<u64>,
+    },
+
+    /// 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<u64>, nodes: Vec<u64>) {
+        self.edits.push(ReplaceWith { nodes, root });
+    }
+
+    pub(crate) fn insert_after(&mut self, root: Option<u64>, nodes: Vec<u64>) {
+        self.edits.push(InsertAfter { nodes, root });
+    }
+
+    pub(crate) fn insert_before(&mut self, root: Option<u64>, nodes: Vec<u64>) {
+        self.edits.push(InsertBefore { nodes, root });
+    }
+
+    pub(crate) fn append_children(&mut self, root: Option<u64>, children: Vec<u64>) {
+        self.edits.push(AppendChildren { root, children });
+    }
+
+    // Remove Nodes from the dom
+    pub(crate) fn remove(&mut self, id: Option<u64>) {
+        self.edits.push(Remove { root: id });
+    }
+
+    // Create
+    pub(crate) fn create_text_node(&mut self, text: &'a str, id: Option<u64>) {
+        self.edits.push(CreateTextNode { text, root: id });
+    }
+
+    pub(crate) fn create_element(
+        &mut self,
+        tag: &'static str,
+        ns: Option<&'static str>,
+        id: Option<u64>,
+        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<u64>) {
+        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<u64>) {
+        self.edits.push(RemoveEventListener { event, root });
+    }
+
+    // modify
+    pub(crate) fn set_text(&mut self, text: &'a str, root: Option<u64>) {
+        self.edits.push(SetText { text, root });
+    }
+
+    pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: Option<u64>) {
+        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<u64>) {
+        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<u64>, new_id: u64) {
+        self.edits.push(CloneNode { id, new_id });
+    }
+
+    pub(crate) fn clone_node_children(&mut self, id: Option<u64>, new_ids: Vec<u64>) {
+        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<once_cell::sync::OnceCell<Box<dyn Any>>>,
+    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<T: 'static>(&self) -> Option<&T> {
+        self.element
+            .as_ref()
+            .and_then(|f| f.get())
+            .and_then(|f| f.downcast_ref::<T>())
+    }
+    pub fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
+        self.element
+            .as_mut()
+            .and_then(|f| f.get_mut())
+            .and_then(|f| f.downcast_mut::<T>())
+    }
+}

+ 15 - 13
packages/core/src/create.rs

@@ -49,20 +49,19 @@ impl VirtualDom {
         let mut dynamic_nodes = template.template.node_paths.iter().enumerate().peekable();
         let mut dynamic_nodes = template.template.node_paths.iter().enumerate().peekable();
         let cur_scope = self.scope_stack.last().copied().unwrap();
         let cur_scope = self.scope_stack.last().copied().unwrap();
 
 
+        println!("creating template: {:#?}", template);
+
         let mut on_stack = 0;
         let mut on_stack = 0;
         for (root_idx, root) in template.template.roots.iter().enumerate() {
         for (root_idx, root) in template.template.roots.iter().enumerate() {
+            mutations.push(LoadTemplate {
+                name: template.template.id,
+                index: root_idx,
+            });
+
             on_stack += match root {
             on_stack += match root {
-                TemplateNode::Element { .. }
-                | TemplateNode::Text(_)
-                | TemplateNode::DynamicText { .. } => {
-                    mutations.push(LoadTemplate {
-                        name: template.template.id,
-                        index: root_idx,
-                    });
-                    1
-                }
+                TemplateNode::Element { .. } | TemplateNode::Text(_) => 1,
 
 
-                TemplateNode::Dynamic(id) => {
+                TemplateNode::DynamicText(id) | TemplateNode::Dynamic(id) => {
                     self.create_dynamic_node(mutations, template, &template.dynamic_nodes[*id], *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(_) => {
             DynamicNode::Placeholder(_) => {
                 let id = self.next_element(template);
                 let id = self.next_element(template);

+ 4 - 2
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<T> {
 impl<'a, 'b, T: IntoDynNode<'a>> IntoDynNode<'a> for Option<T> {
     fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
     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()),
+        }
     }
     }
 }
 }
 
 

+ 3 - 0
packages/core/src/nodes.rs

@@ -9,6 +9,7 @@ use std::{
 pub type TemplateId = &'static str;
 pub type TemplateId = &'static str;
 
 
 /// A reference to a template along with any context needed to hydrate it
 /// A reference to a template along with any context needed to hydrate it
+#[derive(Debug)]
 pub struct VNode<'a> {
 pub struct VNode<'a> {
     // The ID assigned for the root of this template
     // The ID assigned for the root of this template
     pub node_id: Cell<ElementId>,
     pub node_id: Cell<ElementId>,
@@ -106,6 +107,7 @@ pub enum TemplateNode<'a> {
     DynamicText(usize),
     DynamicText(usize),
 }
 }
 
 
+#[derive(Debug)]
 pub enum DynamicNode<'a> {
 pub enum DynamicNode<'a> {
     Component {
     Component {
         name: &'static str,
         name: &'static str,
@@ -137,6 +139,7 @@ pub enum TemplateAttribute<'a> {
     Dynamic(usize),
     Dynamic(usize),
 }
 }
 
 
+#[derive(Debug)]
 pub struct Attribute<'a> {
 pub struct Attribute<'a> {
     pub name: &'a str,
     pub name: &'a str,
     pub value: AttributeValue<'a>,
     pub value: AttributeValue<'a>,

+ 2 - 0
packages/desktop/src/controller.rs

@@ -50,6 +50,8 @@ impl DesktopController {
 
 
                 let edits = dom.rebuild();
                 let edits = dom.rebuild();
 
 
+                println!("got muts: {:#?}", edits);
+
                 {
                 {
                     let mut queue = edit_queue.lock().unwrap();
                     let mut queue = edit_queue.lock().unwrap();
                     queue.push(serde_json::to_string(&edits.template_mutations).unwrap());
                     queue.push(serde_json::to_string(&edits.template_mutations).unwrap());

+ 1 - 1
packages/desktop/src/protocol.rs

@@ -7,7 +7,7 @@ use wry::{
 const MODULE_LOADER: &str = r#"
 const MODULE_LOADER: &str = r#"
 <script>
 <script>
     import("./index.js").then(function (module) {
     import("./index.js").then(function (module) {
-    module.main();
+        module.main();
     });
     });
 </script>
 </script>
 "#;
 "#;

+ 25 - 17
packages/interpreter/src/interpreter.js

@@ -1,7 +1,9 @@
 export function main() {
 export function main() {
   let root = window.document.getElementById("main");
   let root = window.document.getElementById("main");
+  console.log("loading!");
   if (root != null) {
   if (root != null) {
     window.interpreter = new Interpreter(root);
     window.interpreter = new Interpreter(root);
+    console.log("properly loaded!");
     window.ipc.postMessage(serializeIpcMessage("initialize"));
     window.ipc.postMessage(serializeIpcMessage("initialize"));
   }
   }
 }
 }
@@ -62,13 +64,15 @@ class ListenerMap {
 
 
 export class Interpreter {
 export class Interpreter {
   constructor(root) {
   constructor(root) {
+    console.log("interpreter created", root);
     this.root = root;
     this.root = root;
-    this.stack = [root];
     this.listeners = new ListenerMap(root);
     this.listeners = new ListenerMap(root);
+    this.nodes = [root];
+    this.stack = [root];
     this.handlers = {};
     this.handlers = {};
     this.lastNodeWasText = false;
     this.lastNodeWasText = false;
-    this.nodes = [root];
     this.templates = {};
     this.templates = {};
+    console.log(this);
   }
   }
   top() {
   top() {
     return this.stack[this.stack.length - 1];
     return this.stack[this.stack.length - 1];
@@ -208,14 +212,11 @@ export class Interpreter {
     }
     }
   }
   }
   handleEdits(edits) {
   handleEdits(edits) {
-    console.log("handling edits", edits);
+    console.log("handling edits", edits, this.stack.length);
 
 
     for (let edit of edits) {
     for (let edit of edits) {
       this.handleEdit(edit);
       this.handleEdit(edit);
-      console.log(this.stack);
     }
     }
-
-    console.log(this.stack);
   }
   }
   AssignId(path, id) {
   AssignId(path, id) {
     this.nodes[id] = this.LoadChild(path);
     this.nodes[id] = this.LoadChild(path);
@@ -235,16 +236,20 @@ export class Interpreter {
     node.textContent = value;
     node.textContent = value;
     this.nodes[id] = node;
     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) {
   LoadTemplate(name, index) {
     console.log("loading template", name, index);
     console.log("loading template", name, index);
     let node = this.templates[name][index].cloneNode(true);
     let node = this.templates[name][index].cloneNode(true);
     this.stack.push(node);
     this.stack.push(node);
   }
   }
   SaveTemplate(name, m) {
   SaveTemplate(name, m) {
-    this.templates[name] = this.stack.splice(-m);
+    this.templates[name] = this.stack.splice(this.stack.length - m);
   }
   }
   handleEdit(edit) {
   handleEdit(edit) {
-    console.log("handling edit", edit);
     switch (edit.type) {
     switch (edit.type) {
       case "AppendChildren":
       case "AppendChildren":
         this.AppendChildren(edit.m);
         this.AppendChildren(edit.m);
@@ -270,6 +275,9 @@ export class Interpreter {
       case "ReplaceWith":
       case "ReplaceWith":
         this.ReplaceWith(edit.id, edit.m);
         this.ReplaceWith(edit.id, edit.m);
         break;
         break;
+      case "ReplacePlaceholder":
+        this.ReplacePlaceholder(edit.path, edit.m);
+        break;
       case "InsertAfter":
       case "InsertAfter":
         this.InsertAfter(edit.id, edit.n);
         this.InsertAfter(edit.id, edit.n);
         break;
         break;
@@ -288,6 +296,15 @@ export class Interpreter {
       case "CreateElementNs":
       case "CreateElementNs":
         this.CreateElementNs(edit.tag, edit.id, edit.ns);
         this.CreateElementNs(edit.tag, edit.id, edit.ns);
         break;
         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":
       case "RemoveEventListener":
         this.RemoveEventListener(edit.id, edit.event_name);
         this.RemoveEventListener(edit.id, edit.event_name);
         break;
         break;
@@ -385,15 +402,6 @@ export class Interpreter {
         };
         };
         this.NewEventListener(edit.event_name, edit.id, handler, event_bubbles(edit.event_name));
         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;
         break;
     }
     }
   }
   }

+ 7 - 2
packages/rsx/src/lib.rs

@@ -245,11 +245,16 @@ impl<'a> DynamicContext<'a> {
                 quote! { ::dioxus::core::TemplateNode::Text(#text) }
                 quote! { ::dioxus::core::TemplateNode::Text(#text) }
             }
             }
 
 
-            BodyNode::RawExpr(_) | BodyNode::Text(_) | BodyNode::Component(_) => {
+            BodyNode::Text(_) | BodyNode::RawExpr(_) | BodyNode::Component(_) => {
                 let ct = self.dynamic_nodes.len();
                 let ct = self.dynamic_nodes.len();
                 self.dynamic_nodes.push(root);
                 self.dynamic_nodes.push(root);
                 self.node_paths.push(self.current_path.clone());
                 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) }
+                }
             }
             }
         }
         }
     }
     }

+ 40 - 1
packages/ssr/src/helpers.rs

@@ -1,6 +1,8 @@
 use std::fmt::Write;
 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 {
 pub fn pre_render(dom: &VirtualDom) -> String {
     todo!()
     todo!()
@@ -9,3 +11,40 @@ pub fn pre_render(dom: &VirtualDom) -> String {
 pub fn pre_render_to(dom: &VirtualDom, write: impl Write) {
 pub fn pre_render_to(dom: &VirtualDom, write: impl Write) {
     todo!()
     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<String> {
+    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!()
+}