Files
dioxus/packages/rsx/src/template.rs
T
Jon Kelley 8ea61e1b3e feat: simple iterators and conditionals in rsx (#564)
* feat: simple iterators

* fix: into_iter

* feat: add support for unterminated conditionasl

* fix: add tempalte mapping for helpers
2022-11-16 22:10:50 -08:00

1102 lines
42 KiB
Rust

use dioxus_core::{
OwnedAttributeValue, OwnedPathSeg, OwnedTraverse, TemplateAttributeValue, TemplateNodeId,
TextTemplate, TextTemplateSegment, UpdateOp,
};
use proc_macro2::TokenStream;
use quote::TokenStreamExt;
use quote::{quote, ToTokens};
use syn::{Expr, Ident, LitStr};
#[cfg(any(feature = "hot-reload", debug_assertions))]
pub fn try_parse_template(
rsx: &str,
location: OwnedCodeLocation,
previous_template: Option<DynamicTemplateContextBuilder>,
) -> Result<(OwnedTemplate, DynamicTemplateContextBuilder), Error> {
use crate::CallBody;
let call_body: CallBody =
syn::parse_str(rsx).map_err(|e| Error::ParseError(ParseError::new(e, location.clone())))?;
let mut template_builder = TemplateBuilder::from_roots_always(call_body.roots);
if let Some(prev) = previous_template {
template_builder = template_builder
.try_switch_dynamic_context(prev)
.ok_or_else(|| {
Error::RecompileRequiredError(RecompileReason::Variable(
"dynamic context updated".to_string(),
))
})?;
}
let dyn_ctx = template_builder.dynamic_context.clone();
Ok((template_builder.try_into_owned(&location)?, dyn_ctx))
}
#[cfg(any(feature = "hot-reload", debug_assertions))]
use hot_reload_imports::*;
#[cfg(any(feature = "hot-reload", debug_assertions))]
mod hot_reload_imports {
pub use crate::{
attributes::attrbute_to_static_str,
elements::element_to_static_str,
error::{Error, ParseError, RecompileReason},
};
pub use dioxus_core::prelude::OwnedTemplate;
pub use dioxus_core::{
AttributeDiscription, OwnedAttributeValue, OwnedCodeLocation, OwnedDynamicNodeMapping,
OwnedTemplateNode, Template, TemplateAttribute, TemplateAttributeValue, TemplateElement,
TemplateNodeId, TemplateNodeType, TextTemplate, TextTemplateSegment,
};
pub use std::collections::HashMap;
}
use crate::{BodyNode, ElementAttr, FormattedSegment, Segment};
struct TemplateElementBuilder {
tag: Ident,
attributes: Vec<TemplateAttributeBuilder>,
children: Vec<TemplateNodeId>,
listeners: Vec<usize>,
locally_static: bool,
}
#[cfg(any(feature = "hot-reload", debug_assertions))]
type OwndedTemplateElement = TemplateElement<
Vec<TemplateAttribute<OwnedAttributeValue>>,
OwnedAttributeValue,
Vec<TemplateNodeId>,
Vec<usize>,
>;
impl TemplateElementBuilder {
#[cfg(any(feature = "hot-reload", debug_assertions))]
fn try_into_owned(self, location: &OwnedCodeLocation) -> Result<OwndedTemplateElement, Error> {
let Self {
tag,
attributes,
children,
listeners,
..
} = self;
let (element_tag, element_ns) =
element_to_static_str(&tag.to_string()).ok_or_else(|| {
Error::ParseError(ParseError::new(
syn::Error::new(tag.span(), format!("unknown element: {}", tag)),
location.clone(),
))
})?;
let mut owned_attributes = Vec::new();
for a in attributes {
owned_attributes.push(a.try_into_owned(location, element_tag, element_ns)?);
}
Ok(TemplateElement::new(
element_tag,
element_ns,
owned_attributes,
children,
listeners,
))
}
}
impl ToTokens for TemplateElementBuilder {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
tag,
attributes,
children,
listeners,
..
} = self;
let children = children.iter().map(|id| {
let raw = id.0;
quote! {TemplateNodeId(#raw)}
});
tokens.append_all(quote! {
TemplateElement::new(
dioxus_elements::#tag::TAG_NAME,
dioxus_elements::#tag::NAME_SPACE,
&[#(#attributes),*],
&[#(#children),*],
&[#(#listeners),*],
)
})
}
}
enum AttributeName {
Ident(Ident),
Str(LitStr),
}
struct TemplateAttributeBuilder {
element_tag: Ident,
name: AttributeName,
value: TemplateAttributeValue<OwnedAttributeValue>,
}
impl TemplateAttributeBuilder {
#[cfg(any(feature = "hot-reload", debug_assertions))]
fn try_into_owned(
self,
location: &OwnedCodeLocation,
element_tag: &'static str,
element_ns: Option<&'static str>,
) -> Result<TemplateAttribute<OwnedAttributeValue>, Error> {
let Self { name, value, .. } = self;
let (name, span, literal) = match name {
AttributeName::Ident(name) => (name.to_string(), name.span(), false),
AttributeName::Str(name) => (name.value(), name.span(), true),
};
let (name, namespace, volatile) = attrbute_to_static_str(&name, element_tag, element_ns)
.ok_or_else(|| {
if literal {
// literals will be captured when a full recompile is triggered
Error::RecompileRequiredError(RecompileReason::Attribute(name.to_string()))
} else {
Error::ParseError(ParseError::new(
syn::Error::new(span, format!("unknown attribute: {}", name)),
location.clone(),
))
}
})?;
let attribute = AttributeDiscription {
name,
namespace,
volatile,
};
Ok(TemplateAttribute { value, attribute })
}
}
impl ToTokens for TemplateAttributeBuilder {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
element_tag,
name,
value,
} = self;
let value = match value {
TemplateAttributeValue::Static(val) => {
let val = match val {
OwnedAttributeValue::Text(txt) => quote! {StaticAttributeValue::Text(#txt)},
OwnedAttributeValue::Float32(f) => quote! {StaticAttributeValue::Float32(#f)},
OwnedAttributeValue::Float64(f) => quote! {StaticAttributeValue::Float64(#f)},
OwnedAttributeValue::Int32(i) => quote! {StaticAttributeValue::Int32(#i)},
OwnedAttributeValue::Int64(i) => quote! {StaticAttributeValue::Int64(#i)},
OwnedAttributeValue::Uint32(u) => quote! {StaticAttributeValue::Uint32(#u)},
OwnedAttributeValue::Uint64(u) => quote! {StaticAttributeValue::Uint64(#u)},
OwnedAttributeValue::Bool(b) => quote! {StaticAttributeValue::Bool(#b)},
OwnedAttributeValue::Vec3Float(f1, f2, f3) => {
quote! {StaticAttributeValue::Vec3Float(#f1, #f2, #f3)}
}
OwnedAttributeValue::Vec3Int(f1, f2, f3) => {
quote! {StaticAttributeValue::Vec3Int(#f1, #f2, #f3)}
}
OwnedAttributeValue::Vec3Uint(f1, f2, f3) => {
quote! {StaticAttributeValue::Vec3Uint(#f1, #f2, #f3)}
}
OwnedAttributeValue::Vec4Float(f1, f2, f3, f4) => {
quote! {StaticAttributeValue::Vec4Float(#f1, #f2, #f3, #f4)}
}
OwnedAttributeValue::Vec4Int(f1, f2, f3, f4) => {
quote! {StaticAttributeValue::Vec4Int(#f1, #f2, #f3, #f4)}
}
OwnedAttributeValue::Vec4Uint(f1, f2, f3, f4) => {
quote! {StaticAttributeValue::Vec4Uint(#f1, #f2, #f3, #f4)}
}
OwnedAttributeValue::Bytes(b) => {
quote! {StaticAttributeValue::Bytes(&[#(#b),*])}
}
OwnedAttributeValue::Any(_) => todo!(),
};
quote! {TemplateAttributeValue::Static(#val)}
}
TemplateAttributeValue::Dynamic(idx) => quote! {TemplateAttributeValue::Dynamic(#idx)},
};
match name {
AttributeName::Ident(name) => tokens.append_all(quote! {
TemplateAttribute{
attribute: dioxus_elements::#element_tag::#name,
value: #value,
}
}),
AttributeName::Str(lit) => tokens.append_all(quote! {
TemplateAttribute{
attribute: dioxus::prelude::AttributeDiscription{
name: #lit,
namespace: None,
volatile: false
},
value: #value,
}
}),
}
}
}
enum TemplateNodeTypeBuilder {
Element(TemplateElementBuilder),
Text(TextTemplate<Vec<TextTemplateSegment<String>>, String>),
DynamicNode(usize),
}
#[cfg(any(feature = "hot-reload", debug_assertions))]
type OwnedTemplateNodeType = TemplateNodeType<
Vec<TemplateAttribute<OwnedAttributeValue>>,
OwnedAttributeValue,
Vec<TemplateNodeId>,
Vec<usize>,
Vec<TextTemplateSegment<String>>,
String,
>;
impl TemplateNodeTypeBuilder {
#[cfg(any(feature = "hot-reload", debug_assertions))]
fn try_into_owned(self, location: &OwnedCodeLocation) -> Result<OwnedTemplateNodeType, Error> {
match self {
TemplateNodeTypeBuilder::Element(el) => {
Ok(TemplateNodeType::Element(el.try_into_owned(location)?))
}
TemplateNodeTypeBuilder::Text(txt) => Ok(TemplateNodeType::Text(txt)),
TemplateNodeTypeBuilder::DynamicNode(idx) => Ok(TemplateNodeType::DynamicNode(idx)),
}
}
}
impl ToTokens for TemplateNodeTypeBuilder {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
TemplateNodeTypeBuilder::Element(el) => tokens.append_all(quote! {
TemplateNodeType::Element(#el)
}),
TemplateNodeTypeBuilder::Text(txt) => {
let mut length = 0;
let segments = txt.segments.iter().map(|seg| match seg {
TextTemplateSegment::Static(s) => {
length += s.len();
quote!(TextTemplateSegment::Static(#s))
}
TextTemplateSegment::Dynamic(idx) => quote!(TextTemplateSegment::Dynamic(#idx)),
});
tokens.append_all(quote! {
TemplateNodeType::Text(TextTemplate::new(&[#(#segments),*], #length))
});
}
TemplateNodeTypeBuilder::DynamicNode(idx) => tokens.append_all(quote! {
TemplateNodeType::DynamicNode(#idx)
}),
}
}
}
struct TemplateNodeBuilder {
id: TemplateNodeId,
depth: usize,
parent: Option<TemplateNodeId>,
node_type: TemplateNodeTypeBuilder,
fully_static: bool,
}
impl TemplateNodeBuilder {
#[cfg(any(feature = "hot-reload", debug_assertions))]
fn try_into_owned(self, location: &OwnedCodeLocation) -> Result<OwnedTemplateNode, Error> {
let TemplateNodeBuilder {
id,
node_type,
parent,
depth,
..
} = self;
let node_type = node_type.try_into_owned(location)?;
Ok(OwnedTemplateNode {
id,
node_type,
parent,
depth,
})
}
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
id,
node_type,
parent,
depth,
..
} = self;
let raw_id = id.0;
let parent = match parent {
Some(id) => {
let id = id.0;
quote! {Some(TemplateNodeId(#id))}
}
None => quote! {None},
};
tokens.append_all(quote! {
TemplateNode {
id: TemplateNodeId(#raw_id),
node_type: #node_type,
parent: #parent,
depth: #depth,
}
})
}
}
#[derive(Default)]
pub struct TemplateBuilder {
nodes: Vec<TemplateNodeBuilder>,
root_nodes: Vec<TemplateNodeId>,
dynamic_context: DynamicTemplateContextBuilder,
}
impl TemplateBuilder {
/// Create a template builder from nodes if it would improve performance to do so.
pub fn from_roots(roots: Vec<BodyNode>) -> Option<Self> {
let mut builder = Self::default();
for (i, root) in roots.into_iter().enumerate() {
let id = builder.build_node(root, None, vec![i], 0);
builder.root_nodes.push(id);
}
// only build a template if there is at least one static node
if builder
.nodes
.iter()
.all(|r| matches!(&r.node_type, TemplateNodeTypeBuilder::DynamicNode(_)))
{
None
} else {
Some(builder)
}
}
/// Create a template builder from nodes regardless of performance.
#[cfg(any(feature = "hot-reload", debug_assertions))]
fn from_roots_always(roots: Vec<BodyNode>) -> Self {
let mut builder = Self::default();
for (i, root) in roots.into_iter().enumerate() {
let id = builder.build_node(root, None, vec![i], 0);
builder.root_nodes.push(id);
}
builder
}
fn build_node(
&mut self,
node: BodyNode,
parent: Option<TemplateNodeId>,
path: Vec<usize>,
depth: usize,
) -> TemplateNodeId {
let id = TemplateNodeId(self.nodes.len());
match node {
BodyNode::Element(el) => {
let mut locally_static = true;
let mut attributes = Vec::new();
let mut listeners = Vec::new();
for attr in el.attributes {
match attr.attr {
ElementAttr::AttrText { name, value } => {
if let Some(static_value) = value.to_static() {
attributes.push(TemplateAttributeBuilder {
element_tag: el.name.clone(),
name: AttributeName::Ident(name),
value: TemplateAttributeValue::Static(
OwnedAttributeValue::Text(static_value),
),
})
} else {
locally_static = false;
attributes.push(TemplateAttributeBuilder {
element_tag: el.name.clone(),
name: AttributeName::Ident(name),
value: TemplateAttributeValue::Dynamic(
self.dynamic_context.add_attr(quote!(#value)),
),
})
}
}
ElementAttr::CustomAttrText { name, value } => {
if let Some(static_value) = value.to_static() {
attributes.push(TemplateAttributeBuilder {
element_tag: el.name.clone(),
name: AttributeName::Str(name),
value: TemplateAttributeValue::Static(
OwnedAttributeValue::Text(static_value),
),
})
} else {
locally_static = false;
attributes.push(TemplateAttributeBuilder {
element_tag: el.name.clone(),
name: AttributeName::Str(name),
value: TemplateAttributeValue::Dynamic(
self.dynamic_context.add_attr(quote!(#value)),
),
})
}
}
ElementAttr::AttrExpression { name, value } => {
locally_static = false;
attributes.push(TemplateAttributeBuilder {
element_tag: el.name.clone(),
name: AttributeName::Ident(name),
value: TemplateAttributeValue::Dynamic(
self.dynamic_context.add_attr(quote!(#value)),
),
})
}
ElementAttr::CustomAttrExpression { name, value } => {
locally_static = false;
attributes.push(TemplateAttributeBuilder {
element_tag: el.name.clone(),
name: AttributeName::Str(name),
value: TemplateAttributeValue::Dynamic(
self.dynamic_context.add_attr(quote!(#value)),
),
})
}
ElementAttr::EventTokens { name, tokens } => {
locally_static = false;
listeners.push(self.dynamic_context.add_listener(name, tokens))
}
}
}
if let Some(key) = el.key {
self.dynamic_context.add_key(quote!(
dioxus::core::exports::bumpalo::format!(in __bump, "{}", #key)
.into_bump_str()
));
}
self.nodes.push(TemplateNodeBuilder {
id,
node_type: TemplateNodeTypeBuilder::Element(TemplateElementBuilder {
tag: el.name,
attributes,
children: Vec::new(),
listeners,
locally_static,
}),
parent,
depth,
fully_static: false,
});
let children: Vec<_> = el
.children
.into_iter()
.enumerate()
.map(|(i, child)| {
let mut new_path = path.clone();
new_path.push(i);
self.build_node(child, Some(id), new_path, depth + 1)
})
.collect();
let children_fully_static = children.iter().all(|c| self.nodes[c.0].fully_static);
let parent = &mut self.nodes[id.0];
parent.fully_static = locally_static && children_fully_static;
if let TemplateNodeTypeBuilder::Element(element) = &mut parent.node_type {
element.children = children;
}
}
BodyNode::Component(comp) => {
self.nodes.push(TemplateNodeBuilder {
id,
node_type: TemplateNodeTypeBuilder::DynamicNode(
self.dynamic_context.add_node(BodyNode::Component(comp)),
),
parent,
depth,
fully_static: false,
});
}
BodyNode::Text(txt) => {
let mut segments = Vec::new();
let mut length = 0;
let mut fully_static = true;
for segment in txt.segments {
segments.push(match segment {
Segment::Literal(lit) => {
length += lit.len();
TextTemplateSegment::Static(lit)
}
Segment::Formatted(fmted) => {
fully_static = false;
TextTemplateSegment::Dynamic(self.dynamic_context.add_text(fmted))
}
})
}
self.nodes.push(TemplateNodeBuilder {
id,
node_type: TemplateNodeTypeBuilder::Text(TextTemplate::new(segments, length)),
parent,
depth,
fully_static,
});
}
BodyNode::RawExpr(expr) => {
self.nodes.push(TemplateNodeBuilder {
id,
node_type: TemplateNodeTypeBuilder::DynamicNode(
self.dynamic_context.add_node(BodyNode::RawExpr(expr)),
),
parent,
depth,
fully_static: false,
});
}
BodyNode::ForLoop(expr) => {
self.nodes.push(TemplateNodeBuilder {
id,
node_type: TemplateNodeTypeBuilder::DynamicNode(
self.dynamic_context.add_node(BodyNode::ForLoop(expr)),
),
});
}
BodyNode::IfChain(expr) => {
self.nodes.push(TemplateNodeBuilder {
id,
node_type: TemplateNodeTypeBuilder::DynamicNode(
self.dynamic_context.add_node(BodyNode::IfChain(expr)),
),
});
}
}
id
}
#[cfg(any(feature = "hot-reload", debug_assertions))]
pub fn try_switch_dynamic_context(
mut self,
dynamic_context: DynamicTemplateContextBuilder,
) -> Option<Self> {
let attribute_mapping: HashMap<String, usize> = dynamic_context
.attributes
.iter()
.enumerate()
.map(|(i, ts)| (ts.to_string(), i))
.collect();
let text_mapping: HashMap<String, usize> = dynamic_context
.text
.iter()
.enumerate()
.map(|(i, ts)| (ts.to_token_stream().to_string(), i))
.collect();
let listener_mapping: HashMap<(String, Expr), usize> = dynamic_context
.listeners
.iter()
.enumerate()
.map(|(i, ts)| (ts.clone(), i))
.collect();
let node_mapping: HashMap<String, usize> = dynamic_context
.nodes
.iter()
.enumerate()
.map(|(i, ts)| (ts.to_token_stream().to_string(), i))
.collect();
for node in &mut self.nodes {
match &mut node.node_type {
TemplateNodeTypeBuilder::Element(element) => {
for listener in &mut element.listeners {
*listener =
*listener_mapping.get(&self.dynamic_context.listeners[*listener])?;
}
for attribute in &mut element.attributes {
if let TemplateAttributeValue::Dynamic(idx) = &mut attribute.value {
*idx = *attribute_mapping
.get(&self.dynamic_context.attributes[*idx].to_string())?;
}
}
}
TemplateNodeTypeBuilder::Text(txt) => {
for seg in &mut txt.segments {
if let TextTemplateSegment::Dynamic(idx) = seg {
*idx = *text_mapping.get(
&self.dynamic_context.text[*idx]
.to_token_stream()
.to_string(),
)?;
}
}
}
TemplateNodeTypeBuilder::DynamicNode(idx) => {
*idx = *node_mapping.get(
&self.dynamic_context.nodes[*idx]
.to_token_stream()
.to_string(),
)?;
}
}
}
self.dynamic_context = dynamic_context;
Some(self)
}
#[cfg(any(feature = "hot-reload", debug_assertions))]
pub fn try_into_owned(self, location: &OwnedCodeLocation) -> Result<OwnedTemplate, Error> {
let mut nodes = Vec::new();
let dynamic_mapping = self.dynamic_mapping(&nodes);
let dynamic_path = self.dynamic_path();
for node in self.nodes {
nodes.push(node.try_into_owned(location)?);
}
Ok(OwnedTemplate {
nodes,
root_nodes: self.root_nodes,
dynamic_mapping,
dynamic_path,
})
}
#[cfg(any(feature = "hot-reload", debug_assertions))]
pub fn dynamic_mapping(
&self,
resolved_nodes: &Vec<OwnedTemplateNode>,
) -> OwnedDynamicNodeMapping {
let dynamic_context = &self.dynamic_context;
let mut node_mapping = vec![None; dynamic_context.nodes.len()];
let nodes = &self.nodes;
for n in nodes {
if let TemplateNodeTypeBuilder::DynamicNode(idx) = &n.node_type {
node_mapping[*idx] = Some(n.id)
}
}
let mut text_mapping = vec![Vec::new(); dynamic_context.text.len()];
for n in nodes {
if let TemplateNodeTypeBuilder::Text(txt) = &n.node_type {
for seg in &txt.segments {
match seg {
TextTemplateSegment::Static(_) => (),
TextTemplateSegment::Dynamic(idx) => text_mapping[*idx].push(n.id),
}
}
}
}
let mut attribute_mapping = vec![Vec::new(); dynamic_context.attributes.len()];
for n in nodes {
if let TemplateNodeTypeBuilder::Element(el) = &n.node_type {
for (i, attr) in el.attributes.iter().enumerate() {
match attr.value {
TemplateAttributeValue::Static(_) => (),
TemplateAttributeValue::Dynamic(idx) => {
attribute_mapping[idx].push((n.id, i));
}
}
}
}
}
let mut listener_mapping = Vec::new();
for n in nodes {
if let TemplateNodeTypeBuilder::Element(el) = &n.node_type {
if !el.listeners.is_empty() {
listener_mapping.push(n.id);
}
}
}
let mut volatile_mapping = Vec::new();
for n in resolved_nodes {
if let TemplateNodeType::Element(el) = &n.node_type {
for (i, attr) in el.attributes.iter().enumerate() {
if attr.attribute.volatile {
volatile_mapping.push((n.id, i));
}
}
}
}
OwnedDynamicNodeMapping::new(
node_mapping,
text_mapping,
attribute_mapping,
volatile_mapping,
listener_mapping,
)
}
fn dynamic_path(&self) -> Option<OwnedPathSeg> {
let mut last_seg: Option<OwnedPathSeg> = None;
let mut nodes_to_insert_after = Vec::new();
// iterating from the last root to the first
for root in self.root_nodes.iter().rev() {
let root_node = &self.nodes[root.0];
if let TemplateNodeTypeBuilder::DynamicNode(_) = root_node.node_type {
match &mut last_seg {
// if there has been no static nodes, we can append the child to the parent node
None => nodes_to_insert_after.push(*root),
// otherwise we insert the child before the last static node
Some(seg) => {
seg.ops.push(UpdateOp::InsertBefore(*root));
}
}
} else if let Some(mut new) = self.construct_path_segment(*root) {
if let Some(last) = last_seg.take() {
match new.traverse {
OwnedTraverse::Halt => {
new.traverse = OwnedTraverse::NextSibling(Box::new(last));
}
OwnedTraverse::FirstChild(b) => {
new.traverse = OwnedTraverse::Both(Box::new((*b, last)));
}
_ => unreachable!(),
}
} else {
for node in nodes_to_insert_after.drain(..) {
new.ops.push(UpdateOp::InsertAfter(node));
}
}
last_seg = Some(new);
} else if let Some(last) = last_seg.take() {
last_seg = Some(OwnedPathSeg {
ops: Vec::new(),
traverse: OwnedTraverse::NextSibling(Box::new(last)),
});
}
}
last_seg
}
fn construct_path_segment(&self, node_id: TemplateNodeId) -> Option<OwnedPathSeg> {
let n = &self.nodes[node_id.0];
if n.fully_static {
return None;
}
match &n.node_type {
TemplateNodeTypeBuilder::Element(el) => {
let mut last_seg: Option<OwnedPathSeg> = None;
let mut children_to_append = Vec::new();
// iterating from the last child to the first
for child in el.children.iter().rev() {
let child_node = &self.nodes[child.0];
if let TemplateNodeTypeBuilder::DynamicNode(_) = child_node.node_type {
match &mut last_seg {
// if there has been no static nodes, we can append the child to the parent node
None => children_to_append.push(*child),
// otherwise we insert the child before the last static node
Some(seg) => {
seg.ops.push(UpdateOp::InsertBefore(*child));
}
}
} else if let Some(mut new) = self.construct_path_segment(*child) {
if let Some(last) = last_seg.take() {
match new.traverse {
OwnedTraverse::Halt => {
new.traverse = OwnedTraverse::NextSibling(Box::new(last));
}
OwnedTraverse::FirstChild(b) => {
new.traverse = OwnedTraverse::Both(Box::new((*b, last)));
}
_ => unreachable!(),
}
}
last_seg = Some(new);
} else if let Some(last) = last_seg.take() {
last_seg = Some(OwnedPathSeg {
ops: Vec::new(),
traverse: OwnedTraverse::NextSibling(Box::new(last)),
});
}
}
let mut ops = Vec::new();
if !el.locally_static || n.parent.is_none() {
ops.push(UpdateOp::StoreNode(node_id));
}
for child in children_to_append.into_iter().rev() {
ops.push(UpdateOp::AppendChild(child));
}
Some(OwnedPathSeg {
ops,
traverse: match last_seg {
Some(last) => OwnedTraverse::FirstChild(Box::new(last)),
None => OwnedTraverse::Halt,
},
})
}
TemplateNodeTypeBuilder::Text(_) => Some(OwnedPathSeg {
ops: vec![UpdateOp::StoreNode(n.id)],
traverse: dioxus_core::OwnedTraverse::Halt,
}),
TemplateNodeTypeBuilder::DynamicNode(_) => unreachable!(
"constructing path segment for dynamic nodes is handled in the parent node"
),
}
}
}
impl ToTokens for TemplateBuilder {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Self {
nodes,
root_nodes,
dynamic_context,
} = self;
let mut node_mapping = vec![None; dynamic_context.nodes.len()];
for n in nodes {
if let TemplateNodeTypeBuilder::DynamicNode(idx) = &n.node_type {
node_mapping[*idx] = Some(n.id);
}
}
let mut text_mapping = vec![Vec::new(); dynamic_context.text.len()];
for n in nodes {
if let TemplateNodeTypeBuilder::Text(txt) = &n.node_type {
for seg in &txt.segments {
match seg {
TextTemplateSegment::Static(_) => (),
TextTemplateSegment::Dynamic(idx) => text_mapping[*idx].push(n.id),
}
}
}
}
let mut attribute_mapping = vec![Vec::new(); dynamic_context.attributes.len()];
for n in nodes {
if let TemplateNodeTypeBuilder::Element(el) = &n.node_type {
for (i, attr) in el.attributes.iter().enumerate() {
match attr.value {
TemplateAttributeValue::Static(_) => (),
TemplateAttributeValue::Dynamic(idx) => {
attribute_mapping[idx].push((n.id, i));
}
}
}
}
}
let mut listener_mapping = Vec::new();
for n in nodes {
if let TemplateNodeTypeBuilder::Element(el) = &n.node_type {
if !el.listeners.is_empty() {
listener_mapping.push(n.id);
}
}
}
let root_nodes = root_nodes.iter().map(|id| {
let raw = id.0;
quote! { TemplateNodeId(#raw) }
});
let node_mapping_quoted = node_mapping.iter().map(|op| match op {
Some(id) => {
let raw_id = id.0;
quote! {Some(TemplateNodeId(#raw_id))}
}
None => quote! {None},
});
let text_mapping_quoted = text_mapping.iter().map(|inner| {
let raw = inner.iter().map(|id| id.0);
quote! {&[#(TemplateNodeId(#raw)),*]}
});
let attribute_mapping_quoted = attribute_mapping.iter().map(|inner| {
let raw = inner.iter().map(|(id, _)| id.0);
let indecies = inner.iter().map(|(_, idx)| idx);
quote! {&[#((TemplateNodeId(#raw), #indecies)),*]}
});
let listener_mapping_quoted = listener_mapping.iter().map(|id| {
let raw = id.0;
quote! {TemplateNodeId(#raw)}
});
let mut nodes_quoted = TokenStream::new();
for n in nodes {
n.to_tokens(&mut nodes_quoted);
quote! {,}.to_tokens(&mut nodes_quoted);
}
let dynamic_path = match self.dynamic_path() {
Some(seg) => {
let seg = quote_owned_segment(seg);
quote! {Some(#seg)}
}
None => quote! {None},
};
let quoted = quote! {
{
const __NODES: dioxus::prelude::StaticTemplateNodes = &[#nodes_quoted];
const __TEXT_MAPPING: &'static [&'static [dioxus::prelude::TemplateNodeId]] = &[#(#text_mapping_quoted),*];
const __ATTRIBUTE_MAPPING: &'static [&'static [(dioxus::prelude::TemplateNodeId, usize)]] = &[#(#attribute_mapping_quoted),*];
const __ROOT_NODES: &'static [dioxus::prelude::TemplateNodeId] = &[#(#root_nodes),*];
const __NODE_MAPPING: &'static [Option<dioxus::prelude::TemplateNodeId>] = &[#(#node_mapping_quoted),*];
const __NODES_WITH_LISTENERS: &'static [dioxus::prelude::TemplateNodeId] = &[#(#listener_mapping_quoted),*];
static __VOLITALE_MAPPING_INNER: dioxus::core::exports::once_cell::sync::Lazy<Vec<(dioxus::prelude::TemplateNodeId, usize)>> = dioxus::core::exports::once_cell::sync::Lazy::new(||{
// check each property to see if it is volatile
let mut volatile = Vec::new();
for n in __NODES {
if let TemplateNodeType::Element(el) = &n.node_type {
for (i, attr) in el.attributes.iter().enumerate() {
if attr.attribute.volatile {
volatile.push((n.id, i));
}
}
}
}
volatile
});
static __VOLITALE_MAPPING: &'static dioxus::core::exports::once_cell::sync::Lazy<Vec<(dioxus::prelude::TemplateNodeId, usize)>> = &__VOLITALE_MAPPING_INNER;
static __STATIC_VOLITALE_MAPPING: dioxus::prelude::LazyStaticVec<(dioxus::prelude::TemplateNodeId, usize)> = LazyStaticVec(__VOLITALE_MAPPING);
static __TEMPLATE: dioxus::prelude::Template = Template::Static(&StaticTemplate {
nodes: __NODES,
root_nodes: __ROOT_NODES,
dynamic_mapping: StaticDynamicNodeMapping::new(__NODE_MAPPING, __TEXT_MAPPING, __ATTRIBUTE_MAPPING, __STATIC_VOLITALE_MAPPING, __NODES_WITH_LISTENERS),
dynamic_path: #dynamic_path,
});
let __bump = __cx.bump();
__cx.template_ref(dioxus::prelude::TemplateId(get_line_num!()), __TEMPLATE.clone(), #dynamic_context)
}
};
tokens.append_all(quoted)
}
}
#[derive(Default, Clone, Debug)]
pub struct DynamicTemplateContextBuilder {
nodes: Vec<BodyNode>,
text: Vec<FormattedSegment>,
attributes: Vec<TokenStream>,
listeners: Vec<(String, Expr)>,
key: Option<TokenStream>,
}
impl DynamicTemplateContextBuilder {
fn add_node(&mut self, node: BodyNode) -> usize {
let node_id = self.nodes.len();
self.nodes.push(node);
node_id
}
fn add_text(&mut self, text: FormattedSegment) -> usize {
let text_id = self.text.len();
self.text.push(text);
text_id
}
fn add_attr(&mut self, attr: TokenStream) -> usize {
let attr_id = self.attributes.len();
self.attributes.push(attr);
attr_id
}
fn add_listener(&mut self, name: Ident, listener: Expr) -> usize {
let listener_id = self.listeners.len();
self.listeners.push((name.to_string(), listener));
listener_id
}
fn add_key(&mut self, key: TokenStream) {
self.key = Some(key);
}
}
impl ToTokens for DynamicTemplateContextBuilder {
fn to_tokens(&self, tokens: &mut TokenStream) {
let nodes = &self.nodes;
let text = &self.text;
let attributes = &self.attributes;
let listeners_names = self
.listeners
.iter()
.map(|(n, _)| syn::parse_str::<Ident>(n).expect(n));
let listeners_exprs = self.listeners.iter().map(|(_, e)| e);
let key = match &self.key {
Some(k) => quote!(Some(#k)),
None => quote!(None),
};
tokens.append_all(quote! {
TemplateContext {
nodes: __cx.bump().alloc([#(#nodes),*]),
text_segments: __cx.bump().alloc([#(&*dioxus::core::exports::bumpalo::format!(in __bump, "{}", #text).into_bump_str()),*]),
attributes: __cx.bump().alloc([#({#attributes}.into_value(__cx.bump())),*]),
listeners: __cx.bump().alloc([#(dioxus_elements::on::#listeners_names(__cx, #listeners_exprs)),*]),
key: #key,
}
})
}
}
fn quote_owned_segment(seg: OwnedPathSeg) -> proc_macro2::TokenStream {
let OwnedPathSeg { ops, traverse } = seg;
let ops = ops
.into_iter()
.map(|op| match op {
UpdateOp::StoreNode(id) => {
let id = quote_template_node_id(id);
quote!(UpdateOp::StoreNode(#id))
}
UpdateOp::InsertBefore(id) => {
let id = quote_template_node_id(id);
quote!(UpdateOp::InsertBefore(#id))
}
UpdateOp::InsertAfter(id) => {
let id = quote_template_node_id(id);
quote!(UpdateOp::InsertAfter(#id))
}
UpdateOp::AppendChild(id) => {
let id = quote_template_node_id(id);
quote!(UpdateOp::AppendChild(#id))
}
})
.collect::<Vec<_>>();
let traverse = quote_owned_traverse(traverse);
quote! {
StaticPathSeg {
ops: &[#(#ops),*],
traverse: #traverse,
}
}
}
fn quote_owned_traverse(traverse: OwnedTraverse) -> proc_macro2::TokenStream {
match traverse {
OwnedTraverse::Halt => {
quote! {StaticTraverse::Halt}
}
OwnedTraverse::FirstChild(seg) => {
let seg = quote_owned_segment(*seg);
quote! {StaticTraverse::FirstChild(&#seg)}
}
OwnedTraverse::NextSibling(seg) => {
let seg = quote_owned_segment(*seg);
quote! {StaticTraverse::NextSibling(&#seg)}
}
OwnedTraverse::Both(b) => {
let (child, sibling) = *b;
let child = quote_owned_segment(child);
let sibling = quote_owned_segment(sibling);
quote! {StaticTraverse::Both(&(#child, #sibling))}
}
}
}
fn quote_template_node_id(id: TemplateNodeId) -> proc_macro2::TokenStream {
let raw = id.0;
quote! {
TemplateNodeId(#raw)
}
}