lib.rs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597
  1. #![doc = include_str!("../README.md")]
  2. use std::fmt::{Display, Formatter, Write};
  3. use dioxus_core::exports::bumpalo;
  4. use dioxus_core::IntoVNode;
  5. use dioxus_core::*;
  6. fn app(_cx: Scope) -> Element {
  7. None
  8. }
  9. pub struct SsrRenderer {
  10. vdom: VirtualDom,
  11. cfg: SsrConfig,
  12. }
  13. impl SsrRenderer {
  14. pub fn new(cfg: impl FnOnce(SsrConfig) -> SsrConfig) -> Self {
  15. Self {
  16. cfg: cfg(SsrConfig::default()),
  17. vdom: VirtualDom::new(app),
  18. }
  19. }
  20. pub fn render_lazy<'a>(&'a mut self, f: LazyNodes<'a, '_>) -> String {
  21. let scope = self.vdom.base_scope();
  22. let factory = NodeFactory::new(scope);
  23. let root = f.into_vnode(factory);
  24. format!(
  25. "{:}",
  26. TextRenderer {
  27. cfg: self.cfg.clone(),
  28. root: &root,
  29. vdom: Some(&self.vdom),
  30. bump: bumpalo::Bump::new(),
  31. }
  32. )
  33. }
  34. }
  35. #[allow(clippy::needless_lifetimes)]
  36. pub fn render_lazy<'a>(f: LazyNodes<'a, '_>) -> String {
  37. let vdom = VirtualDom::new(app);
  38. let scope: *const ScopeState = vdom.base_scope();
  39. // Safety
  40. //
  41. // The lifetimes bounds on LazyNodes are really complicated - they need to support the nesting restrictions in
  42. // regular component usage. The <'a> lifetime is used to enforce that all calls of IntoVnode use the same allocator.
  43. //
  44. // When LazyNodes are provided, they are FnOnce, but do not come with a allocator selected to borrow from. The <'a>
  45. // lifetime is therefore longer than the lifetime of the allocator which doesn't exist... yet.
  46. //
  47. // Therefore, we cast our local bump allocator to the right lifetime. This is okay because our usage of the bump
  48. // arena is *definitely* shorter than the <'a> lifetime, and we return *owned* data - not borrowed data.
  49. let scope = unsafe { &*scope };
  50. let root = f.into_vnode(NodeFactory::new(scope));
  51. let vdom = Some(&vdom);
  52. let ssr_renderer = TextRenderer {
  53. cfg: SsrConfig::default(),
  54. root: &root,
  55. vdom,
  56. bump: bumpalo::Bump::new(),
  57. };
  58. let r = ssr_renderer.to_string();
  59. drop(ssr_renderer);
  60. drop(vdom);
  61. r
  62. }
  63. pub fn render_vdom(dom: &VirtualDom) -> String {
  64. format!("{:}", TextRenderer::from_vdom(dom, SsrConfig::default()))
  65. }
  66. pub fn pre_render_vdom(dom: &VirtualDom) -> String {
  67. format!(
  68. "{:}",
  69. TextRenderer::from_vdom(dom, SsrConfig::default().pre_render(true))
  70. )
  71. }
  72. pub fn render_vdom_cfg(dom: &VirtualDom, cfg: impl FnOnce(SsrConfig) -> SsrConfig) -> String {
  73. format!(
  74. "{:}",
  75. TextRenderer::from_vdom(dom, cfg(SsrConfig::default()))
  76. )
  77. }
  78. pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
  79. Some(format!(
  80. "{:}",
  81. TextRenderer {
  82. cfg: SsrConfig::default(),
  83. root: vdom.get_scope(scope).unwrap().root_node(),
  84. vdom: Some(vdom),
  85. bump: bumpalo::Bump::new()
  86. }
  87. ))
  88. }
  89. /// A configurable text renderer for the Dioxus VirtualDOM.
  90. ///
  91. ///
  92. /// ## Details
  93. ///
  94. /// This uses the `Formatter` infrastructure so you can write into anything that supports `write_fmt`. We can't accept
  95. /// any generic writer, so you need to "Display" the text renderer. This is done through `format!` or `format_args!`
  96. ///
  97. /// ## Example
  98. /// ```ignore
  99. /// static App: Component = |cx| cx.render(rsx!(div { "hello world" }));
  100. /// let mut vdom = VirtualDom::new(App);
  101. /// vdom.rebuild();
  102. ///
  103. /// let renderer = TextRenderer::new(&vdom);
  104. /// let output = format!("{}", renderer);
  105. /// assert_eq!(output, "<div>hello world</div>");
  106. /// ```
  107. pub struct TextRenderer<'a, 'b, 'c> {
  108. vdom: Option<&'c VirtualDom>,
  109. root: &'b VNode<'a>,
  110. cfg: SsrConfig,
  111. bump: bumpalo::Bump,
  112. }
  113. impl<'a: 'c, 'c> Display for TextRenderer<'a, '_, 'c> {
  114. fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
  115. let mut last_node_was_text = false;
  116. self.html_render(self.root, f, 0, &mut last_node_was_text)
  117. }
  118. }
  119. impl<'a> TextRenderer<'a, '_, 'a> {
  120. pub fn from_vdom(vdom: &'a VirtualDom, cfg: SsrConfig) -> Self {
  121. Self {
  122. cfg,
  123. root: vdom.base_scope().root_node(),
  124. vdom: Some(vdom),
  125. bump: bumpalo::Bump::new(),
  126. }
  127. }
  128. }
  129. impl<'a: 'c, 'c> TextRenderer<'a, '_, 'c> {
  130. fn html_render(
  131. &self,
  132. node: &VNode,
  133. f: &mut impl Write,
  134. il: u16,
  135. last_node_was_text: &mut bool,
  136. ) -> std::fmt::Result {
  137. match &node {
  138. VNode::Text(text) => {
  139. if *last_node_was_text {
  140. write!(f, "<!--spacer-->")?;
  141. }
  142. if self.cfg.indent {
  143. for _ in 0..il {
  144. write!(f, " ")?;
  145. }
  146. }
  147. *last_node_was_text = true;
  148. write!(f, "{}", text.text)?
  149. }
  150. VNode::Placeholder(_anchor) => {
  151. *last_node_was_text = false;
  152. if self.cfg.indent {
  153. for _ in 0..il {
  154. write!(f, " ")?;
  155. }
  156. }
  157. write!(f, "<!--placeholder-->")?;
  158. }
  159. VNode::Element(el) => {
  160. *last_node_was_text = false;
  161. if self.cfg.indent {
  162. for _ in 0..il {
  163. write!(f, " ")?;
  164. }
  165. }
  166. write!(f, "<{}", el.tag)?;
  167. let inner_html = render_attributes(el.attributes.iter(), f)?;
  168. match self.cfg.newline {
  169. true => writeln!(f, ">")?,
  170. false => write!(f, ">")?,
  171. }
  172. if let Some(inner_html) = inner_html {
  173. write!(f, "{}", inner_html)?;
  174. } else {
  175. let mut last_node_was_text = false;
  176. for child in el.children {
  177. self.html_render(child, f, il + 1, &mut last_node_was_text)?;
  178. }
  179. }
  180. if self.cfg.newline {
  181. writeln!(f)?;
  182. }
  183. if self.cfg.indent {
  184. for _ in 0..il {
  185. write!(f, " ")?;
  186. }
  187. }
  188. write!(f, "</{}>", el.tag)?;
  189. if self.cfg.newline {
  190. writeln!(f)?;
  191. }
  192. }
  193. VNode::Fragment(frag) => {
  194. for child in frag.children {
  195. self.html_render(child, f, il + 1, last_node_was_text)?;
  196. }
  197. }
  198. VNode::Component(vcomp) => {
  199. let idx = vcomp.scope.get().unwrap();
  200. if let (Some(vdom), false) = (self.vdom, self.cfg.skip_components) {
  201. let new_node = vdom.get_scope(idx).unwrap().root_node();
  202. self.html_render(new_node, f, il + 1, last_node_was_text)?;
  203. } else {
  204. }
  205. }
  206. VNode::TemplateRef(tmpl) => {
  207. if let Some(vdom) = self.vdom {
  208. let template_id = &tmpl.template_id;
  209. let dynamic_context = &tmpl.dynamic_context;
  210. vdom.with_template(template_id, move |tmpl| {
  211. match tmpl {
  212. Template::Static(s) => {
  213. for r in s.root_nodes {
  214. self.render_template_node(
  215. &s.nodes,
  216. &s.nodes[r.0],
  217. dynamic_context,
  218. &s.dynamic_mapping,
  219. f,
  220. last_node_was_text,
  221. il,
  222. )?;
  223. }
  224. }
  225. Template::Owned(o) => {
  226. for r in &o.root_nodes {
  227. self.render_template_node(
  228. &o.nodes,
  229. &o.nodes[r.0],
  230. dynamic_context,
  231. &o.dynamic_mapping,
  232. f,
  233. last_node_was_text,
  234. il,
  235. )?;
  236. }
  237. }
  238. };
  239. Ok(())
  240. })?
  241. } else {
  242. panic!("Cannot render template without vdom");
  243. }
  244. }
  245. }
  246. Ok(())
  247. }
  248. fn render_template_node<
  249. TemplateNodes,
  250. Attributes,
  251. V,
  252. Children,
  253. Listeners,
  254. TextSegments,
  255. Text,
  256. Nodes,
  257. TextOuter,
  258. TextInner,
  259. AttributesOuter,
  260. AttributesInner,
  261. Volatile,
  262. Listeners2,
  263. >(
  264. &self,
  265. template_nodes: &TemplateNodes,
  266. node: &TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>,
  267. dynamic_context: &TemplateContext,
  268. dynamic_node_mapping: &DynamicNodeMapping<
  269. Nodes,
  270. TextOuter,
  271. TextInner,
  272. AttributesOuter,
  273. AttributesInner,
  274. Volatile,
  275. Listeners2,
  276. >,
  277. f: &mut impl Write,
  278. last_node_was_text: &mut bool,
  279. il: u16,
  280. ) -> std::fmt::Result
  281. where
  282. TemplateNodes:
  283. AsRef<[TemplateNode<Attributes, V, Children, Listeners, TextSegments, Text>]>,
  284. Attributes: AsRef<[TemplateAttribute<V>]>,
  285. AttributesInner: AsRef<[(TemplateNodeId, usize)]>,
  286. AttributesOuter: AsRef<[AttributesInner]>,
  287. Children: AsRef<[TemplateNodeId]>,
  288. Listeners: AsRef<[usize]>,
  289. Listeners2: AsRef<[TemplateNodeId]>,
  290. Nodes: AsRef<[Option<TemplateNodeId>]>,
  291. Text: AsRef<str>,
  292. TextInner: AsRef<[TemplateNodeId]>,
  293. TextOuter: AsRef<[TextInner]>,
  294. TextSegments: AsRef<[TextTemplateSegment<Text>]>,
  295. V: TemplateValue,
  296. Volatile: AsRef<[(TemplateNodeId, usize)]>,
  297. {
  298. match &node.node_type {
  299. TemplateNodeType::Element(el) => {
  300. *last_node_was_text = false;
  301. if self.cfg.indent {
  302. for _ in 0..il {
  303. write!(f, " ")?;
  304. }
  305. }
  306. write!(f, "<{}", el.tag)?;
  307. let mut inner_html = None;
  308. let mut attr_iter = el.attributes.as_ref().into_iter().peekable();
  309. while let Some(attr) = attr_iter.next() {
  310. match attr.attribute.namespace {
  311. None => {
  312. if attr.attribute.name == "dangerous_inner_html" {
  313. inner_html = {
  314. let text = match &attr.value {
  315. TemplateAttributeValue::Static(val) => {
  316. val.allocate(&self.bump).as_text().unwrap()
  317. }
  318. TemplateAttributeValue::Dynamic(idx) => dynamic_context
  319. .resolve_attribute(*idx)
  320. .as_text()
  321. .unwrap(),
  322. };
  323. Some(text)
  324. }
  325. } else if is_boolean_attribute(attr.attribute.name) {
  326. match &attr.value {
  327. TemplateAttributeValue::Static(val) => {
  328. let val = val.allocate(&self.bump);
  329. if val.is_truthy() {
  330. write!(f, " {}=\"{}\"", attr.attribute.name, val)?
  331. }
  332. }
  333. TemplateAttributeValue::Dynamic(idx) => {
  334. let val = dynamic_context.resolve_attribute(*idx);
  335. if val.is_truthy() {
  336. write!(f, " {}=\"{}\"", attr.attribute.name, val)?
  337. }
  338. }
  339. }
  340. } else {
  341. match &attr.value {
  342. TemplateAttributeValue::Static(val) => {
  343. let val = val.allocate(&self.bump);
  344. write!(f, " {}=\"{}\"", attr.attribute.name, val)?
  345. }
  346. TemplateAttributeValue::Dynamic(idx) => {
  347. let val = dynamic_context.resolve_attribute(*idx);
  348. write!(f, " {}=\"{}\"", attr.attribute.name, val)?
  349. }
  350. }
  351. }
  352. }
  353. Some(ns) => {
  354. // write the opening tag
  355. write!(f, " {}=\"", ns)?;
  356. let mut cur_ns_el = attr;
  357. loop {
  358. match &attr.value {
  359. TemplateAttributeValue::Static(val) => {
  360. let val = val.allocate(&self.bump);
  361. write!(f, "{}:{};", cur_ns_el.attribute.name, val)?;
  362. }
  363. TemplateAttributeValue::Dynamic(idx) => {
  364. let val = dynamic_context.resolve_attribute(*idx);
  365. write!(f, "{}:{};", cur_ns_el.attribute.name, val)?;
  366. }
  367. }
  368. match attr_iter.peek() {
  369. Some(next_attr)
  370. if next_attr.attribute.namespace == Some(ns) =>
  371. {
  372. cur_ns_el = attr_iter.next().unwrap();
  373. }
  374. _ => break,
  375. }
  376. }
  377. // write the closing tag
  378. write!(f, "\"")?;
  379. }
  380. }
  381. }
  382. match self.cfg.newline {
  383. true => writeln!(f, ">")?,
  384. false => write!(f, ">")?,
  385. }
  386. if let Some(inner_html) = inner_html {
  387. write!(f, "{}", inner_html)?;
  388. } else {
  389. let mut last_node_was_text = false;
  390. for child in el.children.as_ref() {
  391. self.render_template_node(
  392. template_nodes,
  393. &template_nodes.as_ref()[child.0],
  394. dynamic_context,
  395. dynamic_node_mapping,
  396. f,
  397. &mut last_node_was_text,
  398. il + 1,
  399. )?;
  400. }
  401. }
  402. if self.cfg.newline {
  403. writeln!(f)?;
  404. }
  405. if self.cfg.indent {
  406. for _ in 0..il {
  407. write!(f, " ")?;
  408. }
  409. }
  410. write!(f, "</{}>", el.tag)?;
  411. if self.cfg.newline {
  412. writeln!(f)?;
  413. }
  414. }
  415. TemplateNodeType::Text(txt) => {
  416. if *last_node_was_text {
  417. write!(f, "<!--spacer-->")?;
  418. }
  419. if self.cfg.indent {
  420. for _ in 0..il {
  421. write!(f, " ")?;
  422. }
  423. }
  424. *last_node_was_text = true;
  425. let text = dynamic_context.resolve_text(&txt.segments);
  426. write!(f, "{}", text)?
  427. }
  428. TemplateNodeType::DynamicNode(idx) => {
  429. let node = dynamic_context.resolve_node(*idx);
  430. self.html_render(node, f, il, last_node_was_text)?;
  431. }
  432. }
  433. Ok(())
  434. }
  435. }
  436. fn render_attributes<'a, 'b: 'a, I>(
  437. attrs: I,
  438. f: &mut impl Write,
  439. ) -> Result<Option<&'b str>, std::fmt::Error>
  440. where
  441. I: Iterator<Item = &'a Attribute<'b>>,
  442. {
  443. let mut inner_html = None;
  444. let mut attr_iter = attrs.peekable();
  445. while let Some(attr) = attr_iter.next() {
  446. match attr.attribute.namespace {
  447. None => {
  448. if attr.attribute.name == "dangerous_inner_html" {
  449. inner_html = Some(attr.value.as_text().unwrap())
  450. } else {
  451. if is_boolean_attribute(attr.attribute.name) {
  452. if !attr.value.is_truthy() {
  453. continue;
  454. }
  455. }
  456. write!(f, " {}=\"{}\"", attr.attribute.name, attr.value)?
  457. }
  458. }
  459. Some(ns) => {
  460. // write the opening tag
  461. write!(f, " {}=\"", ns)?;
  462. let mut cur_ns_el = attr;
  463. loop {
  464. write!(f, "{}:{};", cur_ns_el.attribute.name, cur_ns_el.value)?;
  465. match attr_iter.peek() {
  466. Some(next_attr) if next_attr.attribute.namespace == Some(ns) => {
  467. cur_ns_el = attr_iter.next().unwrap();
  468. }
  469. _ => break,
  470. }
  471. }
  472. // write the closing tag
  473. write!(f, "\"")?;
  474. }
  475. }
  476. }
  477. Ok(inner_html)
  478. }
  479. fn is_boolean_attribute(attribute: &'static str) -> bool {
  480. if let "allowfullscreen"
  481. | "allowpaymentrequest"
  482. | "async"
  483. | "autofocus"
  484. | "autoplay"
  485. | "checked"
  486. | "controls"
  487. | "default"
  488. | "defer"
  489. | "disabled"
  490. | "formnovalidate"
  491. | "hidden"
  492. | "ismap"
  493. | "itemscope"
  494. | "loop"
  495. | "multiple"
  496. | "muted"
  497. | "nomodule"
  498. | "novalidate"
  499. | "open"
  500. | "playsinline"
  501. | "readonly"
  502. | "required"
  503. | "reversed"
  504. | "selected"
  505. | "truespeed" = attribute
  506. {
  507. true
  508. } else {
  509. false
  510. }
  511. }
  512. #[derive(Clone, Debug, Default)]
  513. pub struct SsrConfig {
  514. /// currently not supported - control if we indent the HTML output
  515. indent: bool,
  516. /// Control if elements are written onto a new line
  517. newline: bool,
  518. /// Choose to write ElementIDs into elements so the page can be re-hydrated later on
  519. pre_render: bool,
  520. // Currently not implemented
  521. // Don't proceed onto new components. Instead, put the name of the component.
  522. // TODO: components don't have names :(
  523. skip_components: bool,
  524. }
  525. impl SsrConfig {
  526. pub fn indent(mut self, a: bool) -> Self {
  527. self.indent = a;
  528. self
  529. }
  530. pub fn newline(mut self, a: bool) -> Self {
  531. self.newline = a;
  532. self
  533. }
  534. pub fn pre_render(mut self, a: bool) -> Self {
  535. self.pre_render = a;
  536. self
  537. }
  538. pub fn skip_components(mut self, a: bool) -> Self {
  539. self.skip_components = a;
  540. self
  541. }
  542. }