lib.rs 25 KB

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