Selaa lähdekoodia

feat: rip out mutations for templates

Jonathan Kelley 2 vuotta sitten
vanhempi
commit
85657d3906

+ 5 - 79
packages/core/src/create.rs

@@ -7,7 +7,7 @@ use crate::mutations::Mutation::*;
 use crate::nodes::VNode;
 use crate::nodes::{DynamicNode, TemplateNode};
 use crate::virtual_dom::VirtualDom;
-use crate::{AttributeValue, ElementId, ScopeId, SuspenseContext, TemplateAttribute};
+use crate::{AttributeValue, ElementId, ScopeId, SuspenseContext};
 
 impl<'b> VirtualDom {
     /// Create a new template [`VNode`] and write it to the [`Mutations`] buffer.
@@ -224,81 +224,7 @@ impl<'b> VirtualDom {
             return;
         }
 
-        for node in template.template.roots {
-            self.create_static_node(node);
-        }
-
-        self.mutations.template_edits.push(SaveTemplate {
-            name: template.template.id,
-            m: template.template.roots.len(),
-        });
-    }
-
-    // todo: we shouldn't have any of instructions for building templates - renderers should be able to work with the
-    // template type directly, right?
-    pub(crate) fn create_static_node(&mut self, node: &'b TemplateNode<'static>) {
-        match *node {
-            // Todo: create the children's template
-            TemplateNode::Dynamic(_) => self
-                .mutations
-                .template_edits
-                .push(CreateStaticPlaceholder {}),
-            TemplateNode::Text(value) => self
-                .mutations
-                .template_edits
-                .push(CreateStaticText { value }),
-
-            TemplateNode::DynamicText { .. } => {
-                self.mutations.template_edits.push(CreateTextPlaceholder)
-            }
-
-            TemplateNode::Element {
-                attrs,
-                children,
-                namespace,
-                tag,
-                ..
-            } => {
-                match namespace {
-                    Some(namespace) => self.mutations.template_edits.push(CreateElementNamespace {
-                        name: tag,
-                        namespace,
-                    }),
-                    None => self
-                        .mutations
-                        .template_edits
-                        .push(CreateElement { name: tag }),
-                }
-
-                self.mutations
-                    .template_edits
-                    .extend(attrs.iter().filter_map(|attr| match attr {
-                        TemplateAttribute::Static {
-                            name,
-                            value,
-                            namespace,
-                            ..
-                        } => Some(SetStaticAttribute {
-                            name,
-                            value,
-                            ns: *namespace,
-                        }),
-                        _ => None,
-                    }));
-
-                if children.is_empty() {
-                    return;
-                }
-
-                children
-                    .iter()
-                    .for_each(|child| self.create_static_node(child));
-
-                self.mutations
-                    .template_edits
-                    .push(AppendChildren { m: children.len() })
-            }
-        }
+        self.mutations.templates.push(template.template);
     }
 
     pub(crate) fn create_dynamic_node(
@@ -404,7 +330,7 @@ impl<'b> VirtualDom {
     ) -> usize {
         // Keep track of how many mutations are in the buffer in case we need to split them out if a suspense boundary
         // is encountered
-        let mutations_to_this_point = self.mutations.dom_edits.len();
+        let mutations_to_this_point = self.mutations.edits.len();
 
         // Create the component's root element
         let created = self.create_scope(scope, new);
@@ -430,10 +356,10 @@ impl<'b> VirtualDom {
         // Note that we break off dynamic mutations only - since static mutations aren't rendered immediately
         let split_off = unsafe {
             std::mem::transmute::<Vec<Mutation>, Vec<Mutation>>(
-                self.mutations.dom_edits.split_off(mutations_to_this_point),
+                self.mutations.edits.split_off(mutations_to_this_point),
             )
         };
-        boundary.mutations.borrow_mut().dom_edits.extend(split_off);
+        boundary.mutations.borrow_mut().edits.extend(split_off);
         boundary.created_on_stack.set(created);
         boundary
             .waiting_on

+ 18 - 57
packages/core/src/mutations.rs

@@ -1,6 +1,6 @@
 use fxhash::FxHashSet;
 
-use crate::{arena::ElementId, ScopeId};
+use crate::{arena::ElementId, ScopeId, Template};
 
 /// A container for all the relevant steps to modify the Real DOM
 ///
@@ -18,11 +18,13 @@ pub struct Mutations<'a> {
     /// The list of Scopes that were diffed, created, and removed during the Diff process.
     pub dirty_scopes: FxHashSet<ScopeId>,
 
-    /// Any mutations required to build the templates using [`Mutations`]
-    pub template_edits: Vec<Mutation<'a>>,
+    /// Any templates encountered while diffing the DOM.
+    ///
+    /// These must be loaded into a cache before applying the edits
+    pub templates: Vec<Template<'a>>,
 
     /// Any mutations required to patch the renderer to match the layout of the VirtualDom
-    pub dom_edits: Vec<Mutation<'a>>,
+    pub edits: Vec<Mutation<'a>>,
 }
 
 impl<'a> Mutations<'a> {
@@ -30,21 +32,22 @@ impl<'a> Mutations<'a> {
     ///
     /// Used really only for testing
     pub fn santize(mut self) -> Self {
-        self.template_edits
-            .iter_mut()
-            .chain(self.dom_edits.iter_mut())
-            .for_each(|edit| match edit {
-                Mutation::LoadTemplate { name, .. } => *name = "template",
-                Mutation::SaveTemplate { name, .. } => *name = "template",
-                _ => {}
-            });
-
-        self
+        todo!()
+        // self.templates
+        //     .iter_mut()
+        //     .chain(self.dom_edits.iter_mut())
+        //     .for_each(|edit| match edit {
+        //         Mutation::LoadTemplate { name, .. } => *name = "template",
+        //         Mutation::SaveTemplate { name, .. } => *name = "template",
+        //         _ => {}
+        //     });
+
+        // self
     }
 
     /// Push a new mutation into the dom_edits list
     pub(crate) fn push(&mut self, mutation: Mutation<'static>) {
-        self.dom_edits.push(mutation)
+        self.edits.push(mutation)
     }
 }
 
@@ -59,34 +62,14 @@ each subtree has its own numbering scheme
 )]
 #[derive(Debug, PartialEq, Eq)]
 pub enum Mutation<'a> {
-    /// Pop the topmost node from our stack and append them to the node
-    /// at the top of the stack.
-    AppendChildren {
-        /// How many nodes should be popped from the stack.
-        /// The node remaining on the stack will be the target for the append.
-        m: usize,
-    },
-
     AssignId {
         path: &'static [u8],
         id: ElementId,
     },
 
-    CreateElement {
-        name: &'a str,
-    },
-    CreateElementNamespace {
-        name: &'a str,
-        namespace: &'a str,
-    },
     CreatePlaceholder {
         id: ElementId,
     },
-    CreateStaticPlaceholder,
-    CreateTextPlaceholder,
-    CreateStaticText {
-        value: &'a str,
-    },
     CreateTextNode {
         value: &'a str,
         id: ElementId,
@@ -131,15 +114,6 @@ pub enum Mutation<'a> {
         m: usize,
     },
 
-    /// Save the top m nodes as a placeholder
-    SaveTemplate {
-        /// The name of the template that we're saving
-        name: &'static str,
-
-        /// How many nodes are being saved into this template
-        m: usize,
-    },
-
     /// Set the value of a node's attribute.
     SetAttribute {
         /// The name of the attribute to set.
@@ -155,19 +129,6 @@ pub enum Mutation<'a> {
         ns: Option<&'a str>,
     },
 
-    /// Set the value of a node's attribute.
-    SetStaticAttribute {
-        /// The name of the attribute to set.
-        name: &'a str,
-
-        /// The value of the attribute.
-        value: &'a str,
-
-        /// The (optional) namespace of the attribute.
-        /// For instance, "style" is in the "style" namespace.
-        ns: Option<&'a str>,
-    },
-
     /// Set the value of a node's attribute.
     SetBoolAttribute {
         /// The name of the attribute to set.

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

@@ -62,6 +62,7 @@ impl<'a> VNode<'a> {
     }
 }
 
+#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
 #[derive(Debug, Clone, Copy)]
 pub struct Template<'a> {
     pub id: &'a str,
@@ -71,6 +72,7 @@ pub struct Template<'a> {
 }
 
 #[derive(Debug, Clone, Copy)]
+#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
 pub enum TemplateNode<'a> {
     Element {
         tag: &'a str,
@@ -126,11 +128,12 @@ pub struct VText<'a> {
 }
 
 #[derive(Debug)]
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
 pub enum TemplateAttribute<'a> {
     Static {
-        name: &'static str,
+        name: &'a str,
         value: &'a str,
-        namespace: Option<&'static str>,
+        namespace: Option<&'a str>,
         volatile: bool,
     },
     Dynamic(usize),

+ 8 - 8
packages/core/src/virtual_dom.rs

@@ -490,7 +490,7 @@ impl VirtualDom {
             // Rebuilding implies we append the created elements to the root
             RenderReturn::Sync(Ok(node)) => {
                 let m = self.create_scope(ScopeId(0), node);
-                self.mutations.push(Mutation::AppendChildren { m });
+                // self.mutations.push(Mutation::AppendChildren { m });
             }
             // If an error occurs, we should try to render the default error component and context where the error occured
             RenderReturn::Sync(Err(e)) => panic!("Cannot catch errors during rebuild {:?}", e),
@@ -534,12 +534,12 @@ impl VirtualDom {
                 let context = scope.has_context::<SuspenseContext>().unwrap();
 
                 self.mutations
-                    .template_edits
-                    .extend(context.mutations.borrow_mut().template_edits.drain(..));
+                    .templates
+                    .extend(context.mutations.borrow_mut().templates.drain(..));
 
                 self.mutations
-                    .dom_edits
-                    .extend(context.mutations.borrow_mut().dom_edits.drain(..));
+                    .edits
+                    .extend(context.mutations.borrow_mut().edits.drain(..));
 
                 // TODO: count how many nodes are on the stack?
                 self.mutations.push(Mutation::ReplaceWith {
@@ -559,7 +559,7 @@ impl VirtualDom {
                 }
 
                 // Save the current mutations length so we can split them into boundary
-                let mutations_to_this_point = self.mutations.dom_edits.len();
+                let mutations_to_this_point = self.mutations.edits.len();
 
                 // Run the scope and get the mutations
                 self.run_scope(dirty.id);
@@ -578,8 +578,8 @@ impl VirtualDom {
                     boundary_mut
                         .mutations
                         .borrow_mut()
-                        .dom_edits
-                        .extend(self.mutations.dom_edits.split_off(mutations_to_this_point));
+                        .edits
+                        .extend(self.mutations.edits.split_off(mutations_to_this_point));
 
                     // Attach suspended leaves
                     boundary

+ 5 - 5
packages/core/tests/attr_cleanup.rs

@@ -23,7 +23,7 @@ fn attrs_cycle() {
     });
 
     assert_eq!(
-        dom.rebuild().santize().dom_edits,
+        dom.rebuild().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
             AppendChildren { m: 1 },
@@ -32,7 +32,7 @@ fn attrs_cycle() {
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
             AssignId { path: &[0,], id: ElementId(3,) },
@@ -44,7 +44,7 @@ fn attrs_cycle() {
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(3) },
             ReplaceWith { id: ElementId(2), m: 1 }
@@ -53,7 +53,7 @@ fn attrs_cycle() {
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(2) },
             AssignId { path: &[0], id: ElementId(1) },
@@ -66,7 +66,7 @@ fn attrs_cycle() {
     // we take the node taken by attributes since we reused it
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1) },
             ReplaceWith { id: ElementId(2), m: 1 }

+ 1 - 1
packages/core/tests/boolattrs.rs

@@ -5,7 +5,7 @@ use dioxus::prelude::*;
 fn bool_test() {
     let mut app = VirtualDom::new(|cx| cx.render(rsx!(div { hidden: false })));
     assert_eq!(
-        app.rebuild().santize().dom_edits,
+        app.rebuild().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1) },
             SetBoolAttribute { name: "hidden", value: false, id: ElementId(1,) },

+ 1 - 1
packages/core/tests/borrowedstate.rs

@@ -8,7 +8,7 @@ fn test_borrowed_state() {
     let mut dom = VirtualDom::new(Parent);
 
     assert_eq!(
-        dom.rebuild().santize().dom_edits,
+        dom.rebuild().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
             LoadTemplate { name: "template", index: 0, id: ElementId(2,) },

+ 3 - 3
packages/core/tests/context_api.rs

@@ -20,7 +20,7 @@ fn state_shares() {
 
     let mut dom = VirtualDom::new(app);
     assert_eq!(
-        dom.rebuild().santize().dom_edits,
+        dom.rebuild().santize().edits,
         [
             CreateTextNode { value: "Value is 0", id: ElementId(1,) },
             AppendChildren { m: 1 },
@@ -37,7 +37,7 @@ fn state_shares() {
 
     dom.mark_dirty(ScopeId(2));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [SetText { value: "Value is 2", id: ElementId(1,) },]
     );
 
@@ -45,7 +45,7 @@ fn state_shares() {
     dom.mark_dirty(ScopeId(2));
     let edits = dom.render_immediate();
     assert_eq!(
-        edits.santize().dom_edits,
+        edits.santize().edits,
         [SetText { value: "Value is 3", id: ElementId(1,) },]
     );
 }

+ 8 - 8
packages/core/tests/create_dom.rs

@@ -23,7 +23,7 @@ fn test_original_diff() {
 
     let edits = dom.rebuild().santize();
     assert_eq!(
-        edits.template_edits,
+        edits.templates,
         [
             // create template
             CreateElement { name: "div" },
@@ -36,7 +36,7 @@ fn test_original_diff() {
     );
 
     assert_eq!(
-        edits.dom_edits,
+        edits.edits,
         [
             // add to root
             LoadTemplate { name: "template", index: 0, id: ElementId(1) },
@@ -67,7 +67,7 @@ fn create() {
 
     let edits = dom.rebuild().santize();
     assert_eq!(
-        edits.template_edits,
+        edits.templates,
         [
             // create template
             CreateElement { name: "div" },
@@ -99,7 +99,7 @@ fn create_list() {
 
     let edits = dom.rebuild().santize();
     assert_eq!(
-        edits.template_edits,
+        edits.templates,
         [
             // create template
             CreateElement { name: "div" },
@@ -123,7 +123,7 @@ fn create_simple() {
 
     let edits = dom.rebuild().santize();
     assert_eq!(
-        edits.template_edits,
+        edits.templates,
         [
             // create template
             CreateElement { name: "div" },
@@ -160,7 +160,7 @@ fn create_components() {
 
     let edits = dom.rebuild().santize();
     assert_eq!(
-        edits.template_edits,
+        edits.templates,
         [
             // The "child" template
             CreateElement { name: "h1" },
@@ -196,7 +196,7 @@ fn anchors() {
     // note that the template under "false" doesn't show up since it's not loaded
     let edits = dom.rebuild().santize();
     assert_eq!(
-        edits.template_edits,
+        edits.templates,
         [
             // create each template
             CreateElement { name: "div" },
@@ -207,7 +207,7 @@ fn anchors() {
     );
 
     assert_eq!(
-        edits.dom_edits,
+        edits.edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1) },
             CreatePlaceholder { id: ElementId(2) },

+ 1 - 1
packages/core/tests/create_element.rs

@@ -12,7 +12,7 @@ fn multiroot() {
     });
 
     assert_eq!(
-        dom.rebuild().santize().template_edits,
+        dom.rebuild().santize().templates,
         [
             CreateElement { name: "div" },
             CreateStaticText { value: "Hello a" },

+ 5 - 5
packages/core/tests/create_fragments.rs

@@ -14,7 +14,7 @@ fn empty_fragment_creates_nothing() {
     let edits = vdom.rebuild();
 
     assert_eq!(
-        edits.dom_edits,
+        edits.edits,
         [
             CreatePlaceholder { id: ElementId(1) },
             AppendChildren { m: 1 }
@@ -32,7 +32,7 @@ fn root_fragments_work() {
     });
 
     assert_eq!(
-        vdom.rebuild().dom_edits.last().unwrap(),
+        vdom.rebuild().edits.last().unwrap(),
         &AppendChildren { m: 2 }
     );
 }
@@ -59,7 +59,7 @@ fn fragments_nested() {
     });
 
     assert_eq!(
-        vdom.rebuild().dom_edits.last().unwrap(),
+        vdom.rebuild().edits.last().unwrap(),
         &AppendChildren { m: 8 }
     );
 }
@@ -84,7 +84,7 @@ fn fragments_across_components() {
     }
 
     assert_eq!(
-        VirtualDom::new(app).rebuild().dom_edits.last().unwrap(),
+        VirtualDom::new(app).rebuild().edits.last().unwrap(),
         &AppendChildren { m: 8 }
     );
 }
@@ -98,7 +98,7 @@ fn list_fragments() {
         ))
     }
     assert_eq!(
-        VirtualDom::new(app).rebuild().dom_edits.last().unwrap(),
+        VirtualDom::new(app).rebuild().edits.last().unwrap(),
         &AppendChildren { m: 7 }
     );
 }

+ 2 - 2
packages/core/tests/create_lists.rs

@@ -28,7 +28,7 @@ fn list_renders() {
     let edits = dom.rebuild().santize();
 
     assert_eq!(
-        edits.template_edits,
+        edits.templates,
         [
             // Create the outer div
             CreateElement { name: "div" },
@@ -51,7 +51,7 @@ fn list_renders() {
     );
 
     assert_eq!(
-        edits.dom_edits,
+        edits.edits,
         [
             // Load the outer div
             LoadTemplate { name: "template", index: 0, id: ElementId(1) },

+ 4 - 4
packages/core/tests/create_passthru.rs

@@ -26,7 +26,7 @@ fn nested_passthru_creates() {
     let edits = dom.rebuild().santize();
 
     assert_eq!(
-        edits.dom_edits,
+        edits.edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1) },
             AppendChildren { m: 1 },
@@ -64,7 +64,7 @@ fn nested_passthru_creates_add() {
     let mut dom = VirtualDom::new(app);
 
     assert_eq!(
-        dom.rebuild().santize().dom_edits,
+        dom.rebuild().santize().edits,
         [
             // load 1
             LoadTemplate { name: "template", index: 0, id: ElementId(1) },
@@ -92,11 +92,11 @@ fn dynamic_node_as_root() {
     let edits = dom.rebuild().santize();
 
     // Since the roots were all dynamic, they should not cause any template muations
-    assert_eq!(edits.template_edits, []);
+    assert_eq!(edits.templates, []);
 
     // The root node is text, so we just create it on the spot
     assert_eq!(
-        edits.dom_edits,
+        edits.edits,
         [
             CreateTextNode { value: "123", id: ElementId(1) },
             CreateTextNode { value: "456", id: ElementId(2) },

+ 4 - 4
packages/core/tests/cycle.rs

@@ -14,7 +14,7 @@ fn cycling_elements() {
 
     let edits = dom.rebuild().santize();
     assert_eq!(
-        edits.dom_edits,
+        edits.edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
             AppendChildren { m: 1 },
@@ -23,7 +23,7 @@ fn cycling_elements() {
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
             ReplaceWith { id: ElementId(1,), m: 1 },
@@ -33,7 +33,7 @@ fn cycling_elements() {
     // notice that the IDs cycle back to ElementId(1), preserving a minimal memory footprint
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
             ReplaceWith { id: ElementId(2,), m: 1 },
@@ -42,7 +42,7 @@ fn cycling_elements() {
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
             ReplaceWith { id: ElementId(1,), m: 1 },

+ 4 - 4
packages/core/tests/diff_component.rs

@@ -61,7 +61,7 @@ fn component_swap() {
     let mut dom = VirtualDom::new(app);
     let edits = dom.rebuild().santize();
     assert_eq!(
-        edits.dom_edits,
+        edits.edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1) },
             LoadTemplate { name: "template", index: 0, id: ElementId(2) },
@@ -75,7 +75,7 @@ fn component_swap() {
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(6) },
             ReplaceWith { id: ElementId(5), m: 1 }
@@ -84,7 +84,7 @@ fn component_swap() {
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(5) },
             ReplaceWith { id: ElementId(6), m: 1 }
@@ -93,7 +93,7 @@ fn component_swap() {
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(6) },
             ReplaceWith { id: ElementId(5), m: 1 }

+ 7 - 7
packages/core/tests/diff_element.rs

@@ -14,19 +14,19 @@ fn text_diff() {
 
     vdom.mark_dirty(ScopeId(0));
     assert_eq!(
-        vdom.render_immediate().dom_edits,
+        vdom.render_immediate().edits,
         [SetText { value: "hello 1", id: ElementId(2) }]
     );
 
     vdom.mark_dirty(ScopeId(0));
     assert_eq!(
-        vdom.render_immediate().dom_edits,
+        vdom.render_immediate().edits,
         [SetText { value: "hello 2", id: ElementId(2) }]
     );
 
     vdom.mark_dirty(ScopeId(0));
     assert_eq!(
-        vdom.render_immediate().dom_edits,
+        vdom.render_immediate().edits,
         [SetText { value: "hello 3", id: ElementId(2) }]
     );
 }
@@ -48,7 +48,7 @@ fn element_swap() {
 
     vdom.mark_dirty(ScopeId(0));
     assert_eq!(
-        vdom.render_immediate().santize().dom_edits,
+        vdom.render_immediate().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
             ReplaceWith { id: ElementId(1,), m: 1 },
@@ -57,7 +57,7 @@ fn element_swap() {
 
     vdom.mark_dirty(ScopeId(0));
     assert_eq!(
-        vdom.render_immediate().santize().dom_edits,
+        vdom.render_immediate().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
             ReplaceWith { id: ElementId(2,), m: 1 },
@@ -66,7 +66,7 @@ fn element_swap() {
 
     vdom.mark_dirty(ScopeId(0));
     assert_eq!(
-        vdom.render_immediate().santize().dom_edits,
+        vdom.render_immediate().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
             ReplaceWith { id: ElementId(1,), m: 1 },
@@ -75,7 +75,7 @@ fn element_swap() {
 
     vdom.mark_dirty(ScopeId(0));
     assert_eq!(
-        vdom.render_immediate().santize().dom_edits,
+        vdom.render_immediate().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
             ReplaceWith { id: ElementId(2,), m: 1 },

+ 13 - 13
packages/core/tests/diff_keyed_list.rs

@@ -21,7 +21,7 @@ fn keyed_diffing_out_of_order() {
     });
 
     assert_eq!(
-        dom.rebuild().santize().dom_edits,
+        dom.rebuild().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
             LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
@@ -39,7 +39,7 @@ fn keyed_diffing_out_of_order() {
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().dom_edits,
+        dom.render_immediate().edits,
         [
             PushRoot { id: ElementId(7,) },
             InsertBefore { id: ElementId(5,), m: 1 },
@@ -64,7 +64,7 @@ fn keyed_diffing_out_of_order_adds() {
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().dom_edits,
+        dom.render_immediate().edits,
         [
             PushRoot { id: ElementId(5,) },
             PushRoot { id: ElementId(4,) },
@@ -90,7 +90,7 @@ fn keyed_diffing_out_of_order_adds_3() {
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().dom_edits,
+        dom.render_immediate().edits,
         [
             PushRoot { id: ElementId(5,) },
             PushRoot { id: ElementId(4,) },
@@ -116,7 +116,7 @@ fn keyed_diffing_out_of_order_adds_4() {
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().dom_edits,
+        dom.render_immediate().edits,
         [
             PushRoot { id: ElementId(5,) },
             PushRoot { id: ElementId(4,) },
@@ -142,7 +142,7 @@ fn keyed_diffing_out_of_order_adds_5() {
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().dom_edits,
+        dom.render_immediate().edits,
         [
             PushRoot { id: ElementId(5,) },
             InsertBefore { id: ElementId(4,), m: 1 },
@@ -167,7 +167,7 @@ fn keyed_diffing_additions() {
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(6) },
             LoadTemplate { name: "template", index: 0, id: ElementId(7) },
@@ -192,7 +192,7 @@ fn keyed_diffing_additions_and_moves_on_ends() {
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             // create 11, 12
             LoadTemplate { name: "template", index: 0, id: ElementId(5) },
@@ -222,7 +222,7 @@ fn keyed_diffing_additions_and_moves_in_middle() {
     // LIS: 4, 5, 6
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             // create 5, 6
             LoadTemplate { name: "template", index: 0, id: ElementId(5) },
@@ -256,7 +256,7 @@ fn controlled_keyed_diffing_out_of_order() {
     // LIS: 5, 6
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             // remove 7
             Remove { id: ElementId(4,) },
@@ -289,7 +289,7 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             Remove { id: ElementId(5,) },
             LoadTemplate { name: "template", index: 0, id: ElementId(5) },
@@ -318,7 +318,7 @@ fn remove_list() {
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             Remove { id: ElementId(3) },
             Remove { id: ElementId(4) },
@@ -343,7 +343,7 @@ fn no_common_keys() {
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             Remove { id: ElementId(2) },
             Remove { id: ElementId(3) },

+ 26 - 26
packages/core/tests/diff_unkeyed_list.rs

@@ -17,7 +17,7 @@ fn list_creates_one_by_one() {
 
     // load the div and then assign the empty fragment as a placeholder
     assert_eq!(
-        dom.rebuild().santize().dom_edits,
+        dom.rebuild().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
             AssignId { path: &[0], id: ElementId(2,) },
@@ -28,7 +28,7 @@ fn list_creates_one_by_one() {
     // Rendering the first item should replace the placeholder with an element
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
             HydrateText { path: &[0], value: "0", id: ElementId(4,) },
@@ -39,7 +39,7 @@ fn list_creates_one_by_one() {
     // Rendering the next item should insert after the previous
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
             HydrateText { path: &[0], value: "1", id: ElementId(5,) },
@@ -50,7 +50,7 @@ fn list_creates_one_by_one() {
     // ... and again!
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(6,) },
             HydrateText { path: &[0], value: "2", id: ElementId(7,) },
@@ -61,7 +61,7 @@ fn list_creates_one_by_one() {
     // once more
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(8,) },
             HydrateText { path: &[0], value: "3", id: ElementId(9,) },
@@ -86,7 +86,7 @@ fn removes_one_by_one() {
 
     // load the div and then assign the empty fragment as a placeholder
     assert_eq!(
-        dom.rebuild().santize().dom_edits,
+        dom.rebuild().santize().edits,
         [
             // The container
             LoadTemplate { name: "template", index: 0, id: ElementId(1) },
@@ -108,14 +108,14 @@ fn removes_one_by_one() {
     // Rendering the first item should replace the placeholder with an element
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [Remove { id: ElementId(6) }]
     );
 
     // Remove div(2)
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [Remove { id: ElementId(4) }]
     );
 
@@ -123,7 +123,7 @@ fn removes_one_by_one() {
     // todo: this should just be a remove with no placeholder
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             CreatePlaceholder { id: ElementId(3) },
             ReplaceWith { id: ElementId(2), m: 1 }
@@ -134,7 +134,7 @@ fn removes_one_by_one() {
     // todo: this should actually be append to, but replace placeholder is fine for now
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(2) },
             HydrateText { path: &[0], value: "0", id: ElementId(4) },
@@ -161,7 +161,7 @@ fn list_shrink_multiroot() {
     });
 
     assert_eq!(
-        dom.rebuild().santize().dom_edits,
+        dom.rebuild().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
             AssignId { path: &[0,], id: ElementId(2,) },
@@ -171,7 +171,7 @@ fn list_shrink_multiroot() {
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(3) },
             HydrateText { path: &[0], value: "0", id: ElementId(4) },
@@ -183,7 +183,7 @@ fn list_shrink_multiroot() {
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(2) },
             HydrateText { path: &[0], value: "1", id: ElementId(7) },
@@ -195,7 +195,7 @@ fn list_shrink_multiroot() {
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(10) },
             HydrateText { path: &[0], value: "2", id: ElementId(11) },
@@ -223,7 +223,7 @@ fn removes_one_by_one_multiroot() {
 
     // load the div and then assign the empty fragment as a placeholder
     assert_eq!(
-        dom.rebuild().santize().dom_edits,
+        dom.rebuild().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1) },
             //
@@ -250,19 +250,19 @@ fn removes_one_by_one_multiroot() {
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [Remove { id: ElementId(10) }, Remove { id: ElementId(12) }]
     );
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [Remove { id: ElementId(6) }, Remove { id: ElementId(8) }]
     );
 
     dom.mark_dirty(ScopeId(0));
     assert_eq!(
-        dom.render_immediate().santize().dom_edits,
+        dom.render_immediate().santize().edits,
         [
             Remove { id: ElementId(4) },
             CreatePlaceholder { id: ElementId(5) },
@@ -282,7 +282,7 @@ fn two_equal_fragments_are_equal_static() {
     });
 
     _ = dom.rebuild();
-    assert!(dom.render_immediate().dom_edits.is_empty());
+    assert!(dom.render_immediate().edits.is_empty());
 }
 
 #[test]
@@ -296,7 +296,7 @@ fn two_equal_fragments_are_equal() {
     });
 
     _ = dom.rebuild();
-    assert!(dom.render_immediate().dom_edits.is_empty());
+    assert!(dom.render_immediate().edits.is_empty());
 }
 
 #[test]
@@ -315,9 +315,9 @@ fn remove_many() {
     });
 
     let edits = dom.rebuild().santize();
-    assert!(edits.template_edits.is_empty());
+    assert!(edits.templates.is_empty());
     assert_eq!(
-        edits.dom_edits,
+        edits.edits,
         [
             CreatePlaceholder { id: ElementId(1,) },
             AppendChildren { m: 1 },
@@ -327,7 +327,7 @@ fn remove_many() {
     dom.mark_dirty(ScopeId(0));
     let edits = dom.render_immediate().santize();
     assert_eq!(
-        edits.dom_edits,
+        edits.edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
             HydrateText { path: &[0,], value: "hello 0", id: ElementId(3,) },
@@ -338,7 +338,7 @@ fn remove_many() {
     dom.mark_dirty(ScopeId(0));
     let edits = dom.render_immediate().santize();
     assert_eq!(
-        edits.dom_edits,
+        edits.edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
             HydrateText { path: &[0,], value: "hello 1", id: ElementId(4,) },
@@ -355,7 +355,7 @@ fn remove_many() {
     dom.mark_dirty(ScopeId(0));
     let edits = dom.render_immediate().santize();
     assert_eq!(
-        edits.dom_edits,
+        edits.edits,
         [
             Remove { id: ElementId(1,) },
             Remove { id: ElementId(5,) },
@@ -369,7 +369,7 @@ fn remove_many() {
     dom.mark_dirty(ScopeId(0));
     let edits = dom.render_immediate().santize();
     assert_eq!(
-        edits.dom_edits,
+        edits.edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
             HydrateText { path: &[0,], value: "hello 0", id: ElementId(10,) },

+ 2 - 2
packages/core/tests/kitchen_sink.rs

@@ -30,7 +30,7 @@ fn dual_stream() {
 
     use Mutation::*;
     assert_eq!(
-        edits.template_edits,
+        edits.templates,
         [
             CreateElement { name: "div" },
             SetStaticAttribute { name: "class", value: "asd", ns: None },
@@ -66,7 +66,7 @@ fn dual_stream() {
     );
 
     assert_eq!(
-        edits.dom_edits,
+        edits.edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1) },
             SetAttribute { name: "class", value: "123", id: ElementId(1), ns: None },

+ 2 - 2
packages/core/tests/lifecycle.rs

@@ -28,7 +28,7 @@ fn manual_diffing() {
     *value.lock().unwrap() = "goodbye";
 
     assert_eq!(
-        dom.rebuild().santize().dom_edits,
+        dom.rebuild().santize().edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(3) },
             HydrateText { path: &[0], value: "goodbye", id: ElementId(4) },
@@ -62,7 +62,7 @@ fn events_generate() {
     let edits = dom.render_immediate();
 
     assert_eq!(
-        edits.dom_edits,
+        edits.edits,
         [
             CreatePlaceholder { id: ElementId(2) },
             ReplaceWith { id: ElementId(1), m: 1 }

+ 2 - 2
packages/core/tests/suspense.rs

@@ -13,7 +13,7 @@ async fn it_works() {
 
     // We should at least get the top-level template in before pausing for the children
     assert_eq!(
-        mutations.template_edits,
+        mutations.templates,
         [
             CreateElement { name: "div" },
             CreateStaticText { value: "Waiting for child..." },
@@ -25,7 +25,7 @@ async fn it_works() {
 
     // And we should load it in and assign the placeholder properly
     assert_eq!(
-        mutations.dom_edits,
+        mutations.edits,
         [
             LoadTemplate { name: "template", index: 0, id: ElementId(1) },
             // hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly?

+ 4 - 4
packages/desktop/src/controller.rs

@@ -54,8 +54,8 @@ impl DesktopController {
                 {
                     let edits = dom.rebuild();
                     let mut queue = edit_queue.lock().unwrap();
-                    queue.push(serde_json::to_string(&edits.template_edits).unwrap());
-                    queue.push(serde_json::to_string(&edits.dom_edits).unwrap());
+                    queue.push(serde_json::to_string(&edits.templates).unwrap());
+                    queue.push(serde_json::to_string(&edits.edits).unwrap());
                     proxy.send_event(UserWindowEvent::EditsReady).unwrap();
                 }
 
@@ -79,8 +79,8 @@ impl DesktopController {
 
                     {
                         let mut queue = edit_queue.lock().unwrap();
-                        queue.push(serde_json::to_string(&muts.template_edits).unwrap());
-                        queue.push(serde_json::to_string(&muts.dom_edits).unwrap());
+                        queue.push(serde_json::to_string(&muts.templates).unwrap());
+                        queue.push(serde_json::to_string(&muts.edits).unwrap());
                         let _ = proxy.send_event(UserWindowEvent::EditsReady);
                     }
                 }

+ 1 - 1
packages/dioxus/benches/jsframework.rs

@@ -46,7 +46,7 @@ fn create_rows(c: &mut Criterion) {
 
         b.iter(|| {
             let g = dom.rebuild();
-            assert!(g.dom_edits.len() > 1);
+            assert!(g.edits.len() > 1);
         })
     });
 }

+ 2 - 0
packages/html/src/lib.rs

@@ -18,12 +18,14 @@ pub mod events;
 pub mod geometry;
 mod global_attributes;
 pub mod input_data;
+mod render_template;
 #[cfg(feature = "wasm-bind")]
 mod web_sys_bind;
 
 pub use elements::*;
 pub use events::*;
 pub use global_attributes::*;
+pub use render_template::*;
 
 pub mod prelude {
     pub use crate::events::*;

+ 41 - 0
packages/html/src/render_template.rs

@@ -0,0 +1,41 @@
+use dioxus_core::{Template, TemplateAttribute, TemplateNode};
+use std::fmt::Write;
+
+/// Render a template to an HTML string
+///
+/// Useful for sending over the wire. Can be used to with innerHtml to create templates with little work
+pub fn render_template_to_html(template: &Template) -> String {
+    let mut out = String::new();
+
+    for root in template.roots {
+        render_template_node(root, &mut out).unwrap();
+    }
+
+    out
+}
+
+fn render_template_node(node: &TemplateNode, out: &mut String) -> std::fmt::Result {
+    match node {
+        TemplateNode::Element {
+            tag,
+            attrs,
+            children,
+            ..
+        } => {
+            write!(out, "<{tag}")?;
+            for attr in *attrs {
+                if let TemplateAttribute::Static { name, value, .. } = attr {
+                    write!(out, "{}=\"{}\"", name, value)?;
+                }
+            }
+            for child in *children {
+                render_template_node(child, out)?;
+            }
+            write!(out, "</{tag}>")?;
+        }
+        TemplateNode::Text(t) => write!(out, "{t}")?,
+        TemplateNode::Dynamic(_) => write!(out, "<pre hidden />")?,
+        TemplateNode::DynamicText(t) => write!(out, "<!-- --> {t} <!-- -->")?,
+    };
+    Ok(())
+}

+ 4 - 22
packages/interpreter/src/bindings.rs

@@ -2,7 +2,7 @@
 
 use js_sys::Function;
 use wasm_bindgen::prelude::*;
-use web_sys::Element;
+use web_sys::{Element, Node};
 
 #[wasm_bindgen(module = "/src/interpreter.js")]
 extern "C" {
@@ -12,29 +12,17 @@ extern "C" {
     pub fn new(arg: Element) -> Interpreter;
 
     #[wasm_bindgen(method)]
-    pub fn AppendChildren(this: &Interpreter, many: u32);
+    pub fn SaveTemplate(this: &Interpreter, nodes: Vec<Node>, name: &str);
 
     #[wasm_bindgen(method)]
-    pub fn AssignId(this: &Interpreter, path: &[u8], id: u32);
-
-    #[wasm_bindgen(method)]
-    pub fn CreateElement(this: &Interpreter, tag: &str);
+    pub fn MountToRoot(this: &Interpreter);
 
     #[wasm_bindgen(method)]
-    pub fn CreateElementNs(this: &Interpreter, tag: &str, ns: &str);
+    pub fn AssignId(this: &Interpreter, path: &[u8], id: u32);
 
     #[wasm_bindgen(method)]
     pub fn CreatePlaceholder(this: &Interpreter, id: u32);
 
-    #[wasm_bindgen(method)]
-    pub fn CreateStaticPlaceholder(this: &Interpreter);
-
-    #[wasm_bindgen(method)]
-    pub fn CreateTextPlaceholder(this: &Interpreter);
-
-    #[wasm_bindgen(method)]
-    pub fn CreateStaticText(this: &Interpreter, value: &str);
-
     #[wasm_bindgen(method)]
     pub fn CreateTextNode(this: &Interpreter, value: JsValue, id: u32);
 
@@ -56,15 +44,9 @@ extern "C" {
     #[wasm_bindgen(method)]
     pub fn InsertBefore(this: &Interpreter, id: u32, n: u32);
 
-    #[wasm_bindgen(method)]
-    pub fn SaveTemplate(this: &Interpreter, name: &str, m: u32);
-
     #[wasm_bindgen(method)]
     pub fn SetAttribute(this: &Interpreter, id: u32, name: &str, value: JsValue, ns: Option<&str>);
 
-    #[wasm_bindgen(method)]
-    pub fn SetStaticAttribute(this: &Interpreter, name: &str, value: JsValue, ns: Option<&str>);
-
     #[wasm_bindgen(method)]
     pub fn SetBoolAttribute(this: &Interpreter, id: u32, name: &str, value: bool);
 

+ 7 - 26
packages/interpreter/src/interpreter.js

@@ -79,6 +79,13 @@ export class Interpreter {
   pop() {
     return this.stack.pop();
   }
+  SaveTemplate(nodes, name) {
+    this.templates[name] = nodes;
+    console.log(this.templates);
+  }
+  MountToRoot() {
+    this.AppendChildren(this.stack.length - 1);
+  }
   SetNode(id, node) {
     this.nodes[id] = node;
   }
@@ -132,25 +139,12 @@ export class Interpreter {
     this.nodes[root] = node;
     this.stack.push(node);
   }
-  CreateElement(tag) {
-    const el = document.createElement(tag);
-    this.stack.push(el);
-  }
-  CreateElementNs(tag, ns) {
-    let el = document.createElementNS(ns, tag);
-    this.stack.push(el);
-  }
   CreatePlaceholder(root) {
     let el = document.createElement("pre");
     el.hidden = true;
     this.stack.push(el);
     this.nodes[root] = el;
   }
-  CreateStaticPlaceholder() {
-    let el = document.createElement("pre");
-    el.hidden = true;
-    this.stack.push(el);
-  }
   NewEventListener(event_name, root, handler, bubbles) {
     const element = this.nodes[root];
     element.setAttribute("data-dioxus-id", `${root}`);
@@ -168,10 +162,6 @@ export class Interpreter {
     const node = this.nodes[id];
     this.SetAttributeInner(node, field, value, ns);
   }
-  SetStaticAttribute(field, value, ns) {
-    const node = this.top();
-    this.SetAttributeInner(node, field, value, ns);
-  }
   SetAttributeInner(node, field, value, ns) {
     const name = field;
     if (ns === "style") {
@@ -262,15 +252,6 @@ export class Interpreter {
     this.nodes[id] = node;
     this.stack.push(node);
   }
-  SaveTemplate(name, m) {
-    this.templates[name] = this.stack.splice(this.stack.length - m);
-  }
-  CreateStaticText(text) {
-    this.CreateTextNode(text);
-  }
-  CreateTextPlaceholder() {
-    this.CreateRawText("placeholder");
-  }
   handleEdit(edit) {
     console.log(edit);
     switch (edit.type) {

+ 1 - 1
packages/native-core/src/real_dom.rs

@@ -67,7 +67,7 @@ impl<S: State> RealDom<S> {
         let mut nodes_updated = Vec::new();
         nodes_updated.push((RealNodeId::ElementId(ElementId(0)), NodeMask::ALL));
         for mutations in mutations_vec {
-            for e in mutations.dom_edits {
+            for e in mutations.edits {
                 use dioxus_core::DomEdit::*;
                 match e {
                     AppendChildren { root, children } => {

+ 2 - 2
packages/native-core/src/utils.rs

@@ -72,7 +72,7 @@ impl PersistantElementIter {
     pub fn prune<S: State>(&mut self, mutations: &Mutations, rdom: &RealDom<S>) -> bool {
         let mut changed = false;
         let ids_removed: Vec<_> = mutations
-            .dom_edits
+            .edits
             .iter()
             .filter_map(|e| {
                 // nodes within templates will never be removed
@@ -97,7 +97,7 @@ impl PersistantElementIter {
         for (el_id, child_idx) in self.stack.iter_mut() {
             if let NodePosition::InChild(child_idx) = child_idx {
                 if let NodeType::Element { children, .. } = &rdom[*el_id].node_data.node_type {
-                    for m in &mutations.dom_edits {
+                    for m in &mutations.edits {
                         match m {
                             DomEdit::Remove { root } => {
                                 let id = rdom.resolve_maybe_id(*root);

+ 1 - 1
packages/tui/src/focus.rs

@@ -256,7 +256,7 @@ impl FocusState {
         if self.focus_iter.prune(mutations, rdom) {
             self.dirty = true;
         }
-        for m in &mutations.dom_edits {
+        for m in &mutations.edits {
             match m {
                 dioxus_core::DomEdit::ReplaceWith { root, .. } => remove_children(
                     &mut [&mut self.last_focused_id],

+ 67 - 11
packages/web/src/dom.rs

@@ -7,7 +7,7 @@
 //! - tests to ensure dyn_into works for various event types.
 //! - Partial delegation?>
 
-use dioxus_core::{ElementId, Mutation, Mutations};
+use dioxus_core::{ElementId, Mutation, Mutations, Template, TemplateAttribute, TemplateNode};
 use dioxus_html::{event_bubbles, CompositionData, FormData};
 use dioxus_interpreter_js::Interpreter;
 use futures_channel::mpsc;
@@ -19,6 +19,7 @@ use web_sys::{Document, Element, Event, HtmlElement};
 use crate::Config;
 
 pub struct WebsysDom {
+    document: Document,
     interpreter: Interpreter,
     handler: Closure<dyn FnMut(&Event)>,
     root: Element,
@@ -35,6 +36,7 @@ impl WebsysDom {
         };
 
         Self {
+            document,
             interpreter: Interpreter::new(root.clone()),
             root,
             handler: Closure::wrap(Box::new(move |event: &web_sys::Event| {
@@ -43,20 +45,78 @@ impl WebsysDom {
         }
     }
 
+    pub fn mount(&mut self) {
+        self.interpreter.MountToRoot();
+    }
+
+    pub fn load_templates(&mut self, templates: &[Template]) {
+        log::debug!("Loading templates {:?}", templates);
+
+        for template in templates {
+            let mut roots = vec![];
+
+            for root in template.roots {
+                roots.push(self.create_template_node(root))
+            }
+
+            self.interpreter.SaveTemplate(roots, template.id);
+        }
+    }
+
+    fn create_template_node(&self, v: &TemplateNode) -> web_sys::Node {
+        match v {
+            TemplateNode::Element {
+                tag,
+                namespace,
+                attrs,
+                children,
+                ..
+            } => {
+                let el = match namespace {
+                    Some(ns) => self.document.create_element_ns(Some(ns), tag).unwrap(),
+                    None => self.document.create_element(tag).unwrap(),
+                };
+
+                for attr in *attrs {
+                    if let TemplateAttribute::Static {
+                        name,
+                        value,
+                        namespace,
+                        volatile,
+                    } = attr
+                    {
+                        match namespace {
+                            Some(ns) => el.set_attribute_ns(Some(ns), name, value).unwrap(),
+                            None => el.set_attribute(name, value).unwrap(),
+                        }
+                    }
+                }
+
+                for child in *children {
+                    el.append_child(&self.create_template_node(child));
+                }
+
+                el.dyn_into().unwrap()
+            }
+
+            TemplateNode::Text(t) => self.document.create_text_node(t).dyn_into().unwrap(),
+            TemplateNode::DynamicText(_) => self.document.create_text_node("p").dyn_into().unwrap(),
+            TemplateNode::Dynamic(_) => {
+                let el = self.document.create_element("pre").unwrap();
+                el.toggle_attribute("hidden");
+                el.dyn_into().unwrap()
+            }
+        }
+    }
+
     pub fn apply_edits(&mut self, mut edits: Vec<Mutation>) {
         use Mutation::*;
         let i = &self.interpreter;
 
         for edit in edits.drain(..) {
             match edit {
-                AppendChildren { m } => i.AppendChildren(m as u32),
                 AssignId { path, id } => i.AssignId(path, id.0 as u32),
-                CreateElement { name } => i.CreateElement(name),
-                CreateElementNamespace { name, namespace } => i.CreateElementNs(name, namespace),
                 CreatePlaceholder { id } => i.CreatePlaceholder(id.0 as u32),
-                CreateStaticPlaceholder => i.CreateStaticPlaceholder(),
-                CreateTextPlaceholder => i.CreateTextPlaceholder(),
-                CreateStaticText { value } => i.CreateStaticText(value),
                 CreateTextNode { value, id } => i.CreateTextNode(value.into(), id.0 as u32),
                 HydrateText { path, value, id } => i.HydrateText(path, value, id.0 as u32),
                 LoadTemplate { name, index, id } => i.LoadTemplate(name, index as u32, id.0 as u32),
@@ -64,16 +124,12 @@ impl WebsysDom {
                 ReplacePlaceholder { path, m } => i.ReplacePlaceholder(path, m as u32),
                 InsertAfter { id, m } => i.InsertAfter(id.0 as u32, m as u32),
                 InsertBefore { id, m } => i.InsertBefore(id.0 as u32, m as u32),
-                SaveTemplate { name, m } => i.SaveTemplate(name, m as u32),
                 SetAttribute {
                     name,
                     value,
                     id,
                     ns,
                 } => i.SetAttribute(id.0 as u32, name, value.into(), ns),
-                SetStaticAttribute { name, value, ns } => {
-                    i.SetStaticAttribute(name, value.into(), ns)
-                }
                 SetBoolAttribute { name, value, id } => {
                     i.SetBoolAttribute(id.0 as u32, name, value)
                 }

+ 13 - 9
packages/web/src/lib.rs

@@ -189,17 +189,20 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
 
     log::info!("rebuilding app");
 
-    if should_hydrate {
-    } else {
-        let edits = dom.rebuild();
-        websys_dom.apply_edits(edits.template_edits);
-        websys_dom.apply_edits(edits.dom_edits);
-    }
+    // if should_hydrate {
+    // } else {
+    let edits = dom.rebuild();
+
+    log::debug!("Initial edits {:#?}", edits);
+
+    websys_dom.load_templates(&edits.templates);
+    websys_dom.apply_edits(edits.edits);
+    websys_dom.mount();
 
     let mut work_loop = ric_raf::RafLoop::new();
 
     loop {
-        log::trace!("waiting for work");
+        log::debug!("waiting for work");
 
         // if virtualdom has nothing, wait for it to have something before requesting idle time
         // if there is work then this future resolves immediately.
@@ -240,6 +243,7 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
         let deadline = work_loop.wait_for_idle_time().await;
 
         // run the virtualdom work phase until the frame deadline is reached
+        // let deadline = gloo_timers::future::sleep(Duration::from_millis(10000));
         let edits = dom.render_with_deadline(deadline).await;
 
         // wait for the animation frame to fire so we can apply our changes
@@ -247,8 +251,8 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
 
         log::debug!("edits {:#?}", edits);
 
-        websys_dom.apply_edits(edits.template_edits);
-        websys_dom.apply_edits(edits.dom_edits);
+        websys_dom.load_templates(&edits.templates);
+        websys_dom.apply_edits(edits.edits);
     }
 }
 

+ 1 - 1
packages/web/tests/hydrate.rs

@@ -23,7 +23,7 @@ fn makes_tree() {
     let mut dom = VirtualDom::new(app);
     let muts = dom.rebuild();
 
-    dbg!(muts.dom_edits);
+    dbg!(muts.edits);
 }
 
 #[wasm_bindgen_test]