lib.rs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754
  1. #![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
  2. #![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
  3. //! Parse the root tokens in the rsx!{} macro
  4. //! =========================================
  5. //!
  6. //! This parsing path emerges directly from the macro call, with `RsxRender` being the primary entrance into parsing.
  7. //! This feature must support:
  8. //! - [x] Optionally rendering if the `in XYZ` pattern is present
  9. //! - [x] Fragments as top-level element (through ambiguous)
  10. //! - [x] Components as top-level element (through ambiguous)
  11. //! - [x] Tags as top-level elements (through ambiguous)
  12. //! - [x] Good errors if parsing fails
  13. //!
  14. //! Any errors in using rsx! will likely occur when people start using it, so the first errors must be really helpful.
  15. #[macro_use]
  16. mod errors;
  17. mod attribute;
  18. mod component;
  19. mod element;
  20. #[cfg(feature = "hot_reload")]
  21. pub mod hot_reload;
  22. mod ifmt;
  23. mod node;
  24. use std::{fmt::Debug, hash::Hash};
  25. // Re-export the namespaces into each other
  26. pub use attribute::*;
  27. pub use component::*;
  28. #[cfg(feature = "hot_reload")]
  29. use dioxus_core::{Template, TemplateAttribute, TemplateNode};
  30. pub use element::*;
  31. #[cfg(feature = "hot_reload")]
  32. pub use hot_reload::HotReloadingContext;
  33. pub use ifmt::*;
  34. #[cfg(feature = "hot_reload")]
  35. use internment::Intern;
  36. pub use node::*;
  37. // imports
  38. use proc_macro2::TokenStream as TokenStream2;
  39. use quote::{quote, ToTokens, TokenStreamExt};
  40. use syn::{
  41. parse::{Parse, ParseStream},
  42. Result, Token,
  43. };
  44. #[cfg(feature = "hot_reload")]
  45. // interns a object into a static object, resusing the value if it already exists
  46. fn intern<T: Eq + Hash + Send + Sync + ?Sized + 'static>(s: impl Into<Intern<T>>) -> &'static T {
  47. s.into().as_ref()
  48. }
  49. /// Fundametnally, every CallBody is a template
  50. #[derive(Default, Debug)]
  51. pub struct CallBody {
  52. pub roots: Vec<BodyNode>,
  53. }
  54. impl CallBody {
  55. #[cfg(feature = "hot_reload")]
  56. /// 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.
  57. /// This function intentionally leaks memory to create a static template.
  58. /// Keeping the template static allows us to simplify the core of dioxus and leaking memory in dev mode is less of an issue.
  59. /// the previous_location is the location of the previous template at the time the template was originally compiled.
  60. pub fn update_template<Ctx: HotReloadingContext>(
  61. &self,
  62. template: Option<CallBody>,
  63. location: &'static str,
  64. ) -> Option<Template> {
  65. let mut renderer: TemplateRenderer = TemplateRenderer {
  66. roots: &self.roots,
  67. location: None,
  68. };
  69. renderer.update_template::<Ctx>(template, location)
  70. }
  71. /// Render the template with a manually set file location. This should be used when multiple rsx! calls are used in the same macro
  72. pub fn render_with_location(&self, location: String) -> TokenStream2 {
  73. let body = TemplateRenderer {
  74. roots: &self.roots,
  75. location: Some(location),
  76. };
  77. quote! {
  78. ::dioxus::core::LazyNodes::new( move | __cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode {
  79. #body
  80. })
  81. }
  82. }
  83. }
  84. impl Parse for CallBody {
  85. fn parse(input: ParseStream) -> Result<Self> {
  86. let mut roots = Vec::new();
  87. while !input.is_empty() {
  88. let node = input.parse::<BodyNode>()?;
  89. if input.peek(Token![,]) {
  90. let _ = input.parse::<Token![,]>();
  91. }
  92. roots.push(node);
  93. }
  94. Ok(Self { roots })
  95. }
  96. }
  97. /// Serialize the same way, regardless of flavor
  98. impl ToTokens for CallBody {
  99. fn to_tokens(&self, out_tokens: &mut TokenStream2) {
  100. let body = TemplateRenderer {
  101. roots: &self.roots,
  102. location: None,
  103. };
  104. out_tokens.append_all(quote! {
  105. ::dioxus::core::LazyNodes::new( move | __cx: &::dioxus::core::ScopeState| -> ::dioxus::core::VNode {
  106. #body
  107. })
  108. })
  109. }
  110. }
  111. #[derive(Default, Debug)]
  112. pub struct RenderCallBody(pub CallBody);
  113. impl ToTokens for RenderCallBody {
  114. fn to_tokens(&self, out_tokens: &mut TokenStream2) {
  115. let body: TemplateRenderer = TemplateRenderer {
  116. roots: &self.0.roots,
  117. location: None,
  118. };
  119. out_tokens.append_all(quote! {
  120. Some({
  121. #body
  122. })
  123. })
  124. }
  125. }
  126. pub struct TemplateRenderer<'a> {
  127. pub roots: &'a [BodyNode],
  128. pub location: Option<String>,
  129. }
  130. impl<'a> TemplateRenderer<'a> {
  131. #[cfg(feature = "hot_reload")]
  132. fn update_template<Ctx: HotReloadingContext>(
  133. &mut self,
  134. previous_call: Option<CallBody>,
  135. location: &'static str,
  136. ) -> Option<Template> {
  137. let mut mapping = previous_call.map(|call| DynamicMapping::from(call.roots));
  138. let mut context = DynamicContext::default();
  139. let mut roots = Vec::new();
  140. for (idx, root) in self.roots.iter().enumerate() {
  141. context.current_path.push(idx as u8);
  142. roots.push(context.update_node::<Ctx>(root, &mut mapping)?);
  143. context.current_path.pop();
  144. }
  145. Some(Template {
  146. name: location,
  147. roots: intern(roots.as_slice()),
  148. node_paths: intern(
  149. context
  150. .node_paths
  151. .into_iter()
  152. .map(|path| intern(path.as_slice()))
  153. .collect::<Vec<_>>()
  154. .as_slice(),
  155. ),
  156. attr_paths: intern(
  157. context
  158. .attr_paths
  159. .into_iter()
  160. .map(|path| intern(path.as_slice()))
  161. .collect::<Vec<_>>()
  162. .as_slice(),
  163. ),
  164. })
  165. }
  166. }
  167. impl<'a> ToTokens for TemplateRenderer<'a> {
  168. fn to_tokens(&self, out_tokens: &mut TokenStream2) {
  169. let mut context = DynamicContext::default();
  170. let key = match self.roots.first() {
  171. Some(BodyNode::Element(el)) if self.roots.len() == 1 => el.key.clone(),
  172. Some(BodyNode::Component(comp)) if self.roots.len() == 1 => comp.key().cloned(),
  173. _ => None,
  174. };
  175. let key_tokens = match key {
  176. Some(tok) => quote! { Some( #tok.to_string() ) },
  177. None => quote! { None },
  178. };
  179. let root_col = match self.roots.first() {
  180. Some(first_root) => {
  181. let first_root_span = format!("{:?}", first_root.span());
  182. first_root_span
  183. .rsplit_once("..")
  184. .and_then(|(_, after)| after.split_once(')').map(|(before, _)| before))
  185. .unwrap_or_default()
  186. .to_string()
  187. }
  188. _ => "0".to_string(),
  189. };
  190. let root_printer = self.roots.iter().enumerate().map(|(idx, root)| {
  191. context.current_path.push(idx as u8);
  192. let out = context.render_static_node(root);
  193. context.current_path.pop();
  194. out
  195. });
  196. let name = match self.location {
  197. Some(ref loc) => quote! { #loc },
  198. None => quote! {
  199. concat!(
  200. file!(),
  201. ":",
  202. line!(),
  203. ":",
  204. column!(),
  205. ":",
  206. #root_col
  207. )
  208. },
  209. };
  210. // Render and release the mutable borrow on context
  211. let roots = quote! { #( #root_printer ),* };
  212. let root_count = self.roots.len();
  213. let node_printer = &context.dynamic_nodes;
  214. let dyn_attr_printer = &context.dynamic_attributes;
  215. let node_paths = context.node_paths.iter().map(|it| quote!(&[#(#it),*]));
  216. let attr_paths = context.attr_paths.iter().map(|it| quote!(&[#(#it),*]));
  217. out_tokens.append_all(quote! {
  218. static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {
  219. name: #name,
  220. roots: &[ #roots ],
  221. node_paths: &[ #(#node_paths),* ],
  222. attr_paths: &[ #(#attr_paths),* ],
  223. };
  224. ::dioxus::core::VNode::new(
  225. #key_tokens,
  226. TEMPLATE,
  227. Vec::with_capacity(#root_count),
  228. vec![ #( #node_printer ),* ],
  229. vec![ #( #dyn_attr_printer ),* ],
  230. )
  231. });
  232. }
  233. }
  234. #[cfg(feature = "hot_reload")]
  235. #[derive(Default, Debug)]
  236. struct DynamicMapping {
  237. attribute_to_idx: std::collections::HashMap<ElementAttr, Vec<usize>>,
  238. last_attribute_idx: usize,
  239. node_to_idx: std::collections::HashMap<BodyNode, Vec<usize>>,
  240. last_element_idx: usize,
  241. }
  242. #[cfg(feature = "hot_reload")]
  243. impl DynamicMapping {
  244. fn from(nodes: Vec<BodyNode>) -> Self {
  245. let mut new = Self::default();
  246. for node in nodes {
  247. new.add_node(node);
  248. }
  249. new
  250. }
  251. fn get_attribute_idx(&mut self, attr: &ElementAttr) -> Option<usize> {
  252. self.attribute_to_idx
  253. .get_mut(attr)
  254. .and_then(|idxs| idxs.pop())
  255. }
  256. fn get_node_idx(&mut self, node: &BodyNode) -> Option<usize> {
  257. self.node_to_idx.get_mut(node).and_then(|idxs| idxs.pop())
  258. }
  259. fn insert_attribute(&mut self, attr: ElementAttr) -> usize {
  260. let idx = self.last_attribute_idx;
  261. self.last_attribute_idx += 1;
  262. self.attribute_to_idx.entry(attr).or_default().push(idx);
  263. idx
  264. }
  265. fn insert_node(&mut self, node: BodyNode) -> usize {
  266. let idx = self.last_element_idx;
  267. self.last_element_idx += 1;
  268. self.node_to_idx.entry(node).or_default().push(idx);
  269. idx
  270. }
  271. fn add_node(&mut self, node: BodyNode) {
  272. match node {
  273. BodyNode::Element(el) => {
  274. for attr in el.merged_attributes {
  275. match &attr.attr.value {
  276. ElementAttrValue::AttrLiteral(input) if input.is_static() => {}
  277. _ => {
  278. self.insert_attribute(attr.attr);
  279. }
  280. }
  281. }
  282. for child in el.children {
  283. self.add_node(child);
  284. }
  285. }
  286. BodyNode::Text(text) if text.is_static() => {}
  287. BodyNode::RawExpr(_)
  288. | BodyNode::Text(_)
  289. | BodyNode::ForLoop(_)
  290. | BodyNode::IfChain(_)
  291. | BodyNode::Component(_) => {
  292. self.insert_node(node);
  293. }
  294. }
  295. }
  296. }
  297. // As we create the dynamic nodes, we want to keep track of them in a linear fashion
  298. // We'll use the size of the vecs to determine the index of the dynamic node in the final output
  299. #[derive(Default, Debug)]
  300. pub struct DynamicContext<'a> {
  301. dynamic_nodes: Vec<&'a BodyNode>,
  302. dynamic_attributes: Vec<&'a ElementAttrNamed>,
  303. current_path: Vec<u8>,
  304. node_paths: Vec<Vec<u8>>,
  305. attr_paths: Vec<Vec<u8>>,
  306. }
  307. impl<'a> DynamicContext<'a> {
  308. #[cfg(feature = "hot_reload")]
  309. fn update_node<Ctx: HotReloadingContext>(
  310. &mut self,
  311. root: &'a BodyNode,
  312. mapping: &mut Option<DynamicMapping>,
  313. ) -> Option<TemplateNode> {
  314. match root {
  315. BodyNode::Element(el) => {
  316. let element_name_rust = el.name.to_string();
  317. let mut static_attrs = Vec::new();
  318. for attr in &el.merged_attributes {
  319. match &attr.attr.value {
  320. ElementAttrValue::AttrLiteral(value) if value.is_static() => {
  321. let value = value.source.as_ref().unwrap();
  322. let name = &attr.attr.name;
  323. let attribute_name_rust = name.to_string();
  324. let (name, namespace) =
  325. Ctx::map_attribute(&element_name_rust, &attribute_name_rust)
  326. .unwrap_or((intern(attribute_name_rust.as_str()), None));
  327. static_attrs.push(TemplateAttribute::Static {
  328. name,
  329. namespace,
  330. value: intern(value.value().as_str()),
  331. })
  332. }
  333. _ => {
  334. let idx = match mapping {
  335. Some(mapping) => mapping.get_attribute_idx(&attr.attr)?,
  336. None => self.dynamic_attributes.len(),
  337. };
  338. self.dynamic_attributes.push(attr);
  339. if self.attr_paths.len() <= idx {
  340. self.attr_paths.resize_with(idx + 1, Vec::new);
  341. }
  342. self.attr_paths[idx] = self.current_path.clone();
  343. static_attrs.push(TemplateAttribute::Dynamic { id: idx })
  344. }
  345. }
  346. }
  347. let mut children = Vec::new();
  348. for (idx, root) in el.children.iter().enumerate() {
  349. self.current_path.push(idx as u8);
  350. children.push(self.update_node::<Ctx>(root, mapping)?);
  351. self.current_path.pop();
  352. }
  353. let (tag, namespace) = Ctx::map_element(&element_name_rust)
  354. .unwrap_or((intern(element_name_rust.as_str()), None));
  355. Some(TemplateNode::Element {
  356. tag,
  357. namespace,
  358. attrs: intern(static_attrs.into_boxed_slice()),
  359. children: intern(children.as_slice()),
  360. })
  361. }
  362. BodyNode::Text(text) if text.is_static() => {
  363. let text = text.source.as_ref().unwrap();
  364. Some(TemplateNode::Text {
  365. text: intern(text.value().as_str()),
  366. })
  367. }
  368. BodyNode::RawExpr(_)
  369. | BodyNode::Text(_)
  370. | BodyNode::ForLoop(_)
  371. | BodyNode::IfChain(_)
  372. | BodyNode::Component(_) => {
  373. let idx = match mapping {
  374. Some(mapping) => mapping.get_node_idx(root)?,
  375. None => self.dynamic_nodes.len(),
  376. };
  377. self.dynamic_nodes.push(root);
  378. if self.node_paths.len() <= idx {
  379. self.node_paths.resize_with(idx + 1, Vec::new);
  380. }
  381. self.node_paths[idx] = self.current_path.clone();
  382. Some(match root {
  383. BodyNode::Text(_) => TemplateNode::DynamicText { id: idx },
  384. _ => TemplateNode::Dynamic { id: idx },
  385. })
  386. }
  387. }
  388. }
  389. fn render_static_node(&mut self, root: &'a BodyNode) -> TokenStream2 {
  390. match root {
  391. BodyNode::Element(el) => {
  392. let el_name = &el.name;
  393. let ns = |name| match el_name {
  394. ElementName::Ident(i) => quote! { dioxus_elements::#i::#name },
  395. ElementName::Custom(_) => quote! { None },
  396. };
  397. let static_attrs = el
  398. .merged_attributes
  399. .iter()
  400. .map(|attr| match &attr.attr.value {
  401. ElementAttrValue::AttrLiteral(value) if value.is_static() => {
  402. let value = value.to_static().unwrap();
  403. let ns = {
  404. match &attr.attr.name {
  405. ElementAttrName::BuiltIn(name) => ns(quote!(#name.1)),
  406. ElementAttrName::Custom(_) => quote!(None),
  407. }
  408. };
  409. let name = &attr.attr.name;
  410. let name = match (el_name, name) {
  411. (ElementName::Ident(_), ElementAttrName::BuiltIn(_)) => {
  412. quote! { #el_name::#name.0 }
  413. }
  414. _ => {
  415. let as_string = name.to_string();
  416. quote! { #as_string }
  417. }
  418. };
  419. quote! {
  420. ::dioxus::core::TemplateAttribute::Static {
  421. name: #name,
  422. namespace: #ns,
  423. value: #value,
  424. // todo: we don't diff these so we never apply the volatile flag
  425. // volatile: dioxus_elements::#el_name::#name.2,
  426. }
  427. }
  428. }
  429. _ => {
  430. let ct = self.dynamic_attributes.len();
  431. self.dynamic_attributes.push(attr);
  432. self.attr_paths.push(self.current_path.clone());
  433. quote! { ::dioxus::core::TemplateAttribute::Dynamic { id: #ct } }
  434. }
  435. });
  436. let attrs = quote! { #(#static_attrs),*};
  437. let children = el.children.iter().enumerate().map(|(idx, root)| {
  438. self.current_path.push(idx as u8);
  439. let out = self.render_static_node(root);
  440. self.current_path.pop();
  441. out
  442. });
  443. let _opt = el.children.len() == 1;
  444. let children = quote! { #(#children),* };
  445. let ns = ns(quote!(NAME_SPACE));
  446. let el_name = el_name.tag_name();
  447. quote! {
  448. ::dioxus::core::TemplateNode::Element {
  449. tag: #el_name,
  450. namespace: #ns,
  451. attrs: &[ #attrs ],
  452. children: &[ #children ],
  453. }
  454. }
  455. }
  456. BodyNode::Text(text) if text.is_static() => {
  457. let text = text.to_static().unwrap();
  458. quote! { ::dioxus::core::TemplateNode::Text{ text: #text } }
  459. }
  460. BodyNode::RawExpr(_)
  461. | BodyNode::Text(_)
  462. | BodyNode::ForLoop(_)
  463. | BodyNode::IfChain(_)
  464. | BodyNode::Component(_) => {
  465. let ct = self.dynamic_nodes.len();
  466. self.dynamic_nodes.push(root);
  467. self.node_paths.push(self.current_path.clone());
  468. match root {
  469. BodyNode::Text(_) => {
  470. quote! { ::dioxus::core::TemplateNode::DynamicText { id: #ct } }
  471. }
  472. _ => quote! { ::dioxus::core::TemplateNode::Dynamic { id: #ct } },
  473. }
  474. }
  475. }
  476. }
  477. }
  478. #[cfg(feature = "hot_reload")]
  479. #[test]
  480. fn create_template() {
  481. let input = quote! {
  482. svg {
  483. width: 100,
  484. height: "100px",
  485. "width2": 100,
  486. "height2": "100px",
  487. p {
  488. "hello world"
  489. }
  490. (0..10).map(|i| rsx!{"{i}"})
  491. }
  492. };
  493. struct Mock;
  494. impl HotReloadingContext for Mock {
  495. fn map_attribute(
  496. element_name_rust: &str,
  497. attribute_name_rust: &str,
  498. ) -> Option<(&'static str, Option<&'static str>)> {
  499. match element_name_rust {
  500. "svg" => match attribute_name_rust {
  501. "width" => Some(("width", Some("style"))),
  502. "height" => Some(("height", Some("style"))),
  503. _ => None,
  504. },
  505. _ => None,
  506. }
  507. }
  508. fn map_element(element_name_rust: &str) -> Option<(&'static str, Option<&'static str>)> {
  509. match element_name_rust {
  510. "svg" => Some(("svg", Some("svg"))),
  511. _ => None,
  512. }
  513. }
  514. }
  515. let call_body: CallBody = syn::parse2(input).unwrap();
  516. let template = call_body.update_template::<Mock>(None, "testing").unwrap();
  517. dbg!(template);
  518. assert_eq!(
  519. template,
  520. Template {
  521. name: "testing",
  522. roots: &[TemplateNode::Element {
  523. tag: "svg",
  524. namespace: Some("svg"),
  525. attrs: &[
  526. TemplateAttribute::Dynamic { id: 0 },
  527. TemplateAttribute::Static {
  528. name: "height",
  529. namespace: Some("style"),
  530. value: "100px",
  531. },
  532. TemplateAttribute::Dynamic { id: 1 },
  533. TemplateAttribute::Static {
  534. name: "height2",
  535. namespace: None,
  536. value: "100px",
  537. },
  538. ],
  539. children: &[
  540. TemplateNode::Element {
  541. tag: "p",
  542. namespace: None,
  543. attrs: &[],
  544. children: &[TemplateNode::Text {
  545. text: "hello world",
  546. }],
  547. },
  548. TemplateNode::Dynamic { id: 0 }
  549. ],
  550. }],
  551. node_paths: &[&[0, 1,],],
  552. attr_paths: &[&[0,], &[0,],],
  553. },
  554. )
  555. }
  556. #[cfg(feature = "hot_reload")]
  557. #[test]
  558. fn diff_template() {
  559. use dioxus_core::Scope;
  560. #[allow(unused, non_snake_case)]
  561. fn Comp(_: Scope) -> dioxus_core::Element {
  562. None
  563. }
  564. let input = quote! {
  565. svg {
  566. width: 100,
  567. height: "100px",
  568. "width2": 100,
  569. "height2": "100px",
  570. p {
  571. "hello world"
  572. }
  573. (0..10).map(|i| rsx!{"{i}"}),
  574. (0..10).map(|i| rsx!{"{i}"}),
  575. (0..11).map(|i| rsx!{"{i}"}),
  576. Comp{}
  577. }
  578. };
  579. #[derive(Debug)]
  580. struct Mock;
  581. impl HotReloadingContext for Mock {
  582. fn map_attribute(
  583. element_name_rust: &str,
  584. attribute_name_rust: &str,
  585. ) -> Option<(&'static str, Option<&'static str>)> {
  586. match element_name_rust {
  587. "svg" => match attribute_name_rust {
  588. "width" => Some(("width", Some("style"))),
  589. "height" => Some(("height", Some("style"))),
  590. _ => None,
  591. },
  592. _ => None,
  593. }
  594. }
  595. fn map_element(element_name_rust: &str) -> Option<(&'static str, Option<&'static str>)> {
  596. match element_name_rust {
  597. "svg" => Some(("svg", Some("svg"))),
  598. _ => None,
  599. }
  600. }
  601. }
  602. let call_body1: CallBody = syn::parse2(input).unwrap();
  603. let template = call_body1.update_template::<Mock>(None, "testing").unwrap();
  604. dbg!(template);
  605. // scrambling the attributes should not cause a full rebuild
  606. let input = quote! {
  607. div {
  608. "width2": 100,
  609. height: "100px",
  610. "height2": "100px",
  611. width: 100,
  612. Comp{}
  613. (0..11).map(|i| rsx!{"{i}"}),
  614. (0..10).map(|i| rsx!{"{i}"}),
  615. (0..10).map(|i| rsx!{"{i}"}),
  616. p {
  617. "hello world"
  618. }
  619. }
  620. };
  621. let call_body2: CallBody = syn::parse2(input).unwrap();
  622. let template = call_body2
  623. .update_template::<Mock>(Some(call_body1), "testing")
  624. .unwrap();
  625. dbg!(template);
  626. assert_eq!(
  627. template,
  628. Template {
  629. name: "testing",
  630. roots: &[TemplateNode::Element {
  631. tag: "div",
  632. namespace: None,
  633. attrs: &[
  634. TemplateAttribute::Dynamic { id: 1 },
  635. TemplateAttribute::Static {
  636. name: "height",
  637. namespace: None,
  638. value: "100px",
  639. },
  640. TemplateAttribute::Static {
  641. name: "height2",
  642. namespace: None,
  643. value: "100px",
  644. },
  645. TemplateAttribute::Dynamic { id: 0 },
  646. ],
  647. children: &[
  648. TemplateNode::Dynamic { id: 3 },
  649. TemplateNode::Dynamic { id: 2 },
  650. TemplateNode::Dynamic { id: 1 },
  651. TemplateNode::Dynamic { id: 0 },
  652. TemplateNode::Element {
  653. tag: "p",
  654. namespace: None,
  655. attrs: &[],
  656. children: &[TemplateNode::Text {
  657. text: "hello world",
  658. }],
  659. },
  660. ],
  661. }],
  662. node_paths: &[&[0, 3], &[0, 2], &[0, 1], &[0, 0]],
  663. attr_paths: &[&[0], &[0]]
  664. },
  665. )
  666. }