Przeglądaj źródła

Merge pull request #25 from jkelleyrtp/jk/ssr_lazy

wip: clean up and add lazy renderer
Jonathan Kelley 3 lat temu
rodzic
commit
b8085b0742
1 zmienionych plików z 87 dodań i 55 usunięć
  1. 87 55
      packages/ssr/src/lib.rs

+ 87 - 55
packages/ssr/src/lib.rs

@@ -13,32 +13,36 @@
 use std::fmt::{Display, Formatter};
 
 use dioxus_core::exports::bumpalo;
+use dioxus_core::exports::bumpalo::Bump;
 use dioxus_core::nodes::IntoVNode;
 use dioxus_core::*;
 
-macro_rules! render_lazy {
-    ($f:expr) => {
-        $crate::SsrRenderer::new().render_lazy($f)
-    };
-}
-
+/// A memory pool for rendering
 pub struct SsrRenderer {
     inner: bumpalo::Bump,
+    cfg: SsrConfig,
 }
-impl Default for SsrRenderer {
-    fn default() -> Self {
+
+impl SsrRenderer {
+    pub fn new(cfg: impl FnOnce(SsrConfig) -> SsrConfig) -> Self {
         Self {
+            cfg: cfg(SsrConfig::default()),
             inner: bumpalo::Bump::new(),
         }
     }
-}
 
-impl SsrRenderer {
-    pub fn new() -> Self {
-        SsrRenderer::default()
+    pub fn render_lazy<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>>(
+        &'a mut self,
+        f: LazyNodes<'a, F>,
+    ) -> String {
+        let bump = &mut self.inner as *mut _;
+        let s = self.render_inner(f);
+        // reuse the bump's memory
+        unsafe { (&mut *bump as &mut bumpalo::Bump).reset() };
+        s
     }
 
-    pub fn render_lazy<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>>(
+    fn render_inner<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>>(
         &'a self,
         f: LazyNodes<'a, F>,
     ) -> String {
@@ -47,7 +51,7 @@ impl SsrRenderer {
         format!(
             "{:}",
             TextRenderer {
-                cfg: SsrConfig::default(),
+                cfg: self.cfg.clone(),
                 root: &root,
                 vdom: None
             }
@@ -55,7 +59,34 @@ impl SsrRenderer {
     }
 }
 
-pub fn render_vnode(vnode: &VNode, string: &mut String) {}
+pub fn render_lazy<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>>(f: LazyNodes<'a, F>) -> String {
+    let bump = bumpalo::Bump::new();
+    let borrowed = &bump;
+
+    // Safety
+    //
+    // The lifetimes bounds on LazyNodes are really complicated - they need to support the nesting restrictions in
+    // regular component usage. The <'a> lifetime is used to enforce that all calls of IntoVnode use the same allocator.
+    //
+    // When LazyNodes are provided, they are FnOnce, but do not come with a allocator selected to borrow from. The <'a>
+    // lifetime is therefore longer than the lifetime of the allocator which doesn't exist yet.
+    //
+    // Therefore, we cast our local bump alloactor into right lifetime. This is okay because our usage of the bump arena
+    // is *definitely* shorter than the <'a> lifetime, and we return owned data - not borrowed data.
+    // Therefore, we know that no references are leaking.
+    let _b: &'a Bump = unsafe { std::mem::transmute(borrowed) };
+
+    let root = f.into_vnode(NodeFactory::new(_b));
+
+    format!(
+        "{:}",
+        TextRenderer {
+            cfg: SsrConfig::default(),
+            root: &root,
+            vdom: None
+        }
+    )
+}
 
 pub fn render_vdom(dom: &VirtualDom, cfg: impl FnOnce(SsrConfig) -> SsrConfig) -> String {
     format!(
@@ -124,7 +155,7 @@ impl<'a> TextRenderer<'a, '_> {
                 }
                 write!(f, "{}", text.text)?
             }
-            VNode::Anchor(anchor) => {
+            VNode::Anchor(_anchor) => {
                 //
                 if self.cfg.indent {
                     for _ in 0..il {
@@ -170,19 +201,16 @@ impl<'a> TextRenderer<'a, '_> {
                 //
                 // when the page is loaded, the `querySelectorAll` will be used to collect all the nodes, and then add
                 // them interpreter's stack
-                match (self.cfg.pre_render, node.try_mounted_id()) {
-                    (true, Some(id)) => {
-                        write!(f, " dio_el=\"{}\"", id)?;
-                        //
-                        for listener in el.listeners {
-                            // write the listeners
-                        }
+                if let (true, Some(id)) = (self.cfg.pre_render, node.try_mounted_id()) {
+                    write!(f, " dio_el=\"{}\"", id)?;
+
+                    for _listener in el.listeners {
+                        // todo: write the listeners
                     }
-                    _ => {}
                 }
 
                 match self.cfg.newline {
-                    true => write!(f, ">\n")?,
+                    true => writeln!(f, ">")?,
                     false => write!(f, ">")?,
                 }
 
@@ -191,7 +219,7 @@ impl<'a> TextRenderer<'a, '_> {
                 }
 
                 if self.cfg.newline {
-                    write!(f, "\n")?;
+                    writeln!(f)?;
                 }
                 if self.cfg.indent {
                     for _ in 0..il {
@@ -201,7 +229,7 @@ impl<'a> TextRenderer<'a, '_> {
 
                 write!(f, "</{}>", el.tag_name)?;
                 if self.cfg.newline {
-                    write!(f, "\n")?;
+                    writeln!(f)?;
                 }
             }
             VNode::Fragment(frag) => {
@@ -211,14 +239,11 @@ impl<'a> TextRenderer<'a, '_> {
             }
             VNode::Component(vcomp) => {
                 let idx = vcomp.associated_scope.get().unwrap();
-                match (self.vdom, self.cfg.skip_components) {
-                    (Some(vdom), false) => {
-                        let new_node = vdom.get_scope(idx).unwrap().root_node();
-                        self.html_render(new_node, f, il + 1)?;
-                    }
-                    _ => {
-                        // render the component by name
-                    }
+
+                if let (Some(vdom), false) = (self.vdom, self.cfg.skip_components) {
+                    let new_node = vdom.get_scope(idx).unwrap().root_node();
+                    self.html_render(new_node, f, il + 1)?;
+                } else {
                 }
             }
             VNode::Suspended { .. } => {
@@ -229,6 +254,7 @@ impl<'a> TextRenderer<'a, '_> {
     }
 }
 
+#[derive(Clone, Debug, Default)]
 pub struct SsrConfig {
     /// currently not supported - control if we indent the HTML output
     indent: bool,
@@ -245,17 +271,6 @@ pub struct SsrConfig {
     skip_components: bool,
 }
 
-impl Default for SsrConfig {
-    fn default() -> Self {
-        Self {
-            indent: false,
-            pre_render: false,
-            newline: false,
-            skip_components: false,
-        }
-    }
-}
-
 impl SsrConfig {
     pub fn indent(mut self, a: bool) -> Self {
         self.indent = a;
@@ -284,13 +299,13 @@ mod tests {
     use dioxus_core_macro::*;
     use dioxus_html as dioxus_elements;
 
-    static SIMPLE_APP: FC<()> = |(cx, props)| {
+    static SIMPLE_APP: FC<()> = |(cx, _)| {
         cx.render(rsx!(div {
             "hello world!"
         }))
     };
 
-    static SLIGHTLY_MORE_COMPLEX: FC<()> = |(cx, props)| {
+    static SLIGHTLY_MORE_COMPLEX: FC<()> = |(cx, _)| {
         cx.render(rsx! {
             div {
                 title: "About W3Schools"
@@ -309,14 +324,14 @@ mod tests {
         })
     };
 
-    static NESTED_APP: FC<()> = |(cx, props)| {
+    static NESTED_APP: FC<()> = |(cx, _)| {
         cx.render(rsx!(
             div {
                 SIMPLE_APP {}
             }
         ))
     };
-    static FRAGMENT_APP: FC<()> = |(cx, props)| {
+    static FRAGMENT_APP: FC<()> = |(cx, _)| {
         cx.render(rsx!(
             div { "f1" }
             div { "f2" }
@@ -372,7 +387,7 @@ mod tests {
 
     #[test]
     fn styles() {
-        static STLYE_APP: FC<()> = |(cx, props)| {
+        static STLYE_APP: FC<()> = |(cx, _)| {
             cx.render(rsx! {
                 div { style: { color: "blue", font_size: "46px" } }
             })
@@ -385,17 +400,34 @@ mod tests {
 
     #[test]
     fn lazy() {
-        let p1 = SsrRenderer::new().render_lazy(rsx! {
+        let p1 = SsrRenderer::new(|c| c).render_lazy(rsx! {
+            div { "ello"  }
+        });
+
+        let p2 = render_lazy(rsx! {
             div {
                 "ello"
             }
         });
+        assert_eq!(p1, p2);
+    }
 
-        let p2 = render_lazy!(rsx! {
+    #[test]
+    fn big_lazy() {
+        let s = render_lazy(rsx! {
             div {
-                "ello"
+                div {
+                    div {
+                        h1 { "ello world" }
+                        h1 { "ello world" }
+                        h1 { "ello world" }
+                        h1 { "ello world" }
+                        h1 { "ello world" }
+                    }
+                }
             }
         });
-        assert_eq!(p1, p2);
+
+        dbg!(s);
     }
 }