rsx_call.rs 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. //! The actual rsx! macro implementation.
  2. //!
  3. //! This mostly just defers to the root TemplateBody with some additional tooling to provide better errors.
  4. //! Currently the additional tooling doesn't do much.
  5. use proc_macro2::TokenStream as TokenStream2;
  6. use quote::{quote, ToTokens};
  7. use std::{cell::Cell, fmt::Debug};
  8. use syn::{
  9. parse::{Parse, ParseStream},
  10. Result,
  11. };
  12. use crate::{BodyNode, TemplateBody};
  13. /// The Callbody is the contents of the rsx! macro
  14. ///
  15. /// It is a list of BodyNodes, which are the different parts of the template.
  16. /// The Callbody contains no information about how the template will be rendered, only information about the parsed tokens.
  17. ///
  18. /// Every callbody should be valid, so you can use it to build a template.
  19. /// To generate the code used to render the template, use the ToTokens impl on the Callbody, or with the `render_with_location` method.
  20. ///
  21. /// Ideally we don't need the metadata here and can bake the idx-es into the templates themselves but I haven't figured out how to do that yet.
  22. #[derive(Debug, Clone)]
  23. pub struct CallBody {
  24. pub body: TemplateBody,
  25. pub ifmt_idx: Cell<usize>,
  26. pub template_idx: Cell<usize>,
  27. }
  28. impl Parse for CallBody {
  29. fn parse(input: ParseStream) -> Result<Self> {
  30. // Defer to the `new` method such that we can wire up hotreload information
  31. Ok(CallBody::new(input.parse()?))
  32. }
  33. }
  34. impl ToTokens for CallBody {
  35. fn to_tokens(&self, out: &mut TokenStream2) {
  36. match self.body.is_empty() {
  37. true => quote! { dioxus_core::VNode::empty() }.to_tokens(out),
  38. false => self.body.to_tokens(out),
  39. }
  40. }
  41. }
  42. impl CallBody {
  43. /// Create a new CallBody from a TemplateBody
  44. ///
  45. /// This will overwrite all internal metadata regarding hotreloading.
  46. pub fn new(body: TemplateBody) -> Self {
  47. let body = CallBody {
  48. body,
  49. ifmt_idx: Cell::new(0),
  50. template_idx: Cell::new(0),
  51. };
  52. body.body.template_idx.set(body.next_template_idx());
  53. body.cascade_hotreload_info(&body.body.roots);
  54. body
  55. }
  56. /// Parse a stream into a CallBody. Return all error immediately instead of trying to partially expand the macro
  57. ///
  58. /// This should be preferred over `parse` if you are outside of a macro
  59. pub fn parse_strict(input: ParseStream) -> Result<Self> {
  60. // todo: actually throw warnings if there are any
  61. Self::parse(input)
  62. }
  63. /// With the entire knowledge of the macro call, wire up location information for anything hotreloading
  64. /// specific. It's a little bit simpler just to have a global id per callbody than to try and track it
  65. /// relative to each template, though we could do that if we wanted to.
  66. ///
  67. /// For now this is just information for ifmts and templates so that when they generate, they can be
  68. /// tracked back to the original location in the source code, to support formatted string hotreloading.
  69. ///
  70. /// Note that there are some more complex cases we could in theory support, but have bigger plans
  71. /// to enable just pure rust hotreloading that would make those tricks moot. So, manage more of
  72. /// the simple cases until that proper stuff ships.
  73. ///
  74. /// We need to make sure to wire up:
  75. /// - subtemplate IDs
  76. /// - ifmt IDs
  77. /// - dynamic node IDs
  78. /// - dynamic attribute IDs
  79. /// - paths for dynamic nodes and attributes
  80. ///
  81. /// Lots of wiring!
  82. ///
  83. /// However, here, we only need to wire up ifmt and template IDs since TemplateBody will handle the rest.
  84. ///
  85. /// This is better though since we can save the relevant data on the structures themselves.
  86. fn cascade_hotreload_info(&self, nodes: &[BodyNode]) {
  87. for node in nodes.iter() {
  88. match node {
  89. BodyNode::RawExpr(_) => { /* one day maybe provide hr here?*/ }
  90. BodyNode::Text(text) => {
  91. // one day we could also provide HR here to allow dynamic parts on the fly
  92. if !text.is_static() {
  93. text.hr_idx.set(self.next_ifmt_idx());
  94. }
  95. }
  96. BodyNode::Element(el) => {
  97. // Walk the attributes looking for hotreload opportunities
  98. for attr in &el.merged_attributes {
  99. attr.with_literal(|lit| lit.hr_idx.set(self.next_ifmt_idx()));
  100. }
  101. self.cascade_hotreload_info(&el.children);
  102. }
  103. BodyNode::Component(comp) => {
  104. // walk the props looking for hotreload opportunities
  105. for prop in comp.fields.iter() {
  106. prop.with_literal(|lit| lit.hr_idx.set(self.next_ifmt_idx()));
  107. }
  108. comp.children.template_idx.set(self.next_template_idx());
  109. self.cascade_hotreload_info(&comp.children.roots);
  110. }
  111. BodyNode::ForLoop(floop) => {
  112. floop.body.template_idx.set(self.next_template_idx());
  113. self.cascade_hotreload_info(&floop.body.roots);
  114. }
  115. BodyNode::IfChain(chain) => chain.for_each_branch(&mut |body| {
  116. body.template_idx.set(self.next_template_idx());
  117. self.cascade_hotreload_info(&body.roots)
  118. }),
  119. }
  120. }
  121. }
  122. fn next_ifmt_idx(&self) -> usize {
  123. let idx = self.ifmt_idx.get();
  124. self.ifmt_idx.set(idx + 1);
  125. idx
  126. }
  127. fn next_template_idx(&self) -> usize {
  128. let idx = self.template_idx.get();
  129. self.template_idx.set(idx + 1);
  130. idx
  131. }
  132. }