123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773 |
- #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
- #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
- //! 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 attribute;
- mod component;
- mod element;
- #[cfg(feature = "hot_reload")]
- pub mod hot_reload;
- mod ifmt;
- mod node;
- use std::{fmt::Debug, hash::Hash};
- // Re-export the namespaces into each other
- pub use attribute::*;
- pub use component::*;
- #[cfg(feature = "hot_reload")]
- use dioxus_core::{Template, TemplateAttribute, TemplateNode};
- pub use element::*;
- #[cfg(feature = "hot_reload")]
- pub use hot_reload::HotReloadingContext;
- pub use ifmt::*;
- #[cfg(feature = "hot_reload")]
- use internment::Intern;
- pub use node::*;
- // imports
- use proc_macro2::TokenStream as TokenStream2;
- use quote::{quote, ToTokens, TokenStreamExt};
- use syn::{
- parse::{Parse, ParseStream},
- Result, Token,
- };
- #[cfg(feature = "hot_reload")]
- // interns a object into a static object, resusing the value if it already exists
- fn intern<T: Eq + Hash + Send + Sync + ?Sized + 'static>(s: impl Into<Intern<T>>) -> &'static T {
- s.into().as_ref()
- }
- /// Fundametnally, every CallBody is a template
- #[derive(Default, Debug)]
- pub struct CallBody {
- pub roots: Vec<BodyNode>,
- }
- impl CallBody {
- #[cfg(feature = "hot_reload")]
- /// 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.
- /// 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>(
- &self,
- template: Option<CallBody>,
- location: &'static str,
- ) -> Option<Template> {
- let mut renderer: TemplateRenderer = TemplateRenderer {
- roots: &self.roots,
- location: None,
- };
- renderer.update_template::<Ctx>(template, location)
- }
- /// 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),
- };
- // Empty templates just are placeholders for "none"
- if self.roots.is_empty() {
- return quote! { None };
- }
- quote! {
- Some({ #body })
- }
- }
- }
- 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);
- }
- Ok(Self { roots })
- }
- }
- impl ToTokens for CallBody {
- fn to_tokens(&self, out_tokens: &mut TokenStream2) {
- let body: TemplateRenderer = TemplateRenderer {
- roots: &self.roots,
- location: None,
- };
- // Empty templates just are placeholders for "none"
- if self.roots.is_empty() {
- return out_tokens.append_all(quote! { None });
- }
- out_tokens.append_all(quote! {
- Some({ #body })
- })
- }
- }
- pub struct TemplateRenderer<'a> {
- pub roots: &'a [BodyNode],
- pub location: Option<String>,
- }
- impl<'a> TemplateRenderer<'a> {
- #[cfg(feature = "hot_reload")]
- fn update_template<Ctx: HotReloadingContext>(
- &mut self,
- previous_call: Option<CallBody>,
- location: &'static str,
- ) -> Option<Template> {
- let mut mapping = previous_call.map(|call| DynamicMapping::from(call.roots));
- let mut context = DynamicContext::default();
- 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)?);
- context.current_path.pop();
- }
- Some(Template {
- name: location,
- roots: intern(roots.as_slice()),
- node_paths: intern(
- context
- .node_paths
- .into_iter()
- .map(|path| intern(path.as_slice()))
- .collect::<Vec<_>>()
- .as_slice(),
- ),
- attr_paths: intern(
- context
- .attr_paths
- .into_iter()
- .map(|path| intern(path.as_slice()))
- .collect::<Vec<_>>()
- .as_slice(),
- ),
- })
- }
- }
- impl<'a> ToTokens for TemplateRenderer<'a> {
- fn to_tokens(&self, out_tokens: &mut TokenStream2) {
- let mut context = DynamicContext::default();
- let key = match self.roots.first() {
- 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,
- };
- let key_tokens = match key {
- Some(tok) => quote! { Some( #tok.to_string() ) },
- None => quote! { None },
- };
- let root_col = match self.roots.first() {
- Some(first_root) => {
- let first_root_span = format!("{:?}", first_root.span());
- first_root_span
- .rsplit_once("..")
- .and_then(|(_, after)| after.split_once(')').map(|(before, _)| before))
- .unwrap_or_default()
- .to_string()
- }
- _ => "0".to_string(),
- };
- let root_printer = self.roots.iter().enumerate().map(|(idx, root)| {
- context.current_path.push(idx as u8);
- let out = context.render_static_node(root);
- 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 ),* };
- let node_printer = &context.dynamic_nodes;
- let dyn_attr_printer = context
- .dynamic_attributes
- .iter()
- .map(|attrs| AttributeType::merge_quote(attrs));
- let node_paths = context.node_paths.iter().map(|it| quote!(&[#(#it),*]));
- let attr_paths = context.attr_paths.iter().map(|it| quote!(&[#(#it),*]));
- out_tokens.append_all(quote! {
- static TEMPLATE: dioxus_core::Template = dioxus_core::Template {
- name: #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
- let __vnodes = dioxus_core::VNode::new(
- #key_tokens,
- TEMPLATE,
- Box::new([ #( #node_printer),* ]),
- Box::new([ #(#dyn_attr_printer),* ]),
- );
- __vnodes
- }
- });
- }
- }
- #[cfg(feature = "hot_reload")]
- #[derive(Default, Debug)]
- struct DynamicMapping {
- attribute_to_idx: std::collections::HashMap<AttributeType, Vec<usize>>,
- last_attribute_idx: usize,
- node_to_idx: std::collections::HashMap<BodyNode, Vec<usize>>,
- last_element_idx: usize,
- }
- #[cfg(feature = "hot_reload")]
- impl DynamicMapping {
- fn from(nodes: Vec<BodyNode>) -> Self {
- let mut new = Self::default();
- for node in nodes {
- new.add_node(node);
- }
- new
- }
- fn get_attribute_idx(&mut self, attr: &AttributeType) -> Option<usize> {
- self.attribute_to_idx
- .get_mut(attr)
- .and_then(|idxs| idxs.pop())
- }
- fn get_node_idx(&mut self, node: &BodyNode) -> Option<usize> {
- self.node_to_idx.get_mut(node).and_then(|idxs| idxs.pop())
- }
- fn insert_attribute(&mut self, attr: AttributeType) -> usize {
- let idx = self.last_attribute_idx;
- self.last_attribute_idx += 1;
- self.attribute_to_idx.entry(attr).or_default().push(idx);
- idx
- }
- fn insert_node(&mut self, node: BodyNode) -> usize {
- let idx = self.last_element_idx;
- self.last_element_idx += 1;
- self.node_to_idx.entry(node).or_default().push(idx);
- idx
- }
- fn add_node(&mut self, node: BodyNode) {
- match node {
- BodyNode::Element(el) => {
- for attr in el.merged_attributes {
- match &attr {
- AttributeType::Named(ElementAttrNamed {
- attr:
- ElementAttr {
- value: ElementAttrValue::AttrLiteral(input),
- ..
- },
- ..
- }) if input.is_static() => {}
- _ => {
- self.insert_attribute(attr);
- }
- }
- }
- 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);
- }
- }
- }
- }
- // 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)]
- pub struct DynamicContext<'a> {
- dynamic_nodes: Vec<&'a BodyNode>,
- dynamic_attributes: Vec<Vec<&'a AttributeType>>,
- current_path: Vec<u8>,
- node_paths: Vec<Vec<u8>>,
- attr_paths: Vec<Vec<u8>>,
- }
- impl<'a> DynamicContext<'a> {
- #[cfg(feature = "hot_reload")]
- fn update_node<Ctx: HotReloadingContext>(
- &mut self,
- root: &'a BodyNode,
- mapping: &mut Option<DynamicMapping>,
- ) -> Option<TemplateNode> {
- match root {
- BodyNode::Element(el) => {
- let element_name_rust = el.name.to_string();
- let mut static_attrs = Vec::new();
- for attr in &el.merged_attributes {
- match &attr {
- AttributeType::Named(ElementAttrNamed {
- attr:
- ElementAttr {
- value: ElementAttrValue::AttrLiteral(value),
- name,
- },
- ..
- }) if value.is_static() => {
- let value = value.source.as_ref().unwrap();
- let attribute_name_rust = name.to_string();
- let (name, namespace) =
- Ctx::map_attribute(&element_name_rust, &attribute_name_rust)
- .unwrap_or((intern(attribute_name_rust.as_str()), None));
- static_attrs.push(TemplateAttribute::Static {
- name,
- namespace,
- value: intern(value.value().as_str()),
- })
- }
- _ => {
- let idx = match mapping {
- Some(mapping) => mapping.get_attribute_idx(attr)?,
- None => self.dynamic_attributes.len(),
- };
- self.dynamic_attributes.push(vec![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 })
- }
- }
- }
- 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)?);
- self.current_path.pop();
- }
- let (tag, namespace) = Ctx::map_element(&element_name_rust)
- .unwrap_or((intern(element_name_rust.as_str()), None));
- Some(TemplateNode::Element {
- tag,
- namespace,
- attrs: intern(static_attrs.into_boxed_slice()),
- children: intern(children.as_slice()),
- })
- }
- BodyNode::Text(text) if text.is_static() => {
- let text = text.source.as_ref().unwrap();
- Some(TemplateNode::Text {
- text: intern(text.value().as_str()),
- })
- }
- BodyNode::RawExpr(_)
- | BodyNode::Text(_)
- | BodyNode::ForLoop(_)
- | BodyNode::IfChain(_)
- | BodyNode::Component(_) => {
- let idx = match mapping {
- Some(mapping) => mapping.get_node_idx(root)?,
- None => self.dynamic_nodes.len(),
- };
- 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();
- Some(match root {
- BodyNode::Text(_) => TemplateNode::DynamicText { id: idx },
- _ => TemplateNode::Dynamic { id: idx },
- })
- }
- }
- }
- 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 },
- };
- let static_attrs = el.merged_attributes.iter().map(|attr| match attr {
- AttributeType::Named(ElementAttrNamed {
- attr:
- ElementAttr {
- value: ElementAttrValue::AttrLiteral(value),
- name,
- },
- ..
- }) if value.is_static() => {
- let value = value.to_static().unwrap();
- let ns = {
- match name {
- ElementAttrName::BuiltIn(name) => ns(quote!(#name.1)),
- ElementAttrName::Custom(_) => quote!(None),
- }
- };
- let name = match (el_name, name) {
- (ElementName::Ident(_), ElementAttrName::BuiltIn(_)) => {
- quote! { #el_name::#name.0 }
- }
- _ => {
- let as_string = name.to_string();
- quote! { #as_string }
- }
- };
- quote! {
- dioxus_core::TemplateAttribute::Static {
- name: #name,
- namespace: #ns,
- value: #value,
- // todo: we don't diff these so we never apply the volatile flag
- // volatile: dioxus_elements::#el_name::#name.2,
- },
- }
- }
- _ => {
- // If this attribute is dynamic, but it already exists in the template, we can reuse the index
- if let Some(attribute_index) = self
- .attr_paths
- .iter()
- .position(|path| path == &self.current_path)
- {
- self.dynamic_attributes[attribute_index].push(attr);
- quote! {}
- } else {
- let ct = self.dynamic_attributes.len();
- self.dynamic_attributes.push(vec![attr]);
- self.attr_paths.push(self.current_path.clone());
- quote! { dioxus_core::TemplateAttribute::Dynamic { id: #ct }, }
- }
- }
- });
- 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
- });
- let _opt = el.children.len() == 1;
- let children = quote! { #(#children),* };
- let ns = ns(quote!(NAME_SPACE));
- let el_name = el_name.tag_name();
- quote! {
- dioxus_core::TemplateNode::Element {
- tag: #el_name,
- namespace: #ns,
- attrs: &[ #attrs ],
- children: &[ #children ],
- }
- }
- }
- BodyNode::Text(text) if text.is_static() => {
- let text = text.to_static().unwrap();
- quote! { dioxus_core::TemplateNode::Text{ text: #text } }
- }
- BodyNode::RawExpr(_)
- | BodyNode::Text(_)
- | BodyNode::ForLoop(_)
- | BodyNode::IfChain(_)
- | BodyNode::Component(_) => {
- let ct = self.dynamic_nodes.len();
- self.dynamic_nodes.push(root);
- self.node_paths.push(self.current_path.clone());
- match root {
- BodyNode::Text(_) => {
- quote! { dioxus_core::TemplateNode::DynamicText { id: #ct } }
- }
- _ => quote! { dioxus_core::TemplateNode::Dynamic { id: #ct } },
- }
- }
- }
- }
- }
- #[cfg(feature = "hot_reload")]
- #[test]
- fn create_template() {
- let input = quote! {
- svg {
- width: 100,
- height: "100px",
- "width2": 100,
- "height2": "100px",
- p {
- "hello world"
- }
- {(0..10).map(|i| rsx!{"{i}"})}
- }
- };
- 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();
- let template = call_body.update_template::<Mock>(None, "testing").unwrap();
- dbg!(template);
- assert_eq!(
- template,
- Template {
- name: "testing",
- roots: &[TemplateNode::Element {
- tag: "svg",
- namespace: Some("svg"),
- attrs: &[
- TemplateAttribute::Dynamic { id: 0 },
- TemplateAttribute::Static {
- name: "height",
- namespace: Some("style"),
- 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,],],
- },
- )
- }
- #[cfg(feature = "hot_reload")]
- #[test]
- fn diff_template() {
- #[allow(unused, non_snake_case)]
- fn Comp() -> dioxus_core::Element {
- None
- }
- let input = quote! {
- svg {
- width: 100,
- height: "100px",
- "width2": 100,
- "height2": "100px",
- p {
- "hello world"
- }
- {(0..10).map(|i| rsx!{"{i}"})},
- {(0..10).map(|i| rsx!{"{i}"})},
- {(0..11).map(|i| rsx!{"{i}"})},
- Comp{}
- }
- };
- #[derive(Debug)]
- 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();
- let template = call_body1.update_template::<Mock>(None, "testing").unwrap();
- dbg!(template);
- // scrambling the attributes should not cause a full rebuild
- let input = quote! {
- div {
- "width2": 100,
- height: "100px",
- "height2": "100px",
- width: 100,
- Comp{}
- {(0..11).map(|i| rsx!{"{i}"})},
- {(0..10).map(|i| rsx!{"{i}"})},
- {(0..10).map(|i| rsx!{"{i}"})},
- p {
- "hello world"
- }
- }
- };
- let call_body2: CallBody = syn::parse2(input).unwrap();
- let template = call_body2
- .update_template::<Mock>(Some(call_body1), "testing")
- .unwrap();
- dbg!(template);
- 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: &[
- TemplateNode::Dynamic { id: 3 },
- 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",
- }],
- },
- ],
- }],
- node_paths: &[&[0, 3], &[0, 2], &[0, 1], &[0, 0]],
- attr_paths: &[&[0], &[0]]
- },
- )
- }
|