template_body.rs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  1. //! I'm so sorry this is so complicated. Here's my best to simplify and explain it:
  2. //!
  3. //! The `Callbody` is the contents of the rsx! macro - this contains all the information about every
  4. //! node that rsx! directly knows about. For loops, if statements, etc.
  5. //!
  6. //! However, there are multiple *templates* inside a callbody - due to how core clones templates and
  7. //! just generally rationalize the concept of a template, nested bodies like for loops and if statements
  8. //! and component children are all templates, contained within the same Callbody.
  9. //!
  10. //! This gets confusing fast since there's lots of IDs bouncing around.
  11. //!
  12. //! The IDs at play:
  13. //! - The id of the template itself so we can find it and apply it to the dom.
  14. //! This is challenging since all calls to file/line/col/id are relative to the macro invocation,
  15. //! so they will have to share the same base ID and we need to give each template a new ID.
  16. //! The id of the template will be something like file!():line!():col!():ID where ID increases for
  17. //! each nested template.
  18. //!
  19. //! - The IDs of dynamic nodes relative to the template they live in. This is somewhat easy to track
  20. //! but needs to happen on a per-template basis.
  21. //!
  22. //! - The IDs of formatted strings in debug mode only. Any formatted segments like "{x:?}" get pulled out
  23. //! into a pool so we can move them around during hot reloading on a per-template basis.
  24. //!
  25. //! - The IDs of component property literals in debug mode only. Any component property literals like
  26. //! 1234 get pulled into the pool so we can hot reload them with the context of the literal pool.
  27. //!
  28. //! We solve this by parsing the structure completely and then doing a second pass that fills in IDs
  29. //! by walking the structure.
  30. //!
  31. //! This means you can't query the ID of any node "in a vacuum" - these are assigned once - but at
  32. //! least they're stable enough for the purposes of hotreloading
  33. //!
  34. //! ```rust, ignore
  35. //! rsx! {
  36. //! div {
  37. //! class: "hello",
  38. //! id: "node-{node_id}", <--- {node_id} has the formatted segment id 0 in the literal pool
  39. //! ..props, <--- spreads are not reloadable
  40. //!
  41. //! "Hello, world! <--- not tracked but reloadable in the template since it's just a string
  42. //!
  43. //! for item in 0..10 { <--- both 0 and 10 are technically reloadable, but we don't hot reload them today...
  44. //! div { "cool-{item}" } <--- {item} has the formatted segment id 1 in the literal pool
  45. //! }
  46. //!
  47. //! Link {
  48. //! to: "/home", <-- hotreloadable since its a component prop literal (with component literal id 0)
  49. //! class: "link {is_ready}", <-- {is_ready} has the formatted segment id 2 in the literal pool and the property has the component literal id 1
  50. //! "Home" <-- hotreloadable since its a component child (via template)
  51. //! }
  52. //! }
  53. //! }
  54. //! ```
  55. use self::location::DynIdx;
  56. use crate::innerlude::Attribute;
  57. use crate::*;
  58. use proc_macro2::TokenStream as TokenStream2;
  59. use proc_macro2_diagnostics::SpanDiagnosticExt;
  60. use syn::parse_quote;
  61. type NodePath = Vec<u8>;
  62. type AttributePath = Vec<u8>;
  63. /// A set of nodes in a template position
  64. ///
  65. /// this could be:
  66. /// - The root of a callbody
  67. /// - The children of a component
  68. /// - The children of a for loop
  69. /// - The children of an if chain
  70. ///
  71. /// The TemplateBody when needs to be parsed into a surrounding `Body` to be correctly re-indexed
  72. /// By default every body has a `0` default index
  73. #[derive(PartialEq, Eq, Clone, Debug)]
  74. pub struct TemplateBody {
  75. pub roots: Vec<BodyNode>,
  76. pub template_idx: DynIdx,
  77. pub node_paths: Vec<NodePath>,
  78. pub attr_paths: Vec<(AttributePath, usize)>,
  79. pub dynamic_text_segments: Vec<FormattedSegment>,
  80. pub diagnostics: Diagnostics,
  81. }
  82. impl Parse for TemplateBody {
  83. /// Parse the nodes of the callbody as `Body`.
  84. fn parse(input: ParseStream) -> Result<Self> {
  85. let children = RsxBlock::parse_children(input)?;
  86. let mut myself = Self::new(children.children);
  87. myself
  88. .diagnostics
  89. .extend(children.diagnostics.into_diagnostics());
  90. Ok(myself)
  91. }
  92. }
  93. /// Our ToTokens impl here just defers to rendering a template out like any other `Body`.
  94. /// This is because the parsing phase filled in all the additional metadata we need
  95. impl ToTokens for TemplateBody {
  96. fn to_tokens(&self, tokens: &mut TokenStream2) {
  97. // If the nodes are completely empty, insert a placeholder node
  98. // Core expects at least one node in the template to make it easier to replace
  99. if self.is_empty() {
  100. // Create an empty template body with a placeholder and diagnostics + the template index from the original
  101. let empty = Self::new(vec![BodyNode::RawExpr(parse_quote! {()})]);
  102. let default = Self {
  103. diagnostics: self.diagnostics.clone(),
  104. template_idx: self.template_idx.clone(),
  105. ..empty
  106. };
  107. // And then render the default template body
  108. default.to_tokens(tokens);
  109. return;
  110. }
  111. // If we have an implicit key, then we need to write its tokens
  112. let key_tokens = match self.implicit_key() {
  113. Some(tok) => quote! { Some( #tok.to_string() ) },
  114. None => quote! { None },
  115. };
  116. let roots = self.quote_roots();
  117. // Print paths is easy - just print the paths
  118. let node_paths = self.node_paths.iter().map(|it| quote!(&[#(#it),*]));
  119. let attr_paths = self.attr_paths.iter().map(|(it, _)| quote!(&[#(#it),*]));
  120. // For printing dynamic nodes, we rely on the ToTokens impl
  121. // Elements have a weird ToTokens - they actually are the entrypoint for Template creation
  122. let dynamic_nodes: Vec<_> = self.dynamic_nodes().collect();
  123. // We could add a ToTokens for Attribute but since we use that for both components and elements
  124. // They actually need to be different, so we just localize that here
  125. let dyn_attr_printer: Vec<_> = self
  126. .dynamic_attributes()
  127. .map(|attr| attr.rendered_as_dynamic_attr())
  128. .collect();
  129. let dynamic_text = self.dynamic_text_segments.iter();
  130. let index = self.template_idx.get();
  131. let diagnostics = &self.diagnostics;
  132. let hot_reload_mapping = self.hot_reload_mapping(quote! { ___TEMPLATE_NAME });
  133. let vnode = quote! {
  134. #[doc(hidden)] // vscode please stop showing these in symbol search
  135. const ___TEMPLATE_NAME: &str = {
  136. const PATH: &str = dioxus_core::const_format::str_replace!(file!(), "\\\\", "/");
  137. const NORMAL: &str = dioxus_core::const_format::str_replace!(PATH, '\\', "/");
  138. dioxus_core::const_format::concatcp!(NORMAL, ':', line!(), ':', column!(), ':', #index)
  139. };
  140. #[cfg(not(debug_assertions))]
  141. {
  142. #[doc(hidden)] // vscode please stop showing these in symbol search
  143. static ___TEMPLATE: dioxus_core::Template = dioxus_core::Template {
  144. name: ___TEMPLATE_NAME,
  145. roots: &[ #( #roots ),* ],
  146. node_paths: &[ #( #node_paths ),* ],
  147. attr_paths: &[ #( #attr_paths ),* ],
  148. };
  149. // NOTE: Allocating a temporary is important to make reads within rsx drop before the value is returned
  150. #[allow(clippy::let_and_return)]
  151. let __vnodes = dioxus_core::VNode::new(
  152. #key_tokens,
  153. ___TEMPLATE,
  154. Box::new([ #( #dynamic_nodes ),* ]),
  155. Box::new([ #( #dyn_attr_printer ),* ]),
  156. );
  157. __vnodes
  158. }
  159. #[cfg(debug_assertions)]
  160. {
  161. // The key is important here - we're creating a new GlobalSignal each call to this
  162. // But the key is what's keeping it stable
  163. let __template = GlobalSignal::with_key(
  164. || #hot_reload_mapping,
  165. ___TEMPLATE_NAME
  166. );
  167. __template.maybe_with_rt(|__template_read| {
  168. let mut __dynamic_literal_pool = dioxus_core::internal::DynamicLiteralPool::new(
  169. vec![ #( #dynamic_text.to_string() ),* ],
  170. );
  171. let mut __dynamic_value_pool = dioxus_core::internal::DynamicValuePool::new(
  172. vec![ #( #dynamic_nodes ),* ],
  173. vec![ #( #dyn_attr_printer ),* ],
  174. __dynamic_literal_pool
  175. );
  176. __dynamic_value_pool.render_with(__template_read)
  177. })
  178. }
  179. };
  180. tokens.append_all(quote! {
  181. dioxus_core::Element::Ok({
  182. #diagnostics
  183. #vnode
  184. })
  185. });
  186. }
  187. }
  188. impl TemplateBody {
  189. /// Create a new TemplateBody from a set of nodes
  190. ///
  191. /// This will fill in all the necessary path information for the nodes in the template and will
  192. /// overwrite data like dynamic indexes.
  193. pub fn new(nodes: Vec<BodyNode>) -> Self {
  194. let mut body = Self {
  195. roots: vec![],
  196. template_idx: DynIdx::default(),
  197. node_paths: Vec::new(),
  198. attr_paths: Vec::new(),
  199. dynamic_text_segments: Vec::new(),
  200. diagnostics: Diagnostics::new(),
  201. };
  202. // Assign paths to all nodes in the template
  203. body.assign_paths_inner(&nodes);
  204. body.validate_key();
  205. // And then save the roots
  206. body.roots = nodes;
  207. body
  208. }
  209. pub fn is_empty(&self) -> bool {
  210. self.roots.is_empty()
  211. }
  212. pub(crate) fn implicit_key(&self) -> Option<&AttributeValue> {
  213. match self.roots.first() {
  214. Some(BodyNode::Element(el)) => el.key(),
  215. Some(BodyNode::Component(comp)) => comp.get_key(),
  216. _ => None,
  217. }
  218. }
  219. /// Ensure only one key and that the key is not a static str
  220. ///
  221. /// todo: we want to allow arbitrary exprs for keys provided they impl hash / eq
  222. fn validate_key(&mut self) {
  223. let key = self.implicit_key();
  224. if let Some(attr) = key {
  225. let diagnostic = match &attr {
  226. AttributeValue::AttrLiteral(ifmt) => {
  227. if ifmt.is_static() {
  228. ifmt.span().error("Key must not be a static string. Make sure to use a formatted string like `key: \"{value}\"")
  229. } else {
  230. return;
  231. }
  232. }
  233. _ => attr
  234. .span()
  235. .error("Key must be in the form of a formatted string like `key: \"{value}\""),
  236. };
  237. self.diagnostics.push(diagnostic);
  238. }
  239. }
  240. pub fn get_dyn_node(&self, path: &[u8]) -> &BodyNode {
  241. let mut node = self.roots.get(path[0] as usize).unwrap();
  242. for idx in path.iter().skip(1) {
  243. node = node.element_children().get(*idx as usize).unwrap();
  244. }
  245. node
  246. }
  247. pub fn get_dyn_attr(&self, path: &AttributePath, idx: usize) -> &Attribute {
  248. match self.get_dyn_node(path) {
  249. BodyNode::Element(el) => &el.merged_attributes[idx],
  250. _ => unreachable!(),
  251. }
  252. }
  253. pub fn dynamic_attributes(&self) -> impl DoubleEndedIterator<Item = &Attribute> {
  254. self.attr_paths
  255. .iter()
  256. .map(|(path, idx)| self.get_dyn_attr(path, *idx))
  257. }
  258. pub fn dynamic_nodes(&self) -> impl DoubleEndedIterator<Item = &BodyNode> {
  259. self.node_paths.iter().map(|path| self.get_dyn_node(path))
  260. }
  261. fn quote_roots(&self) -> impl Iterator<Item = TokenStream2> + '_ {
  262. self.roots.iter().map(|node| match node {
  263. BodyNode::Element(el) => quote! { #el },
  264. BodyNode::Text(text) if text.is_static() => {
  265. let text = text.input.to_static().unwrap();
  266. quote! { dioxus_core::TemplateNode::Text { text: #text } }
  267. }
  268. _ => {
  269. let id = node.get_dyn_idx();
  270. quote! { dioxus_core::TemplateNode::Dynamic { id: #id } }
  271. }
  272. })
  273. }
  274. /// Iterate through the literal component properties of this rsx call in depth-first order
  275. pub(crate) fn literal_component_properties(&self) -> impl Iterator<Item = &HotLiteral> + '_ {
  276. self.dynamic_nodes()
  277. .filter_map(|node| {
  278. if let BodyNode::Component(component) = node {
  279. Some(component)
  280. } else {
  281. None
  282. }
  283. })
  284. .flat_map(|component| {
  285. component.fields.iter().filter_map(|field| {
  286. if let AttributeValue::AttrLiteral(literal) = &field.value {
  287. Some(literal)
  288. } else {
  289. None
  290. }
  291. })
  292. })
  293. }
  294. fn hot_reload_mapping(&self, name: impl ToTokens) -> TokenStream2 {
  295. let key = if let Some(AttributeValue::AttrLiteral(HotLiteral::Fmted(key))) =
  296. self.implicit_key()
  297. {
  298. quote! { Some(#key) }
  299. } else {
  300. quote! { None }
  301. };
  302. let roots = self.quote_roots();
  303. let dynamic_nodes = self.dynamic_nodes().map(|node| {
  304. let id = node.get_dyn_idx();
  305. quote! { dioxus_core::internal::HotReloadDynamicNode::Dynamic(#id) }
  306. });
  307. let dyn_attr_printer = self.dynamic_attributes().map(|attr| {
  308. let id = attr.get_dyn_idx();
  309. quote! { dioxus_core::internal::HotReloadDynamicAttribute::Dynamic(#id) }
  310. });
  311. let component_values = self
  312. .literal_component_properties()
  313. .map(|literal| literal.quote_as_hot_reload_literal());
  314. quote! {
  315. dioxus_core::internal::HotReloadedTemplate::new(
  316. #name,
  317. #key,
  318. vec![ #( #dynamic_nodes ),* ],
  319. vec![ #( #dyn_attr_printer ),* ],
  320. vec![ #( #component_values ),* ],
  321. &[ #( #roots ),* ],
  322. )
  323. }
  324. }
  325. }