mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
Fix nested rsx expansion by not using template titles (#2799)
* Fix nested rsx expansion by not using template titles * fix writers with nameless templates * fix clippy * dont commit vscode fix * fix release mode, pull out __template_name * fix axum_desktop * Fix core tests * Make most fields of HotReloadedTemplate public for testing * wip: formatting, compare all diff cases * slightly smarter diffing for dynamic nodes * add a comment about generic node diffing * clean up mutations a bit * fix load template * simplify maybe_rebuild * Restore write mutations flag in web * write_mutations -> skip_mutations --------- Co-authored-by: Evan Almloff <evanalmloff@gmail.com>
This commit is contained in:
parent
a2180b92e9
commit
0de3bf7aeb
40 changed files with 597 additions and 1038 deletions
|
@ -102,7 +102,7 @@ impl AssetConfigDropGuard {
|
|||
None => "/".to_string(),
|
||||
};
|
||||
manganis_cli_support::Config::default()
|
||||
.with_assets_serve_location(&base)
|
||||
.with_assets_serve_location(base)
|
||||
.save();
|
||||
Self {}
|
||||
}
|
||||
|
|
|
@ -27,10 +27,10 @@ impl MountId {
|
|||
pub(crate) const PLACEHOLDER: Self = Self(usize::MAX);
|
||||
|
||||
pub(crate) fn as_usize(self) -> Option<usize> {
|
||||
if self == Self::PLACEHOLDER {
|
||||
None
|
||||
} else {
|
||||
if self.mounted() {
|
||||
Some(self.0)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,11 +10,10 @@
|
|||
#![allow(clippy::too_many_arguments)]
|
||||
|
||||
use crate::{
|
||||
arena::ElementId,
|
||||
innerlude::{ElementRef, MountId, WriteMutations},
|
||||
innerlude::{ElementRef, WriteMutations},
|
||||
nodes::VNode,
|
||||
virtual_dom::VirtualDom,
|
||||
Template, TemplateNode,
|
||||
TemplateNode,
|
||||
};
|
||||
|
||||
mod component;
|
||||
|
@ -34,62 +33,6 @@ impl VirtualDom {
|
|||
.sum()
|
||||
}
|
||||
|
||||
/// Simply replace a placeholder with a list of nodes
|
||||
fn replace_placeholder(
|
||||
&mut self,
|
||||
mut to: Option<&mut impl WriteMutations>,
|
||||
placeholder_id: ElementId,
|
||||
r: &[VNode],
|
||||
parent: Option<ElementRef>,
|
||||
) {
|
||||
let m = self.create_children(to.as_deref_mut(), r, parent);
|
||||
if let Some(to) = to {
|
||||
self.replace_placeholder_with_nodes_on_stack(to, placeholder_id, m)
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_placeholder_with_nodes_on_stack(
|
||||
&mut self,
|
||||
to: &mut impl WriteMutations,
|
||||
placeholder_id: ElementId,
|
||||
m: usize,
|
||||
) {
|
||||
to.replace_node_with(placeholder_id, m);
|
||||
self.reclaim(placeholder_id);
|
||||
}
|
||||
|
||||
fn nodes_to_placeholder(
|
||||
&mut self,
|
||||
mut to: Option<&mut impl WriteMutations>,
|
||||
mount: MountId,
|
||||
dyn_node_idx: usize,
|
||||
old_nodes: &[VNode],
|
||||
) {
|
||||
// Create the placeholder first, ensuring we get a dedicated ID for the placeholder
|
||||
let placeholder = self.next_element();
|
||||
|
||||
// Set the id of the placeholder
|
||||
self.mounts[mount.0].mounted_dynamic_nodes[dyn_node_idx] = placeholder.0;
|
||||
|
||||
if let Some(to) = to.as_deref_mut() {
|
||||
to.create_placeholder(placeholder);
|
||||
}
|
||||
|
||||
self.replace_nodes(to, old_nodes, 1);
|
||||
}
|
||||
|
||||
/// Replace many nodes with a number of nodes on the stack
|
||||
fn replace_nodes(&mut self, to: Option<&mut impl WriteMutations>, nodes: &[VNode], m: usize) {
|
||||
debug_assert!(
|
||||
!nodes.is_empty(),
|
||||
"replace_nodes must have at least one node"
|
||||
);
|
||||
|
||||
// We want to optimize the replace case to use one less mutation if possible
|
||||
// Instead of *just* removing it, we can use the replace mutation
|
||||
self.remove_nodes(to, nodes, Some(m));
|
||||
}
|
||||
|
||||
/// Remove these nodes from the dom
|
||||
/// Wont generate mutations for the inner nodes
|
||||
fn remove_nodes(
|
||||
|
@ -103,26 +46,6 @@ impl VirtualDom {
|
|||
node.remove_node(self, to.as_deref_mut(), replace_with.filter(|_| last_node));
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a new template into the VirtualDom's template registry
|
||||
// used in conditional compilation
|
||||
#[allow(unused_mut)]
|
||||
pub(crate) fn register_template(
|
||||
&mut self,
|
||||
to: &mut impl WriteMutations,
|
||||
mut template: Template,
|
||||
) {
|
||||
if self.templates.contains(&template.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
_ = self.templates.insert(template.name);
|
||||
|
||||
// If it's all dynamic nodes, then we don't need to register it
|
||||
if !template.is_completely_dynamic() {
|
||||
to.register_template(template)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// We can apply various optimizations to dynamic nodes that are the single child of their parent.
|
||||
|
|
|
@ -5,7 +5,7 @@ use core::iter::Peekable;
|
|||
|
||||
use crate::{
|
||||
arena::ElementId,
|
||||
innerlude::{ElementPath, ElementRef, VComponent, VNodeMount, VText},
|
||||
innerlude::{ElementPath, ElementRef, VNodeMount, VText},
|
||||
nodes::DynamicNode,
|
||||
scopes::ScopeId,
|
||||
TemplateNode,
|
||||
|
@ -21,9 +21,12 @@ impl VNode {
|
|||
// The node we are diffing from should always be mounted
|
||||
debug_assert!(dom.mounts.get(self.mount.get().0).is_some() || to.is_none());
|
||||
|
||||
// If the templates are different by name, we need to replace the entire template
|
||||
if self.templates_are_different(new) {
|
||||
return self.light_diff_templates(new, dom, to);
|
||||
// If the templates are different, we need to replace the entire template
|
||||
if self.template != new.template {
|
||||
let mount_id = self.mount.get();
|
||||
let mount = &dom.mounts[mount_id.0];
|
||||
let parent = mount.parent;
|
||||
return self.replace(std::slice::from_ref(new), parent, dom, to);
|
||||
}
|
||||
|
||||
let mount_id = self.mount.get();
|
||||
|
@ -73,75 +76,62 @@ impl VNode {
|
|||
old_node: &DynamicNode,
|
||||
new_node: &DynamicNode,
|
||||
dom: &mut VirtualDom,
|
||||
to: Option<&mut impl WriteMutations>,
|
||||
mut to: Option<&mut impl WriteMutations>,
|
||||
) {
|
||||
tracing::trace!("diffing dynamic node from {old_node:?} to {new_node:?}");
|
||||
match (old_node, new_node) {
|
||||
(Text(old), Text(new)) => {
|
||||
// Diffing text is just a side effect, if we are diffing suspended nodes and are not outputting mutations, we can skip it
|
||||
if let Some(to) = to{
|
||||
if let Some(to) = to {
|
||||
let mount = &dom.mounts[mount.0];
|
||||
self.diff_vtext(to, mount, idx, old, new)
|
||||
}
|
||||
},
|
||||
(Text(_), Placeholder(_)) => {
|
||||
self.replace_text_with_placeholder(to, mount, idx, dom)
|
||||
},
|
||||
(Placeholder(_), Text(new)) => {
|
||||
self.replace_placeholder_with_text(to, mount, idx, new, dom)
|
||||
},
|
||||
(Placeholder(_), Placeholder(_)) => {},
|
||||
(Fragment(old), Fragment(new)) => dom.diff_non_empty_fragment(to, old, new, Some(self.reference_to_dynamic_node(mount, idx))),
|
||||
}
|
||||
(Placeholder(_), Placeholder(_)) => {}
|
||||
(Fragment(old), Fragment(new)) => dom.diff_non_empty_fragment(
|
||||
to,
|
||||
old,
|
||||
new,
|
||||
Some(self.reference_to_dynamic_node(mount, idx)),
|
||||
),
|
||||
(Component(old), Component(new)) => {
|
||||
let scope_id = ScopeId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
|
||||
self.diff_vcomponent(mount, idx, new, old, scope_id, Some(self.reference_to_dynamic_node(mount, idx)), dom, to)
|
||||
},
|
||||
(Placeholder(_), Fragment(right)) => {
|
||||
let placeholder_id = ElementId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
|
||||
dom.replace_placeholder(to, placeholder_id, right, Some(self.reference_to_dynamic_node(mount, idx)))},
|
||||
(Fragment(left), Placeholder(_)) => {
|
||||
dom.nodes_to_placeholder(to, mount, idx, left,)
|
||||
},
|
||||
_ => todo!("This is an usual custom case for dynamic nodes. We don't know how to handle it yet."),
|
||||
let scope_id = ScopeId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
|
||||
self.diff_vcomponent(
|
||||
mount,
|
||||
idx,
|
||||
new,
|
||||
old,
|
||||
scope_id,
|
||||
Some(self.reference_to_dynamic_node(mount, idx)),
|
||||
dom,
|
||||
to,
|
||||
)
|
||||
}
|
||||
(old, new) => {
|
||||
// TODO: we should pass around the mount instead of the mount id
|
||||
// that would make moving the mount around here much easier
|
||||
|
||||
// Mark the mount as unused. When a scope is created, it reads the mount and
|
||||
// if it is the placeholder value, it will create the scope, otherwise it will
|
||||
// reuse the scope
|
||||
let old_mount = dom.mounts[mount.0].mounted_dynamic_nodes[idx];
|
||||
dom.mounts[mount.0].mounted_dynamic_nodes[idx] = usize::MAX;
|
||||
|
||||
let new_nodes_on_stack =
|
||||
self.create_dynamic_node(new, mount, idx, dom, to.as_deref_mut());
|
||||
|
||||
// Restore the mount for the scope we are removing
|
||||
let new_mount = dom.mounts[mount.0].mounted_dynamic_nodes[idx];
|
||||
dom.mounts[mount.0].mounted_dynamic_nodes[idx] = old_mount;
|
||||
|
||||
self.remove_dynamic_node(mount, dom, to, true, idx, old, Some(new_nodes_on_stack));
|
||||
|
||||
// Restore the mount for the node we created
|
||||
dom.mounts[mount.0].mounted_dynamic_nodes[idx] = new_mount;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// Replace a text node with a placeholder node
|
||||
pub(crate) fn replace_text_with_placeholder(
|
||||
&self,
|
||||
to: Option<&mut impl WriteMutations>,
|
||||
mount: MountId,
|
||||
idx: usize,
|
||||
dom: &mut VirtualDom,
|
||||
) {
|
||||
if let Some(to) = to {
|
||||
// Grab the text element id from the mount and replace it with a new placeholder
|
||||
let text_id = ElementId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
|
||||
let (id, _) = self.create_dynamic_node_with_path(mount, idx, dom);
|
||||
to.create_placeholder(id);
|
||||
to.replace_node_with(text_id, 1);
|
||||
dom.reclaim(text_id);
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace a placeholder node with a text node
|
||||
pub(crate) fn replace_placeholder_with_text(
|
||||
&self,
|
||||
to: Option<&mut impl WriteMutations>,
|
||||
mount: MountId,
|
||||
idx: usize,
|
||||
new: &VText,
|
||||
dom: &mut VirtualDom,
|
||||
) {
|
||||
if let Some(to) = to {
|
||||
// Grab the placeholder id from the mount and replace it with a new text node
|
||||
let placeholder_id = ElementId(dom.mounts[mount.0].mounted_dynamic_nodes[idx]);
|
||||
let (new_id, _) = self.create_dynamic_node_with_path(mount, idx, dom);
|
||||
to.create_text_node(&new.value, new_id);
|
||||
dom.replace_placeholder_with_nodes_on_stack(to, placeholder_id, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to get the dynamic node and its index for a root node
|
||||
pub(crate) fn get_dynamic_root_node_and_id(
|
||||
&self,
|
||||
|
@ -401,12 +391,6 @@ impl VNode {
|
|||
};
|
||||
}
|
||||
|
||||
fn templates_are_different(&self, other: &VNode) -> bool {
|
||||
let self_node_name = self.template.id();
|
||||
let other_node_name = other.template.id();
|
||||
self_node_name != other_node_name
|
||||
}
|
||||
|
||||
pub(super) fn reclaim_attributes(&self, mount: MountId, dom: &mut VirtualDom) {
|
||||
let mut next_id = None;
|
||||
for (idx, path) in self.template.attr_paths.iter().enumerate() {
|
||||
|
@ -530,83 +514,6 @@ impl VNode {
|
|||
}
|
||||
}
|
||||
|
||||
/// Lightly diff the two templates, checking only their roots.
|
||||
///
|
||||
/// The goal here is to preserve any existing component state that might exist. This is to preserve some React-like
|
||||
/// behavior where the component state is preserved when the component is re-rendered.
|
||||
///
|
||||
/// This is implemented by iterating each root, checking if the component is the same, if it is, then diff it.
|
||||
///
|
||||
/// We then pass the new template through "create" which should be smart enough to skip roots.
|
||||
///
|
||||
/// Currently, we only handle the case where the roots are the same component list. If there's any sort of deviation,
|
||||
/// IE more nodes, less nodes, different nodes, or expressions, then we just replace the whole thing.
|
||||
///
|
||||
/// This is mostly implemented to help solve the issue where the same component is rendered under two different
|
||||
/// conditions:
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # let enabled = true;
|
||||
/// # #[component]
|
||||
/// # fn Component(enabled_sign: String) -> Element { unimplemented!() }
|
||||
/// if enabled {
|
||||
/// rsx!{ Component { enabled_sign: "abc" } }
|
||||
/// } else {
|
||||
/// rsx!{ Component { enabled_sign: "xyz" } }
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// However, we should not that it's explicit in the docs that this is not a guarantee. If you need to preserve state,
|
||||
/// then you should be passing in separate props instead.
|
||||
///
|
||||
/// ```rust, no_run
|
||||
/// # use dioxus::prelude::*;
|
||||
/// # #[component]
|
||||
/// # fn Component(enabled_sign: String) -> Element { unimplemented!() }
|
||||
/// # let enabled = true;
|
||||
/// let props = if enabled {
|
||||
/// ComponentProps { enabled_sign: "abc".to_string() }
|
||||
/// } else {
|
||||
/// ComponentProps { enabled_sign: "xyz".to_string() }
|
||||
/// };
|
||||
///
|
||||
/// rsx! {
|
||||
/// Component { ..props }
|
||||
/// };
|
||||
/// ```
|
||||
pub(crate) fn light_diff_templates(
|
||||
&self,
|
||||
new: &VNode,
|
||||
dom: &mut VirtualDom,
|
||||
mut to: Option<&mut impl WriteMutations>,
|
||||
) {
|
||||
let mount_id = self.mount.get();
|
||||
let mount = &dom.mounts[mount_id.0];
|
||||
let parent = mount.parent;
|
||||
match matching_components(self, new) {
|
||||
None => self.replace(std::slice::from_ref(new), parent, dom, to),
|
||||
Some(components) => {
|
||||
self.move_mount_to(new, dom);
|
||||
|
||||
for (idx, (old_component, new_component)) in components.into_iter().enumerate() {
|
||||
let mount = &dom.mounts[mount_id.0];
|
||||
let scope_id = ScopeId(mount.mounted_dynamic_nodes[idx]);
|
||||
self.diff_vcomponent(
|
||||
mount_id,
|
||||
idx,
|
||||
new_component,
|
||||
old_component,
|
||||
scope_id,
|
||||
parent,
|
||||
dom,
|
||||
to.as_deref_mut(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Create this rsx block. This will create scopes from components that this rsx block contains, but it will not write anything to the DOM.
|
||||
pub(crate) fn create(
|
||||
&self,
|
||||
|
@ -634,13 +541,6 @@ impl VNode {
|
|||
});
|
||||
}
|
||||
|
||||
// If we are outputting mutations, mount the node as well
|
||||
if let Some(to) = to.as_deref_mut() {
|
||||
// The best renderers will have templates pre-hydrated and registered
|
||||
// Just in case, let's create the template using instructions anyways
|
||||
dom.register_template(to, template);
|
||||
}
|
||||
|
||||
// Walk the roots, creating nodes and assigning IDs
|
||||
// nodes in an iterator of (dynamic_node_index, path) and attrs in an iterator of (attr_index, path)
|
||||
let mut nodes = template.node_paths.iter().copied().enumerate().peekable();
|
||||
|
@ -671,7 +571,13 @@ impl VNode {
|
|||
// Take a dynamic node off the depth first iterator
|
||||
nodes.next().unwrap();
|
||||
// Then mount the node
|
||||
self.create_dynamic_node(mount, *id, dom, to.as_deref_mut())
|
||||
self.create_dynamic_node(
|
||||
&self.dynamic_nodes[*id],
|
||||
mount,
|
||||
*id,
|
||||
dom,
|
||||
to.as_deref_mut(),
|
||||
)
|
||||
}
|
||||
// For static text and element nodes, just load the template root. This may be a placeholder or just a static node. We now know that each root node has a unique id
|
||||
TemplateNode::Text { .. } | TemplateNode::Element { .. } => {
|
||||
|
@ -720,13 +626,13 @@ impl VNode {
|
|||
|
||||
pub(crate) fn create_dynamic_node(
|
||||
&self,
|
||||
node: &DynamicNode,
|
||||
mount: MountId,
|
||||
dynamic_node_id: usize,
|
||||
dom: &mut VirtualDom,
|
||||
to: Option<&mut impl WriteMutations>,
|
||||
) -> usize {
|
||||
use DynamicNode::*;
|
||||
let node = &self.dynamic_nodes[dynamic_node_id];
|
||||
match node {
|
||||
Component(component) => {
|
||||
let parent = Some(self.reference_to_dynamic_node(mount, dynamic_node_id));
|
||||
|
@ -786,7 +692,13 @@ impl VNode {
|
|||
// Only take nodes that are under this root node
|
||||
let from_root_node = |(_, path): &(usize, &[u8])| path.first() == Some(&root_idx);
|
||||
while let Some((dynamic_node_id, path)) = dynamic_nodes_iter.next_if(from_root_node) {
|
||||
let m = self.create_dynamic_node(mount, dynamic_node_id, dom, to.as_deref_mut());
|
||||
let m = self.create_dynamic_node(
|
||||
&self.dynamic_nodes[dynamic_node_id],
|
||||
mount,
|
||||
dynamic_node_id,
|
||||
dom,
|
||||
to.as_deref_mut(),
|
||||
);
|
||||
if let Some(to) = to.as_deref_mut() {
|
||||
// If we actually created real new nodes, we need to replace the placeholder for this dynamic node with the new dynamic nodes
|
||||
if m > 0 {
|
||||
|
@ -856,7 +768,7 @@ impl VNode {
|
|||
let this_id = dom.next_element();
|
||||
dom.mounts[mount.0].root_ids[root_idx] = this_id;
|
||||
|
||||
to.load_template(self.template.name, root_idx, this_id);
|
||||
to.load_template(self.template, root_idx, this_id);
|
||||
|
||||
this_id
|
||||
}
|
||||
|
@ -888,22 +800,6 @@ impl VNode {
|
|||
id
|
||||
}
|
||||
|
||||
/// Mount a root node and return its ID and the path to the node
|
||||
fn create_dynamic_node_with_path(
|
||||
&self,
|
||||
mount: MountId,
|
||||
idx: usize,
|
||||
dom: &mut VirtualDom,
|
||||
) -> (ElementId, &'static [u8]) {
|
||||
// Add the mutation to the list
|
||||
let path = self.template.node_paths[idx];
|
||||
|
||||
// Allocate a dynamic element reference for this text node
|
||||
let new_id = mount.mount_node(idx, dom);
|
||||
|
||||
(new_id, &path[1..])
|
||||
}
|
||||
|
||||
fn create_dynamic_text(
|
||||
&self,
|
||||
mount: MountId,
|
||||
|
@ -912,19 +808,12 @@ impl VNode {
|
|||
dom: &mut VirtualDom,
|
||||
to: &mut impl WriteMutations,
|
||||
) -> usize {
|
||||
let (new_id, path) = self.create_dynamic_node_with_path(mount, idx, dom);
|
||||
let new_id = mount.mount_node(idx, dom);
|
||||
|
||||
// If this is a root node, the path is empty and we need to create a new text node
|
||||
if path.is_empty() {
|
||||
to.create_text_node(&text.value, new_id);
|
||||
// We create one node on the stack
|
||||
1
|
||||
} else {
|
||||
// Dynamic text nodes always exist as a placeholder text node in the template, we can just hydrate that text node instead of creating a new one
|
||||
to.hydrate_text_node(path, &text.value, new_id);
|
||||
// Since we're hydrating an existing node, we don't create any new nodes
|
||||
0
|
||||
}
|
||||
to.create_text_node(&text.value, new_id);
|
||||
// We create one node on the stack
|
||||
1
|
||||
}
|
||||
|
||||
pub(crate) fn create_placeholder(
|
||||
|
@ -934,19 +823,12 @@ impl VNode {
|
|||
dom: &mut VirtualDom,
|
||||
to: &mut impl WriteMutations,
|
||||
) -> usize {
|
||||
let (id, path) = self.create_dynamic_node_with_path(mount, idx, dom);
|
||||
let new_id = mount.mount_node(idx, dom);
|
||||
|
||||
// If this is a root node, the path is empty and we need to create a new placeholder node
|
||||
if path.is_empty() {
|
||||
to.create_placeholder(id);
|
||||
// We create one node on the stack
|
||||
1
|
||||
} else {
|
||||
// Assign the ID to the existing node in the template
|
||||
to.assign_node_id(path, id);
|
||||
// Since the placeholder is already in the DOM, we don't create any new nodes
|
||||
0
|
||||
}
|
||||
to.create_placeholder(new_id);
|
||||
// We create one node on the stack
|
||||
1
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -957,34 +839,3 @@ impl MountId {
|
|||
id
|
||||
}
|
||||
}
|
||||
|
||||
fn matching_components<'a>(
|
||||
left: &'a VNode,
|
||||
right: &'a VNode,
|
||||
) -> Option<Vec<(&'a VComponent, &'a VComponent)>> {
|
||||
let left_node = left.template;
|
||||
let right_node = right.template;
|
||||
if left_node.roots.len() != right_node.roots.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// run through the components, ensuring they're the same
|
||||
left_node
|
||||
.roots
|
||||
.iter()
|
||||
.zip(right_node.roots.iter())
|
||||
.map(|(l, r)| {
|
||||
let (l, r) = match (l, r) {
|
||||
(TemplateNode::Dynamic { id: l }, TemplateNode::Dynamic { id: r }) => (l, r),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let (l, r) = match (&left.dynamic_nodes[*l], &right.dynamic_nodes[*r]) {
|
||||
(Component(l), Component(r)) => (l, r),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some((l, r))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
|
@ -527,7 +527,6 @@ impl<F: Fn(ErrorContext) -> Element + 'static> From<F> for ErrorHandler {
|
|||
|
||||
fn default_handler(errors: ErrorContext) -> Element {
|
||||
static TEMPLATE: Template = Template {
|
||||
name: "error_handle.rs:42:5:884",
|
||||
roots: &[TemplateNode::Element {
|
||||
tag: "div",
|
||||
namespace: None,
|
||||
|
@ -549,7 +548,6 @@ fn default_handler(errors: ErrorContext) -> Element {
|
|||
.iter()
|
||||
.map(|e| {
|
||||
static TEMPLATE: Template = Template {
|
||||
name: "error_handle.rs:43:5:884",
|
||||
roots: &[TemplateNode::Element {
|
||||
tag: "pre",
|
||||
namespace: None,
|
||||
|
@ -753,7 +751,6 @@ pub fn ErrorBoundary(props: ErrorBoundaryProps) -> Element {
|
|||
if errors.is_empty() {
|
||||
std::result::Result::Ok({
|
||||
static TEMPLATE: Template = Template {
|
||||
name: "examples/error_handle.rs:81:17:2342",
|
||||
roots: &[TemplateNode::Dynamic { id: 0usize }],
|
||||
node_paths: &[&[0u8]],
|
||||
attr_paths: &[],
|
||||
|
|
|
@ -235,15 +235,6 @@ impl DynamicValuePool {
|
|||
|
||||
pub fn render_with(&mut self, hot_reload: &HotReloadedTemplate) -> VNode {
|
||||
// Get the node_paths from a depth first traversal of the template
|
||||
let node_paths = hot_reload.node_paths();
|
||||
let attr_paths = hot_reload.attr_paths();
|
||||
|
||||
let template = Template {
|
||||
name: hot_reload.name,
|
||||
roots: hot_reload.roots,
|
||||
node_paths,
|
||||
attr_paths,
|
||||
};
|
||||
let key = hot_reload
|
||||
.key
|
||||
.as_ref()
|
||||
|
@ -259,7 +250,7 @@ impl DynamicValuePool {
|
|||
.map(|attr| self.render_attribute(attr))
|
||||
.collect();
|
||||
|
||||
VNode::new(key, template, dynamic_nodes, dynamic_attrs)
|
||||
VNode::new(key, hot_reload.template, dynamic_nodes, dynamic_attrs)
|
||||
}
|
||||
|
||||
fn render_dynamic_node(&mut self, node: &HotReloadDynamicNode) -> DynamicNode {
|
||||
|
@ -318,8 +309,8 @@ pub struct HotReloadTemplateWithLocation {
|
|||
#[doc(hidden)]
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "'de: 'static")))]
|
||||
pub struct HotReloadedTemplate {
|
||||
pub name: &'static str,
|
||||
pub key: Option<FmtedSegments>,
|
||||
pub dynamic_nodes: Vec<HotReloadDynamicNode>,
|
||||
pub dynamic_attributes: Vec<HotReloadDynamicAttribute>,
|
||||
|
@ -329,28 +320,37 @@ pub struct HotReloadedTemplate {
|
|||
serde(deserialize_with = "crate::nodes::deserialize_leaky")
|
||||
)]
|
||||
pub roots: &'static [TemplateNode],
|
||||
/// The template that is computed from the hot reload roots
|
||||
template: Template,
|
||||
}
|
||||
|
||||
impl HotReloadedTemplate {
|
||||
pub fn new(
|
||||
name: &'static str,
|
||||
key: Option<FmtedSegments>,
|
||||
dynamic_nodes: Vec<HotReloadDynamicNode>,
|
||||
dynamic_attributes: Vec<HotReloadDynamicAttribute>,
|
||||
component_values: Vec<HotReloadLiteral>,
|
||||
roots: &'static [TemplateNode],
|
||||
) -> Self {
|
||||
let node_paths = Self::node_paths(roots);
|
||||
let attr_paths = Self::attr_paths(roots);
|
||||
|
||||
let template = Template {
|
||||
roots,
|
||||
node_paths,
|
||||
attr_paths,
|
||||
};
|
||||
Self {
|
||||
name,
|
||||
key,
|
||||
dynamic_nodes,
|
||||
dynamic_attributes,
|
||||
component_values,
|
||||
roots,
|
||||
template,
|
||||
}
|
||||
}
|
||||
|
||||
fn node_paths(&self) -> &'static [&'static [u8]] {
|
||||
fn node_paths(roots: &'static [TemplateNode]) -> &'static [&'static [u8]] {
|
||||
fn add_node_paths(
|
||||
roots: &[TemplateNode],
|
||||
node_paths: &mut Vec<&'static [u8]>,
|
||||
|
@ -373,12 +373,12 @@ impl HotReloadedTemplate {
|
|||
}
|
||||
|
||||
let mut node_paths = Vec::new();
|
||||
add_node_paths(self.roots, &mut node_paths, Vec::new());
|
||||
add_node_paths(roots, &mut node_paths, Vec::new());
|
||||
let leaked: &'static [&'static [u8]] = Box::leak(node_paths.into_boxed_slice());
|
||||
leaked
|
||||
}
|
||||
|
||||
fn attr_paths(&self) -> &'static [&'static [u8]] {
|
||||
fn attr_paths(roots: &'static [TemplateNode]) -> &'static [&'static [u8]] {
|
||||
fn add_attr_paths(
|
||||
roots: &[TemplateNode],
|
||||
attr_paths: &mut Vec<&'static [u8]>,
|
||||
|
@ -403,7 +403,7 @@ impl HotReloadedTemplate {
|
|||
}
|
||||
|
||||
let mut attr_paths = Vec::new();
|
||||
add_attr_paths(self.roots, &mut attr_paths, Vec::new());
|
||||
add_attr_paths(roots, &mut attr_paths, Vec::new());
|
||||
let leaked: &'static [&'static [u8]] = Box::leak(attr_paths.into_boxed_slice());
|
||||
leaked
|
||||
}
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::{arena::ElementId, AttributeValue, ScopeId, Template};
|
||||
use crate::{arena::ElementId, AttributeValue, Template};
|
||||
|
||||
/// Something that can handle the mutations that are generated by the diffing process and apply them to the Real DOM
|
||||
///
|
||||
|
@ -14,9 +12,6 @@ use crate::{arena::ElementId, AttributeValue, ScopeId, Template};
|
|||
///
|
||||
/// Mutations are the only link between the RealDOM and the VirtualDOM.
|
||||
pub trait WriteMutations {
|
||||
/// Register a template with the renderer
|
||||
fn register_template(&mut self, template: Template);
|
||||
|
||||
/// Add these m children to the target element
|
||||
///
|
||||
/// Id: The ID of the element being mounted to
|
||||
|
@ -45,15 +40,6 @@ pub trait WriteMutations {
|
|||
/// Id: The ID we're assigning to this specific text nodes. This will be used later to modify the element or replace it with another element.
|
||||
fn create_text_node(&mut self, value: &str, id: ElementId);
|
||||
|
||||
/// Hydrate an existing text node at the given path with the given text.
|
||||
///
|
||||
/// Assign this text node the given ID since we will likely need to modify this text at a later point
|
||||
///
|
||||
/// Path: The path of the child of the topmost node on the stack. A path of `[]` represents the topmost node. A path of `[0]` represents the first child. `[0,1,2]` represents 1st child's 2nd child's 3rd child.
|
||||
/// Value: The value of the textnode that we want to set the placeholder with
|
||||
/// Id: The ID we're assigning to this specific text nodes. This will be used later to modify the element or replace it with another element.
|
||||
fn hydrate_text_node(&mut self, path: &'static [u8], value: &str, id: ElementId);
|
||||
|
||||
/// Load and clone an existing node from a template saved under that specific name
|
||||
///
|
||||
/// Dioxus guarantees that the renderer will have already been provided the template.
|
||||
|
@ -62,7 +48,7 @@ pub trait WriteMutations {
|
|||
/// Name: The unique "name" of the template based on the template location. When paired with `rsx!`, this is autogenerated
|
||||
/// Index: The index root we loading from the template. The template is stored as a list of nodes. This index represents the position of that root
|
||||
/// Id: The ID we're assigning to this element being loaded from the template (This will be used later to move the element around in lists)
|
||||
fn load_template(&mut self, name: &'static str, index: usize, id: ElementId);
|
||||
fn load_template(&mut self, template: Template, index: usize, id: ElementId);
|
||||
|
||||
/// Replace the target element (given by its ID) with the topmost m nodes on the stack
|
||||
///
|
||||
|
@ -129,12 +115,6 @@ pub trait WriteMutations {
|
|||
///
|
||||
/// Id: The ID of the root node to push.
|
||||
fn push_root(&mut self, id: ElementId);
|
||||
|
||||
/// Swap to a new subtree
|
||||
fn swap_subtree(&mut self, _subtree_index: usize) {}
|
||||
|
||||
/// Mark a scope as dirty
|
||||
fn mark_scope_dirty(&mut self, _scope_id: ScopeId) {}
|
||||
}
|
||||
|
||||
/// A `Mutation` represents a single instruction for the renderer to use to modify the UI tree to match the state
|
||||
|
@ -156,8 +136,6 @@ pub enum Mutation {
|
|||
///
|
||||
/// The path is in the form of a list of indices based on children. Templates cannot have more than 255 children per
|
||||
/// element, hence the use of a single byte.
|
||||
///
|
||||
///
|
||||
AssignId {
|
||||
/// The path of the child of the topmost node on the stack
|
||||
///
|
||||
|
@ -192,33 +170,11 @@ pub enum Mutation {
|
|||
id: ElementId,
|
||||
},
|
||||
|
||||
/// Hydrate an existing text node at the given path with the given text.
|
||||
///
|
||||
/// Assign this text node the given ID since we will likely need to modify this text at a later point
|
||||
HydrateText {
|
||||
/// The path of the child of the topmost node on the stack
|
||||
///
|
||||
/// A path of `[]` represents the topmost node. A path of `[0]` represents the first child.
|
||||
/// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
|
||||
path: &'static [u8],
|
||||
|
||||
/// The value of the textnode that we want to set the placeholder with
|
||||
value: String,
|
||||
|
||||
/// The ID we're assigning to this specific text nodes
|
||||
///
|
||||
/// This will be used later to modify the element or replace it with another element.
|
||||
id: ElementId,
|
||||
},
|
||||
|
||||
/// Load and clone an existing node from a template saved under that specific name
|
||||
/// Load and clone an existing node from a template with a given ID
|
||||
///
|
||||
/// Dioxus guarantees that the renderer will have already been provided the template.
|
||||
/// When the template is picked up in the template list, it should be saved under its "name" - here, the name
|
||||
LoadTemplate {
|
||||
/// The "name" of the template. When paired with `rsx!`, this is autogenerated
|
||||
name: &'static str,
|
||||
|
||||
/// Which root are we loading from the template?
|
||||
///
|
||||
/// The template is stored as a list of nodes. This index represents the position of that root
|
||||
|
@ -328,38 +284,11 @@ pub enum Mutation {
|
|||
/// A static list of mutations that can be applied to the DOM. Note: this list does not contain any `Any` attribute values
|
||||
#[derive(Debug, PartialEq, Default)]
|
||||
pub struct Mutations {
|
||||
/// The list of Scopes that were diffed, created, and removed during the Diff process.
|
||||
pub dirty_scopes: FxHashSet<ScopeId>,
|
||||
|
||||
/// Any templates encountered while diffing the DOM.
|
||||
///
|
||||
/// These must be loaded into a cache before applying the edits
|
||||
pub templates: Vec<Template>,
|
||||
|
||||
/// Any mutations required to patch the renderer to match the layout of the VirtualDom
|
||||
pub edits: Vec<Mutation>,
|
||||
}
|
||||
|
||||
impl Mutations {
|
||||
/// Rewrites IDs to just be "template", so you can compare the mutations
|
||||
///
|
||||
/// Used really only for testing
|
||||
pub fn sanitize(mut self) -> Self {
|
||||
for edit in self.edits.iter_mut() {
|
||||
if let Mutation::LoadTemplate { name, .. } = edit {
|
||||
*name = "template"
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl WriteMutations for Mutations {
|
||||
fn register_template(&mut self, template: Template) {
|
||||
self.templates.push(template)
|
||||
}
|
||||
|
||||
fn append_children(&mut self, id: ElementId, m: usize) {
|
||||
self.edits.push(Mutation::AppendChildren { id, m })
|
||||
}
|
||||
|
@ -379,16 +308,8 @@ impl WriteMutations for Mutations {
|
|||
})
|
||||
}
|
||||
|
||||
fn hydrate_text_node(&mut self, path: &'static [u8], value: &str, id: ElementId) {
|
||||
self.edits.push(Mutation::HydrateText {
|
||||
path,
|
||||
value: value.into(),
|
||||
id,
|
||||
})
|
||||
}
|
||||
|
||||
fn load_template(&mut self, name: &'static str, index: usize, id: ElementId) {
|
||||
self.edits.push(Mutation::LoadTemplate { name, index, id })
|
||||
fn load_template(&mut self, _template: Template, index: usize, id: ElementId) {
|
||||
self.edits.push(Mutation::LoadTemplate { index, id })
|
||||
}
|
||||
|
||||
fn replace_node_with(&mut self, id: ElementId, m: usize) {
|
||||
|
@ -457,20 +378,12 @@ impl WriteMutations for Mutations {
|
|||
fn push_root(&mut self, id: ElementId) {
|
||||
self.edits.push(Mutation::PushRoot { id })
|
||||
}
|
||||
|
||||
fn swap_subtree(&mut self, _subtree_index: usize) {}
|
||||
|
||||
fn mark_scope_dirty(&mut self, scope_id: ScopeId) {
|
||||
self.dirty_scopes.insert(scope_id);
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct that ignores all mutations
|
||||
pub struct NoOpMutations;
|
||||
|
||||
impl WriteMutations for NoOpMutations {
|
||||
fn register_template(&mut self, _: Template) {}
|
||||
|
||||
fn append_children(&mut self, _: ElementId, _: usize) {}
|
||||
|
||||
fn assign_node_id(&mut self, _: &'static [u8], _: ElementId) {}
|
||||
|
@ -479,9 +392,7 @@ impl WriteMutations for NoOpMutations {
|
|||
|
||||
fn create_text_node(&mut self, _: &str, _: ElementId) {}
|
||||
|
||||
fn hydrate_text_node(&mut self, _: &'static [u8], _: &str, _: ElementId) {}
|
||||
|
||||
fn load_template(&mut self, _: &'static str, _: usize, _: ElementId) {}
|
||||
fn load_template(&mut self, _: Template, _: usize, _: ElementId) {}
|
||||
|
||||
fn replace_node_with(&mut self, _: ElementId, _: usize) {}
|
||||
|
||||
|
|
|
@ -15,8 +15,6 @@ use std::{
|
|||
fmt::{Arguments, Debug},
|
||||
};
|
||||
|
||||
pub type TemplateId = &'static str;
|
||||
|
||||
/// The actual state of the component's most recent computation
|
||||
///
|
||||
/// If the component returned early (e.g. `return None`), this will be Aborted(None)
|
||||
|
@ -252,7 +250,6 @@ impl VNode {
|
|||
dynamic_nodes: Box::new([DynamicNode::Placeholder(Default::default())]),
|
||||
dynamic_attrs: Box::new([]),
|
||||
template: Template {
|
||||
name: "packages/core/nodes.rs:198:0:0",
|
||||
roots: &[TemplateNode::Dynamic { id: 0 }],
|
||||
node_paths: &[&[0]],
|
||||
attr_paths: &[],
|
||||
|
@ -345,17 +342,9 @@ impl VNode {
|
|||
/// For this to work properly, the [`Template::name`] *must* be unique across your entire project. This can be done via variety of
|
||||
/// ways, with the suggested approach being the unique code location (file, line, col, etc).
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "serialize", serde(bound(deserialize = "'de: 'static")))]
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialOrd, Ord)]
|
||||
pub struct Template {
|
||||
/// The name of the template. This must be unique across your entire program for template diffing to work properly
|
||||
///
|
||||
/// If two templates have the same name, it's likely that Dioxus will panic when diffing.
|
||||
#[cfg_attr(
|
||||
feature = "serialize",
|
||||
serde(deserialize_with = "deserialize_string_leaky")
|
||||
)]
|
||||
pub name: &'static str,
|
||||
|
||||
/// The list of template nodes that make up the template
|
||||
///
|
||||
/// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm.
|
||||
|
@ -383,6 +372,22 @@ pub struct Template {
|
|||
pub attr_paths: &'static [&'static [u8]],
|
||||
}
|
||||
|
||||
impl std::hash::Hash for Template {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
std::ptr::hash(self.roots as *const _, state);
|
||||
std::ptr::hash(self.node_paths as *const _, state);
|
||||
std::ptr::hash(self.attr_paths as *const _, state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Template {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
std::ptr::eq(self.roots as *const _, other.roots as *const _)
|
||||
&& std::ptr::eq(self.node_paths as *const _, other.node_paths as *const _)
|
||||
&& std::ptr::eq(self.attr_paths as *const _, other.attr_paths as *const _)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
pub(crate) fn deserialize_string_leaky<'a, 'de, D>(deserializer: D) -> Result<&'a str, D::Error>
|
||||
where
|
||||
|
@ -442,13 +447,6 @@ impl Template {
|
|||
use TemplateNode::*;
|
||||
self.roots.iter().all(|root| matches!(root, Dynamic { .. }))
|
||||
}
|
||||
|
||||
/// Get a unique id for this template. If the id between two templates are different, the contents of the template may be different.
|
||||
pub fn id(&self) -> usize {
|
||||
// We compare the template name by pointer so that the id is different after hot reloading even if the name is the same
|
||||
let ptr: *const str = self.name;
|
||||
ptr as *const () as usize
|
||||
}
|
||||
}
|
||||
|
||||
/// A statically known node in a layout.
|
||||
|
|
|
@ -5,7 +5,6 @@ use crate::{prelude::*, properties::RootProps, DynamicNode, VComponent};
|
|||
#[allow(clippy::let_and_return)]
|
||||
pub(crate) fn RootScopeWrapper(props: RootProps<VComponent>) -> Element {
|
||||
static TEMPLATE: Template = Template {
|
||||
name: "root_wrapper.rs:16:5:561",
|
||||
roots: &[TemplateNode::Dynamic { id: 0usize }],
|
||||
node_paths: &[&[0u8]],
|
||||
attr_paths: &[],
|
||||
|
|
|
@ -11,14 +11,12 @@ use crate::{
|
|||
ElementRef, NoOpMutations, SchedulerMsg, ScopeOrder, ScopeState, VNodeMount, VProps,
|
||||
WriteMutations,
|
||||
},
|
||||
nodes::{Template, TemplateId},
|
||||
runtime::{Runtime, RuntimeGuard},
|
||||
scopes::ScopeId,
|
||||
AttributeValue, ComponentFunction, Element, Event, Mutations,
|
||||
};
|
||||
use crate::{Task, VComponent};
|
||||
use futures_util::StreamExt;
|
||||
use rustc_hash::FxHashSet;
|
||||
use slab::Slab;
|
||||
use std::collections::BTreeSet;
|
||||
use std::{any::Any, rc::Rc};
|
||||
|
@ -208,12 +206,6 @@ pub struct VirtualDom {
|
|||
|
||||
pub(crate) dirty_scopes: BTreeSet<ScopeOrder>,
|
||||
|
||||
// A map of templates we have sent to the renderer
|
||||
pub(crate) templates: FxHashSet<TemplateId>,
|
||||
|
||||
// Templates changes that are queued for the next render
|
||||
pub(crate) queued_templates: Vec<Template>,
|
||||
|
||||
// The element ids that are used in the renderer
|
||||
// These mark a specific place in a whole rsx block
|
||||
pub(crate) elements: Slab<Option<ElementRef>>,
|
||||
|
@ -336,8 +328,6 @@ impl VirtualDom {
|
|||
runtime: Runtime::new(tx),
|
||||
scopes: Default::default(),
|
||||
dirty_scopes: Default::default(),
|
||||
templates: Default::default(),
|
||||
queued_templates: Default::default(),
|
||||
elements: Default::default(),
|
||||
mounts: Default::default(),
|
||||
resolved_scopes: Default::default(),
|
||||
|
@ -612,7 +602,6 @@ impl VirtualDom {
|
|||
/// ```
|
||||
#[instrument(skip(self, to), level = "trace", name = "VirtualDom::rebuild")]
|
||||
pub fn rebuild(&mut self, to: &mut impl WriteMutations) {
|
||||
self.flush_templates(to);
|
||||
let _runtime = RuntimeGuard::new(self.runtime.clone());
|
||||
let new_nodes = self.run_scope(ScopeId::ROOT);
|
||||
|
||||
|
@ -628,8 +617,6 @@ impl VirtualDom {
|
|||
/// suspended subtrees.
|
||||
#[instrument(skip(self, to), level = "trace", name = "VirtualDom::render_immediate")]
|
||||
pub fn render_immediate(&mut self, to: &mut impl WriteMutations) {
|
||||
self.flush_templates(to);
|
||||
|
||||
// Process any events that might be pending in the queue
|
||||
// Signals marked with .write() need a chance to be handled by the effect driver
|
||||
// This also processes futures which might progress into immediately rerunning a scope
|
||||
|
@ -785,14 +772,6 @@ impl VirtualDom {
|
|||
self.runtime.clone()
|
||||
}
|
||||
|
||||
/// Flush any queued template changes
|
||||
#[instrument(skip(self, to), level = "trace", name = "VirtualDom::flush_templates")]
|
||||
fn flush_templates(&mut self, to: &mut impl WriteMutations) {
|
||||
for template in self.queued_templates.drain(..) {
|
||||
to.register_template(template);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------
|
||||
The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when
|
||||
|
|
|
@ -21,18 +21,18 @@ fn attrs_cycle() {
|
|||
});
|
||||
|
||||
assert_eq!(
|
||||
dom.rebuild_to_vec().sanitize().edits,
|
||||
dom.rebuild_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
||||
LoadTemplate { index: 0, id: ElementId(1,) },
|
||||
AppendChildren { m: 1, id: ElementId(0) },
|
||||
]
|
||||
);
|
||||
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
|
||||
LoadTemplate { index: 0, id: ElementId(2,) },
|
||||
AssignId { path: &[0,], id: ElementId(3,) },
|
||||
SetAttribute { name: "class", value: "1".into_value(), id: ElementId(3,), ns: None },
|
||||
SetAttribute { name: "id", value: "1".into_value(), id: ElementId(3,), ns: None },
|
||||
|
@ -42,18 +42,18 @@ fn attrs_cycle() {
|
|||
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||
LoadTemplate { index: 0, id: ElementId(1) },
|
||||
ReplaceWith { id: ElementId(2), m: 1 }
|
||||
]
|
||||
);
|
||||
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2) },
|
||||
LoadTemplate { index: 0, id: ElementId(2) },
|
||||
AssignId { path: &[0], id: ElementId(3) },
|
||||
SetAttribute {
|
||||
name: "class",
|
||||
|
@ -74,9 +74,9 @@ fn attrs_cycle() {
|
|||
// we take the node taken by attributes since we reused it
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||
LoadTemplate { index: 0, id: ElementId(1) },
|
||||
ReplaceWith { id: ElementId(2), m: 1 }
|
||||
]
|
||||
);
|
||||
|
|
|
@ -6,9 +6,9 @@ fn bool_test() {
|
|||
let mut app = VirtualDom::new(|| rsx!(div { hidden: false }));
|
||||
|
||||
assert_eq!(
|
||||
app.rebuild_to_vec().sanitize().edits,
|
||||
app.rebuild_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||
LoadTemplate { index: 0, id: ElementId(1) },
|
||||
SetAttribute {
|
||||
name: "hidden",
|
||||
value: dioxus_core::AttributeValue::Bool(false),
|
||||
|
|
|
@ -19,7 +19,7 @@ fn bubbles_error() {
|
|||
let mut dom = VirtualDom::new(app);
|
||||
|
||||
{
|
||||
let _edits = dom.rebuild_to_vec().sanitize();
|
||||
let _edits = dom.rebuild_to_vec();
|
||||
}
|
||||
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
|
|
|
@ -10,7 +10,7 @@ async fn child_futures_drop_first() {
|
|||
|
||||
fn app() -> Element {
|
||||
if generation() == 0 {
|
||||
rsx! {Child {}}
|
||||
rsx! { Child {} }
|
||||
} else {
|
||||
rsx! {}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ fn state_shares() {
|
|||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
assert_eq!(
|
||||
dom.rebuild_to_vec().sanitize().edits,
|
||||
dom.rebuild_to_vec().edits,
|
||||
[
|
||||
CreateTextNode { value: "Value is 0".to_string(), id: ElementId(1,) },
|
||||
AppendChildren { m: 1, id: ElementId(0) },
|
||||
|
@ -41,7 +41,7 @@ fn state_shares() {
|
|||
|
||||
dom.mark_dirty(ScopeId(ScopeId::APP.0 + 2));
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[SetText { value: "Value is 2".to_string(), id: ElementId(1,) },]
|
||||
);
|
||||
|
||||
|
@ -49,7 +49,7 @@ fn state_shares() {
|
|||
dom.mark_dirty(ScopeId(ScopeId::APP.0 + 2));
|
||||
let edits = dom.render_immediate_to_vec();
|
||||
assert_eq!(
|
||||
edits.sanitize().edits,
|
||||
edits.edits,
|
||||
[SetText { value: "Value is 3".to_string(), id: ElementId(1,) },]
|
||||
);
|
||||
}
|
||||
|
|
|
@ -17,13 +17,13 @@ fn test_original_diff() {
|
|||
}
|
||||
});
|
||||
|
||||
let edits = dom.rebuild_to_vec().sanitize();
|
||||
let edits = dom.rebuild_to_vec();
|
||||
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
// add to root
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||
LoadTemplate { index: 0, id: ElementId(1) },
|
||||
AppendChildren { m: 1, id: ElementId(0) }
|
||||
]
|
||||
)
|
||||
|
@ -46,7 +46,7 @@ fn create() {
|
|||
}
|
||||
});
|
||||
|
||||
let _edits = dom.rebuild_to_vec().sanitize();
|
||||
let _edits = dom.rebuild_to_vec();
|
||||
|
||||
// todo: we don't test template mutations anymore since the templates are passed along
|
||||
|
||||
|
@ -64,11 +64,11 @@ fn create() {
|
|||
// AppendChildren { m: 1 },
|
||||
// AppendChildren { m: 2 },
|
||||
// AppendChildren { m: 1 },
|
||||
// SaveTemplate { name: "template", m: 1 },
|
||||
// SaveTemplate { m: 1 },
|
||||
// // The fragment child template
|
||||
// CreateStaticText { value: "hello" },
|
||||
// CreateStaticText { value: "world" },
|
||||
// SaveTemplate { name: "template", m: 2 },
|
||||
// SaveTemplate { m: 2 },
|
||||
// ]
|
||||
// );
|
||||
}
|
||||
|
@ -77,7 +77,7 @@ fn create() {
|
|||
fn create_list() {
|
||||
let mut dom = VirtualDom::new(|| rsx! {{(0..3).map(|f| rsx!( div { "hello" } ))}});
|
||||
|
||||
let _edits = dom.rebuild_to_vec().sanitize();
|
||||
let _edits = dom.rebuild_to_vec();
|
||||
|
||||
// note: we dont test template edits anymore
|
||||
// assert_eq!(
|
||||
|
@ -87,7 +87,7 @@ fn create_list() {
|
|||
// CreateElement { name: "div" },
|
||||
// CreateStaticText { value: "hello" },
|
||||
// AppendChildren { m: 1 },
|
||||
// SaveTemplate { name: "template", m: 1 }
|
||||
// SaveTemplate { m: 1 }
|
||||
// ]
|
||||
// );
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ fn create_simple() {
|
|||
}
|
||||
});
|
||||
|
||||
let edits = dom.rebuild_to_vec().sanitize();
|
||||
let edits = dom.rebuild_to_vec();
|
||||
|
||||
// note: we dont test template edits anymore
|
||||
// assert_eq!(
|
||||
|
@ -115,7 +115,7 @@ fn create_simple() {
|
|||
// CreateElement { name: "div" },
|
||||
// CreateElement { name: "div" },
|
||||
// // add to root
|
||||
// SaveTemplate { name: "template", m: 4 }
|
||||
// SaveTemplate { m: 4 }
|
||||
// ]
|
||||
// );
|
||||
}
|
||||
|
@ -142,7 +142,7 @@ fn create_components() {
|
|||
}
|
||||
}
|
||||
|
||||
let _edits = dom.rebuild_to_vec().sanitize();
|
||||
let _edits = dom.rebuild_to_vec();
|
||||
|
||||
// todo: test this
|
||||
}
|
||||
|
@ -161,7 +161,7 @@ fn anchors() {
|
|||
});
|
||||
|
||||
// note that the template under "false" doesn't show up since it's not loaded
|
||||
let edits = dom.rebuild_to_vec().sanitize();
|
||||
let edits = dom.rebuild_to_vec();
|
||||
|
||||
// note: we dont test template edits anymore
|
||||
// assert_eq!(
|
||||
|
@ -178,7 +178,7 @@ fn anchors() {
|
|||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||
LoadTemplate { index: 0, id: ElementId(1) },
|
||||
CreatePlaceholder { id: ElementId(2) },
|
||||
AppendChildren { m: 2, id: ElementId(0) }
|
||||
]
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
// use dioxus::dioxus_core::Mutation::*;
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn multiroot() {
|
||||
let mut dom = VirtualDom::new(|| {
|
||||
rsx! {
|
||||
div { "Hello a" }
|
||||
div { "Hello b" }
|
||||
div { "Hello c" }
|
||||
}
|
||||
});
|
||||
|
||||
// note: we dont test template edits anymore
|
||||
let _templates = dom.rebuild_to_vec().sanitize().templates;
|
||||
|
||||
// assert_eq!(
|
||||
// dom.rebuild_to_vec().sanitize().templates,
|
||||
// [
|
||||
// CreateElement { name: "div" },
|
||||
// CreateStaticText { value: "Hello a" },
|
||||
// AppendChildren { m: 1 },
|
||||
// CreateElement { name: "div" },
|
||||
// CreateStaticText { value: "Hello b" },
|
||||
// AppendChildren { m: 1 },
|
||||
// CreateElement { name: "div" },
|
||||
// CreateStaticText { value: "Hello c" },
|
||||
// AppendChildren { m: 1 },
|
||||
// SaveTemplate { name: "template", m: 3 }
|
||||
// ]
|
||||
// )
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
use dioxus::dioxus_core::Mutation::*;
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_core::ElementId;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
// A real-world usecase of templates at peak performance
|
||||
// In react, this would be a lot of node creation.
|
||||
|
@ -25,7 +26,7 @@ fn app() -> Element {
|
|||
fn list_renders() {
|
||||
let mut dom = VirtualDom::new(app);
|
||||
|
||||
let edits = dom.rebuild_to_vec().sanitize();
|
||||
let edits = dom.rebuild_to_vec();
|
||||
|
||||
// note: we dont test template edits anymore
|
||||
// assert_eq!(
|
||||
|
@ -37,7 +38,7 @@ fn list_renders() {
|
|||
// // append when modify the values (IE no need for a placeholder)
|
||||
// CreateStaticPlaceholder,
|
||||
// AppendChildren { m: 1 },
|
||||
// SaveTemplate { name: "template", m: 1 },
|
||||
// SaveTemplate { m: 1 },
|
||||
// // Create the inner template div
|
||||
// CreateElement { name: "div" },
|
||||
// CreateElement { name: "h1" },
|
||||
|
@ -47,7 +48,7 @@ fn list_renders() {
|
|||
// CreateTextPlaceholder,
|
||||
// AppendChildren { m: 1 },
|
||||
// AppendChildren { m: 2 },
|
||||
// SaveTemplate { name: "template", m: 1 }
|
||||
// SaveTemplate { m: 1 }
|
||||
// ],
|
||||
// );
|
||||
|
||||
|
@ -55,14 +56,17 @@ fn list_renders() {
|
|||
edits.edits,
|
||||
[
|
||||
// Load the outer div
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||
LoadTemplate { index: 0, id: ElementId(1) },
|
||||
// Load each template one-by-one, rehydrating it
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2) },
|
||||
HydrateText { path: &[1, 0], value: "0".to_string(), id: ElementId(3) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(4) },
|
||||
HydrateText { path: &[1, 0], value: "1".to_string(), id: ElementId(5) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(6) },
|
||||
HydrateText { path: &[1, 0], value: "2".to_string(), id: ElementId(7) },
|
||||
LoadTemplate { index: 0, id: ElementId(2) },
|
||||
CreateTextNode { value: "0".to_string(), id: ElementId(3) },
|
||||
ReplacePlaceholder { path: &[1, 0], m: 1 },
|
||||
LoadTemplate { index: 0, id: ElementId(4) },
|
||||
CreateTextNode { value: "1".to_string(), id: ElementId(5) },
|
||||
ReplacePlaceholder { path: &[1, 0], m: 1 },
|
||||
LoadTemplate { index: 0, id: ElementId(6) },
|
||||
CreateTextNode { value: "2".to_string(), id: ElementId(7) },
|
||||
ReplacePlaceholder { path: &[1, 0], m: 1 },
|
||||
// Replace the 0th childn on the div with the 3 templates on the stack
|
||||
ReplacePlaceholder { m: 3, path: &[0] },
|
||||
// Append the container div to the dom
|
||||
|
|
|
@ -21,12 +21,12 @@ fn nested_passthru_creates() {
|
|||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let edits = dom.rebuild_to_vec().sanitize();
|
||||
let edits = dom.rebuild_to_vec();
|
||||
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||
LoadTemplate { index: 0, id: ElementId(1) },
|
||||
AppendChildren { m: 1, id: ElementId(0) },
|
||||
]
|
||||
)
|
||||
|
@ -60,16 +60,16 @@ fn nested_passthru_creates_add() {
|
|||
let mut dom = VirtualDom::new(app);
|
||||
|
||||
assert_eq!(
|
||||
dom.rebuild_to_vec().sanitize().edits,
|
||||
dom.rebuild_to_vec().edits,
|
||||
[
|
||||
// load 1
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||
LoadTemplate { index: 0, id: ElementId(1) },
|
||||
// load 2
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2) },
|
||||
LoadTemplate { index: 0, id: ElementId(2) },
|
||||
// load 3
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(3) },
|
||||
LoadTemplate { index: 0, id: ElementId(3) },
|
||||
// load div that contains 4
|
||||
LoadTemplate { name: "template", index: 1, id: ElementId(4) },
|
||||
LoadTemplate { index: 1, id: ElementId(4) },
|
||||
AppendChildren { id: ElementId(0), m: 4 },
|
||||
]
|
||||
);
|
||||
|
@ -85,11 +85,9 @@ fn dynamic_node_as_root() {
|
|||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let edits = dom.rebuild_to_vec().sanitize();
|
||||
let edits = dom.rebuild_to_vec();
|
||||
|
||||
// Since the roots were all dynamic, they should not cause any template muations
|
||||
assert!(edits.templates.is_empty());
|
||||
|
||||
// The root node is text, so we just create it on the spot
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
|
|
|
@ -11,11 +11,11 @@ fn cycling_elements() {
|
|||
});
|
||||
|
||||
{
|
||||
let edits = dom.rebuild_to_vec().sanitize();
|
||||
let edits = dom.rebuild_to_vec();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
||||
LoadTemplate { index: 0, id: ElementId(1,) },
|
||||
AppendChildren { m: 1, id: ElementId(0) },
|
||||
]
|
||||
);
|
||||
|
@ -23,9 +23,9 @@ fn cycling_elements() {
|
|||
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
|
||||
LoadTemplate { index: 0, id: ElementId(2,) },
|
||||
ReplaceWith { id: ElementId(1,), m: 1 },
|
||||
]
|
||||
);
|
||||
|
@ -33,18 +33,18 @@ fn cycling_elements() {
|
|||
// notice that the IDs cycle back to ElementId(1), preserving a minimal memory footprint
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
||||
LoadTemplate { index: 0, id: ElementId(1,) },
|
||||
ReplaceWith { id: ElementId(2,), m: 1 },
|
||||
]
|
||||
);
|
||||
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
|
||||
LoadTemplate { index: 0, id: ElementId(2,) },
|
||||
ReplaceWith { id: ElementId(1,), m: 1 },
|
||||
]
|
||||
);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use dioxus::dioxus_core::{ElementId, Mutation::*};
|
||||
use dioxus::prelude::*;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
/// When returning sets of components, we do a light diff of the contents to preserve some react-like functionality
|
||||
///
|
||||
|
@ -7,6 +8,15 @@ use dioxus::prelude::*;
|
|||
/// different pointers
|
||||
#[test]
|
||||
fn component_swap() {
|
||||
// Check that templates with the same structure are deduplicated at compile time
|
||||
// If they are not, this test will fail because it is being run in debug mode where templates are not deduped
|
||||
let dynamic = 0;
|
||||
let template_1 = rsx! { "{dynamic}" };
|
||||
let template_2 = rsx! { "{dynamic}" };
|
||||
if template_1.unwrap().template != template_2.unwrap().template {
|
||||
return;
|
||||
}
|
||||
|
||||
fn app() -> Element {
|
||||
let mut render_phase = use_signal(|| 0);
|
||||
|
||||
|
@ -62,16 +72,16 @@ fn component_swap() {
|
|||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
{
|
||||
let edits = dom.rebuild_to_vec().sanitize();
|
||||
let edits = dom.rebuild_to_vec();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(3) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(4) },
|
||||
LoadTemplate { index: 0, id: ElementId(1) },
|
||||
LoadTemplate { index: 0, id: ElementId(2) },
|
||||
LoadTemplate { index: 0, id: ElementId(3) },
|
||||
LoadTemplate { index: 0, id: ElementId(4) },
|
||||
ReplacePlaceholder { path: &[1], m: 3 },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(5) },
|
||||
LoadTemplate { index: 0, id: ElementId(5) },
|
||||
AppendChildren { m: 2, id: ElementId(0) }
|
||||
]
|
||||
);
|
||||
|
@ -79,27 +89,27 @@ fn component_swap() {
|
|||
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(6) },
|
||||
LoadTemplate { index: 0, id: ElementId(6) },
|
||||
ReplaceWith { id: ElementId(5), m: 1 }
|
||||
]
|
||||
);
|
||||
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(5) },
|
||||
LoadTemplate { index: 0, id: ElementId(5) },
|
||||
ReplaceWith { id: ElementId(6), m: 1 }
|
||||
]
|
||||
);
|
||||
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(6) },
|
||||
LoadTemplate { index: 0, id: ElementId(6) },
|
||||
ReplaceWith { id: ElementId(5), m: 1 }
|
||||
]
|
||||
);
|
||||
|
|
|
@ -7,6 +7,7 @@ fn toggle_option_text() {
|
|||
let mut dom = VirtualDom::new(|| {
|
||||
let gen = generation();
|
||||
let text = if gen % 2 != 0 { Some("hello") } else { None };
|
||||
println!("{:?}", text);
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
|
@ -17,10 +18,11 @@ fn toggle_option_text() {
|
|||
|
||||
// load the div and then assign the None as a placeholder
|
||||
assert_eq!(
|
||||
dom.rebuild_to_vec().sanitize().edits,
|
||||
dom.rebuild_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
||||
AssignId { path: &[0], id: ElementId(2,) },
|
||||
LoadTemplate { index: 0, id: ElementId(1,) },
|
||||
CreatePlaceholder { id: ElementId(2,) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
AppendChildren { id: ElementId(0), m: 1 },
|
||||
]
|
||||
);
|
||||
|
@ -28,7 +30,7 @@ fn toggle_option_text() {
|
|||
// Rendering again should replace the placeholder with an text node
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
CreateTextNode { value: "hello".to_string(), id: ElementId(3,) },
|
||||
ReplaceWith { id: ElementId(2,), m: 1 },
|
||||
|
@ -38,7 +40,7 @@ fn toggle_option_text() {
|
|||
// Rendering again should replace the placeholder with an text node
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
CreatePlaceholder { id: ElementId(2,) },
|
||||
ReplaceWith { id: ElementId(3,), m: 1 },
|
||||
|
|
|
@ -48,36 +48,36 @@ fn element_swap() {
|
|||
|
||||
vdom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
vdom.render_immediate_to_vec().sanitize().edits,
|
||||
vdom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
|
||||
LoadTemplate { index: 0, id: ElementId(2,) },
|
||||
ReplaceWith { id: ElementId(1,), m: 1 },
|
||||
]
|
||||
);
|
||||
|
||||
vdom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
vdom.render_immediate_to_vec().sanitize().edits,
|
||||
vdom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
||||
LoadTemplate { index: 0, id: ElementId(1,) },
|
||||
ReplaceWith { id: ElementId(2,), m: 1 },
|
||||
]
|
||||
);
|
||||
|
||||
vdom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
vdom.render_immediate_to_vec().sanitize().edits,
|
||||
vdom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
|
||||
LoadTemplate { index: 0, id: ElementId(2,) },
|
||||
ReplaceWith { id: ElementId(1,), m: 1 },
|
||||
]
|
||||
);
|
||||
|
||||
vdom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
vdom.render_immediate_to_vec().sanitize().edits,
|
||||
vdom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
||||
LoadTemplate { index: 0, id: ElementId(1,) },
|
||||
ReplaceWith { id: ElementId(2,), m: 1 },
|
||||
]
|
||||
);
|
||||
|
@ -128,7 +128,7 @@ fn attribute_diff() {
|
|||
|
||||
vdom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
vdom.render_immediate_to_vec().sanitize().edits,
|
||||
vdom.render_immediate_to_vec().edits,
|
||||
[
|
||||
SetAttribute {
|
||||
name: "b",
|
||||
|
@ -147,7 +147,7 @@ fn attribute_diff() {
|
|||
|
||||
vdom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
vdom.render_immediate_to_vec().sanitize().edits,
|
||||
vdom.render_immediate_to_vec().edits,
|
||||
[
|
||||
SetAttribute { name: "a", value: AttributeValue::None, id: ElementId(1,), ns: None },
|
||||
SetAttribute { name: "b", value: AttributeValue::None, id: ElementId(1,), ns: None },
|
||||
|
@ -168,7 +168,7 @@ fn attribute_diff() {
|
|||
|
||||
vdom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
vdom.render_immediate_to_vec().sanitize().edits,
|
||||
vdom.render_immediate_to_vec().edits,
|
||||
[
|
||||
SetAttribute { name: "c", value: AttributeValue::None, id: ElementId(1,), ns: None },
|
||||
SetAttribute {
|
||||
|
@ -196,7 +196,7 @@ fn diff_empty() {
|
|||
vdom.rebuild(&mut NoOpMutations);
|
||||
|
||||
vdom.mark_dirty(ScopeId::APP);
|
||||
let edits = vdom.render_immediate_to_vec().sanitize().edits;
|
||||
let edits = vdom.render_immediate_to_vec().edits;
|
||||
|
||||
assert_eq!(
|
||||
edits,
|
||||
|
|
|
@ -22,18 +22,18 @@ fn keyed_diffing_out_of_order() {
|
|||
|
||||
{
|
||||
assert_eq!(
|
||||
dom.rebuild_to_vec().sanitize().edits,
|
||||
dom.rebuild_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(4,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(6,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(8,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(10,) },
|
||||
LoadTemplate { index: 0, id: ElementId(1,) },
|
||||
LoadTemplate { index: 0, id: ElementId(2,) },
|
||||
LoadTemplate { index: 0, id: ElementId(3,) },
|
||||
LoadTemplate { index: 0, id: ElementId(4,) },
|
||||
LoadTemplate { index: 0, id: ElementId(5,) },
|
||||
LoadTemplate { index: 0, id: ElementId(6,) },
|
||||
LoadTemplate { index: 0, id: ElementId(7,) },
|
||||
LoadTemplate { index: 0, id: ElementId(8,) },
|
||||
LoadTemplate { index: 0, id: ElementId(9,) },
|
||||
LoadTemplate { index: 0, id: ElementId(10,) },
|
||||
AppendChildren { m: 10, id: ElementId(0) },
|
||||
]
|
||||
);
|
||||
|
@ -169,10 +169,10 @@ fn keyed_diffing_additions() {
|
|||
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(6) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(7) },
|
||||
LoadTemplate { index: 0, id: ElementId(6) },
|
||||
LoadTemplate { index: 0, id: ElementId(7) },
|
||||
InsertAfter { id: ElementId(5), m: 2 }
|
||||
]
|
||||
);
|
||||
|
@ -194,11 +194,11 @@ fn keyed_diffing_additions_and_moves_on_ends() {
|
|||
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
// create 11, 12
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(5) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(6) },
|
||||
LoadTemplate { index: 0, id: ElementId(5) },
|
||||
LoadTemplate { index: 0, id: ElementId(6) },
|
||||
InsertAfter { id: ElementId(3), m: 2 },
|
||||
// move 7 to the front
|
||||
PushRoot { id: ElementId(4) },
|
||||
|
@ -224,15 +224,15 @@ fn keyed_diffing_additions_and_moves_in_middle() {
|
|||
// LIS: 4, 5, 6
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
// create 5, 6
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(5) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(6) },
|
||||
LoadTemplate { index: 0, id: ElementId(5) },
|
||||
LoadTemplate { index: 0, id: ElementId(6) },
|
||||
InsertBefore { id: ElementId(3), m: 2 },
|
||||
// create 7, 8
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(7) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(8) },
|
||||
LoadTemplate { index: 0, id: ElementId(7) },
|
||||
LoadTemplate { index: 0, id: ElementId(8) },
|
||||
InsertBefore { id: ElementId(2), m: 2 },
|
||||
// move 7
|
||||
PushRoot { id: ElementId(4) },
|
||||
|
@ -258,7 +258,7 @@ fn controlled_keyed_diffing_out_of_order() {
|
|||
// LIS: 5, 6
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
// remove 7
|
||||
Remove { id: ElementId(4,) },
|
||||
|
@ -266,10 +266,10 @@ fn controlled_keyed_diffing_out_of_order() {
|
|||
PushRoot { id: ElementId(1) },
|
||||
InsertAfter { id: ElementId(3,), m: 1 },
|
||||
// create 9 and insert before 6
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(4) },
|
||||
LoadTemplate { index: 0, id: ElementId(4) },
|
||||
InsertBefore { id: ElementId(3,), m: 1 },
|
||||
// create 0 and insert before 5
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(5) },
|
||||
LoadTemplate { index: 0, id: ElementId(5) },
|
||||
InsertBefore { id: ElementId(2,), m: 1 },
|
||||
]
|
||||
);
|
||||
|
@ -291,10 +291,10 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
|
|||
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
Remove { id: ElementId(5,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(5) },
|
||||
LoadTemplate { index: 0, id: ElementId(5) },
|
||||
InsertBefore { id: ElementId(3,), m: 1 },
|
||||
PushRoot { id: ElementId(4) },
|
||||
InsertBefore { id: ElementId(1,), m: 1 },
|
||||
|
@ -320,7 +320,7 @@ fn remove_list() {
|
|||
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
Remove { id: ElementId(5) },
|
||||
Remove { id: ElementId(4) },
|
||||
|
@ -345,11 +345,11 @@ fn no_common_keys() {
|
|||
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(4) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(5) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(6) },
|
||||
LoadTemplate { index: 0, id: ElementId(4) },
|
||||
LoadTemplate { index: 0, id: ElementId(5) },
|
||||
LoadTemplate { index: 0, id: ElementId(6) },
|
||||
Remove { id: ElementId(3) },
|
||||
Remove { id: ElementId(2) },
|
||||
ReplaceWith { id: ElementId(1), m: 3 }
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use std::collections::HashSet;
|
||||
|
||||
use dioxus::dioxus_core::{ElementId, Mutation::*};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_core::Mutation;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
#[test]
|
||||
|
@ -18,10 +21,11 @@ fn list_creates_one_by_one() {
|
|||
|
||||
// load the div and then assign the empty fragment as a placeholder
|
||||
assert_eq!(
|
||||
dom.rebuild_to_vec().sanitize().edits,
|
||||
dom.rebuild_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
||||
AssignId { path: &[0], id: ElementId(2,) },
|
||||
LoadTemplate { index: 0, id: ElementId(1,) },
|
||||
CreatePlaceholder { id: ElementId(2,) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
AppendChildren { id: ElementId(0), m: 1 },
|
||||
]
|
||||
);
|
||||
|
@ -29,10 +33,11 @@ fn list_creates_one_by_one() {
|
|||
// Rendering the first item should replace the placeholder with an element
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
|
||||
HydrateText { path: &[0], value: "0".to_string(), id: ElementId(4,) },
|
||||
LoadTemplate { index: 0, id: ElementId(3,) },
|
||||
CreateTextNode { value: "0".to_string(), id: ElementId(4,) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
ReplaceWith { id: ElementId(2,), m: 1 },
|
||||
]
|
||||
);
|
||||
|
@ -40,10 +45,11 @@ fn list_creates_one_by_one() {
|
|||
// Rendering the next item should insert after the previous
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
|
||||
HydrateText { path: &[0], value: "1".to_string(), id: ElementId(5,) },
|
||||
LoadTemplate { index: 0, id: ElementId(2,) },
|
||||
CreateTextNode { value: "1".to_string(), id: ElementId(5,) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
InsertAfter { id: ElementId(3,), m: 1 },
|
||||
]
|
||||
);
|
||||
|
@ -51,10 +57,11 @@ fn list_creates_one_by_one() {
|
|||
// ... and again!
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(6,) },
|
||||
HydrateText { path: &[0], value: "2".to_string(), id: ElementId(7,) },
|
||||
LoadTemplate { index: 0, id: ElementId(6,) },
|
||||
CreateTextNode { value: "2".to_string(), id: ElementId(7,) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
InsertAfter { id: ElementId(2,), m: 1 },
|
||||
]
|
||||
);
|
||||
|
@ -62,10 +69,11 @@ fn list_creates_one_by_one() {
|
|||
// once more
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(8,) },
|
||||
HydrateText { path: &[0], value: "3".to_string(), id: ElementId(9,) },
|
||||
LoadTemplate { index: 0, id: ElementId(8,) },
|
||||
CreateTextNode { value: "3".to_string(), id: ElementId(9,) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
InsertAfter { id: ElementId(6,), m: 1 },
|
||||
]
|
||||
);
|
||||
|
@ -87,17 +95,20 @@ fn removes_one_by_one() {
|
|||
|
||||
// load the div and then assign the empty fragment as a placeholder
|
||||
assert_eq!(
|
||||
dom.rebuild_to_vec().sanitize().edits,
|
||||
dom.rebuild_to_vec().edits,
|
||||
[
|
||||
// The container
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||
LoadTemplate { index: 0, id: ElementId(1) },
|
||||
// each list item
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2) },
|
||||
HydrateText { path: &[0], value: "0".to_string(), id: ElementId(3) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(4) },
|
||||
HydrateText { path: &[0], value: "1".to_string(), id: ElementId(5) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(6) },
|
||||
HydrateText { path: &[0], value: "2".to_string(), id: ElementId(7) },
|
||||
LoadTemplate { index: 0, id: ElementId(2) },
|
||||
CreateTextNode { value: "0".to_string(), id: ElementId(3) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
LoadTemplate { index: 0, id: ElementId(4) },
|
||||
CreateTextNode { value: "1".to_string(), id: ElementId(5) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
LoadTemplate { index: 0, id: ElementId(6) },
|
||||
CreateTextNode { value: "2".to_string(), id: ElementId(7) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
// replace the placeholder in the template with the 3 templates on the stack
|
||||
ReplacePlaceholder { m: 3, path: &[0] },
|
||||
// Mount the div
|
||||
|
@ -109,14 +120,14 @@ fn removes_one_by_one() {
|
|||
// Rendering the first item should replace the placeholder with an element
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[Remove { id: ElementId(6) }]
|
||||
);
|
||||
|
||||
// Remove div(2)
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[Remove { id: ElementId(4) }]
|
||||
);
|
||||
|
||||
|
@ -124,7 +135,7 @@ fn removes_one_by_one() {
|
|||
// todo: this should just be a remove with no placeholder
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
CreatePlaceholder { id: ElementId(4) },
|
||||
ReplaceWith { id: ElementId(2), m: 1 }
|
||||
|
@ -135,14 +146,17 @@ fn removes_one_by_one() {
|
|||
// todo: this should actually be append to, but replace placeholder is fine for now
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2) },
|
||||
HydrateText { path: &[0], value: "0".to_string(), id: ElementId(6) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(8) },
|
||||
HydrateText { path: &[0], value: "1".to_string(), id: ElementId(9) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(10) },
|
||||
HydrateText { path: &[0], value: "2".to_string(), id: ElementId(11) },
|
||||
LoadTemplate { index: 0, id: ElementId(2) },
|
||||
CreateTextNode { value: "0".to_string(), id: ElementId(6) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
LoadTemplate { index: 0, id: ElementId(8) },
|
||||
CreateTextNode { value: "1".to_string(), id: ElementId(9) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
LoadTemplate { index: 0, id: ElementId(10) },
|
||||
CreateTextNode { value: "2".to_string(), id: ElementId(11) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
ReplaceWith { id: ElementId(4), m: 3 }
|
||||
]
|
||||
);
|
||||
|
@ -162,46 +176,53 @@ fn list_shrink_multiroot() {
|
|||
});
|
||||
|
||||
assert_eq!(
|
||||
dom.rebuild_to_vec().sanitize().edits,
|
||||
dom.rebuild_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
||||
AssignId { path: &[0,], id: ElementId(2,) },
|
||||
LoadTemplate { index: 0, id: ElementId(1,) },
|
||||
CreatePlaceholder { id: ElementId(2,) },
|
||||
ReplacePlaceholder { path: &[0,], m: 1 },
|
||||
AppendChildren { id: ElementId(0), m: 1 }
|
||||
]
|
||||
);
|
||||
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(3) },
|
||||
HydrateText { path: &[0], value: "0".to_string(), id: ElementId(4) },
|
||||
LoadTemplate { name: "template", index: 1, id: ElementId(5) },
|
||||
HydrateText { path: &[0], value: "0".to_string(), id: ElementId(6) },
|
||||
LoadTemplate { index: 0, id: ElementId(3) },
|
||||
CreateTextNode { value: "0".to_string(), id: ElementId(4) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
LoadTemplate { index: 1, id: ElementId(5) },
|
||||
CreateTextNode { value: "0".to_string(), id: ElementId(6) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
ReplaceWith { id: ElementId(2), m: 2 }
|
||||
]
|
||||
);
|
||||
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2) },
|
||||
HydrateText { path: &[0], value: "1".to_string(), id: ElementId(7) },
|
||||
LoadTemplate { name: "template", index: 1, id: ElementId(8) },
|
||||
HydrateText { path: &[0], value: "1".to_string(), id: ElementId(9) },
|
||||
LoadTemplate { index: 0, id: ElementId(2) },
|
||||
CreateTextNode { value: "1".to_string(), id: ElementId(7) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
LoadTemplate { index: 1, id: ElementId(8) },
|
||||
CreateTextNode { value: "1".to_string(), id: ElementId(9) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
InsertAfter { id: ElementId(5), m: 2 }
|
||||
]
|
||||
);
|
||||
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(10) },
|
||||
HydrateText { path: &[0], value: "2".to_string(), id: ElementId(11) },
|
||||
LoadTemplate { name: "template", index: 1, id: ElementId(12) },
|
||||
HydrateText { path: &[0], value: "2".to_string(), id: ElementId(13) },
|
||||
LoadTemplate { index: 0, id: ElementId(10) },
|
||||
CreateTextNode { value: "2".to_string(), id: ElementId(11) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
LoadTemplate { index: 1, id: ElementId(12) },
|
||||
CreateTextNode { value: "2".to_string(), id: ElementId(13) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
InsertAfter { id: ElementId(8), m: 2 }
|
||||
]
|
||||
);
|
||||
|
@ -224,46 +245,48 @@ fn removes_one_by_one_multiroot() {
|
|||
|
||||
// load the div and then assign the empty fragment as a placeholder
|
||||
assert_eq!(
|
||||
dom.rebuild_to_vec().sanitize().edits,
|
||||
dom.rebuild_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||
//
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2) },
|
||||
HydrateText { path: &[0], value: "0".to_string(), id: ElementId(3) },
|
||||
LoadTemplate { name: "template", index: 1, id: ElementId(4) },
|
||||
HydrateText { path: &[0], value: "0".to_string(), id: ElementId(5) },
|
||||
//
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(6) },
|
||||
HydrateText { path: &[0], value: "1".to_string(), id: ElementId(7) },
|
||||
LoadTemplate { name: "template", index: 1, id: ElementId(8) },
|
||||
HydrateText { path: &[0], value: "1".to_string(), id: ElementId(9) },
|
||||
//
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(10) },
|
||||
HydrateText { path: &[0], value: "2".to_string(), id: ElementId(11) },
|
||||
LoadTemplate { name: "template", index: 1, id: ElementId(12) },
|
||||
HydrateText { path: &[0], value: "2".to_string(), id: ElementId(13) },
|
||||
LoadTemplate { index: 0, id: ElementId(1) },
|
||||
//
|
||||
LoadTemplate { index: 0, id: ElementId(2) },
|
||||
CreateTextNode { value: "0".to_string(), id: ElementId(3) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
LoadTemplate { index: 1, id: ElementId(4) },
|
||||
CreateTextNode { value: "0".to_string(), id: ElementId(5) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
LoadTemplate { index: 0, id: ElementId(6) },
|
||||
CreateTextNode { value: "1".to_string(), id: ElementId(7) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
LoadTemplate { index: 1, id: ElementId(8) },
|
||||
CreateTextNode { value: "1".to_string(), id: ElementId(9) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
LoadTemplate { index: 0, id: ElementId(10) },
|
||||
CreateTextNode { value: "2".to_string(), id: ElementId(11) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
LoadTemplate { index: 1, id: ElementId(12) },
|
||||
CreateTextNode { value: "2".to_string(), id: ElementId(13) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
ReplacePlaceholder { path: &[0], m: 6 },
|
||||
//
|
||||
AppendChildren { id: ElementId(0), m: 1 }
|
||||
]
|
||||
);
|
||||
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[Remove { id: ElementId(10) }, Remove { id: ElementId(12) }]
|
||||
);
|
||||
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[Remove { id: ElementId(6) }, Remove { id: ElementId(8) }]
|
||||
);
|
||||
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
assert_eq!(
|
||||
dom.render_immediate_to_vec().sanitize().edits,
|
||||
dom.render_immediate_to_vec().edits,
|
||||
[
|
||||
CreatePlaceholder { id: ElementId(8) },
|
||||
Remove { id: ElementId(2) },
|
||||
|
@ -317,9 +340,9 @@ fn remove_many() {
|
|||
}
|
||||
});
|
||||
|
||||
// len = 0
|
||||
{
|
||||
let edits = dom.rebuild_to_vec().sanitize();
|
||||
assert!(edits.templates.is_empty());
|
||||
let edits = dom.rebuild_to_vec();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
|
@ -329,62 +352,76 @@ fn remove_many() {
|
|||
);
|
||||
}
|
||||
|
||||
// len = 1
|
||||
{
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
let edits = dom.render_immediate_to_vec().sanitize();
|
||||
let edits = dom.render_immediate_to_vec();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
|
||||
HydrateText { path: &[0,], value: "hello 0".to_string(), id: ElementId(3,) },
|
||||
LoadTemplate { index: 0, id: ElementId(2,) },
|
||||
CreateTextNode { value: "hello 0".to_string(), id: ElementId(3,) },
|
||||
ReplacePlaceholder { path: &[0,], m: 1 },
|
||||
ReplaceWith { id: ElementId(1,), m: 1 },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// len = 5
|
||||
{
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
let edits = dom.render_immediate_to_vec().sanitize();
|
||||
let edits = dom.render_immediate_to_vec();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
||||
HydrateText { path: &[0,], value: "hello 1".to_string(), id: ElementId(4,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
|
||||
HydrateText { path: &[0,], value: "hello 2".to_string(), id: ElementId(6,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
|
||||
HydrateText { path: &[0,], value: "hello 3".to_string(), id: ElementId(8,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
|
||||
HydrateText { path: &[0,], value: "hello 4".to_string(), id: ElementId(10,) },
|
||||
LoadTemplate { index: 0, id: ElementId(1,) },
|
||||
CreateTextNode { value: "hello 1".to_string(), id: ElementId(4,) },
|
||||
ReplacePlaceholder { path: &[0,], m: 1 },
|
||||
LoadTemplate { index: 0, id: ElementId(5,) },
|
||||
CreateTextNode { value: "hello 2".to_string(), id: ElementId(6,) },
|
||||
ReplacePlaceholder { path: &[0,], m: 1 },
|
||||
LoadTemplate { index: 0, id: ElementId(7,) },
|
||||
CreateTextNode { value: "hello 3".to_string(), id: ElementId(8,) },
|
||||
ReplacePlaceholder { path: &[0,], m: 1 },
|
||||
LoadTemplate { index: 0, id: ElementId(9,) },
|
||||
CreateTextNode { value: "hello 4".to_string(), id: ElementId(10,) },
|
||||
ReplacePlaceholder { path: &[0,], m: 1 },
|
||||
InsertAfter { id: ElementId(2,), m: 4 },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// len = 0
|
||||
{
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
let edits = dom.render_immediate_to_vec().sanitize();
|
||||
let edits = dom.render_immediate_to_vec();
|
||||
assert_eq!(edits.edits[0], CreatePlaceholder { id: ElementId(11,) });
|
||||
let removed = edits.edits[1..5]
|
||||
.iter()
|
||||
.map(|edit| match edit {
|
||||
Mutation::Remove { id } => *id,
|
||||
_ => panic!("Expected remove"),
|
||||
})
|
||||
.collect::<HashSet<_>>();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
CreatePlaceholder { id: ElementId(11,) },
|
||||
Remove { id: ElementId(9,) },
|
||||
Remove { id: ElementId(7,) },
|
||||
Remove { id: ElementId(5,) },
|
||||
Remove { id: ElementId(1,) },
|
||||
ReplaceWith { id: ElementId(2,), m: 1 },
|
||||
]
|
||||
removed,
|
||||
[ElementId(7), ElementId(5), ElementId(2), ElementId(1)]
|
||||
.into_iter()
|
||||
.collect::<HashSet<_>>()
|
||||
);
|
||||
assert_eq!(edits.edits[5..], [ReplaceWith { id: ElementId(9,), m: 1 },]);
|
||||
}
|
||||
|
||||
// len = 1
|
||||
{
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
let edits = dom.render_immediate_to_vec().sanitize();
|
||||
let edits = dom.render_immediate_to_vec();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
|
||||
HydrateText { path: &[0,], value: "hello 0".to_string(), id: ElementId(1,) },
|
||||
LoadTemplate { index: 0, id: ElementId(9,) },
|
||||
CreateTextNode { value: "hello 0".to_string(), id: ElementId(7,) },
|
||||
ReplacePlaceholder { path: &[0,], m: 1 },
|
||||
ReplaceWith { id: ElementId(11,), m: 1 },
|
||||
]
|
||||
)
|
||||
|
@ -415,12 +452,13 @@ fn replace_and_add_items() {
|
|||
|
||||
// The list starts empty with a placeholder
|
||||
{
|
||||
let edits = dom.rebuild_to_vec().sanitize();
|
||||
let edits = dom.rebuild_to_vec();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
||||
AssignId { path: &[0], id: ElementId(2,) },
|
||||
LoadTemplate { index: 0, id: ElementId(1,) },
|
||||
CreatePlaceholder { id: ElementId(2,) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
AppendChildren { id: ElementId(0), m: 1 },
|
||||
]
|
||||
);
|
||||
|
@ -429,11 +467,11 @@ fn replace_and_add_items() {
|
|||
// Rerendering adds an a static template
|
||||
{
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
let edits = dom.render_immediate_to_vec().sanitize();
|
||||
let edits = dom.render_immediate_to_vec();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
|
||||
LoadTemplate { index: 0, id: ElementId(3,) },
|
||||
ReplaceWith { id: ElementId(2,), m: 1 },
|
||||
]
|
||||
);
|
||||
|
@ -442,7 +480,7 @@ fn replace_and_add_items() {
|
|||
// Rerendering replaces the old node with a placeholder and adds a new placeholder
|
||||
{
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
let edits = dom.render_immediate_to_vec().sanitize();
|
||||
let edits = dom.render_immediate_to_vec();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
|
@ -457,15 +495,15 @@ fn replace_and_add_items() {
|
|||
// Rerendering replaces both placeholders with the static nodes and add a new static node
|
||||
{
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
let edits = dom.render_immediate_to_vec().sanitize();
|
||||
let edits = dom.render_immediate_to_vec();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
|
||||
LoadTemplate { index: 0, id: ElementId(3,) },
|
||||
InsertAfter { id: ElementId(2,), m: 1 },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
|
||||
LoadTemplate { index: 0, id: ElementId(5,) },
|
||||
ReplaceWith { id: ElementId(4,), m: 1 },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(4,) },
|
||||
LoadTemplate { index: 0, id: ElementId(4,) },
|
||||
ReplaceWith { id: ElementId(2,), m: 1 },
|
||||
]
|
||||
);
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
#![cfg(not(miri))]
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_core::{AttributeValue, DynamicNode, NoOpMutations, VComponent, VNode, *};
|
||||
use std::{
|
||||
cfg, collections::HashSet, default::Default, sync::atomic::AtomicUsize, sync::atomic::Ordering,
|
||||
};
|
||||
use dioxus_core::{AttributeValue, DynamicNode, NoOpMutations, Template, VComponent, VNode, *};
|
||||
use std::{cfg, collections::HashSet, default::Default};
|
||||
|
||||
fn random_ns() -> Option<&'static str> {
|
||||
let namespace = rand::random::<u8>() % 2;
|
||||
|
@ -129,7 +127,7 @@ enum DynamicNodeType {
|
|||
Other,
|
||||
}
|
||||
|
||||
fn create_random_template(name: &'static str) -> (Template, Vec<DynamicNodeType>) {
|
||||
fn create_random_template() -> (Template, Vec<DynamicNodeType>) {
|
||||
let mut dynamic_node_type = Vec::new();
|
||||
let mut template_idx = 0;
|
||||
let mut attr_idx = 0;
|
||||
|
@ -160,7 +158,7 @@ fn create_random_template(name: &'static str) -> (Template, Vec<DynamicNodeType>
|
|||
.into_boxed_slice(),
|
||||
);
|
||||
(
|
||||
Template { name, roots, node_paths, attr_paths },
|
||||
Template { roots, node_paths, attr_paths },
|
||||
dynamic_node_type,
|
||||
)
|
||||
}
|
||||
|
@ -174,7 +172,6 @@ fn create_random_dynamic_node(depth: usize) -> DynamicNode {
|
|||
VNode::new(
|
||||
None,
|
||||
Template {
|
||||
name: create_template_location(),
|
||||
roots: &[TemplateNode::Dynamic { id: 0 }],
|
||||
node_paths: &[&[0]],
|
||||
attr_paths: &[],
|
||||
|
@ -219,19 +216,6 @@ fn create_random_dynamic_attr() -> Attribute {
|
|||
)
|
||||
}
|
||||
|
||||
static TEMPLATE_COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||
|
||||
fn create_template_location() -> &'static str {
|
||||
Box::leak(
|
||||
format!(
|
||||
"{}{}",
|
||||
concat!(file!(), ":", line!(), ":", column!(), ":"),
|
||||
TEMPLATE_COUNT.fetch_add(1, Ordering::Relaxed)
|
||||
)
|
||||
.into_boxed_str(),
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Props, Clone)]
|
||||
struct DepthProps {
|
||||
depth: usize,
|
||||
|
@ -245,7 +229,7 @@ fn create_random_element(cx: DepthProps) -> Element {
|
|||
let range = if cx.root { 2 } else { 3 };
|
||||
let node = match rand::random::<usize>() % range {
|
||||
0 | 1 => {
|
||||
let (template, dynamic_node_types) = create_random_template(create_template_location());
|
||||
let (template, dynamic_node_types) = create_random_template();
|
||||
let node = VNode::new(
|
||||
None,
|
||||
template,
|
||||
|
@ -314,8 +298,6 @@ fn diff() {
|
|||
struct InsertEventListenerMutationHandler<'a>(&'a mut HashSet<ElementId>);
|
||||
|
||||
impl WriteMutations for InsertEventListenerMutationHandler<'_> {
|
||||
fn register_template(&mut self, _: Template) {}
|
||||
|
||||
fn append_children(&mut self, _: ElementId, _: usize) {}
|
||||
|
||||
fn assign_node_id(&mut self, _: &'static [u8], _: ElementId) {}
|
||||
|
@ -324,9 +306,7 @@ impl WriteMutations for InsertEventListenerMutationHandler<'_> {
|
|||
|
||||
fn create_text_node(&mut self, _: &str, _: ElementId) {}
|
||||
|
||||
fn hydrate_text_node(&mut self, _: &'static [u8], _: &str, _: ElementId) {}
|
||||
|
||||
fn load_template(&mut self, _: &'static str, _: usize, _: ElementId) {}
|
||||
fn load_template(&mut self, _: Template, _: usize, _: ElementId) {}
|
||||
|
||||
fn replace_node_with(&mut self, _: ElementId, _: usize) {}
|
||||
|
||||
|
|
|
@ -33,13 +33,14 @@ fn basic_syntax_is_a_template() -> Element {
|
|||
#[test]
|
||||
fn dual_stream() {
|
||||
let mut dom = VirtualDom::new(basic_syntax_is_a_template);
|
||||
let edits = dom.rebuild_to_vec().sanitize();
|
||||
let edits = dom.rebuild_to_vec();
|
||||
|
||||
use Mutation::*;
|
||||
assert_eq!(edits.edits, {
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||
HydrateText { path: &[0, 0], value: "123".to_string(), id: ElementId(2) },
|
||||
LoadTemplate { index: 0, id: ElementId(1) },
|
||||
CreateTextNode { value: "123".to_string(), id: ElementId(2) },
|
||||
ReplacePlaceholder { path: &[0, 0], m: 1 },
|
||||
SetAttribute {
|
||||
name: "class",
|
||||
value: "asd 123 123 ".into_value(),
|
||||
|
|
|
@ -30,10 +30,11 @@ fn manual_diffing() {
|
|||
*value.lock().unwrap() = "goodbye";
|
||||
|
||||
assert_eq!(
|
||||
dom.rebuild_to_vec().sanitize().edits,
|
||||
dom.rebuild_to_vec().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(3) },
|
||||
HydrateText { path: &[0], value: "goodbye".to_string(), id: ElementId(4) },
|
||||
LoadTemplate { index: 0, id: ElementId(3) },
|
||||
CreateTextNode { value: "goodbye".to_string(), id: ElementId(4) },
|
||||
ReplacePlaceholder { path: &[0], m: 1 },
|
||||
AppendChildren { m: 1, id: ElementId(0) }
|
||||
]
|
||||
);
|
||||
|
|
|
@ -415,20 +415,20 @@ fn toggle_suspense() {
|
|||
.unwrap()
|
||||
.block_on(async {
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let mutations = dom.rebuild_to_vec().sanitize();
|
||||
let mutations = dom.rebuild_to_vec();
|
||||
|
||||
// First create goodbye world
|
||||
println!("{:#?}", mutations);
|
||||
assert_eq!(
|
||||
mutations.edits,
|
||||
[
|
||||
Mutation::LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||
Mutation::LoadTemplate { index: 0, id: ElementId(1) },
|
||||
Mutation::AppendChildren { id: ElementId(0), m: 1 }
|
||||
]
|
||||
);
|
||||
|
||||
dom.mark_dirty(ScopeId::APP);
|
||||
let mutations = dom.render_immediate_to_vec().sanitize();
|
||||
let mutations = dom.render_immediate_to_vec();
|
||||
|
||||
// Then replace that with nothing
|
||||
println!("{:#?}", mutations);
|
||||
|
@ -441,20 +441,20 @@ fn toggle_suspense() {
|
|||
);
|
||||
|
||||
dom.wait_for_work().await;
|
||||
let mutations = dom.render_immediate_to_vec().sanitize();
|
||||
let mutations = dom.render_immediate_to_vec();
|
||||
|
||||
// Then replace it with a placeholder
|
||||
println!("{:#?}", mutations);
|
||||
assert_eq!(
|
||||
mutations.edits,
|
||||
[
|
||||
Mutation::LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||
Mutation::LoadTemplate { index: 0, id: ElementId(1) },
|
||||
Mutation::ReplaceWith { id: ElementId(2), m: 1 },
|
||||
]
|
||||
);
|
||||
|
||||
dom.wait_for_work().await;
|
||||
let mutations = dom.render_immediate_to_vec().sanitize();
|
||||
let mutations = dom.render_immediate_to_vec();
|
||||
|
||||
// Then replace it with the resolved node
|
||||
println!("{:#?}", mutations);
|
||||
|
@ -463,7 +463,7 @@ fn toggle_suspense() {
|
|||
[
|
||||
Mutation::CreatePlaceholder { id: ElementId(2,) },
|
||||
Mutation::ReplaceWith { id: ElementId(1,), m: 1 },
|
||||
Mutation::LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||
Mutation::LoadTemplate { index: 0, id: ElementId(1) },
|
||||
Mutation::ReplaceWith { id: ElementId(2), m: 1 },
|
||||
]
|
||||
);
|
||||
|
@ -600,14 +600,14 @@ fn nested_suspense_resolves_client() {
|
|||
// DOM STATE:
|
||||
// placeholder // ID: 1
|
||||
// "Loading 0..." // ID: 2
|
||||
let mutations = dom.render_immediate_to_vec().sanitize();
|
||||
let mutations = dom.render_immediate_to_vec();
|
||||
// Fill in the contents of the initial message and start loading the nested suspense
|
||||
// The title also finishes loading
|
||||
assert_eq!(
|
||||
mutations.edits,
|
||||
vec![
|
||||
// Creating and swapping these placeholders doesn't do anything
|
||||
// It is just extra work that we are forced to do because mutations are not
|
||||
// It is just extra work that we are forced to do because mutations are not
|
||||
// reversible. We start rendering the children and then realize it is suspended.
|
||||
// Then we need to replace what we just rendered with the suspense placeholder
|
||||
CreatePlaceholder { id: ElementId(3,) },
|
||||
|
@ -622,12 +622,9 @@ fn nested_suspense_resolves_client() {
|
|||
ReplaceWith { id: ElementId(2,), m: 1 },
|
||||
|
||||
// Load the title
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
|
||||
HydrateText {
|
||||
path: &[0,],
|
||||
value: "The robot says hello world".to_string(),
|
||||
id: ElementId(4,),
|
||||
},
|
||||
LoadTemplate { index: 0, id: ElementId(2,) },
|
||||
CreateTextNode { value: "The robot says hello world".to_string(), id: ElementId(4,) },
|
||||
ReplacePlaceholder { path: &[0,], m: 1 },
|
||||
SetAttribute {
|
||||
name: "id",
|
||||
ns: None,
|
||||
|
@ -636,12 +633,9 @@ fn nested_suspense_resolves_client() {
|
|||
},
|
||||
|
||||
// Then load the body
|
||||
LoadTemplate { name: "template", index: 1, id: ElementId(5,) },
|
||||
HydrateText {
|
||||
path: &[0,],
|
||||
value: "The robot becomes sentient and says hello world".to_string(),
|
||||
id: ElementId(6,),
|
||||
},
|
||||
LoadTemplate { index: 1, id: ElementId(5,) },
|
||||
CreateTextNode { value: "The robot becomes sentient and says hello world".to_string(), id: ElementId(6,) },
|
||||
ReplacePlaceholder { path: &[0,], m: 1 },
|
||||
SetAttribute {
|
||||
name: "id",
|
||||
ns: None,
|
||||
|
@ -650,7 +644,7 @@ fn nested_suspense_resolves_client() {
|
|||
},
|
||||
|
||||
// Then load the suspended children
|
||||
LoadTemplate { name: "template", index: 2, id: ElementId(7,) },
|
||||
LoadTemplate { index: 2, id: ElementId(7,) },
|
||||
CreateTextNode { value: "Loading 1...".to_string(), id: ElementId(8,) },
|
||||
CreateTextNode { value: "Loading 2...".to_string(), id: ElementId(9,) },
|
||||
ReplacePlaceholder { path: &[0,], m: 2 },
|
||||
|
@ -674,7 +668,7 @@ fn nested_suspense_resolves_client() {
|
|||
// div // ID: 7
|
||||
// "Loading 1..." // ID: 8
|
||||
// "Loading 2..." // ID: 9
|
||||
let mutations = dom.render_immediate_to_vec().sanitize();
|
||||
let mutations = dom.render_immediate_to_vec();
|
||||
assert_eq!(
|
||||
mutations.edits,
|
||||
vec![
|
||||
|
@ -693,20 +687,18 @@ fn nested_suspense_resolves_client() {
|
|||
|
||||
// Load the nested suspense
|
||||
LoadTemplate {
|
||||
name: "template",
|
||||
|
||||
index: 0,
|
||||
id: ElementId(
|
||||
8,
|
||||
),
|
||||
},
|
||||
HydrateText {
|
||||
CreateTextNode { value: "The world says hello back".to_string(), id: ElementId(10,) },
|
||||
ReplacePlaceholder {
|
||||
path: &[
|
||||
0,
|
||||
],
|
||||
value: "The world says hello back".to_string(),
|
||||
id: ElementId(
|
||||
10,
|
||||
),
|
||||
m: 1,
|
||||
},
|
||||
SetAttribute {
|
||||
name: "id",
|
||||
|
@ -717,20 +709,18 @@ fn nested_suspense_resolves_client() {
|
|||
),
|
||||
},
|
||||
LoadTemplate {
|
||||
name: "template",
|
||||
|
||||
index: 1,
|
||||
id: ElementId(
|
||||
11,
|
||||
),
|
||||
},
|
||||
HydrateText {
|
||||
CreateTextNode { value: "In a stunning turn of events, the world collectively unites and says hello back".to_string(), id: ElementId(12,) },
|
||||
ReplacePlaceholder {
|
||||
path: &[
|
||||
0,
|
||||
],
|
||||
value: "In a stunning turn of events, the world collectively unites and says hello back".to_string(),
|
||||
id: ElementId(
|
||||
12,
|
||||
),
|
||||
m: 1,
|
||||
},
|
||||
SetAttribute {
|
||||
name: "id",
|
||||
|
@ -741,19 +731,17 @@ fn nested_suspense_resolves_client() {
|
|||
),
|
||||
},
|
||||
LoadTemplate {
|
||||
name: "template",
|
||||
index: 2,
|
||||
id: ElementId(
|
||||
13,
|
||||
),
|
||||
},
|
||||
AssignId {
|
||||
CreatePlaceholder { id: ElementId(14,) },
|
||||
ReplacePlaceholder {
|
||||
path: &[
|
||||
0,
|
||||
],
|
||||
id: ElementId(
|
||||
14,
|
||||
),
|
||||
m: 1,
|
||||
},
|
||||
SetAttribute {
|
||||
name: "id",
|
||||
|
@ -783,20 +771,17 @@ fn nested_suspense_resolves_client() {
|
|||
m: 1,
|
||||
},
|
||||
LoadTemplate {
|
||||
name: "template",
|
||||
index: 0,
|
||||
id: ElementId(
|
||||
9,
|
||||
),
|
||||
},
|
||||
HydrateText {
|
||||
CreateTextNode { value: "Goodbye Robot".to_string(), id: ElementId(15,) },
|
||||
ReplacePlaceholder {
|
||||
path: &[
|
||||
0,
|
||||
],
|
||||
value: "Goodbye Robot".to_string(),
|
||||
id: ElementId(
|
||||
15,
|
||||
),
|
||||
m: 1,
|
||||
},
|
||||
SetAttribute {
|
||||
name: "id",
|
||||
|
@ -807,20 +792,17 @@ fn nested_suspense_resolves_client() {
|
|||
),
|
||||
},
|
||||
LoadTemplate {
|
||||
name: "template",
|
||||
index: 1,
|
||||
id: ElementId(
|
||||
16,
|
||||
),
|
||||
},
|
||||
HydrateText {
|
||||
CreateTextNode { value: "The robot says goodbye".to_string(), id: ElementId(17,) },
|
||||
ReplacePlaceholder {
|
||||
path: &[
|
||||
0,
|
||||
],
|
||||
value: "The robot says goodbye".to_string(),
|
||||
id: ElementId(
|
||||
17,
|
||||
),
|
||||
m: 1,
|
||||
},
|
||||
SetAttribute {
|
||||
name: "id",
|
||||
|
@ -831,7 +813,7 @@ fn nested_suspense_resolves_client() {
|
|||
),
|
||||
},
|
||||
LoadTemplate {
|
||||
name: "template",
|
||||
|
||||
index: 2,
|
||||
id: ElementId(
|
||||
18,
|
||||
|
@ -860,7 +842,7 @@ fn nested_suspense_resolves_client() {
|
|||
);
|
||||
|
||||
dom.wait_for_work().await;
|
||||
let mutations = dom.render_immediate_to_vec().sanitize();
|
||||
let mutations = dom.render_immediate_to_vec();
|
||||
assert_eq!(
|
||||
mutations.edits,
|
||||
vec![
|
||||
|
@ -876,20 +858,18 @@ fn nested_suspense_resolves_client() {
|
|||
m: 1,
|
||||
},
|
||||
LoadTemplate {
|
||||
name: "template",
|
||||
|
||||
index: 0,
|
||||
id: ElementId(
|
||||
19,
|
||||
),
|
||||
},
|
||||
HydrateText {
|
||||
CreateTextNode { value: "Goodbye Robot again".to_string(), id: ElementId(20,) },
|
||||
ReplacePlaceholder {
|
||||
path: &[
|
||||
0,
|
||||
],
|
||||
value: "Goodbye Robot again".to_string(),
|
||||
id: ElementId(
|
||||
20,
|
||||
),
|
||||
m: 1,
|
||||
},
|
||||
SetAttribute {
|
||||
name: "id",
|
||||
|
@ -900,20 +880,17 @@ fn nested_suspense_resolves_client() {
|
|||
),
|
||||
},
|
||||
LoadTemplate {
|
||||
name: "template",
|
||||
index: 1,
|
||||
id: ElementId(
|
||||
21,
|
||||
),
|
||||
},
|
||||
HydrateText {
|
||||
CreateTextNode { value: "The robot says goodbye again".to_string(), id: ElementId(22,) },
|
||||
ReplacePlaceholder {
|
||||
path: &[
|
||||
0,
|
||||
],
|
||||
value: "The robot says goodbye again".to_string(),
|
||||
id: ElementId(
|
||||
22,
|
||||
),
|
||||
m: 1,
|
||||
},
|
||||
SetAttribute {
|
||||
name: "id",
|
||||
|
@ -924,19 +901,17 @@ fn nested_suspense_resolves_client() {
|
|||
),
|
||||
},
|
||||
LoadTemplate {
|
||||
name: "template",
|
||||
index: 2,
|
||||
id: ElementId(
|
||||
23,
|
||||
),
|
||||
},
|
||||
AssignId {
|
||||
CreatePlaceholder { id: ElementId(24,) },
|
||||
ReplacePlaceholder {
|
||||
path: &[
|
||||
0,
|
||||
0
|
||||
],
|
||||
id: ElementId(
|
||||
24,
|
||||
),
|
||||
m: 1,
|
||||
},
|
||||
SetAttribute {
|
||||
name: "id",
|
||||
|
|
|
@ -165,11 +165,11 @@ fn test_mouse_dblclick_div() -> Element {
|
|||
utils::mock_event(
|
||||
"mouse_dblclick_div",
|
||||
r#"new MouseEvent("dblclick", {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
buttons: 1|2,
|
||||
button: 2,
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
buttons: 1|2,
|
||||
button: 2,
|
||||
})"#,
|
||||
);
|
||||
|
||||
|
@ -205,11 +205,11 @@ fn test_mouse_down_div() -> Element {
|
|||
utils::mock_event(
|
||||
"mouse_down_div",
|
||||
r#"new MouseEvent("mousedown", {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
buttons: 2,
|
||||
button: 2,
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
buttons: 2,
|
||||
button: 2,
|
||||
})"#,
|
||||
);
|
||||
|
||||
|
@ -239,11 +239,11 @@ fn test_mouse_up_div() -> Element {
|
|||
utils::mock_event(
|
||||
"mouse_up_div",
|
||||
r#"new MouseEvent("mouseup", {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
buttons: 0,
|
||||
button: 0,
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
buttons: 0,
|
||||
button: 0,
|
||||
})"#,
|
||||
);
|
||||
|
||||
|
@ -268,12 +268,12 @@ fn test_mouse_scroll_div() -> Element {
|
|||
utils::mock_event(
|
||||
"wheel_div",
|
||||
r#"new WheelEvent("wheel", {
|
||||
view: window,
|
||||
deltaX: 1.0,
|
||||
deltaY: 2.0,
|
||||
deltaZ: 3.0,
|
||||
deltaMode: 0x00,
|
||||
bubbles: true,
|
||||
view: window,
|
||||
deltaX: 1.0,
|
||||
deltaY: 2.0,
|
||||
deltaZ: 3.0,
|
||||
deltaMode: 0x00,
|
||||
bubbles: true,
|
||||
})"#,
|
||||
);
|
||||
|
||||
|
|
|
@ -34,6 +34,9 @@ async fn main() {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
#[cfg(all(not(feature = "desktop"), not(feature = "server")))]
|
||||
fn main() {}
|
||||
|
||||
pub fn app() -> Element {
|
||||
let mut count = use_signal(|| 0);
|
||||
let mut text = use_signal(|| "...".to_string());
|
||||
|
|
|
@ -126,19 +126,6 @@ mod js {
|
|||
fn assign_id(ptr: u32, len: u8, id: u32) {
|
||||
"{this.nodes[$id$] = this.loadChild($ptr$, $len$);}"
|
||||
}
|
||||
fn hydrate_text(ptr: u32, len: u8, value: &str, id: u32) {
|
||||
r#"{
|
||||
let node = this.loadChild($ptr$, $len$);
|
||||
if (node.nodeType == node.TEXT_NODE) {
|
||||
node.textContent = value;
|
||||
} else {
|
||||
let text = document.createTextNode(value);
|
||||
node.replaceWith(text);
|
||||
node = text;
|
||||
}
|
||||
this.nodes[$id$] = node;
|
||||
}"#
|
||||
}
|
||||
fn replace_placeholder(ptr: u32, len: u8, n: u16) {
|
||||
"{let els = this.stack.splice(this.stack.length - $n$); let node = this.loadChild($ptr$, $len$); node.replaceWith(...els);}"
|
||||
}
|
||||
|
@ -149,11 +136,11 @@ mod js {
|
|||
#[cfg(feature = "binary-protocol")]
|
||||
fn append_children_to_top(many: u16) {
|
||||
"{
|
||||
let root = this.stack[this.stack.length-many-1];
|
||||
let els = this.stack.splice(this.stack.length-many);
|
||||
for (let k = 0; k < many; k++) {
|
||||
root.appendChild(els[k]);
|
||||
}
|
||||
let root = this.stack[this.stack.length-many-1];
|
||||
let els = this.stack.splice(this.stack.length-many);
|
||||
for (let k = 0; k < many; k++) {
|
||||
root.appendChild(els[k]);
|
||||
}
|
||||
}"
|
||||
}
|
||||
|
||||
|
@ -218,22 +205,6 @@ mod js {
|
|||
"{this.nodes[$id$] = this.loadChild($array$);}"
|
||||
}
|
||||
|
||||
/// The coolest ID ever!
|
||||
#[cfg(feature = "binary-protocol")]
|
||||
fn hydrate_text_ref(array: &[u8], value: &str, id: u32) {
|
||||
r#"{
|
||||
let node = this.loadChild($array$);
|
||||
if (node.nodeType == node.TEXT_NODE) {
|
||||
node.textContent = value;
|
||||
} else {
|
||||
let text = document.createTextNode(value);
|
||||
node.replaceWith(text);
|
||||
node = text;
|
||||
}
|
||||
this.nodes[$id$] = node;
|
||||
}"#
|
||||
}
|
||||
|
||||
#[cfg(feature = "binary-protocol")]
|
||||
fn replace_placeholder_ref(array: &[u8], n: u16) {
|
||||
"{let els = this.stack.splice(this.stack.length - $n$); let node = this.loadChild($array$); node.replaceWith(...els);}"
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
use crate::unified_bindings::Interpreter as Channel;
|
||||
use dioxus_core::{TemplateAttribute, TemplateNode, WriteMutations};
|
||||
use dioxus_core::{Template, TemplateAttribute, TemplateNode, WriteMutations};
|
||||
use dioxus_html::event_bubbles;
|
||||
use sledgehammer_utils::rustc_hash::FxHashMap;
|
||||
|
||||
/// The state needed to apply mutations to a channel. This state should be kept across all mutations for the app
|
||||
#[derive(Default)]
|
||||
pub struct MutationState {
|
||||
/// The maximum number of templates that we have registered
|
||||
max_template_count: u16,
|
||||
|
||||
/// The currently registered templates with the template ids
|
||||
templates: FxHashMap<String, u16>,
|
||||
templates: FxHashMap<Template, u16>,
|
||||
|
||||
/// The channel that we are applying mutations to
|
||||
channel: Channel,
|
||||
|
@ -81,19 +78,6 @@ impl MutationState {
|
|||
}
|
||||
|
||||
impl WriteMutations for MutationState {
|
||||
fn register_template(&mut self, template: dioxus_core::prelude::Template) {
|
||||
let current_max_template_count = self.max_template_count;
|
||||
for root in template.roots.iter() {
|
||||
self.create_template_node(root);
|
||||
self.templates
|
||||
.insert(template.name.to_owned(), current_max_template_count);
|
||||
}
|
||||
self.channel
|
||||
.add_templates(current_max_template_count, template.roots.len() as u16);
|
||||
|
||||
self.max_template_count += 1;
|
||||
}
|
||||
|
||||
fn append_children(&mut self, id: dioxus_core::ElementId, m: usize) {
|
||||
self.channel.append_children(id.0 as u32, m as u16);
|
||||
}
|
||||
|
@ -110,15 +94,21 @@ impl WriteMutations for MutationState {
|
|||
self.channel.create_text_node(value, id.0 as u32);
|
||||
}
|
||||
|
||||
fn hydrate_text_node(&mut self, path: &'static [u8], value: &str, id: dioxus_core::ElementId) {
|
||||
self.channel.hydrate_text_ref(path, value, id.0 as u32);
|
||||
}
|
||||
fn load_template(&mut self, template: Template, index: usize, id: dioxus_core::ElementId) {
|
||||
// Get the template or create it if we haven't seen it before
|
||||
let tmpl_id = self.templates.get(&template).cloned().unwrap_or_else(|| {
|
||||
let current_max_template_count = self.templates.len() as u16;
|
||||
for root in template.roots.iter() {
|
||||
self.create_template_node(root);
|
||||
self.templates.insert(template, current_max_template_count);
|
||||
}
|
||||
let id = template.roots.len() as u16;
|
||||
self.channel.add_templates(current_max_template_count, id);
|
||||
current_max_template_count
|
||||
});
|
||||
|
||||
fn load_template(&mut self, name: &'static str, index: usize, id: dioxus_core::ElementId) {
|
||||
if let Some(tmpl_id) = self.templates.get(name) {
|
||||
self.channel
|
||||
.load_template(*tmpl_id, index as u16, id.0 as u32)
|
||||
}
|
||||
self.channel
|
||||
.load_template(tmpl_id, index as u16, id.0 as u32);
|
||||
}
|
||||
|
||||
fn replace_node_with(&mut self, id: dioxus_core::ElementId, m: usize) {
|
||||
|
|
|
@ -70,9 +70,6 @@ use dioxus_core::internal::{
|
|||
HotReloadLiteral, HotReloadedTemplate, NamedAttribute,
|
||||
};
|
||||
use std::collections::HashMap;
|
||||
use std::hash::DefaultHasher;
|
||||
use std::hash::Hash;
|
||||
use std::hash::Hasher;
|
||||
|
||||
use super::last_build_state::LastBuildState;
|
||||
|
||||
|
@ -200,23 +197,7 @@ impl HotReloadResult {
|
|||
.collect();
|
||||
let roots: &[dioxus_core::TemplateNode] = intern(&*roots);
|
||||
|
||||
// Add the template name, the dyn index and the hash of the template to get a unique name
|
||||
let name = {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
key.hash(&mut hasher);
|
||||
new_dynamic_attributes.hash(&mut hasher);
|
||||
new_dynamic_nodes.hash(&mut hasher);
|
||||
literal_component_properties.hash(&mut hasher);
|
||||
roots.hash(&mut hasher);
|
||||
let hash = hasher.finish();
|
||||
let name = &self.full_rebuild_state.name;
|
||||
|
||||
format!("{}:{}-{}", name, hash, new.template_idx.get())
|
||||
};
|
||||
let name = Box::leak(name.into_boxed_str());
|
||||
|
||||
let template = HotReloadedTemplate::new(
|
||||
name,
|
||||
key,
|
||||
new_dynamic_nodes,
|
||||
new_dynamic_attributes,
|
||||
|
|
|
@ -139,65 +139,58 @@ impl ToTokens for TemplateBody {
|
|||
|
||||
let dynamic_text = self.dynamic_text_segments.iter();
|
||||
|
||||
let index = self.template_idx.get();
|
||||
|
||||
let diagnostics = &self.diagnostics;
|
||||
let hot_reload_mapping = self.hot_reload_mapping(quote! { ___TEMPLATE_NAME });
|
||||
let index = self.template_idx.get();
|
||||
let hot_reload_mapping = self.hot_reload_mapping();
|
||||
|
||||
let vnode = quote! {
|
||||
#[doc(hidden)] // vscode please stop showing these in symbol search
|
||||
const ___TEMPLATE_NAME: &str = {
|
||||
const PATH: &str = dioxus_core::const_format::str_replace!(file!(), "\\\\", "/");
|
||||
const NORMAL: &str = dioxus_core::const_format::str_replace!(PATH, '\\', "/");
|
||||
dioxus_core::const_format::concatcp!(NORMAL, ':', line!(), ':', column!(), ':', #index)
|
||||
};
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
// The key is important here - we're creating a new GlobalSignal each call to this
|
||||
// But the key is what's keeping it stable
|
||||
let __template = GlobalSignal::with_key(
|
||||
|| #hot_reload_mapping,
|
||||
___TEMPLATE_NAME
|
||||
);
|
||||
|
||||
__template.maybe_with_rt(|__template_read| {
|
||||
let mut __dynamic_literal_pool = dioxus_core::internal::DynamicLiteralPool::new(
|
||||
vec![ #( #dynamic_text.to_string() ),* ],
|
||||
);
|
||||
let mut __dynamic_value_pool = dioxus_core::internal::DynamicValuePool::new(
|
||||
vec![ #( #dynamic_nodes ),* ],
|
||||
vec![ #( #dyn_attr_printer ),* ],
|
||||
__dynamic_literal_pool
|
||||
);
|
||||
__dynamic_value_pool.render_with(__template_read)
|
||||
})
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
#[doc(hidden)] // vscode please stop showing these in symbol search
|
||||
static ___TEMPLATE: dioxus_core::Template = dioxus_core::Template {
|
||||
name: ___TEMPLATE_NAME,
|
||||
roots: &[ #( #roots ),* ],
|
||||
node_paths: &[ #( #node_paths ),* ],
|
||||
attr_paths: &[ #( #attr_paths ),* ],
|
||||
};
|
||||
|
||||
// NOTE: Allocating a temporary is important to make reads within rsx drop before the value is returned
|
||||
#[allow(clippy::let_and_return)]
|
||||
let __vnodes = dioxus_core::VNode::new(
|
||||
#key_tokens,
|
||||
___TEMPLATE,
|
||||
Box::new([ #( #dynamic_nodes ),* ]),
|
||||
Box::new([ #( #dyn_attr_printer ),* ]),
|
||||
);
|
||||
__vnodes
|
||||
}
|
||||
};
|
||||
tokens.append_all(quote! {
|
||||
dioxus_core::Element::Ok({
|
||||
#diagnostics
|
||||
|
||||
#vnode
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
// The key is important here - we're creating a new GlobalSignal each call to this
|
||||
// But the key is what's keeping it stable
|
||||
let __template = GlobalSignal::with_key(
|
||||
|| #hot_reload_mapping,
|
||||
{
|
||||
const PATH: &str = dioxus_core::const_format::str_replace!(file!(), "\\\\", "/");
|
||||
const NORMAL: &str = dioxus_core::const_format::str_replace!(PATH, '\\', "/");
|
||||
dioxus_core::const_format::concatcp!(NORMAL, ':', line!(), ':', column!(), ':', #index)
|
||||
}
|
||||
);
|
||||
|
||||
__template.maybe_with_rt(|__template_read| {
|
||||
let mut __dynamic_literal_pool = dioxus_core::internal::DynamicLiteralPool::new(
|
||||
vec![ #( #dynamic_text.to_string() ),* ],
|
||||
);
|
||||
let mut __dynamic_value_pool = dioxus_core::internal::DynamicValuePool::new(
|
||||
vec![ #( #dynamic_nodes ),* ],
|
||||
vec![ #( #dyn_attr_printer ),* ],
|
||||
__dynamic_literal_pool
|
||||
);
|
||||
__dynamic_value_pool.render_with(__template_read)
|
||||
})
|
||||
}
|
||||
#[cfg(not(debug_assertions))]
|
||||
{
|
||||
#[doc(hidden)] // vscode please stop showing these in symbol search
|
||||
static ___TEMPLATE: dioxus_core::Template = dioxus_core::Template {
|
||||
roots: &[ #( #roots ),* ],
|
||||
node_paths: &[ #( #node_paths ),* ],
|
||||
attr_paths: &[ #( #attr_paths ),* ],
|
||||
};
|
||||
|
||||
// NOTE: Allocating a temporary is important to make reads within rsx drop before the value is returned
|
||||
#[allow(clippy::let_and_return)]
|
||||
let __vnodes = dioxus_core::VNode::new(
|
||||
#key_tokens,
|
||||
___TEMPLATE,
|
||||
Box::new([ #( #dynamic_nodes ),* ]),
|
||||
Box::new([ #( #dyn_attr_printer ),* ]),
|
||||
);
|
||||
__vnodes
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
@ -324,7 +317,7 @@ impl TemplateBody {
|
|||
})
|
||||
}
|
||||
|
||||
fn hot_reload_mapping(&self, name: impl ToTokens) -> TokenStream2 {
|
||||
fn hot_reload_mapping(&self) -> TokenStream2 {
|
||||
let key = if let Some(AttributeValue::AttrLiteral(HotLiteral::Fmted(key))) =
|
||||
self.implicit_key()
|
||||
{
|
||||
|
@ -346,7 +339,6 @@ impl TemplateBody {
|
|||
.map(|literal| literal.quote_as_hot_reload_literal());
|
||||
quote! {
|
||||
dioxus_core::internal::HotReloadedTemplate::new(
|
||||
#name,
|
||||
#key,
|
||||
vec![ #( #dynamic_nodes ),* ],
|
||||
vec![ #( #dyn_attr_printer ),* ],
|
||||
|
|
|
@ -20,7 +20,7 @@ pub struct Renderer {
|
|||
render_components: Option<ComponentRenderCallback>,
|
||||
|
||||
/// A cache of templates that have been rendered
|
||||
template_cache: FxHashMap<usize, Arc<StringCache>>,
|
||||
template_cache: FxHashMap<Template, Arc<StringCache>>,
|
||||
|
||||
/// The current dynamic node id for hydration
|
||||
dynamic_node_id: usize,
|
||||
|
@ -108,7 +108,7 @@ impl Renderer {
|
|||
) -> std::fmt::Result {
|
||||
let entry = self
|
||||
.template_cache
|
||||
.entry(template.template.id())
|
||||
.entry(template.template)
|
||||
.or_insert_with(move || Arc::new(StringCache::from_template(template).unwrap()))
|
||||
.clone();
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
//! - tests to ensure dyn_into works for various event types.
|
||||
//! - Partial delegation?
|
||||
|
||||
use dioxus_core::ElementId;
|
||||
use dioxus_core::{ElementId, Template};
|
||||
use dioxus_html::PlatformEventData;
|
||||
use dioxus_interpreter_js::unified_bindings::Interpreter;
|
||||
use futures_channel::mpsc;
|
||||
|
@ -20,8 +20,7 @@ pub struct WebsysDom {
|
|||
#[allow(dead_code)]
|
||||
pub(crate) root: Element,
|
||||
pub(crate) document: Document,
|
||||
pub(crate) templates: FxHashMap<String, u16>,
|
||||
pub(crate) max_template_id: u16,
|
||||
pub(crate) templates: FxHashMap<Template, u16>,
|
||||
pub(crate) interpreter: Interpreter,
|
||||
|
||||
#[cfg(feature = "mounted")]
|
||||
|
@ -41,10 +40,10 @@ pub struct WebsysDom {
|
|||
// Instead we now store a flag to see if we should be writing templates at all if hydration is enabled.
|
||||
// This has a small overhead, but it avoids dynamic dispatch and reduces the binary size
|
||||
//
|
||||
// NOTE: running the virtual dom with the `only_write_templates` flag set to true is different from running
|
||||
// NOTE: running the virtual dom with the `write_mutations` flag set to true is different from running
|
||||
// it with no mutation writer because it still assigns ids to nodes, but it doesn't write them to the dom
|
||||
#[cfg(feature = "hydrate")]
|
||||
pub(crate) only_write_templates: bool,
|
||||
pub(crate) skip_mutations: bool,
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
pub(crate) suspense_hydration_ids: crate::hydration::SuspenseHydrationIds,
|
||||
|
@ -145,13 +144,12 @@ impl WebsysDom {
|
|||
root,
|
||||
interpreter,
|
||||
templates: FxHashMap::default(),
|
||||
max_template_id: 0,
|
||||
#[cfg(feature = "mounted")]
|
||||
event_channel,
|
||||
#[cfg(feature = "mounted")]
|
||||
queued_mounted_events: Default::default(),
|
||||
#[cfg(feature = "hydrate")]
|
||||
only_write_templates: false,
|
||||
skip_mutations: false,
|
||||
#[cfg(feature = "hydrate")]
|
||||
suspense_hydration_ids: Default::default(),
|
||||
}
|
||||
|
|
|
@ -151,11 +151,11 @@ impl WebsysDom {
|
|||
self,
|
||||
|to| {
|
||||
// Switch to only writing templates
|
||||
to.only_write_templates = true;
|
||||
to.skip_mutations = true;
|
||||
},
|
||||
children.len(),
|
||||
);
|
||||
self.only_write_templates = false;
|
||||
self.skip_mutations = false;
|
||||
});
|
||||
|
||||
// Flush the mutations that will swap the placeholder nodes with the resolved nodes
|
||||
|
|
|
@ -25,6 +25,7 @@ use std::{panic, rc::Rc};
|
|||
pub use crate::cfg::Config;
|
||||
use crate::hydration::SuspenseMessage;
|
||||
use dioxus_core::VirtualDom;
|
||||
use dom::WebsysDom;
|
||||
use futures_util::{pin_mut, select, FutureExt, StreamExt};
|
||||
|
||||
mod cfg;
|
||||
|
@ -77,7 +78,7 @@ pub async fn run(virtual_dom: VirtualDom, web_config: Config) -> ! {
|
|||
|
||||
let should_hydrate = web_config.hydrate;
|
||||
|
||||
let mut websys_dom = dom::WebsysDom::new(web_config, tx);
|
||||
let mut websys_dom = WebsysDom::new(web_config, tx);
|
||||
|
||||
let mut hydration_receiver: Option<futures_channel::mpsc::UnboundedReceiver<SuspenseMessage>> =
|
||||
None;
|
||||
|
@ -85,7 +86,7 @@ pub async fn run(virtual_dom: VirtualDom, web_config: Config) -> ! {
|
|||
if should_hydrate {
|
||||
#[cfg(feature = "hydrate")]
|
||||
{
|
||||
websys_dom.only_write_templates = true;
|
||||
websys_dom.skip_mutations = true;
|
||||
// Get the initial hydration data from the client
|
||||
#[wasm_bindgen::prelude::wasm_bindgen(inline_js = r#"
|
||||
export function get_initial_hydration_data() {
|
||||
|
@ -105,7 +106,7 @@ pub async fn run(virtual_dom: VirtualDom, web_config: Config) -> ! {
|
|||
with_server_data(server_data, || {
|
||||
dom.rebuild(&mut websys_dom);
|
||||
});
|
||||
websys_dom.only_write_templates = false;
|
||||
websys_dom.skip_mutations = false;
|
||||
|
||||
let rx = websys_dom.rehydrate(&dom).unwrap();
|
||||
hydration_receiver = Some(rx);
|
||||
|
|
|
@ -79,10 +79,10 @@ impl WebsysDom {
|
|||
}
|
||||
|
||||
#[inline]
|
||||
fn only_write_templates(&self) -> bool {
|
||||
fn skip_mutations(&self) -> bool {
|
||||
#[cfg(feature = "hydrate")]
|
||||
{
|
||||
self.only_write_templates
|
||||
self.skip_mutations
|
||||
}
|
||||
#[cfg(not(feature = "hydrate"))]
|
||||
{
|
||||
|
@ -92,28 +92,15 @@ impl WebsysDom {
|
|||
}
|
||||
|
||||
impl WriteMutations for WebsysDom {
|
||||
fn register_template(&mut self, template: Template) {
|
||||
let mut roots = vec![];
|
||||
for root in template.roots {
|
||||
roots.push(self.create_template_node(root))
|
||||
}
|
||||
self.templates
|
||||
.insert(template.name.to_owned(), self.max_template_id);
|
||||
self.interpreter
|
||||
.base()
|
||||
.save_template(roots, self.max_template_id);
|
||||
self.max_template_id += 1
|
||||
}
|
||||
|
||||
fn append_children(&mut self, id: ElementId, m: usize) {
|
||||
if self.only_write_templates() {
|
||||
if self.skip_mutations() {
|
||||
return;
|
||||
}
|
||||
self.interpreter.append_children(id.0 as u32, m as u16)
|
||||
}
|
||||
|
||||
fn assign_node_id(&mut self, path: &'static [u8], id: ElementId) {
|
||||
if self.only_write_templates() {
|
||||
if self.skip_mutations() {
|
||||
return;
|
||||
}
|
||||
self.interpreter
|
||||
|
@ -121,46 +108,47 @@ impl WriteMutations for WebsysDom {
|
|||
}
|
||||
|
||||
fn create_placeholder(&mut self, id: ElementId) {
|
||||
if self.only_write_templates() {
|
||||
if self.skip_mutations() {
|
||||
return;
|
||||
}
|
||||
self.interpreter.create_placeholder(id.0 as u32)
|
||||
}
|
||||
|
||||
fn create_text_node(&mut self, value: &str, id: ElementId) {
|
||||
if self.only_write_templates() {
|
||||
if self.skip_mutations() {
|
||||
return;
|
||||
}
|
||||
self.interpreter.create_text_node(value, id.0 as u32)
|
||||
}
|
||||
|
||||
fn hydrate_text_node(&mut self, path: &'static [u8], value: &str, id: ElementId) {
|
||||
if self.only_write_templates() {
|
||||
fn load_template(&mut self, template: Template, index: usize, id: ElementId) {
|
||||
if self.skip_mutations() {
|
||||
return;
|
||||
}
|
||||
self.interpreter
|
||||
.hydrate_text(path.as_ptr() as u32, path.len() as u8, value, id.0 as u32)
|
||||
}
|
||||
let tmpl_id = self.templates.get(&template).cloned().unwrap_or_else(|| {
|
||||
let mut roots = vec![];
|
||||
for root in template.roots {
|
||||
roots.push(self.create_template_node(root))
|
||||
}
|
||||
let id = self.templates.len() as u16;
|
||||
self.templates.insert(template, id);
|
||||
self.interpreter.base().save_template(roots, id);
|
||||
id
|
||||
});
|
||||
|
||||
fn load_template(&mut self, name: &'static str, index: usize, id: ElementId) {
|
||||
if self.only_write_templates() {
|
||||
return;
|
||||
}
|
||||
if let Some(tmpl_id) = self.templates.get(name) {
|
||||
self.interpreter
|
||||
.load_template(*tmpl_id, index as u16, id.0 as u32)
|
||||
}
|
||||
self.interpreter
|
||||
.load_template(tmpl_id, index as u16, id.0 as u32)
|
||||
}
|
||||
|
||||
fn replace_node_with(&mut self, id: ElementId, m: usize) {
|
||||
if self.only_write_templates() {
|
||||
if self.skip_mutations() {
|
||||
return;
|
||||
}
|
||||
self.interpreter.replace_with(id.0 as u32, m as u16)
|
||||
}
|
||||
|
||||
fn replace_placeholder_with_nodes(&mut self, path: &'static [u8], m: usize) {
|
||||
if self.only_write_templates() {
|
||||
if self.skip_mutations() {
|
||||
return;
|
||||
}
|
||||
self.interpreter
|
||||
|
@ -168,14 +156,14 @@ impl WriteMutations for WebsysDom {
|
|||
}
|
||||
|
||||
fn insert_nodes_after(&mut self, id: ElementId, m: usize) {
|
||||
if self.only_write_templates() {
|
||||
if self.skip_mutations() {
|
||||
return;
|
||||
}
|
||||
self.interpreter.insert_after(id.0 as u32, m as u16)
|
||||
}
|
||||
|
||||
fn insert_nodes_before(&mut self, id: ElementId, m: usize) {
|
||||
if self.only_write_templates() {
|
||||
if self.skip_mutations() {
|
||||
return;
|
||||
}
|
||||
self.interpreter.insert_before(id.0 as u32, m as u16)
|
||||
|
@ -188,7 +176,7 @@ impl WriteMutations for WebsysDom {
|
|||
value: &AttributeValue,
|
||||
id: ElementId,
|
||||
) {
|
||||
if self.only_write_templates() {
|
||||
if self.skip_mutations() {
|
||||
return;
|
||||
}
|
||||
match value {
|
||||
|
@ -223,14 +211,14 @@ impl WriteMutations for WebsysDom {
|
|||
}
|
||||
|
||||
fn set_node_text(&mut self, value: &str, id: ElementId) {
|
||||
if self.only_write_templates() {
|
||||
if self.skip_mutations() {
|
||||
return;
|
||||
}
|
||||
self.interpreter.set_text(id.0 as u32, value)
|
||||
}
|
||||
|
||||
fn create_event_listener(&mut self, name: &'static str, id: ElementId) {
|
||||
if self.only_write_templates() {
|
||||
if self.skip_mutations() {
|
||||
return;
|
||||
}
|
||||
// mounted events are fired immediately after the element is mounted.
|
||||
|
@ -245,7 +233,7 @@ impl WriteMutations for WebsysDom {
|
|||
}
|
||||
|
||||
fn remove_event_listener(&mut self, name: &'static str, id: ElementId) {
|
||||
if self.only_write_templates() {
|
||||
if self.skip_mutations() {
|
||||
return;
|
||||
}
|
||||
if name == "mounted" {
|
||||
|
@ -257,14 +245,14 @@ impl WriteMutations for WebsysDom {
|
|||
}
|
||||
|
||||
fn remove_node(&mut self, id: ElementId) {
|
||||
if self.only_write_templates() {
|
||||
if self.skip_mutations() {
|
||||
return;
|
||||
}
|
||||
self.interpreter.remove(id.0 as u32)
|
||||
}
|
||||
|
||||
fn push_root(&mut self, id: ElementId) {
|
||||
if self.only_write_templates() {
|
||||
if self.skip_mutations() {
|
||||
return;
|
||||
}
|
||||
self.interpreter.push_root(id.0 as u32)
|
||||
|
|
Loading…
Reference in a new issue