lib.rs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773
  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. // Empty templates just are placeholders for "none"
  78. if self.roots.is_empty() {
  79. return quote! { None };
  80. }
  81. quote! {
  82. Some({ #body })
  83. }
  84. }
  85. }
  86. impl Parse for CallBody {
  87. fn parse(input: ParseStream) -> Result<Self> {
  88. let mut roots = Vec::new();
  89. while !input.is_empty() {
  90. let node = input.parse::<BodyNode>()?;
  91. if input.peek(Token![,]) {
  92. let _ = input.parse::<Token![,]>();
  93. }
  94. roots.push(node);
  95. }
  96. Ok(Self { roots })
  97. }
  98. }
  99. impl ToTokens for CallBody {
  100. fn to_tokens(&self, out_tokens: &mut TokenStream2) {
  101. let body: TemplateRenderer = TemplateRenderer {
  102. roots: &self.roots,
  103. location: None,
  104. };
  105. // Empty templates just are placeholders for "none"
  106. if self.roots.is_empty() {
  107. return out_tokens.append_all(quote! { None });
  108. }
  109. out_tokens.append_all(quote! {
  110. Some({ #body })
  111. })
  112. }
  113. }
  114. pub struct TemplateRenderer<'a> {
  115. pub roots: &'a [BodyNode],
  116. pub location: Option<String>,
  117. }
  118. impl<'a> TemplateRenderer<'a> {
  119. #[cfg(feature = "hot_reload")]
  120. fn update_template<Ctx: HotReloadingContext>(
  121. &mut self,
  122. previous_call: Option<CallBody>,
  123. location: &'static str,
  124. ) -> Option<Template> {
  125. let mut mapping = previous_call.map(|call| DynamicMapping::from(call.roots));
  126. let mut context = DynamicContext::default();
  127. let mut roots = Vec::new();
  128. for (idx, root) in self.roots.iter().enumerate() {
  129. context.current_path.push(idx as u8);
  130. roots.push(context.update_node::<Ctx>(root, &mut mapping)?);
  131. context.current_path.pop();
  132. }
  133. Some(Template {
  134. name: location,
  135. roots: intern(roots.as_slice()),
  136. node_paths: intern(
  137. context
  138. .node_paths
  139. .into_iter()
  140. .map(|path| intern(path.as_slice()))
  141. .collect::<Vec<_>>()
  142. .as_slice(),
  143. ),
  144. attr_paths: intern(
  145. context
  146. .attr_paths
  147. .into_iter()
  148. .map(|path| intern(path.as_slice()))
  149. .collect::<Vec<_>>()
  150. .as_slice(),
  151. ),
  152. })
  153. }
  154. }
  155. impl<'a> ToTokens for TemplateRenderer<'a> {
  156. fn to_tokens(&self, out_tokens: &mut TokenStream2) {
  157. let mut context = DynamicContext::default();
  158. let key = match self.roots.first() {
  159. Some(BodyNode::Element(el)) if self.roots.len() == 1 => el.key.clone(),
  160. Some(BodyNode::Component(comp)) if self.roots.len() == 1 => comp.key().cloned(),
  161. _ => None,
  162. };
  163. let key_tokens = match key {
  164. Some(tok) => quote! { Some( #tok.to_string() ) },
  165. None => quote! { None },
  166. };
  167. let root_col = match self.roots.first() {
  168. Some(first_root) => {
  169. let first_root_span = format!("{:?}", first_root.span());
  170. first_root_span
  171. .rsplit_once("..")
  172. .and_then(|(_, after)| after.split_once(')').map(|(before, _)| before))
  173. .unwrap_or_default()
  174. .to_string()
  175. }
  176. _ => "0".to_string(),
  177. };
  178. let root_printer = self.roots.iter().enumerate().map(|(idx, root)| {
  179. context.current_path.push(idx as u8);
  180. let out = context.render_static_node(root);
  181. context.current_path.pop();
  182. out
  183. });
  184. let name = match self.location {
  185. Some(ref loc) => quote! { #loc },
  186. None => quote! {
  187. concat!(
  188. file!(),
  189. ":",
  190. line!(),
  191. ":",
  192. column!(),
  193. ":",
  194. #root_col
  195. )
  196. },
  197. };
  198. // Render and release the mutable borrow on context
  199. let roots = quote! { #( #root_printer ),* };
  200. let node_printer = &context.dynamic_nodes;
  201. let dyn_attr_printer = context
  202. .dynamic_attributes
  203. .iter()
  204. .map(|attrs| AttributeType::merge_quote(attrs));
  205. let node_paths = context.node_paths.iter().map(|it| quote!(&[#(#it),*]));
  206. let attr_paths = context.attr_paths.iter().map(|it| quote!(&[#(#it),*]));
  207. out_tokens.append_all(quote! {
  208. static TEMPLATE: dioxus_core::Template = dioxus_core::Template {
  209. name: #name,
  210. roots: &[ #roots ],
  211. node_paths: &[ #(#node_paths),* ],
  212. attr_paths: &[ #(#attr_paths),* ],
  213. };
  214. {
  215. // NOTE: Allocating a temporary is important to make reads within rsx drop before the value is returned
  216. let __vnodes = dioxus_core::VNode::new(
  217. #key_tokens,
  218. TEMPLATE,
  219. Box::new([ #( #node_printer),* ]),
  220. Box::new([ #(#dyn_attr_printer),* ]),
  221. );
  222. __vnodes
  223. }
  224. });
  225. }
  226. }
  227. #[cfg(feature = "hot_reload")]
  228. #[derive(Default, Debug)]
  229. struct DynamicMapping {
  230. attribute_to_idx: std::collections::HashMap<AttributeType, Vec<usize>>,
  231. last_attribute_idx: usize,
  232. node_to_idx: std::collections::HashMap<BodyNode, Vec<usize>>,
  233. last_element_idx: usize,
  234. }
  235. #[cfg(feature = "hot_reload")]
  236. impl DynamicMapping {
  237. fn from(nodes: Vec<BodyNode>) -> Self {
  238. let mut new = Self::default();
  239. for node in nodes {
  240. new.add_node(node);
  241. }
  242. new
  243. }
  244. fn get_attribute_idx(&mut self, attr: &AttributeType) -> Option<usize> {
  245. self.attribute_to_idx
  246. .get_mut(attr)
  247. .and_then(|idxs| idxs.pop())
  248. }
  249. fn get_node_idx(&mut self, node: &BodyNode) -> Option<usize> {
  250. self.node_to_idx.get_mut(node).and_then(|idxs| idxs.pop())
  251. }
  252. fn insert_attribute(&mut self, attr: AttributeType) -> usize {
  253. let idx = self.last_attribute_idx;
  254. self.last_attribute_idx += 1;
  255. self.attribute_to_idx.entry(attr).or_default().push(idx);
  256. idx
  257. }
  258. fn insert_node(&mut self, node: BodyNode) -> usize {
  259. let idx = self.last_element_idx;
  260. self.last_element_idx += 1;
  261. self.node_to_idx.entry(node).or_default().push(idx);
  262. idx
  263. }
  264. fn add_node(&mut self, node: BodyNode) {
  265. match node {
  266. BodyNode::Element(el) => {
  267. for attr in el.merged_attributes {
  268. match &attr {
  269. AttributeType::Named(ElementAttrNamed {
  270. attr:
  271. ElementAttr {
  272. value: ElementAttrValue::AttrLiteral(input),
  273. ..
  274. },
  275. ..
  276. }) if input.is_static() => {}
  277. _ => {
  278. self.insert_attribute(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<Vec<&'a AttributeType>>,
  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 {
  320. AttributeType::Named(ElementAttrNamed {
  321. attr:
  322. ElementAttr {
  323. value: ElementAttrValue::AttrLiteral(value),
  324. name,
  325. },
  326. ..
  327. }) if value.is_static() => {
  328. let value = value.source.as_ref().unwrap();
  329. let attribute_name_rust = name.to_string();
  330. let (name, namespace) =
  331. Ctx::map_attribute(&element_name_rust, &attribute_name_rust)
  332. .unwrap_or((intern(attribute_name_rust.as_str()), None));
  333. static_attrs.push(TemplateAttribute::Static {
  334. name,
  335. namespace,
  336. value: intern(value.value().as_str()),
  337. })
  338. }
  339. _ => {
  340. let idx = match mapping {
  341. Some(mapping) => mapping.get_attribute_idx(attr)?,
  342. None => self.dynamic_attributes.len(),
  343. };
  344. self.dynamic_attributes.push(vec![attr]);
  345. if self.attr_paths.len() <= idx {
  346. self.attr_paths.resize_with(idx + 1, Vec::new);
  347. }
  348. self.attr_paths[idx] = self.current_path.clone();
  349. static_attrs.push(TemplateAttribute::Dynamic { id: idx })
  350. }
  351. }
  352. }
  353. let mut children = Vec::new();
  354. for (idx, root) in el.children.iter().enumerate() {
  355. self.current_path.push(idx as u8);
  356. children.push(self.update_node::<Ctx>(root, mapping)?);
  357. self.current_path.pop();
  358. }
  359. let (tag, namespace) = Ctx::map_element(&element_name_rust)
  360. .unwrap_or((intern(element_name_rust.as_str()), None));
  361. Some(TemplateNode::Element {
  362. tag,
  363. namespace,
  364. attrs: intern(static_attrs.into_boxed_slice()),
  365. children: intern(children.as_slice()),
  366. })
  367. }
  368. BodyNode::Text(text) if text.is_static() => {
  369. let text = text.source.as_ref().unwrap();
  370. Some(TemplateNode::Text {
  371. text: intern(text.value().as_str()),
  372. })
  373. }
  374. BodyNode::RawExpr(_)
  375. | BodyNode::Text(_)
  376. | BodyNode::ForLoop(_)
  377. | BodyNode::IfChain(_)
  378. | BodyNode::Component(_) => {
  379. let idx = match mapping {
  380. Some(mapping) => mapping.get_node_idx(root)?,
  381. None => self.dynamic_nodes.len(),
  382. };
  383. self.dynamic_nodes.push(root);
  384. if self.node_paths.len() <= idx {
  385. self.node_paths.resize_with(idx + 1, Vec::new);
  386. }
  387. self.node_paths[idx] = self.current_path.clone();
  388. Some(match root {
  389. BodyNode::Text(_) => TemplateNode::DynamicText { id: idx },
  390. _ => TemplateNode::Dynamic { id: idx },
  391. })
  392. }
  393. }
  394. }
  395. fn render_static_node(&mut self, root: &'a BodyNode) -> TokenStream2 {
  396. match root {
  397. BodyNode::Element(el) => {
  398. let el_name = &el.name;
  399. let ns = |name| match el_name {
  400. ElementName::Ident(i) => quote! { dioxus_elements::#i::#name },
  401. ElementName::Custom(_) => quote! { None },
  402. };
  403. let static_attrs = el.merged_attributes.iter().map(|attr| match attr {
  404. AttributeType::Named(ElementAttrNamed {
  405. attr:
  406. ElementAttr {
  407. value: ElementAttrValue::AttrLiteral(value),
  408. name,
  409. },
  410. ..
  411. }) if value.is_static() => {
  412. let value = value.to_static().unwrap();
  413. let ns = {
  414. match name {
  415. ElementAttrName::BuiltIn(name) => ns(quote!(#name.1)),
  416. ElementAttrName::Custom(_) => quote!(None),
  417. }
  418. };
  419. let name = match (el_name, name) {
  420. (ElementName::Ident(_), ElementAttrName::BuiltIn(_)) => {
  421. quote! { #el_name::#name.0 }
  422. }
  423. _ => {
  424. let as_string = name.to_string();
  425. quote! { #as_string }
  426. }
  427. };
  428. quote! {
  429. dioxus_core::TemplateAttribute::Static {
  430. name: #name,
  431. namespace: #ns,
  432. value: #value,
  433. // todo: we don't diff these so we never apply the volatile flag
  434. // volatile: dioxus_elements::#el_name::#name.2,
  435. },
  436. }
  437. }
  438. _ => {
  439. // If this attribute is dynamic, but it already exists in the template, we can reuse the index
  440. if let Some(attribute_index) = self
  441. .attr_paths
  442. .iter()
  443. .position(|path| path == &self.current_path)
  444. {
  445. self.dynamic_attributes[attribute_index].push(attr);
  446. quote! {}
  447. } else {
  448. let ct = self.dynamic_attributes.len();
  449. self.dynamic_attributes.push(vec![attr]);
  450. self.attr_paths.push(self.current_path.clone());
  451. quote! { dioxus_core::TemplateAttribute::Dynamic { id: #ct }, }
  452. }
  453. }
  454. });
  455. let attrs = quote! { #(#static_attrs)* };
  456. let children = el.children.iter().enumerate().map(|(idx, root)| {
  457. self.current_path.push(idx as u8);
  458. let out = self.render_static_node(root);
  459. self.current_path.pop();
  460. out
  461. });
  462. let _opt = el.children.len() == 1;
  463. let children = quote! { #(#children),* };
  464. let ns = ns(quote!(NAME_SPACE));
  465. let el_name = el_name.tag_name();
  466. quote! {
  467. dioxus_core::TemplateNode::Element {
  468. tag: #el_name,
  469. namespace: #ns,
  470. attrs: &[ #attrs ],
  471. children: &[ #children ],
  472. }
  473. }
  474. }
  475. BodyNode::Text(text) if text.is_static() => {
  476. let text = text.to_static().unwrap();
  477. quote! { dioxus_core::TemplateNode::Text{ text: #text } }
  478. }
  479. BodyNode::RawExpr(_)
  480. | BodyNode::Text(_)
  481. | BodyNode::ForLoop(_)
  482. | BodyNode::IfChain(_)
  483. | BodyNode::Component(_) => {
  484. let ct = self.dynamic_nodes.len();
  485. self.dynamic_nodes.push(root);
  486. self.node_paths.push(self.current_path.clone());
  487. match root {
  488. BodyNode::Text(_) => {
  489. quote! { dioxus_core::TemplateNode::DynamicText { id: #ct } }
  490. }
  491. _ => quote! { dioxus_core::TemplateNode::Dynamic { id: #ct } },
  492. }
  493. }
  494. }
  495. }
  496. }
  497. #[cfg(feature = "hot_reload")]
  498. #[test]
  499. fn create_template() {
  500. let input = quote! {
  501. svg {
  502. width: 100,
  503. height: "100px",
  504. "width2": 100,
  505. "height2": "100px",
  506. p {
  507. "hello world"
  508. }
  509. {(0..10).map(|i| rsx!{"{i}"})}
  510. }
  511. };
  512. struct Mock;
  513. impl HotReloadingContext for Mock {
  514. fn map_attribute(
  515. element_name_rust: &str,
  516. attribute_name_rust: &str,
  517. ) -> Option<(&'static str, Option<&'static str>)> {
  518. match element_name_rust {
  519. "svg" => match attribute_name_rust {
  520. "width" => Some(("width", Some("style"))),
  521. "height" => Some(("height", Some("style"))),
  522. _ => None,
  523. },
  524. _ => None,
  525. }
  526. }
  527. fn map_element(element_name_rust: &str) -> Option<(&'static str, Option<&'static str>)> {
  528. match element_name_rust {
  529. "svg" => Some(("svg", Some("svg"))),
  530. _ => None,
  531. }
  532. }
  533. }
  534. let call_body: CallBody = syn::parse2(input).unwrap();
  535. let template = call_body.update_template::<Mock>(None, "testing").unwrap();
  536. dbg!(template);
  537. assert_eq!(
  538. template,
  539. Template {
  540. name: "testing",
  541. roots: &[TemplateNode::Element {
  542. tag: "svg",
  543. namespace: Some("svg"),
  544. attrs: &[
  545. TemplateAttribute::Dynamic { id: 0 },
  546. TemplateAttribute::Static {
  547. name: "height",
  548. namespace: Some("style"),
  549. value: "100px",
  550. },
  551. TemplateAttribute::Dynamic { id: 1 },
  552. TemplateAttribute::Static {
  553. name: "height2",
  554. namespace: None,
  555. value: "100px",
  556. },
  557. ],
  558. children: &[
  559. TemplateNode::Element {
  560. tag: "p",
  561. namespace: None,
  562. attrs: &[],
  563. children: &[TemplateNode::Text {
  564. text: "hello world",
  565. }],
  566. },
  567. TemplateNode::Dynamic { id: 0 }
  568. ],
  569. }],
  570. node_paths: &[&[0, 1,],],
  571. attr_paths: &[&[0,], &[0,],],
  572. },
  573. )
  574. }
  575. #[cfg(feature = "hot_reload")]
  576. #[test]
  577. fn diff_template() {
  578. #[allow(unused, non_snake_case)]
  579. fn Comp() -> dioxus_core::Element {
  580. None
  581. }
  582. let input = quote! {
  583. svg {
  584. width: 100,
  585. height: "100px",
  586. "width2": 100,
  587. "height2": "100px",
  588. p {
  589. "hello world"
  590. }
  591. {(0..10).map(|i| rsx!{"{i}"})},
  592. {(0..10).map(|i| rsx!{"{i}"})},
  593. {(0..11).map(|i| rsx!{"{i}"})},
  594. Comp{}
  595. }
  596. };
  597. #[derive(Debug)]
  598. struct Mock;
  599. impl HotReloadingContext for Mock {
  600. fn map_attribute(
  601. element_name_rust: &str,
  602. attribute_name_rust: &str,
  603. ) -> Option<(&'static str, Option<&'static str>)> {
  604. match element_name_rust {
  605. "svg" => match attribute_name_rust {
  606. "width" => Some(("width", Some("style"))),
  607. "height" => Some(("height", Some("style"))),
  608. _ => None,
  609. },
  610. _ => None,
  611. }
  612. }
  613. fn map_element(element_name_rust: &str) -> Option<(&'static str, Option<&'static str>)> {
  614. match element_name_rust {
  615. "svg" => Some(("svg", Some("svg"))),
  616. _ => None,
  617. }
  618. }
  619. }
  620. let call_body1: CallBody = syn::parse2(input).unwrap();
  621. let template = call_body1.update_template::<Mock>(None, "testing").unwrap();
  622. dbg!(template);
  623. // scrambling the attributes should not cause a full rebuild
  624. let input = quote! {
  625. div {
  626. "width2": 100,
  627. height: "100px",
  628. "height2": "100px",
  629. width: 100,
  630. Comp{}
  631. {(0..11).map(|i| rsx!{"{i}"})},
  632. {(0..10).map(|i| rsx!{"{i}"})},
  633. {(0..10).map(|i| rsx!{"{i}"})},
  634. p {
  635. "hello world"
  636. }
  637. }
  638. };
  639. let call_body2: CallBody = syn::parse2(input).unwrap();
  640. let template = call_body2
  641. .update_template::<Mock>(Some(call_body1), "testing")
  642. .unwrap();
  643. dbg!(template);
  644. assert_eq!(
  645. template,
  646. Template {
  647. name: "testing",
  648. roots: &[TemplateNode::Element {
  649. tag: "div",
  650. namespace: None,
  651. attrs: &[
  652. TemplateAttribute::Dynamic { id: 1 },
  653. TemplateAttribute::Static {
  654. name: "height",
  655. namespace: None,
  656. value: "100px",
  657. },
  658. TemplateAttribute::Static {
  659. name: "height2",
  660. namespace: None,
  661. value: "100px",
  662. },
  663. TemplateAttribute::Dynamic { id: 0 },
  664. ],
  665. children: &[
  666. TemplateNode::Dynamic { id: 3 },
  667. TemplateNode::Dynamic { id: 2 },
  668. TemplateNode::Dynamic { id: 1 },
  669. TemplateNode::Dynamic { id: 0 },
  670. TemplateNode::Element {
  671. tag: "p",
  672. namespace: None,
  673. attrs: &[],
  674. children: &[TemplateNode::Text {
  675. text: "hello world",
  676. }],
  677. },
  678. ],
  679. }],
  680. node_paths: &[&[0, 3], &[0, 2], &[0, 1], &[0, 0]],
  681. attr_paths: &[&[0], &[0]]
  682. },
  683. )
  684. }