lib.rs 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. //! This crate demonstrates how to implement a custom renderer for Dioxus VNodes via the `TextRenderer` renderer.
  2. //! The `TextRenderer` consumes a Dioxus Virtual DOM, progresses its event queue, and renders the VNodes to a String.
  3. //!
  4. //! While `VNode` supports "to_string" directly, it renders child components as the RSX! macro tokens. For custom components,
  5. //! an external renderer is needed to progress the component lifecycles. The `TextRenderer` shows how to use the Virtual DOM
  6. //! API to progress these lifecycle events to generate a fully-mounted Virtual DOM instance which can be renderer in the
  7. //! `render` method.
  8. use std::fmt::{Display, Formatter};
  9. use dioxus_core::*;
  10. pub fn render_root(vdom: &VirtualDom) -> String {
  11. format!("{:}", TextRenderer::new(vdom))
  12. }
  13. pub struct SsrConfig {
  14. // currently not supported - control if we indent the HTML output
  15. indent: bool,
  16. // Control if elements are written onto a new line
  17. newline: bool,
  18. // Currently not implemented
  19. // Don't proceed onto new components. Instead, put the name of the component.
  20. // TODO: components don't have names :(
  21. _skip_components: bool,
  22. }
  23. impl Default for SsrConfig {
  24. fn default() -> Self {
  25. Self {
  26. indent: false,
  27. newline: false,
  28. _skip_components: false,
  29. }
  30. }
  31. }
  32. /// A configurable text renderer for the Dioxus VirtualDOM.
  33. ///
  34. ///
  35. /// ## Details
  36. ///
  37. /// This uses the `Formatter` infrastructure so you can write into anything that supports `write_fmt`. We can't accept
  38. /// any generic writer, so you need to "Display" the text renderer. This is done through `format!` or `format_args!`
  39. ///
  40. /// ## Example
  41. /// ```ignore
  42. /// const App: FC<()> = |cx| cx.render(rsx!(div { "hello world" }));
  43. /// let mut vdom = VirtualDom::new(App);
  44. /// vdom.rebuild_in_place();
  45. ///
  46. /// let renderer = TextRenderer::new(&vdom);
  47. /// let output = format!("{}", renderer);
  48. /// assert_eq!(output, "<div>hello world</div>");
  49. /// ```
  50. pub struct TextRenderer<'a> {
  51. vdom: &'a VirtualDom,
  52. cfg: SsrConfig,
  53. }
  54. impl<'a> TextRenderer<'a> {
  55. fn new(vdom: &'a VirtualDom) -> Self {
  56. Self {
  57. vdom,
  58. cfg: SsrConfig::default(),
  59. }
  60. }
  61. fn html_render(&self, node: &VNode, f: &mut std::fmt::Formatter, il: u16) -> std::fmt::Result {
  62. match &node.kind {
  63. VNodeKind::Text(text) => {
  64. if self.cfg.indent {
  65. for _ in 0..il {
  66. write!(f, " ")?;
  67. }
  68. }
  69. write!(f, "{}", text.text)?
  70. }
  71. VNodeKind::Element(el) => {
  72. if self.cfg.indent {
  73. for _ in 0..il {
  74. write!(f, " ")?;
  75. }
  76. }
  77. write!(f, "<{}", el.tag_name)?;
  78. for attr in el.attributes {
  79. write!(f, " {}=\"{}\"", attr.name, attr.value)?;
  80. }
  81. match self.cfg.newline {
  82. true => write!(f, ">\n")?,
  83. false => write!(f, ">")?,
  84. }
  85. for child in el.children {
  86. self.html_render(child, f, il + 1)?;
  87. }
  88. if self.cfg.newline {
  89. write!(f, "\n")?;
  90. }
  91. if self.cfg.indent {
  92. for _ in 0..il {
  93. write!(f, " ")?;
  94. }
  95. }
  96. write!(f, "</{}>", el.tag_name)?;
  97. if self.cfg.newline {
  98. write!(f, "\n")?;
  99. }
  100. }
  101. VNodeKind::Fragment(frag) => {
  102. for child in frag.children {
  103. self.html_render(child, f, il + 1)?;
  104. }
  105. }
  106. VNodeKind::Component(vcomp) => {
  107. let idx = vcomp.ass_scope.get().unwrap();
  108. let new_node = self
  109. .vdom
  110. .components
  111. .try_get(idx)
  112. .unwrap()
  113. .frames
  114. .current_head_node();
  115. self.html_render(new_node, f, il + 1)?;
  116. }
  117. VNodeKind::Suspended => todo!(),
  118. }
  119. Ok(())
  120. }
  121. }
  122. impl Display for TextRenderer<'_> {
  123. fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
  124. let root = self.vdom.base_scope();
  125. let root_node = root.root();
  126. self.html_render(root_node, f, 0)
  127. }
  128. }
  129. #[cfg(test)]
  130. mod tests {
  131. use super::*;
  132. use dioxus_core as dioxus;
  133. use dioxus_core::prelude::*;
  134. use dioxus_html as dioxus_elements;
  135. const SIMPLE_APP: FC<()> = |cx| {
  136. cx.render(rsx!(div {
  137. "hello world!"
  138. }))
  139. };
  140. const SLIGHTLY_MORE_COMPLEX: FC<()> = |cx| {
  141. cx.render(rsx! {
  142. div {
  143. title: "About W3Schools"
  144. {(0..20).map(|f| rsx!{
  145. div {
  146. title: "About W3Schools"
  147. style: "color:blue;text-align:center"
  148. class: "About W3Schools"
  149. p {
  150. title: "About W3Schools"
  151. "Hello world!: {f}"
  152. }
  153. }
  154. })}
  155. }
  156. })
  157. };
  158. const NESTED_APP: FC<()> = |cx| {
  159. cx.render(rsx!(
  160. div {
  161. SIMPLE_APP {}
  162. }
  163. ))
  164. };
  165. const FRAGMENT_APP: FC<()> = |cx| {
  166. cx.render(rsx!(
  167. div { "f1" }
  168. div { "f2" }
  169. div { "f3" }
  170. div { "f4" }
  171. ))
  172. };
  173. #[test]
  174. fn to_string_works() {
  175. let mut dom = VirtualDom::new(SIMPLE_APP);
  176. dom.rebuild_in_place().expect("failed to run virtualdom");
  177. dbg!(render_root(&dom));
  178. }
  179. #[test]
  180. fn nested() {
  181. let mut dom = VirtualDom::new(NESTED_APP);
  182. dom.rebuild_in_place().expect("failed to run virtualdom");
  183. dbg!(render_root(&dom));
  184. }
  185. #[test]
  186. fn fragment_app() {
  187. let mut dom = VirtualDom::new(FRAGMENT_APP);
  188. dom.rebuild_in_place().expect("failed to run virtualdom");
  189. dbg!(render_root(&dom));
  190. }
  191. #[test]
  192. fn write_to_file() {
  193. use std::fs::File;
  194. use std::io::Write;
  195. let mut file = File::create("index.html").unwrap();
  196. let mut dom = VirtualDom::new(SLIGHTLY_MORE_COMPLEX);
  197. dom.rebuild_in_place().expect("failed to run virtualdom");
  198. file.write_fmt(format_args!("{}", TextRenderer::new(&dom)))
  199. .unwrap();
  200. }
  201. }