use self::location::DynIdx;
use crate::innerlude::Attribute;
use crate::*;
use proc_macro2::{Span, TokenStream as TokenStream2};
use proc_macro2_diagnostics::SpanDiagnosticExt;
use syn::parse_quote;
type NodePath = Vec<u8>;
type AttributePath = Vec<u8>;
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct TemplateBody {
pub roots: Vec<BodyNode>,
pub template_idx: DynIdx,
pub node_paths: Vec<NodePath>,
pub attr_paths: Vec<(AttributePath, usize)>,
pub dynamic_text_segments: Vec<FormattedSegment>,
pub diagnostics: Diagnostics,
}
impl Parse for TemplateBody {
fn parse(input: ParseStream) -> Result<Self> {
let children = RsxBlock::parse_children(input)?;
let mut myself = Self::new(children.children);
myself
.diagnostics
.extend(children.diagnostics.into_diagnostics());
Ok(myself)
}
}
impl ToTokens for TemplateBody {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let node = self.normalized();
let key_tokens = match node.implicit_key() {
Some(tok) => quote! { Some( #tok.to_string() ) },
None => quote! { None },
};
let key_warnings = self.check_for_duplicate_keys();
let roots = node.quote_roots();
let node_paths = node.node_paths.iter().map(|it| quote!(&[#(#it),*]));
let attr_paths = node.attr_paths.iter().map(|(it, _)| quote!(&[#(#it),*]));
let dynamic_nodes: Vec<_> = node.dynamic_nodes().collect();
let dynamic_nodes_len = dynamic_nodes.len();
let dyn_attr_printer: Vec<_> = node
.dynamic_attributes()
.map(|attr| attr.rendered_as_dynamic_attr())
.collect();
let dynamic_attr_len = dyn_attr_printer.len();
let dynamic_text = node.dynamic_text_segments.iter();
let diagnostics = &node.diagnostics;
let index = node.template_idx.get();
let hot_reload_mapping = node.hot_reload_mapping();
tokens.append_all(quote! {
dioxus_core::Element::Ok({
#diagnostics
#key_warnings
#[cfg(debug_assertions)]
fn __original_template() -> &'static dioxus_core::internal::HotReloadedTemplate {
static __ORIGINAL_TEMPLATE: ::std::sync::OnceLock<dioxus_core::internal::HotReloadedTemplate> = ::std::sync::OnceLock::new();
if __ORIGINAL_TEMPLATE.get().is_none() {
_ = __ORIGINAL_TEMPLATE.set(#hot_reload_mapping);
}
__ORIGINAL_TEMPLATE.get().unwrap()
}
#[cfg(debug_assertions)]
let __template_read = {
use dioxus_signals::ReadableExt;
static __NORMALIZED_FILE: &'static str = {
const PATH: &str = dioxus_core::const_format::str_replace!(file!(), "\\\\", "/");
dioxus_core::const_format::str_replace!(PATH, '\\', "/")
};
static __TEMPLATE: dioxus_signals::GlobalSignal<Option<dioxus_core::internal::HotReloadedTemplate>> = dioxus_signals::GlobalSignal::with_location(
|| None::<dioxus_core::internal::HotReloadedTemplate>,
__NORMALIZED_FILE,
line!(),
column!(),
#index
);
dioxus_core::Runtime::try_current().map(|_| __TEMPLATE.read())
};
#[cfg(debug_assertions)]
let __template_read = match __template_read.as_ref().map(|__template_read| __template_read.as_ref()) {
Some(Some(__template_read)) => &__template_read,
_ => __original_template(),
};
#[cfg(debug_assertions)]
let mut __dynamic_literal_pool = dioxus_core::internal::DynamicLiteralPool::new(
vec![ #( #dynamic_text.to_string() ),* ],
);
#[cfg(not(debug_assertions))]
let __key = #key_tokens;
let __dynamic_nodes: [dioxus_core::DynamicNode; #dynamic_nodes_len] = [ #( #dynamic_nodes ),* ];
let __dynamic_attributes: [Box<[dioxus_core::Attribute]>; #dynamic_attr_len] = [ #( #dyn_attr_printer ),* ];
#[doc(hidden)]
static __TEMPLATE_ROOTS: &[dioxus_core::TemplateNode] = &[ #( #roots ),* ];
#[cfg(debug_assertions)]
{
let mut __dynamic_value_pool = dioxus_core::internal::DynamicValuePool::new(
Vec::from(__dynamic_nodes),
Vec::from(__dynamic_attributes),
__dynamic_literal_pool
);
__dynamic_value_pool.render_with(__template_read)
}
#[cfg(not(debug_assertions))]
{
#[doc(hidden)] static ___TEMPLATE: dioxus_core::Template = dioxus_core::Template {
roots: __TEMPLATE_ROOTS,
node_paths: &[ #( #node_paths ),* ],
attr_paths: &[ #( #attr_paths ),* ],
};
#[allow(clippy::let_and_return)]
let __vnodes = dioxus_core::VNode::new(
__key,
___TEMPLATE,
Box::new(__dynamic_nodes),
Box::new(__dynamic_attributes),
);
__vnodes
}
})
});
}
}
impl TemplateBody {
pub fn new(nodes: Vec<BodyNode>) -> Self {
let mut body = Self {
roots: vec![],
template_idx: DynIdx::default(),
node_paths: Vec::new(),
attr_paths: Vec::new(),
dynamic_text_segments: Vec::new(),
diagnostics: Diagnostics::new(),
};
body.assign_paths_inner(&nodes);
body.roots = nodes;
body.validate_key();
body
}
pub fn normalized(&self) -> Self {
if self.is_empty() {
let empty = Self::new(vec![BodyNode::RawExpr(parse_quote! {()})]);
let default = Self {
diagnostics: self.diagnostics.clone(),
template_idx: self.template_idx.clone(),
..empty
};
return default;
}
self.clone()
}
pub fn is_empty(&self) -> bool {
self.roots.is_empty()
}
pub fn implicit_key(&self) -> Option<&AttributeValue> {
self.roots.first().and_then(BodyNode::key)
}
fn validate_key(&mut self) {
let key = self.implicit_key();
if let Some(attr) = key {
let diagnostic = match &attr {
AttributeValue::AttrLiteral(ifmt) => {
if ifmt.is_static() {
ifmt.span().error("Key must not be a static string. Make sure to use a formatted string like `key: \"{value}\"")
} else {
return;
}
}
_ => attr
.span()
.error("Key must be in the form of a formatted string like `key: \"{value}\""),
};
self.diagnostics.push(diagnostic);
}
}
fn check_for_duplicate_keys(&self) -> TokenStream2 {
let mut warnings = TokenStream2::new();
for root in self.roots.iter().skip(1) {
if let Some(key) = root.key() {
warnings.extend(new_diagnostics::warning_diagnostic(
key.span(),
"Keys are only allowed on the first node in the block.",
));
}
}
warnings
}
pub fn get_dyn_node(&self, path: &[u8]) -> &BodyNode {
let mut node = self.roots.get(path[0] as usize).unwrap();
for idx in path.iter().skip(1) {
node = node.element_children().get(*idx as usize).unwrap();
}
node
}
pub fn get_dyn_attr(&self, path: &AttributePath, idx: usize) -> &Attribute {
match self.get_dyn_node(path) {
BodyNode::Element(el) => &el.merged_attributes[idx],
_ => unreachable!(),
}
}
pub fn dynamic_attributes(&self) -> impl DoubleEndedIterator<Item = &Attribute> {
self.attr_paths
.iter()
.map(|(path, idx)| self.get_dyn_attr(path, *idx))
}
pub fn dynamic_nodes(&self) -> impl DoubleEndedIterator<Item = &BodyNode> {
self.node_paths.iter().map(|path| self.get_dyn_node(path))
}
fn quote_roots(&self) -> impl Iterator<Item = TokenStream2> + '_ {
self.roots.iter().map(|node| match node {
BodyNode::Element(el) => quote! { #el },
BodyNode::Text(text) if text.is_static() => {
let text = text.input.to_static().unwrap();
quote! { dioxus_core::TemplateNode::Text { text: #text } }
}
_ => {
let id = node.get_dyn_idx();
quote! { dioxus_core::TemplateNode::Dynamic { id: #id } }
}
})
}
pub fn literal_component_properties(&self) -> impl Iterator<Item = &HotLiteral> + '_ {
self.dynamic_nodes()
.filter_map(|node| {
if let BodyNode::Component(component) = node {
Some(component)
} else {
None
}
})
.flat_map(|component| {
component.component_props().filter_map(|field| {
if let AttributeValue::AttrLiteral(literal) = &field.value {
Some(literal)
} else {
None
}
})
})
}
fn hot_reload_mapping(&self) -> TokenStream2 {
let key = if let Some(AttributeValue::AttrLiteral(HotLiteral::Fmted(key))) =
self.implicit_key()
{
quote! { Some(#key) }
} else {
quote! { None }
};
let dynamic_nodes = self.dynamic_nodes().map(|node| {
let id = node.get_dyn_idx();
quote! { dioxus_core::internal::HotReloadDynamicNode::Dynamic(#id) }
});
let dyn_attr_printer = self.dynamic_attributes().map(|attr| {
let id = attr.get_dyn_idx();
quote! { dioxus_core::internal::HotReloadDynamicAttribute::Dynamic(#id) }
});
let component_values = self
.literal_component_properties()
.map(|literal| literal.quote_as_hot_reload_literal());
quote! {
dioxus_core::internal::HotReloadedTemplate::new(
#key,
vec![ #( #dynamic_nodes ),* ],
vec![ #( #dyn_attr_printer ),* ],
vec![ #( #component_values ),* ],
__TEMPLATE_ROOTS,
)
}
}
pub(crate) fn first_root_span(&self) -> Span {
match self.roots.first() {
Some(root) => root.span(),
_ => Span::call_site(),
}
}
}