dioxus/packages/rsx/src/lib.rs

773 lines
26 KiB
Rust
Raw Normal View History

2022-04-24 02:35:52 -04:00
//! Parse the root tokens in the rsx!{} macro
//! =========================================
//!
//! This parsing path emerges directly from the macro call, with `RsxRender` being the primary entrance into parsing.
//! This feature must support:
//! - [x] Optionally rendering if the `in XYZ` pattern is present
//! - [x] Fragments as top-level element (through ambiguous)
//! - [x] Components as top-level element (through ambiguous)
//! - [x] Tags as top-level elements (through ambiguous)
//! - [x] Good errors if parsing fails
//!
//! Any errors in using rsx! will likely occur when people start using it, so the first errors must be really helpful.
#[macro_use]
mod errors;
mod component;
mod element;
2023-07-19 10:19:23 -07:00
#[cfg(feature = "hot_reload")]
pub mod hot_reload;
2022-05-25 08:58:59 -05:00
mod ifmt;
2022-04-24 02:35:52 -04:00
mod node;
2023-07-19 10:19:23 -07:00
use std::{fmt::Debug, hash::Hash};
2022-12-10 16:21:31 -06:00
2022-04-24 02:35:52 -04:00
// Re-export the namespaces into each other
pub use component::*;
2023-07-19 10:19:23 -07:00
#[cfg(feature = "hot_reload")]
2022-12-10 12:29:15 -06:00
use dioxus_core::{Template, TemplateAttribute, TemplateNode};
2022-04-24 02:35:52 -04:00
pub use element::*;
2023-07-19 10:19:23 -07:00
#[cfg(feature = "hot_reload")]
pub use hot_reload::HotReloadingContext;
2022-05-25 08:58:59 -05:00
pub use ifmt::*;
2023-07-19 10:19:23 -07:00
#[cfg(feature = "hot_reload")]
2022-12-10 16:21:31 -06:00
use internment::Intern;
2022-04-24 02:35:52 -04:00
pub use node::*;
// imports
use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
parse::{Parse, ParseStream},
Result, Token,
2022-04-24 02:35:52 -04:00
};
2023-07-19 10:19:23 -07:00
#[cfg(feature = "hot_reload")]
2022-12-10 16:21:31 -06:00
// interns a object into a static object, resusing the value if it already exists
2022-12-10 21:18:44 -06:00
fn intern<T: Eq + Hash + Send + Sync + ?Sized + 'static>(s: impl Into<Intern<T>>) -> &'static T {
2022-12-10 16:21:31 -06:00
s.into().as_ref()
}
/// Fundametnally, every CallBody is a template
2022-12-11 09:38:38 -06:00
#[derive(Default, Debug)]
2022-04-24 02:35:52 -04:00
pub struct CallBody {
pub roots: Vec<BodyNode>,
}
impl CallBody {
2023-07-19 10:19:23 -07:00
#[cfg(feature = "hot_reload")]
2022-12-10 16:21:31 -06:00
/// This will try to create a new template from the current body and the previous body. This will return None if the rsx has some dynamic part that has changed.
2022-12-10 12:29:15 -06:00
/// This function intentionally leaks memory to create a static template.
/// Keeping the template static allows us to simplify the core of dioxus and leaking memory in dev mode is less of an issue.
/// the previous_location is the location of the previous template at the time the template was originally compiled.
pub fn update_template<Ctx: HotReloadingContext>(
2022-12-10 16:21:31 -06:00
&self,
template: Option<CallBody>,
2022-12-10 16:21:31 -06:00
location: &'static str,
2022-12-19 09:37:15 -06:00
) -> Option<Template<'static>> {
let mut renderer: TemplateRenderer = TemplateRenderer {
roots: &self.roots,
location: None,
};
renderer.update_template::<Ctx>(template, location)
2022-12-10 12:29:15 -06:00
}
/// Render the template with a manually set file location. This should be used when multiple rsx! calls are used in the same macro
pub fn render_with_location(&self, location: String) -> TokenStream2 {
let body = TemplateRenderer {
roots: &self.roots,
location: Some(location),
};
quote! {
::dioxus::core::LazyNodes::new( move | __cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode {
#body
})
}
}
2022-12-10 12:29:15 -06:00
}
2022-04-24 02:35:52 -04:00
impl Parse for CallBody {
fn parse(input: ParseStream) -> Result<Self> {
let mut roots = Vec::new();
while !input.is_empty() {
let node = input.parse::<BodyNode>()?;
if input.peek(Token![,]) {
let _ = input.parse::<Token![,]>();
}
roots.push(node);
}
2022-12-23 17:47:57 -05:00
Ok(Self { roots })
2022-04-24 02:35:52 -04:00
}
}
/// Serialize the same way, regardless of flavor
impl ToTokens for CallBody {
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
let body = TemplateRenderer {
roots: &self.roots,
location: None,
};
2022-11-02 01:00:37 -07:00
2022-12-23 17:47:57 -05:00
out_tokens.append_all(quote! {
::dioxus::core::LazyNodes::new( move | __cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode {
#body
2022-11-02 17:29:18 -07:00
})
2022-12-23 17:47:57 -05:00
})
}
}
#[derive(Default, Debug)]
pub struct RenderCallBody(pub CallBody);
impl ToTokens for RenderCallBody {
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
let body: TemplateRenderer = TemplateRenderer {
2022-12-23 17:47:57 -05:00
roots: &self.0.roots,
location: None,
2022-12-23 17:47:57 -05:00
};
out_tokens.append_all(quote! {
Some({
let __cx = cx;
#body
2022-11-02 17:29:18 -07:00
})
2022-12-23 17:47:57 -05:00
})
2022-11-02 17:29:18 -07:00
}
}
2022-11-02 17:29:18 -07:00
pub struct TemplateRenderer<'a> {
pub roots: &'a [BodyNode],
pub location: Option<String>,
2022-11-02 17:29:18 -07:00
}
impl<'a> TemplateRenderer<'a> {
2023-07-19 10:19:23 -07:00
#[cfg(feature = "hot_reload")]
fn update_template<Ctx: HotReloadingContext>(
2022-12-10 16:21:31 -06:00
&mut self,
previous_call: Option<CallBody>,
2022-12-10 16:21:31 -06:00
location: &'static str,
) -> Option<Template<'static>> {
2022-12-11 09:38:38 -06:00
let mut mapping = previous_call.map(|call| DynamicMapping::from(call.roots));
2022-12-10 21:18:44 -06:00
let mut context = DynamicContext::default();
2022-12-10 12:29:15 -06:00
2022-12-10 21:18:44 -06:00
let mut roots = Vec::new();
for (idx, root) in self.roots.iter().enumerate() {
context.current_path.push(idx as u8);
roots.push(context.update_node::<Ctx>(root, &mut mapping)?);
2022-12-10 21:18:44 -06:00
context.current_path.pop();
}
2022-12-10 12:29:15 -06:00
2022-12-10 16:21:31 -06:00
Some(Template {
name: location,
roots: intern(roots.as_slice()),
node_paths: intern(
2022-12-10 12:29:15 -06:00
context
.node_paths
.into_iter()
2022-12-10 16:21:31 -06:00
.map(|path| intern(path.as_slice()))
2022-12-10 12:29:15 -06:00
.collect::<Vec<_>>()
2022-12-10 16:21:31 -06:00
.as_slice(),
2022-12-10 12:29:15 -06:00
),
2022-12-10 16:21:31 -06:00
attr_paths: intern(
2022-12-10 12:29:15 -06:00
context
.attr_paths
.into_iter()
2022-12-10 16:21:31 -06:00
.map(|path| intern(path.as_slice()))
2022-12-10 12:29:15 -06:00
.collect::<Vec<_>>()
2022-12-10 16:21:31 -06:00
.as_slice(),
2022-12-10 12:29:15 -06:00
),
2022-12-10 16:21:31 -06:00
})
2022-12-10 12:29:15 -06:00
}
}
2022-11-02 17:29:18 -07:00
impl<'a> ToTokens for TemplateRenderer<'a> {
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
let mut context = DynamicContext::default();
2022-11-03 01:24:20 -07:00
let key = match self.roots.get(0) {
Some(BodyNode::Element(el)) if self.roots.len() == 1 => el.key.clone(),
Some(BodyNode::Component(comp)) if self.roots.len() == 1 => comp.key().cloned(),
_ => None,
};
2022-11-03 01:38:18 -07:00
2022-11-03 01:24:20 -07:00
let key_tokens = match key {
2022-11-30 23:54:30 -05:00
Some(tok) => quote! { Some( __cx.raw_text(#tok) ) },
2022-11-03 01:24:20 -07:00
None => quote! { None },
};
2022-11-03 20:56:31 -07:00
let spndbg = format!("{:?}", self.roots[0].span());
2022-12-23 18:27:53 -05:00
let root_col = spndbg
.rsplit_once("..")
.and_then(|(_, after)| after.split_once(')').map(|(before, _)| before))
.unwrap_or_default();
2022-11-03 20:56:31 -07:00
2022-11-02 01:00:37 -07:00
let root_printer = self.roots.iter().enumerate().map(|(idx, root)| {
context.current_path.push(idx as u8);
2022-11-02 17:29:18 -07:00
let out = context.render_static_node(root);
2022-11-02 01:00:37 -07:00
context.current_path.pop();
out
});
let name = match self.location {
Some(ref loc) => quote! { #loc },
None => quote! {
concat!(
file!(),
":",
line!(),
":",
column!(),
":",
#root_col
)
},
};
// Render and release the mutable borrow on context
let roots = quote! { #( #root_printer ),* };
2023-08-15 12:19:05 -05:00
let root_count = self.roots.len();
2022-10-26 18:04:47 -07:00
let node_printer = &context.dynamic_nodes;
2022-11-02 17:29:18 -07:00
let dyn_attr_printer = &context.dynamic_attributes;
2022-11-03 01:38:18 -07:00
let node_paths = context.node_paths.iter().map(|it| quote!(&[#(#it),*]));
let attr_paths = context.attr_paths.iter().map(|it| quote!(&[#(#it),*]));
2022-11-02 01:00:37 -07:00
2022-11-02 17:29:18 -07:00
out_tokens.append_all(quote! {
2022-11-02 01:00:37 -07:00
static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {
name: #name,
2022-11-03 01:24:20 -07:00
roots: &[ #roots ],
node_paths: &[ #(#node_paths),* ],
attr_paths: &[ #(#attr_paths),* ],
2022-11-02 01:00:37 -07:00
};
::dioxus::core::VNode {
parent: None,
2022-11-03 01:24:20 -07:00
key: #key_tokens,
2022-12-19 19:28:44 -06:00
template: std::cell::Cell::new(TEMPLATE),
2023-08-15 12:19:05 -05:00
root_ids: dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in(#root_count, __cx.bump()).into(),
2022-11-02 01:00:37 -07:00
dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]),
2022-11-02 17:29:18 -07:00
dynamic_attrs: __cx.bump().alloc([ #( #dyn_attr_printer ),* ]),
2022-11-02 01:00:37 -07:00
}
2022-11-02 17:29:18 -07:00
});
}
}
2022-12-10 16:21:31 -06:00
2023-07-19 10:19:23 -07:00
#[cfg(feature = "hot_reload")]
2022-12-19 11:48:28 -06:00
#[derive(Default, Debug)]
2022-12-10 21:18:44 -06:00
struct DynamicMapping {
2023-07-19 10:19:23 -07:00
attribute_to_idx: std::collections::HashMap<ElementAttr, Vec<usize>>,
2022-12-10 21:18:44 -06:00
last_attribute_idx: usize,
2023-07-19 10:19:23 -07:00
node_to_idx: std::collections::HashMap<BodyNode, Vec<usize>>,
2022-12-10 21:18:44 -06:00
last_element_idx: usize,
}
2023-07-19 10:19:23 -07:00
#[cfg(feature = "hot_reload")]
2022-12-10 21:18:44 -06:00
impl DynamicMapping {
fn from(nodes: Vec<BodyNode>) -> Self {
let mut new = Self::default();
for node in nodes {
new.add_node(node);
}
new
}
2022-12-11 09:38:38 -06:00
fn get_attribute_idx(&mut self, attr: &ElementAttr) -> Option<usize> {
2022-12-10 21:18:44 -06:00
self.attribute_to_idx
2022-12-11 09:38:38 -06:00
.get_mut(attr)
.and_then(|idxs| idxs.pop())
2022-12-10 21:18:44 -06:00
}
2022-12-11 09:38:38 -06:00
fn get_node_idx(&mut self, node: &BodyNode) -> Option<usize> {
self.node_to_idx.get_mut(node).and_then(|idxs| idxs.pop())
2022-12-10 21:18:44 -06:00
}
2022-12-11 09:38:38 -06:00
fn insert_attribute(&mut self, attr: ElementAttr) -> usize {
2022-12-10 21:18:44 -06:00
let idx = self.last_attribute_idx;
self.last_attribute_idx += 1;
2023-08-08 13:43:57 -07:00
self.attribute_to_idx.entry(attr).or_default().push(idx);
2022-12-10 21:18:44 -06:00
idx
}
fn insert_node(&mut self, node: BodyNode) -> usize {
let idx = self.last_element_idx;
self.last_element_idx += 1;
2023-08-08 13:43:57 -07:00
self.node_to_idx.entry(node).or_default().push(idx);
2022-12-10 21:18:44 -06:00
idx
}
fn add_node(&mut self, node: BodyNode) {
match node {
BodyNode::Element(el) => {
for attr in el.attributes {
match &attr.attr {
ElementAttr::CustomAttrText { value, .. }
| ElementAttr::AttrText { value, .. }
if value.is_static() => {}
ElementAttr::AttrExpression { .. }
| ElementAttr::AttrText { .. }
| ElementAttr::CustomAttrText { .. }
| ElementAttr::CustomAttrExpression { .. }
| ElementAttr::EventTokens { .. } => {
2022-12-11 09:38:38 -06:00
self.insert_attribute(attr.attr);
2022-12-10 21:18:44 -06:00
}
}
}
for child in el.children {
self.add_node(child);
}
}
BodyNode::Text(text) if text.is_static() => {}
BodyNode::RawExpr(_)
| BodyNode::Text(_)
| BodyNode::ForLoop(_)
| BodyNode::IfChain(_)
| BodyNode::Component(_) => {
self.insert_node(node);
}
}
}
}
2022-12-10 16:21:31 -06:00
// As we create the dynamic nodes, we want to keep track of them in a linear fashion
// We'll use the size of the vecs to determine the index of the dynamic node in the final output
#[derive(Default, Debug)]
2022-11-02 17:29:18 -07:00
pub struct DynamicContext<'a> {
dynamic_nodes: Vec<&'a BodyNode>,
dynamic_attributes: Vec<&'a ElementAttrNamed>,
current_path: Vec<u8>,
2022-11-03 01:24:20 -07:00
node_paths: Vec<Vec<u8>>,
attr_paths: Vec<Vec<u8>>,
2022-12-10 14:09:59 -06:00
}
2022-11-02 17:29:18 -07:00
impl<'a> DynamicContext<'a> {
2023-07-19 10:19:23 -07:00
#[cfg(feature = "hot_reload")]
fn update_node<Ctx: HotReloadingContext>(
2022-12-10 21:18:44 -06:00
&mut self,
root: &'a BodyNode,
2022-12-11 09:38:38 -06:00
mapping: &mut Option<DynamicMapping>,
2022-12-10 21:18:44 -06:00
) -> Option<TemplateNode<'static>> {
2022-12-10 12:29:15 -06:00
match root {
BodyNode::Element(el) => {
2022-12-10 14:09:59 -06:00
let element_name_rust = el.name.to_string();
2022-12-10 21:18:44 -06:00
let mut static_attrs = Vec::new();
for attr in &el.attributes {
match &attr.attr {
2022-12-10 14:09:59 -06:00
ElementAttr::AttrText { name, value } if value.is_static() => {
2022-12-10 12:29:15 -06:00
let value = value.source.as_ref().unwrap();
2022-12-10 14:09:59 -06:00
let attribute_name_rust = name.to_string();
let (name, namespace) =
Ctx::map_attribute(&element_name_rust, &attribute_name_rust)
2022-12-10 16:21:31 -06:00
.unwrap_or((intern(attribute_name_rust.as_str()), None));
2022-12-10 21:18:44 -06:00
static_attrs.push(TemplateAttribute::Static {
2022-12-10 14:09:59 -06:00
name,
namespace,
2022-12-10 16:21:31 -06:00
value: intern(value.value().as_str()),
2022-12-10 21:18:44 -06:00
})
2022-12-10 12:29:15 -06:00
}
ElementAttr::CustomAttrText { name, value } if value.is_static() => {
let value = value.source.as_ref().unwrap();
2022-12-10 21:18:44 -06:00
static_attrs.push(TemplateAttribute::Static {
2022-12-10 16:21:31 -06:00
name: intern(name.value().as_str()),
2022-12-10 12:29:15 -06:00
namespace: None,
2022-12-10 16:21:31 -06:00
value: intern(value.value().as_str()),
2022-12-10 21:18:44 -06:00
})
2022-12-10 12:29:15 -06:00
}
ElementAttr::AttrExpression { .. }
| ElementAttr::AttrText { .. }
| ElementAttr::CustomAttrText { .. }
| ElementAttr::CustomAttrExpression { .. }
| ElementAttr::EventTokens { .. } => {
let idx = match mapping {
2022-12-11 09:38:38 -06:00
Some(mapping) => mapping.get_attribute_idx(&attr.attr)?,
2022-12-10 21:18:44 -06:00
None => self.dynamic_attributes.len(),
};
2022-12-10 12:29:15 -06:00
self.dynamic_attributes.push(attr);
if self.attr_paths.len() <= idx {
self.attr_paths.resize_with(idx + 1, Vec::new);
}
self.attr_paths[idx] = self.current_path.clone();
static_attrs.push(TemplateAttribute::Dynamic { id: idx })
2022-12-10 12:29:15 -06:00
}
2022-12-10 21:18:44 -06:00
}
}
let mut children = Vec::new();
for (idx, root) in el.children.iter().enumerate() {
self.current_path.push(idx as u8);
children.push(self.update_node::<Ctx>(root, mapping)?);
2022-12-10 21:18:44 -06:00
self.current_path.pop();
}
2022-12-10 12:29:15 -06:00
2022-12-10 14:09:59 -06:00
let (tag, namespace) = Ctx::map_element(&element_name_rust)
2022-12-10 16:21:31 -06:00
.unwrap_or((intern(element_name_rust.as_str()), None));
2022-12-10 21:18:44 -06:00
Some(TemplateNode::Element {
2022-12-10 14:09:59 -06:00
tag,
namespace,
2022-12-10 16:21:31 -06:00
attrs: intern(static_attrs.into_boxed_slice()),
children: intern(children.as_slice()),
2022-12-10 21:18:44 -06:00
})
2022-12-10 12:29:15 -06:00
}
BodyNode::Text(text) if text.is_static() => {
let text = text.source.as_ref().unwrap();
2022-12-10 21:18:44 -06:00
Some(TemplateNode::Text {
2022-12-10 16:21:31 -06:00
text: intern(text.value().as_str()),
2022-12-10 21:18:44 -06:00
})
2022-12-10 12:29:15 -06:00
}
BodyNode::RawExpr(_)
| BodyNode::Text(_)
| BodyNode::ForLoop(_)
| BodyNode::IfChain(_)
| BodyNode::Component(_) => {
let idx = match mapping {
2022-12-10 21:18:44 -06:00
Some(mapping) => mapping.get_node_idx(root)?,
None => self.dynamic_nodes.len(),
};
2022-12-10 12:29:15 -06:00
self.dynamic_nodes.push(root);
if self.node_paths.len() <= idx {
self.node_paths.resize_with(idx + 1, Vec::new);
}
self.node_paths[idx] = self.current_path.clone();
2022-12-10 12:29:15 -06:00
2022-12-10 21:18:44 -06:00
Some(match root {
BodyNode::Text(_) => TemplateNode::DynamicText { id: idx },
_ => TemplateNode::Dynamic { id: idx },
2022-12-10 21:18:44 -06:00
})
2022-12-10 12:29:15 -06:00
}
}
}
2022-11-02 17:29:18 -07:00
fn render_static_node(&mut self, root: &'a BodyNode) -> TokenStream2 {
match root {
BodyNode::Element(el) => {
let el_name = &el.name;
let ns = |name| match el_name {
ElementName::Ident(i) => quote! { dioxus_elements::#i::#name },
ElementName::Custom(_) => quote! { None },
};
2022-12-06 17:50:25 -08:00
let static_attrs = el.attributes.iter().map(|attr| match &attr.attr {
2022-11-02 17:29:18 -07:00
ElementAttr::AttrText { name, value } if value.is_static() => {
2022-12-10 15:05:41 -06:00
let value = value.to_static().unwrap();
let ns = ns(quote!(#name.1));
2023-03-19 20:47:16 -05:00
let name = match el_name {
ElementName::Ident(_) => quote! { #el_name::#name.0 },
ElementName::Custom(_) => {
2023-03-19 20:47:16 -05:00
let as_string = name.to_string();
quote! { #as_string }
2023-03-19 20:47:16 -05:00
}
};
2022-12-06 17:50:25 -08:00
quote! {
2022-11-02 17:29:18 -07:00
::dioxus::core::TemplateAttribute::Static {
name: #name,
namespace: #ns,
2022-11-02 17:29:18 -07:00
value: #value,
2022-12-02 16:24:49 -08:00
// todo: we don't diff these so we never apply the volatile flag
// volatile: dioxus_elements::#el_name::#name.2,
2022-11-02 17:29:18 -07:00
}
2022-12-06 17:50:25 -08:00
}
2022-11-02 17:29:18 -07:00
}
2022-04-24 02:35:52 -04:00
2022-11-02 17:29:18 -07:00
ElementAttr::CustomAttrText { name, value } if value.is_static() => {
2022-12-10 15:05:41 -06:00
let value = value.to_static().unwrap();
2022-12-06 17:50:25 -08:00
quote! {
2022-11-02 17:29:18 -07:00
::dioxus::core::TemplateAttribute::Static {
2022-12-07 15:11:51 -08:00
name: #name,
namespace: None,
2022-11-02 17:29:18 -07:00
value: #value,
2022-12-02 16:24:49 -08:00
// todo: we don't diff these so we never apply the volatile flag
// volatile: dioxus_elements::#el_name::#name.2,
2022-11-02 17:29:18 -07:00
}
2022-12-06 17:50:25 -08:00
}
2022-11-02 17:29:18 -07:00
}
2022-11-02 17:29:18 -07:00
ElementAttr::AttrExpression { .. }
| ElementAttr::AttrText { .. }
| ElementAttr::CustomAttrText { .. }
2022-11-03 01:24:20 -07:00
| ElementAttr::CustomAttrExpression { .. }
| ElementAttr::EventTokens { .. } => {
2022-11-02 17:29:18 -07:00
let ct = self.dynamic_attributes.len();
self.dynamic_attributes.push(attr);
2022-11-03 01:24:20 -07:00
self.attr_paths.push(self.current_path.clone());
2022-12-06 17:50:25 -08:00
quote! { ::dioxus::core::TemplateAttribute::Dynamic { id: #ct } }
2022-11-02 17:29:18 -07:00
}
});
let attrs = quote! { #(#static_attrs),*};
let children = el.children.iter().enumerate().map(|(idx, root)| {
self.current_path.push(idx as u8);
let out = self.render_static_node(root);
self.current_path.pop();
out
});
2022-12-02 16:24:49 -08:00
let _opt = el.children.len() == 1;
2022-11-02 17:29:18 -07:00
let children = quote! { #(#children),* };
let ns = ns(quote!(NAME_SPACE));
let el_name = el_name.tag_name();
2022-11-02 17:29:18 -07:00
quote! {
::dioxus::core::TemplateNode::Element {
tag: #el_name,
namespace: #ns,
2022-11-02 17:29:18 -07:00
attrs: &[ #attrs ],
children: &[ #children ],
}
2022-11-01 18:42:29 -07:00
}
2022-11-02 17:29:18 -07:00
}
BodyNode::Text(text) if text.is_static() => {
2022-12-10 15:05:41 -06:00
let text = text.to_static().unwrap();
quote! { ::dioxus::core::TemplateNode::Text{ text: #text } }
2022-11-02 17:29:18 -07:00
}
BodyNode::RawExpr(_)
| BodyNode::Text(_)
| BodyNode::ForLoop(_)
| BodyNode::IfChain(_)
| BodyNode::Component(_) => {
2022-11-02 17:29:18 -07:00
let ct = self.dynamic_nodes.len();
self.dynamic_nodes.push(root);
2022-11-03 01:24:20 -07:00
self.node_paths.push(self.current_path.clone());
2022-11-15 23:22:41 -08:00
2022-11-24 09:11:27 -05:00
match root {
BodyNode::Text(_) => {
quote! { ::dioxus::core::TemplateNode::DynamicText { id: #ct } }
}
_ => quote! { ::dioxus::core::TemplateNode::Dynamic { id: #ct } },
2022-11-15 23:22:41 -08:00
}
2022-11-02 17:29:18 -07:00
}
}
2022-10-09 01:24:41 -05:00
}
2022-04-24 02:35:52 -04:00
}
2022-12-10 12:29:15 -06:00
2023-07-19 10:19:23 -07:00
#[cfg(feature = "hot_reload")]
2022-12-10 12:29:15 -06:00
#[test]
2022-12-10 21:18:44 -06:00
fn create_template() {
2022-12-10 12:29:15 -06:00
let input = quote! {
2022-12-10 14:09:59 -06:00
svg {
2022-12-10 12:29:15 -06:00
width: 100,
height: "100px",
"width2": 100,
"height2": "100px",
p {
"hello world"
}
(0..10).map(|i| rsx!{"{i}"})
}
};
2022-12-10 14:09:59 -06:00
struct Mock;
impl HotReloadingContext for Mock {
fn map_attribute(
element_name_rust: &str,
attribute_name_rust: &str,
) -> Option<(&'static str, Option<&'static str>)> {
match element_name_rust {
"svg" => match attribute_name_rust {
"width" => Some(("width", Some("style"))),
"height" => Some(("height", Some("style"))),
_ => None,
},
_ => None,
}
}
fn map_element(element_name_rust: &str) -> Option<(&'static str, Option<&'static str>)> {
match element_name_rust {
"svg" => Some(("svg", Some("svg"))),
_ => None,
}
}
}
let call_body: CallBody = syn::parse2(input).unwrap();
2022-12-10 12:29:15 -06:00
let template = call_body.update_template::<Mock>(None, "testing").unwrap();
2022-12-10 12:29:15 -06:00
dbg!(template);
assert_eq!(
template,
Template {
name: "testing",
roots: &[TemplateNode::Element {
2022-12-10 14:09:59 -06:00
tag: "svg",
namespace: Some("svg"),
2022-12-10 12:29:15 -06:00
attrs: &[
TemplateAttribute::Dynamic { id: 0 },
TemplateAttribute::Static {
name: "height",
2022-12-10 14:09:59 -06:00
namespace: Some("style"),
2022-12-10 12:29:15 -06:00
value: "100px",
},
TemplateAttribute::Dynamic { id: 1 },
TemplateAttribute::Static {
name: "height2",
namespace: None,
value: "100px",
},
],
children: &[
TemplateNode::Element {
tag: "p",
namespace: None,
attrs: &[],
children: &[TemplateNode::Text {
text: "hello world",
}],
},
TemplateNode::Dynamic { id: 0 }
],
}],
node_paths: &[&[0, 1,],],
attr_paths: &[&[0,], &[0,],],
},
)
}
2022-12-10 21:18:44 -06:00
2023-07-19 10:19:23 -07:00
#[cfg(feature = "hot_reload")]
2022-12-10 21:18:44 -06:00
#[test]
fn diff_template() {
2022-12-20 20:48:28 -06:00
use dioxus_core::Scope;
#[allow(unused, non_snake_case)]
fn Comp(_: Scope) -> dioxus_core::Element {
None
}
2022-12-10 21:18:44 -06:00
let input = quote! {
svg {
width: 100,
height: "100px",
"width2": 100,
"height2": "100px",
p {
"hello world"
}
(0..10).map(|i| rsx!{"{i}"}),
2022-12-11 09:38:38 -06:00
(0..10).map(|i| rsx!{"{i}"}),
(0..11).map(|i| rsx!{"{i}"}),
2022-12-20 20:48:28 -06:00
Comp{}
2022-12-10 21:18:44 -06:00
}
};
2022-12-11 09:38:38 -06:00
#[derive(Debug)]
2022-12-10 21:18:44 -06:00
struct Mock;
impl HotReloadingContext for Mock {
fn map_attribute(
element_name_rust: &str,
attribute_name_rust: &str,
) -> Option<(&'static str, Option<&'static str>)> {
match element_name_rust {
"svg" => match attribute_name_rust {
"width" => Some(("width", Some("style"))),
"height" => Some(("height", Some("style"))),
_ => None,
},
_ => None,
}
}
fn map_element(element_name_rust: &str) -> Option<(&'static str, Option<&'static str>)> {
match element_name_rust {
"svg" => Some(("svg", Some("svg"))),
_ => None,
}
}
}
let call_body1: CallBody = syn::parse2(input).unwrap();
2022-12-10 21:18:44 -06:00
let template = call_body1.update_template::<Mock>(None, "testing").unwrap();
2022-12-10 21:18:44 -06:00
dbg!(template);
// scrambling the attributes should not cause a full rebuild
let input = quote! {
div {
"width2": 100,
height: "100px",
"height2": "100px",
2022-12-11 09:38:38 -06:00
width: 100,
2022-12-20 20:48:28 -06:00
Comp{}
2022-12-11 09:38:38 -06:00
(0..11).map(|i| rsx!{"{i}"}),
(0..10).map(|i| rsx!{"{i}"}),
2022-12-10 21:18:44 -06:00
(0..10).map(|i| rsx!{"{i}"}),
p {
"hello world"
}
}
};
let call_body2: CallBody = syn::parse2(input).unwrap();
2022-12-10 21:18:44 -06:00
let template = call_body2
.update_template::<Mock>(Some(call_body1), "testing")
2022-12-10 21:18:44 -06:00
.unwrap();
2022-12-20 20:48:28 -06:00
dbg!(template);
2022-12-10 21:18:44 -06:00
2022-12-11 09:38:38 -06:00
assert_eq!(
template,
Template {
name: "testing",
roots: &[TemplateNode::Element {
tag: "div",
namespace: None,
attrs: &[
TemplateAttribute::Dynamic { id: 1 },
TemplateAttribute::Static {
name: "height",
namespace: None,
value: "100px",
},
TemplateAttribute::Static {
name: "height2",
namespace: None,
value: "100px",
},
TemplateAttribute::Dynamic { id: 0 },
],
children: &[
2022-12-20 20:48:28 -06:00
TemplateNode::Dynamic { id: 3 },
2022-12-11 09:38:38 -06:00
TemplateNode::Dynamic { id: 2 },
TemplateNode::Dynamic { id: 1 },
TemplateNode::Dynamic { id: 0 },
TemplateNode::Element {
tag: "p",
namespace: None,
attrs: &[],
children: &[TemplateNode::Text {
text: "hello world",
}],
},
],
}],
2022-12-22 19:32:21 -06:00
node_paths: &[&[0, 3], &[0, 2], &[0, 1], &[0, 0]],
2022-12-11 09:38:38 -06:00
attr_paths: &[&[0], &[0]]
},
)
2022-12-10 21:18:44 -06:00
}