lib.rs 26 KB

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