diff --git a/.gitignore b/.gitignore index cdbfec4a03..79c1d430ff 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,6 @@ tmp/ # in debugging we frequently dump wasm to wat with `wasm-tools print` *.wat + +# External macos drives have extra ._ files +._* \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 9cb3cb1f98..7b9a26494b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3552,6 +3552,7 @@ dependencies = [ "tracing-fluent-assertions", "tracing-subscriber", "web-sys", + "xxhash-rust", ] [[package]] diff --git a/packages/cli-harnesses/harness-fullstack-with-optional-tokio/Cargo.toml b/packages/cli-harnesses/harness-fullstack-with-optional-tokio/Cargo.toml index 5b0d69a9dc..7424640c22 100644 --- a/packages/cli-harnesses/harness-fullstack-with-optional-tokio/Cargo.toml +++ b/packages/cli-harnesses/harness-fullstack-with-optional-tokio/Cargo.toml @@ -16,4 +16,3 @@ default = [] server = ["dioxus/server", "dep:tokio"] web = ["dioxus/web"] - diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 89a1848e75..076464a087 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -25,6 +25,7 @@ futures-util = { workspace = true, default-features = false, features = ["alloc" serde = { workspace = true, optional = true, features = ["derive"] } subsecond = { workspace = true } anyhow = { workspace = true } +xxhash-rust = { workspace = true, features = ["const_xxh64"] } [dev-dependencies] dioxus = { workspace = true } diff --git a/packages/core/src/diff/iterator.rs b/packages/core/src/diff/iterator.rs index dafd401709..6684a325b6 100644 --- a/packages/core/src/diff/iterator.rs +++ b/packages/core/src/diff/iterator.rs @@ -483,7 +483,7 @@ impl VNode { let mount = mounts.get(self.mount.get().0).unwrap(); template - .roots + .roots() .iter() .enumerate() .map( diff --git a/packages/core/src/diff/mod.rs b/packages/core/src/diff/mod.rs index 6aea3169ff..667bd05f0c 100644 --- a/packages/core/src/diff/mod.rs +++ b/packages/core/src/diff/mod.rs @@ -98,11 +98,11 @@ impl VirtualDom { #[allow(dead_code)] fn is_dyn_node_only_child(node: &VNode, idx: usize) -> bool { let template = node.template; - let path = template.node_paths[idx]; + let path = template.node_paths()[idx]; // use a loop to index every static node's children until the path has run out // only break if the last path index is a dynamic node - let mut static_node = &template.roots[path[0] as usize]; + let mut static_node = &template.roots()[path[0] as usize]; for i in 1..path.len() - 1 { match static_node { diff --git a/packages/core/src/diff/node.rs b/packages/core/src/diff/node.rs index 5bac17d934..c85a49ae8f 100644 --- a/packages/core/src/diff/node.rs +++ b/packages/core/src/diff/node.rs @@ -143,7 +143,7 @@ impl VNode { &self, root_idx: usize, ) -> Option<(usize, &DynamicNode)> { - self.template.roots[root_idx] + self.template.roots()[root_idx] .dynamic_id() .map(|id| (id, &self.dynamic_nodes[id])) } @@ -180,7 +180,7 @@ impl VNode { pub(crate) fn find_last_element(&self, dom: &VirtualDom) -> ElementId { let mount_id = self.mount.get(); - let last_root_index = self.template.roots.len() - 1; + let last_root_index = self.template.roots().len() - 1; let last = match self.get_dynamic_root_node_and_id(last_root_index) { // This node is static, just get the root id None => dom.get_mounted_root_node(mount_id, last_root_index), @@ -306,7 +306,7 @@ impl VNode { destroy_component_state: bool, replace_with: Option, ) { - let roots = self.template.roots; + let roots = self.template.roots(); for (idx, node) in roots.iter().enumerate() { let last_node = idx == roots.len() - 1; if let Some(id) = node.dynamic_id() { @@ -343,7 +343,7 @@ impl VNode { ) { let template = self.template; for (idx, dyn_node) in self.dynamic_nodes.iter().enumerate() { - let path_len = template.node_paths.get(idx).map(|path| path.len()); + let path_len = template.node_paths().get(idx).map(|path| path.len()); // Roots are cleaned up automatically above and nodes with a empty path are placeholders if let Some(2..) = path_len { self.remove_dynamic_node( @@ -398,7 +398,7 @@ impl VNode { 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() { + for (idx, path) in self.template.attr_paths().iter().enumerate() { // We clean up the roots in the next step, so don't worry about them here if path.len() <= 1 { continue; @@ -429,7 +429,7 @@ impl VNode { let mut old_attributes_iter = old_attrs.iter().peekable(); let mut new_attributes_iter = new_attrs.iter().peekable(); let attribute_id = dom.get_mounted_dyn_attr(mount_id, idx); - let path = self.template.attr_paths[idx]; + let path = self.template.attr_paths()[idx]; loop { match (old_attributes_iter.peek(), new_attributes_iter.peek()) { @@ -558,18 +558,18 @@ impl VNode { entry.insert(VNodeMount { node: self.clone(), parent, - root_ids: vec![ElementId(0); template.roots.len()].into_boxed_slice(), - mounted_attributes: vec![ElementId(0); template.attr_paths.len()] + root_ids: vec![ElementId(0); template.roots().len()].into_boxed_slice(), + mounted_attributes: vec![ElementId(0); template.attr_paths().len()] .into_boxed_slice(), - mounted_dynamic_nodes: vec![usize::MAX; template.node_paths.len()] + mounted_dynamic_nodes: vec![usize::MAX; template.node_paths().len()] .into_boxed_slice(), }); } // 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(); - let mut attrs = template.attr_paths.iter().copied().enumerate().peekable(); + let mut nodes = template.node_paths().iter().copied().enumerate().peekable(); + let mut attrs = template.attr_paths().iter().copied().enumerate().peekable(); // Get the mounted id of this block // At this point, we should have already mounted the block @@ -588,7 +588,7 @@ impl VNode { // Go through each root node and create the node, adding it to the stack. // Each node already exists in the template, so we can just clone it from the template let nodes_created = template - .roots + .roots() .iter() .enumerate() .map(|(root_idx, root)| { @@ -646,7 +646,7 @@ impl VNode { fn reference_to_dynamic_node(&self, mount: MountId, dynamic_node_id: usize) -> ElementRef { ElementRef { path: ElementPath { - path: self.template.node_paths[dynamic_node_id], + path: self.template.node_paths()[dynamic_node_id], }, mount, } @@ -772,7 +772,7 @@ impl VNode { // 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 { // The path is one shorter because the top node is the root - let path = &self.template.node_paths[dynamic_node_id][1..]; + let path = &self.template.node_paths()[dynamic_node_id][1..]; to.replace_placeholder_with_nodes(path, m); } } diff --git a/packages/core/src/error_boundary.rs b/packages/core/src/error_boundary.rs index 03edb60066..7feeb99b26 100644 --- a/packages/core/src/error_boundary.rs +++ b/packages/core/src/error_boundary.rs @@ -153,8 +153,8 @@ impl Element + 'static> From for ErrorHandler { } fn default_handler(errors: ErrorContext) -> Element { - static TEMPLATE: Template = Template { - roots: &[TemplateNode::Element { + static TEMPLATE: Template = Template::new( + &[TemplateNode::Element { tag: "div", namespace: None, attrs: &[TemplateAttribute::Static { @@ -164,9 +164,9 @@ fn default_handler(errors: ErrorContext) -> Element { }], children: &[TemplateNode::Dynamic { id: 0usize }], }], - node_paths: &[&[0u8, 0u8]], - attr_paths: &[], - }; + &[&[0u8, 0u8]], + &[], + ); std::result::Result::Ok(VNode::new( None, TEMPLATE, @@ -174,19 +174,19 @@ fn default_handler(errors: ErrorContext) -> Element { .error() .iter() .map(|e| { - static TEMPLATE: Template = Template { - roots: &[TemplateNode::Element { + static INNER_TEMPLATE: Template = Template::new( + &[TemplateNode::Element { tag: "pre", namespace: None, attrs: &[], children: &[TemplateNode::Dynamic { id: 0usize }], }], - node_paths: &[&[0u8, 0u8]], - attr_paths: &[], - }; + &[&[0u8, 0u8]], + &[], + ); VNode::new( None, - TEMPLATE, + INNER_TEMPLATE, Box::new([e.to_string().into_dyn_node()]), Default::default(), ) @@ -320,11 +320,8 @@ pub fn ErrorBoundary(props: ErrorBoundaryProps) -> Element { (props.handle_error.0)(error_boundary.clone()) } else { std::result::Result::Ok({ - static TEMPLATE: Template = Template { - roots: &[TemplateNode::Dynamic { id: 0usize }], - node_paths: &[&[0u8]], - attr_paths: &[], - }; + static TEMPLATE: Template = + Template::new(&[TemplateNode::Dynamic { id: 0usize }], &[&[0u8]], &[]); VNode::new( None, TEMPLATE, diff --git a/packages/core/src/hotreload_utils.rs b/packages/core/src/hotreload_utils.rs index f258c579e3..b7b1807c89 100644 --- a/packages/core/src/hotreload_utils.rs +++ b/packages/core/src/hotreload_utils.rs @@ -373,11 +373,7 @@ impl HotReloadedTemplate { let node_paths = Self::node_paths(roots); let attr_paths = Self::attr_paths(roots); - let template = Template { - roots, - node_paths, - attr_paths, - }; + let template = Template::new(roots, node_paths, attr_paths); Self { key, dynamic_nodes, diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 0d059bd5cb..92f8840ae9 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -139,11 +139,7 @@ impl VNode { key: None, dynamic_nodes: Box::new([DynamicNode::Placeholder(Default::default())]), dynamic_attrs: Box::new([]), - template: Template { - roots: &[TemplateNode::Dynamic { id: 0 }], - node_paths: &[&[0]], - attr_paths: &[], - }, + template: Template::new(&[TemplateNode::Dynamic { id: 0 }], &[&[0]], &[]), }) }) .clone() @@ -176,7 +172,7 @@ impl VNode { /// /// Returns [`None`] if the root is actually a static node (Element/Text) pub fn dynamic_root(&self, idx: usize) -> Option<&DynamicNode> { - self.template.roots[idx] + self.template.roots()[idx] .dynamic_id() .map(|id| &self.dynamic_nodes[id]) } @@ -276,7 +272,7 @@ pub struct Template { /// /// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm. #[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))] - pub roots: StaticTemplateArray, + roots: StaticTemplateArray, /// The paths of each node relative to the root of the template. /// @@ -286,7 +282,7 @@ pub struct Template { feature = "serialize", serde(deserialize_with = "deserialize_bytes_leaky") )] - pub node_paths: StaticPathArray, + node_paths: StaticPathArray, /// The paths of each dynamic attribute relative to the root of the template /// @@ -296,50 +292,153 @@ pub struct Template { feature = "serialize", serde(deserialize_with = "deserialize_bytes_leaky", bound = "") )] - pub attr_paths: StaticPathArray, + attr_paths: StaticPathArray, + + /// Compile-time hash of template content for reliable cross-crate comparison. + /// This ensures identical templates compare equal regardless of optimization levels. + /// + /// Uses xxh64 (64-bit hash). By the birthday paradox, collision probability is: + /// P ≈ 1 - e^(-n²/(2 × 2^64)) where n = number of templates. + /// + /// - 1,000 templates: P ≈ 2.7 × 10^-14 (essentially zero) + /// - 10,000 templates: P ≈ 2.7 × 10^-12 (essentially zero) + /// - 1 million templates: P ≈ 0.000003% + /// - 50% collision chance requires ~5 billion templates + /// + /// For any realistic application, collision probability is negligible. + hash: u64, } -// Are identical static items merged in the current build. Rust doesn't have a cfg(merge_statics) attribute -// so we have to check this manually -#[allow(unpredictable_function_pointer_comparisons)] // This attribute should be removed once MSRV is 1.85 or greater and the below change is made -fn static_items_merged() -> bool { - fn a() {} - fn b() {} - a as fn() == b as fn() - // std::ptr::fn_addr_eq(a as fn(), b as fn()) <<<<---- This should replace the a as fn() === b as fn() once the MSRV is 1.85 or greater +impl Template { + /// Create a new Template with the given roots, node_paths, and attr_paths. + /// The hash is computed automatically from the template content. + pub const fn new( + roots: &'static [TemplateNode], + node_paths: &'static [&'static [u8]], + attr_paths: &'static [&'static [u8]], + ) -> Self { + Self { + roots, + node_paths, + attr_paths, + hash: Self::compute_hash(roots, node_paths, attr_paths), + } + } + + /// Get the template nodes that make up this template. + pub const fn roots(&self) -> &'static [TemplateNode] { + self.roots + } + + /// Get the paths of each dynamic node relative to the root of the template. + pub const fn node_paths(&self) -> &'static [&'static [u8]] { + self.node_paths + } + + /// Get the paths of each dynamic attribute relative to the root of the template. + pub const fn attr_paths(&self) -> &'static [&'static [u8]] { + self.attr_paths + } + + /// Compute a content-based hash of template structure. + /// This is const so it can be used both at compile time and runtime. + const fn compute_hash( + roots: &[TemplateNode], + node_paths: &[&[u8]], + attr_paths: &[&[u8]], + ) -> u64 { + use xxhash_rust::const_xxh64::xxh64; + + const fn hash_template_node(node: &TemplateNode, seed: u64) -> u64 { + match node { + TemplateNode::Element { + tag, + namespace, + attrs, + children, + } => { + let mut h = xxh64(tag.as_bytes(), seed); + if let Some(ns) = *namespace { + h = xxh64(ns.as_bytes(), h); + } + + // Hash attributes (already in deterministic order from macro) + let mut i = 0; + while i < attrs.len() { + h = match &attrs[i] { + TemplateAttribute::Static { + name, + value, + namespace, + } => { + let mut new_h = xxh64(name.as_bytes(), h); + new_h = xxh64(value.as_bytes(), new_h); + if let Some(ns) = *namespace { + new_h = xxh64(ns.as_bytes(), new_h); + } + new_h + } + TemplateAttribute::Dynamic { id } => { + xxh64(&(*id as u64).to_le_bytes(), xxh64(&[0xFE], h)) + } + }; + i += 1; + } + + // Hash children + let mut i = 0; + while i < children.len() { + h = hash_template_node(&children[i], h); + i += 1; + } + + h + } + TemplateNode::Text { text } => xxh64(text.as_bytes(), seed), + TemplateNode::Dynamic { id } => { + xxh64(&(*id as u64).to_le_bytes(), xxh64(&[0xFF], seed)) + } + } + } + + let mut hash = 0u64; + + // Hash roots + let mut i = 0; + while i < roots.len() { + hash = hash_template_node(&roots[i], hash); + i += 1; + } + + // Hash node paths (mixed with a section marker so they can't collapse into attr_paths) + hash = xxh64(&[0xA1], hash); + let mut i = 0; + while i < node_paths.len() { + hash = xxh64(node_paths[i], hash); + i += 1; + } + + // Hash attr paths + hash = xxh64(&[0xA2], hash); + let mut i = 0; + while i < attr_paths.len() { + hash = xxh64(attr_paths[i], hash); + i += 1; + } + + hash + } } impl std::hash::Hash for Template { fn hash(&self, state: &mut H) { - // If identical static items are merged, we can compare templates by pointer - if static_items_merged() { - 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); - } - // Otherwise, we hash by value - else { - self.roots.hash(state); - self.node_paths.hash(state); - self.attr_paths.hash(state); - } + self.hash.hash(state); } } impl PartialEq for Template { fn eq(&self, other: &Self) -> bool { - // If identical static items are merged, we can compare templates by pointer - if static_items_merged() { - 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 _) - } - // Otherwise, we compare by value - else { - self.roots == other.roots - && self.node_paths == other.node_paths - && self.attr_paths == other.attr_paths - } + self.hash == other.hash } } diff --git a/packages/core/src/root_wrapper.rs b/packages/core/src/root_wrapper.rs index c5da7b4d3a..cd72510610 100644 --- a/packages/core/src/root_wrapper.rs +++ b/packages/core/src/root_wrapper.rs @@ -7,11 +7,8 @@ use crate::{ #[allow(non_snake_case)] #[allow(clippy::let_and_return)] pub(crate) fn RootScopeWrapper(props: RootProps) -> Element { - static TEMPLATE: Template = Template { - roots: &[TemplateNode::Dynamic { id: 0usize }], - node_paths: &[&[0u8]], - attr_paths: &[], - }; + static TEMPLATE: Template = + Template::new(&[TemplateNode::Dynamic { id: 0usize }], &[&[0u8]], &[]); Element::Ok(VNode::new( None, TEMPLATE, diff --git a/packages/core/src/runtime.rs b/packages/core/src/runtime.rs index e112e68832..9f7b51ad02 100644 --- a/packages/core/src/runtime.rs +++ b/packages/core/src/runtime.rs @@ -427,7 +427,7 @@ fn MyComponent() -> Element {{ mount_id = el_ref.mount.get().as_usize(); // Accumulate listeners into the listener list bottom to top - for (idx, this_path) in node_template.attr_paths.iter().enumerate() { + for (idx, this_path) in node_template.attr_paths().iter().enumerate() { let attrs = &*el_ref.dynamic_attrs[idx]; for attr in attrs.iter() { @@ -485,7 +485,7 @@ fn MyComponent() -> Element {{ let node_template = el_ref.template; let target_path = node.path; - for (idx, this_path) in node_template.attr_paths.iter().enumerate() { + for (idx, this_path) in node_template.attr_paths().iter().enumerate() { let attrs = &*el_ref.dynamic_attrs[idx]; for attr in attrs.iter() { diff --git a/packages/core/tests/attributes_pass.rs b/packages/core/tests/attributes_pass.rs index 49b4080d38..601ee174e0 100644 --- a/packages/core/tests/attributes_pass.rs +++ b/packages/core/tests/attributes_pass.rs @@ -18,9 +18,9 @@ fn attributes_pass_properly() { let template = &o.template; - assert_eq!(template.attr_paths.len(), 3); + assert_eq!(template.attr_paths().len(), 3); - let _circle = template.roots[0]; + let _circle = template.roots()[0]; let TemplateNode::Element { attrs, tag, namespace, children } = _circle else { panic!("Expected an element"); }; diff --git a/packages/core/tests/fuzzing.rs b/packages/core/tests/fuzzing.rs index ad6389ce07..20af1d93ee 100644 --- a/packages/core/tests/fuzzing.rs +++ b/packages/core/tests/fuzzing.rs @@ -171,7 +171,7 @@ fn create_random_template(depth: u8) -> (Template, Box<[DynamicNode]>) { DynamicNodeType::Other => create_random_dynamic_node(depth + 1), }) .collect(); - (Template { roots, node_paths, attr_paths }, dynamic_nodes) + (Template::new(roots, node_paths, attr_paths), dynamic_nodes) } fn create_random_dynamic_node(depth: u8) -> DynamicNode { @@ -182,11 +182,7 @@ fn create_random_dynamic_node(depth: u8) -> DynamicNode { .map(|_| { VNode::new( None, - Template { - roots: &[TemplateNode::Dynamic { id: 0 }], - node_paths: &[&[0]], - attr_paths: &[], - }, + Template::new(&[TemplateNode::Dynamic { id: 0 }], &[&[0]], &[]), Box::new([DynamicNode::Component(VComponent::new( create_random_element, DepthProps { depth, root: false }, @@ -248,7 +244,7 @@ fn create_random_element(cx: DepthProps) -> Element { None, template, dynamic_nodes, - (0..template.attr_paths.len()) + (0..template.attr_paths().len()) .map(|_| Box::new([create_random_dynamic_attr()]) as Box<[Attribute]>) .collect(), ) @@ -258,7 +254,7 @@ fn create_random_element(cx: DepthProps) -> Element { let (template, dynamic_nodes) = match *last_template.borrow() { Some(template) => ( template, - (0..template.node_paths.len()) + (0..template.node_paths().len()) .map(|_| create_random_dynamic_node(cx.depth + 1)) .collect(), ), @@ -268,7 +264,7 @@ fn create_random_element(cx: DepthProps) -> Element { None, template, dynamic_nodes, - (0..template.attr_paths.len()) + (0..template.attr_paths().len()) .map(|_| Box::new([create_random_dynamic_attr()]) as Box<[Attribute]>) .collect(), ) diff --git a/packages/document/src/elements/mod.rs b/packages/document/src/elements/mod.rs index da3990686f..fdd926ab9a 100644 --- a/packages/document/src/elements/mod.rs +++ b/packages/document/src/elements/mod.rs @@ -2,9 +2,7 @@ use std::{cell::RefCell, collections::HashSet, rc::Rc}; -use dioxus_core::{ - Attribute, DynamicNode, Element, RenderError, Runtime, ScopeId, Template, TemplateNode, -}; +use dioxus_core::{Attribute, DynamicNode, Element, RenderError, Runtime, ScopeId, TemplateNode}; use dioxus_core_macro::*; mod link; @@ -77,29 +75,25 @@ fn extract_single_text_node(children: &Element) -> Result Ok(text.to_string()), - // rsx! { "title: {dynamic_text}" } - Template { - roots: &[TemplateNode::Dynamic { id }], - node_paths: &[&[0]], - attr_paths: &[], - .. - } => { - let node = &vnode.dynamic_nodes[id]; - match node { - DynamicNode::Text(text) => Ok(text.value.clone()), - _ => Err(ExtractSingleTextNodeError::NonTextNode), - } - } - _ => Err(ExtractSingleTextNodeError::NonTemplate), + let template = vnode.template; + let roots = template.roots(); + let node_paths = template.node_paths(); + let attr_paths = template.attr_paths(); + + // rsx! { "static text" } + if let ([TemplateNode::Text { text }], [], []) = (roots, node_paths, attr_paths) { + return Ok(text.to_string()); } + // rsx! { "title: {dynamic_text}" } + if let ([TemplateNode::Dynamic { id }], [&[0]], []) = (roots, node_paths, attr_paths) { + let node = &vnode.dynamic_nodes[*id]; + return match node { + DynamicNode::Text(text) => Ok(text.value.clone()), + _ => Err(ExtractSingleTextNodeError::NonTextNode), + }; + } + + Err(ExtractSingleTextNodeError::NonTemplate) } fn get_or_insert_root_context() -> T { diff --git a/packages/fullstack-server/src/ssr.rs b/packages/fullstack-server/src/ssr.rs index 11c874e307..c90513d04b 100644 --- a/packages/fullstack-server/src/ssr.rs +++ b/packages/fullstack-server/src/ssr.rs @@ -583,8 +583,8 @@ impl SsrRendererPool { fn take_from_vnode(context: &HydrationContext, vdom: &VirtualDom, vnode: &VNode) { let template = &vnode.template; - let mut dynamic_nodes_iter = template.node_paths.iter().copied().enumerate().peekable(); - for (root_idx, node) in template.roots.iter().enumerate() { + let mut dynamic_nodes_iter = template.node_paths().iter().copied().enumerate().peekable(); + for (root_idx, node) in template.roots().iter().enumerate() { match node { TemplateNode::Element { .. } => { // dioxus core runs nodes in an odd order to not mess up template order. We need to match diff --git a/packages/html/src/render_template.rs b/packages/html/src/render_template.rs index 3705095385..ee5d846b7c 100644 --- a/packages/html/src/render_template.rs +++ b/packages/html/src/render_template.rs @@ -7,7 +7,7 @@ use std::fmt::Write; pub fn render_template_to_html(template: &Template) -> String { let mut out = String::new(); - for root in template.roots { + for root in template.roots() { render_template_node(root, &mut out).unwrap(); } diff --git a/packages/interpreter/src/write_native_mutations.rs b/packages/interpreter/src/write_native_mutations.rs index afb5d33e03..0c4c56c803 100644 --- a/packages/interpreter/src/write_native_mutations.rs +++ b/packages/interpreter/src/write_native_mutations.rs @@ -100,11 +100,11 @@ impl WriteMutations for MutationState { let tmpl_id = self.templates.len() as u16; self.templates.insert(template, tmpl_id); - for root in template.roots.iter() { + for root in template.roots().iter() { self.create_template_node(root); } - let len = template.roots.len() as u16; + let len = template.roots().len() as u16; self.channel.add_templates(tmpl_id, len); tmpl_id diff --git a/packages/native-dom/src/mutation_writer.rs b/packages/native-dom/src/mutation_writer.rs index 104df09d45..631ff24cab 100644 --- a/packages/native-dom/src/mutation_writer.rs +++ b/packages/native-dom/src/mutation_writer.rs @@ -224,7 +224,7 @@ impl WriteMutations for MutationWriter<'_> { // TODO: proper template node support let template_entry = self.state.templates.entry(template).or_insert_with(|| { let template_root_ids: Vec = template - .roots + .roots() .iter() .map(|root| create_template_node(&mut self.docm, root)) .collect(); diff --git a/packages/rsx/src/template_body.rs b/packages/rsx/src/template_body.rs index d2ddda1bd2..af08633f22 100644 --- a/packages/rsx/src/template_body.rs +++ b/packages/rsx/src/template_body.rs @@ -207,11 +207,11 @@ impl ToTokens for TemplateBody { #[cfg(not(debug_assertions))] { #[doc(hidden)] // vscode please stop showing these in symbol search - static ___TEMPLATE: dioxus_core::Template = dioxus_core::Template { - roots: __TEMPLATE_ROOTS, - node_paths: &[ #( #node_paths ),* ], - attr_paths: &[ #( #attr_paths ),* ], - }; + static ___TEMPLATE: dioxus_core::Template = dioxus_core::Template::new( + __TEMPLATE_ROOTS, + &[ #( #node_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)] diff --git a/packages/ssr/src/cache.rs b/packages/ssr/src/cache.rs index 1909c297e9..3fd7cf6a8c 100644 --- a/packages/ssr/src/cache.rs +++ b/packages/ssr/src/cache.rs @@ -163,7 +163,7 @@ impl StringCache { let mut cur_path = vec![]; - for (root_idx, root) in template.template.roots.iter().enumerate() { + for (root_idx, root) in template.template.roots().iter().enumerate() { from_template_recursive( root, &mut cur_path, diff --git a/packages/web/src/hydration/hydrate.rs b/packages/web/src/hydration/hydrate.rs index 35b4ad1a64..968a4b7af4 100644 --- a/packages/web/src/hydration/hydrate.rs +++ b/packages/web/src/hydration/hydrate.rs @@ -268,7 +268,7 @@ impl WebsysDom { ids: &mut Vec, to_mount: &mut Vec, ) -> Result<(), RehydrationError> { - for (i, root) in vnode.template.roots.iter().enumerate() { + for (i, root) in vnode.template.roots().iter().enumerate() { self.rehydrate_template_node( dom, vnode, diff --git a/packages/web/src/mutations.rs b/packages/web/src/mutations.rs index ac511abb2d..3cf7752490 100644 --- a/packages/web/src/mutations.rs +++ b/packages/web/src/mutations.rs @@ -129,7 +129,7 @@ impl WriteMutations for WebsysDom { } let tmpl_id = self.templates.get(&template).cloned().unwrap_or_else(|| { let mut roots = vec![]; - for root in template.roots { + for root in template.roots() { roots.push(self.create_template_node(root)) } let id = self.templates.len() as u16;