From 85657d3906c0ad5689e7414d676feb5544adae22 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 1 Dec 2022 00:46:15 -0500 Subject: [PATCH] feat: rip out mutations for templates --- packages/core/src/create.rs | 84 ++---------------------- packages/core/src/mutations.rs | 73 +++++--------------- packages/core/src/nodes.rs | 7 +- packages/core/src/virtual_dom.rs | 16 ++--- packages/core/tests/attr_cleanup.rs | 10 +-- packages/core/tests/boolattrs.rs | 2 +- packages/core/tests/borrowedstate.rs | 2 +- packages/core/tests/context_api.rs | 6 +- packages/core/tests/create_dom.rs | 16 ++--- packages/core/tests/create_element.rs | 2 +- packages/core/tests/create_fragments.rs | 10 +-- packages/core/tests/create_lists.rs | 4 +- packages/core/tests/create_passthru.rs | 8 +-- packages/core/tests/cycle.rs | 8 +-- packages/core/tests/diff_component.rs | 8 +-- packages/core/tests/diff_element.rs | 14 ++-- packages/core/tests/diff_keyed_list.rs | 26 ++++---- packages/core/tests/diff_unkeyed_list.rs | 52 +++++++-------- packages/core/tests/kitchen_sink.rs | 4 +- packages/core/tests/lifecycle.rs | 4 +- packages/core/tests/suspense.rs | 4 +- packages/desktop/src/controller.rs | 8 +-- packages/dioxus/benches/jsframework.rs | 2 +- packages/html/src/lib.rs | 2 + packages/html/src/render_template.rs | 41 ++++++++++++ packages/interpreter/src/bindings.rs | 28 ++------ packages/interpreter/src/interpreter.js | 33 ++-------- packages/native-core/src/real_dom.rs | 2 +- packages/native-core/src/utils.rs | 4 +- packages/tui/src/focus.rs | 2 +- packages/web/src/dom.rs | 78 ++++++++++++++++++---- packages/web/src/lib.rs | 22 ++++--- packages/web/tests/hydrate.rs | 2 +- 33 files changed, 270 insertions(+), 314 deletions(-) create mode 100644 packages/html/src/render_template.rs diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index c64763c5b..0559c3473 100644 --- a/packages/core/src/create.rs +++ b/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>( - 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 diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 208ec65ab..0369c2da8 100644 --- a/packages/core/src/mutations.rs +++ b/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, - /// Any mutations required to build the templates using [`Mutations`] - pub template_edits: Vec>, + /// Any templates encountered while diffing the DOM. + /// + /// These must be loaded into a cache before applying the edits + pub templates: Vec>, /// Any mutations required to patch the renderer to match the layout of the VirtualDom - pub dom_edits: Vec>, + pub edits: Vec>, } 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", - _ => {} - }); + 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 + // 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. diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 9c6eb6cd9..df8c96def 100644 --- a/packages/core/src/nodes.rs +++ b/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), diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 41a54d391..7954413ca 100644 --- a/packages/core/src/virtual_dom.rs +++ b/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::().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 diff --git a/packages/core/tests/attr_cleanup.rs b/packages/core/tests/attr_cleanup.rs index d3e98769a..99ce9a01b 100644 --- a/packages/core/tests/attr_cleanup.rs +++ b/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 } diff --git a/packages/core/tests/boolattrs.rs b/packages/core/tests/boolattrs.rs index 7688e5a49..5187e00f9 100644 --- a/packages/core/tests/boolattrs.rs +++ b/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,) }, diff --git a/packages/core/tests/borrowedstate.rs b/packages/core/tests/borrowedstate.rs index 1f1214189..3da5c8d94 100644 --- a/packages/core/tests/borrowedstate.rs +++ b/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,) }, diff --git a/packages/core/tests/context_api.rs b/packages/core/tests/context_api.rs index b16b2433d..2ec1ea18b 100644 --- a/packages/core/tests/context_api.rs +++ b/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,) },] ); } diff --git a/packages/core/tests/create_dom.rs b/packages/core/tests/create_dom.rs index 8eda5783b..594cea580 100644 --- a/packages/core/tests/create_dom.rs +++ b/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) }, diff --git a/packages/core/tests/create_element.rs b/packages/core/tests/create_element.rs index e6331f9ac..42da01656 100644 --- a/packages/core/tests/create_element.rs +++ b/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" }, diff --git a/packages/core/tests/create_fragments.rs b/packages/core/tests/create_fragments.rs index 33bba1eaa..5b67711a8 100644 --- a/packages/core/tests/create_fragments.rs +++ b/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 } ); } diff --git a/packages/core/tests/create_lists.rs b/packages/core/tests/create_lists.rs index e29044514..163eb577f 100644 --- a/packages/core/tests/create_lists.rs +++ b/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) }, diff --git a/packages/core/tests/create_passthru.rs b/packages/core/tests/create_passthru.rs index 2ffdf0065..c13101320 100644 --- a/packages/core/tests/create_passthru.rs +++ b/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) }, diff --git a/packages/core/tests/cycle.rs b/packages/core/tests/cycle.rs index 87ede2271..72e1a028d 100644 --- a/packages/core/tests/cycle.rs +++ b/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 }, diff --git a/packages/core/tests/diff_component.rs b/packages/core/tests/diff_component.rs index 0f6ec615c..6e72f04cd 100644 --- a/packages/core/tests/diff_component.rs +++ b/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 } diff --git a/packages/core/tests/diff_element.rs b/packages/core/tests/diff_element.rs index 4ca97dcdb..3ab9b284a 100644 --- a/packages/core/tests/diff_element.rs +++ b/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 }, diff --git a/packages/core/tests/diff_keyed_list.rs b/packages/core/tests/diff_keyed_list.rs index a2c00e439..6acbaebbb 100644 --- a/packages/core/tests/diff_keyed_list.rs +++ b/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) }, diff --git a/packages/core/tests/diff_unkeyed_list.rs b/packages/core/tests/diff_unkeyed_list.rs index 75c12aec0..b32218327 100644 --- a/packages/core/tests/diff_unkeyed_list.rs +++ b/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,) }, diff --git a/packages/core/tests/kitchen_sink.rs b/packages/core/tests/kitchen_sink.rs index 737980938..afa1ed64f 100644 --- a/packages/core/tests/kitchen_sink.rs +++ b/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 }, diff --git a/packages/core/tests/lifecycle.rs b/packages/core/tests/lifecycle.rs index bdceb2332..ab7be8793 100644 --- a/packages/core/tests/lifecycle.rs +++ b/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 } diff --git a/packages/core/tests/suspense.rs b/packages/core/tests/suspense.rs index 4e70ec807..ae69acab5 100644 --- a/packages/core/tests/suspense.rs +++ b/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? diff --git a/packages/desktop/src/controller.rs b/packages/desktop/src/controller.rs index 4eca36db1..d01b6545f 100644 --- a/packages/desktop/src/controller.rs +++ b/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); } } diff --git a/packages/dioxus/benches/jsframework.rs b/packages/dioxus/benches/jsframework.rs index 281d36904..24ca2fe69 100644 --- a/packages/dioxus/benches/jsframework.rs +++ b/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); }) }); } diff --git a/packages/html/src/lib.rs b/packages/html/src/lib.rs index 325116a34..ab2e49f1e 100644 --- a/packages/html/src/lib.rs +++ b/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::*; diff --git a/packages/html/src/render_template.rs b/packages/html/src/render_template.rs new file mode 100644 index 000000000..5fbff5ff0 --- /dev/null +++ b/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, "")?; + } + TemplateNode::Text(t) => write!(out, "{t}")?, + TemplateNode::Dynamic(_) => write!(out, "