Merge pull request #25 from jkelleyrtp/jk/ssr_lazy

wip: clean up and add lazy renderer
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2021-10-18 17:46:52 -04:00
commit b8085b0742
1 file changed
+90 -58
+90 -58
View File
@@ -13,32 +13,36 @@
use std::fmt::{Display, Formatter}; use std::fmt::{Display, Formatter};
use dioxus_core::exports::bumpalo; use dioxus_core::exports::bumpalo;
use dioxus_core::exports::bumpalo::Bump;
use dioxus_core::nodes::IntoVNode; use dioxus_core::nodes::IntoVNode;
use dioxus_core::*; use dioxus_core::*;
macro_rules! render_lazy { /// A memory pool for rendering
($f:expr) => {
$crate::SsrRenderer::new().render_lazy($f)
};
}
pub struct SsrRenderer { pub struct SsrRenderer {
inner: bumpalo::Bump, inner: bumpalo::Bump,
} cfg: SsrConfig,
impl Default for SsrRenderer {
fn default() -> Self {
Self {
inner: bumpalo::Bump::new(),
}
}
} }
impl SsrRenderer { impl SsrRenderer {
pub fn new() -> Self { pub fn new(cfg: impl FnOnce(SsrConfig) -> SsrConfig) -> Self {
SsrRenderer::default() Self {
cfg: cfg(SsrConfig::default()),
inner: bumpalo::Bump::new(),
}
} }
pub fn render_lazy<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>>( 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
}
fn render_inner<'a, F: FnOnce(NodeFactory<'a>) -> VNode<'a>>(
&'a self, &'a self,
f: LazyNodes<'a, F>, f: LazyNodes<'a, F>,
) -> String { ) -> String {
@@ -47,7 +51,7 @@ impl SsrRenderer {
format!( format!(
"{:}", "{:}",
TextRenderer { TextRenderer {
cfg: SsrConfig::default(), cfg: self.cfg.clone(),
root: &root, root: &root,
vdom: None 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 { pub fn render_vdom(dom: &VirtualDom, cfg: impl FnOnce(SsrConfig) -> SsrConfig) -> String {
format!( format!(
@@ -124,7 +155,7 @@ impl<'a> TextRenderer<'a, '_> {
} }
write!(f, "{}", text.text)? write!(f, "{}", text.text)?
} }
VNode::Anchor(anchor) => { VNode::Anchor(_anchor) => {
// //
if self.cfg.indent { if self.cfg.indent {
for _ in 0..il { 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 // when the page is loaded, the `querySelectorAll` will be used to collect all the nodes, and then add
// them interpreter's stack // them interpreter's stack
match (self.cfg.pre_render, node.try_mounted_id()) { if let (true, Some(id)) = (self.cfg.pre_render, node.try_mounted_id()) {
(true, Some(id)) => { write!(f, " dio_el=\"{}\"", id)?;
write!(f, " dio_el=\"{}\"", id)?;
// for _listener in el.listeners {
for listener in el.listeners { // todo: write the listeners
// write the listeners
}
} }
_ => {}
} }
match self.cfg.newline { match self.cfg.newline {
true => write!(f, ">\n")?, true => writeln!(f, ">")?,
false => write!(f, ">")?, false => write!(f, ">")?,
} }
@@ -191,7 +219,7 @@ impl<'a> TextRenderer<'a, '_> {
} }
if self.cfg.newline { if self.cfg.newline {
write!(f, "\n")?; writeln!(f)?;
} }
if self.cfg.indent { if self.cfg.indent {
for _ in 0..il { for _ in 0..il {
@@ -201,7 +229,7 @@ impl<'a> TextRenderer<'a, '_> {
write!(f, "</{}>", el.tag_name)?; write!(f, "</{}>", el.tag_name)?;
if self.cfg.newline { if self.cfg.newline {
write!(f, "\n")?; writeln!(f)?;
} }
} }
VNode::Fragment(frag) => { VNode::Fragment(frag) => {
@@ -211,14 +239,11 @@ impl<'a> TextRenderer<'a, '_> {
} }
VNode::Component(vcomp) => { VNode::Component(vcomp) => {
let idx = vcomp.associated_scope.get().unwrap(); let idx = vcomp.associated_scope.get().unwrap();
match (self.vdom, self.cfg.skip_components) {
(Some(vdom), false) => { if let (Some(vdom), false) = (self.vdom, self.cfg.skip_components) {
let new_node = vdom.get_scope(idx).unwrap().root_node(); let new_node = vdom.get_scope(idx).unwrap().root_node();
self.html_render(new_node, f, il + 1)?; self.html_render(new_node, f, il + 1)?;
} } else {
_ => {
// render the component by name
}
} }
} }
VNode::Suspended { .. } => { VNode::Suspended { .. } => {
@@ -229,6 +254,7 @@ impl<'a> TextRenderer<'a, '_> {
} }
} }
#[derive(Clone, Debug, Default)]
pub struct SsrConfig { pub struct SsrConfig {
/// currently not supported - control if we indent the HTML output /// currently not supported - control if we indent the HTML output
indent: bool, indent: bool,
@@ -245,17 +271,6 @@ pub struct SsrConfig {
skip_components: bool, skip_components: bool,
} }
impl Default for SsrConfig {
fn default() -> Self {
Self {
indent: false,
pre_render: false,
newline: false,
skip_components: false,
}
}
}
impl SsrConfig { impl SsrConfig {
pub fn indent(mut self, a: bool) -> Self { pub fn indent(mut self, a: bool) -> Self {
self.indent = a; self.indent = a;
@@ -284,13 +299,13 @@ mod tests {
use dioxus_core_macro::*; use dioxus_core_macro::*;
use dioxus_html as dioxus_elements; use dioxus_html as dioxus_elements;
static SIMPLE_APP: FC<()> = |(cx, props)| { static SIMPLE_APP: FC<()> = |(cx, _)| {
cx.render(rsx!(div { cx.render(rsx!(div {
"hello world!" "hello world!"
})) }))
}; };
static SLIGHTLY_MORE_COMPLEX: FC<()> = |(cx, props)| { static SLIGHTLY_MORE_COMPLEX: FC<()> = |(cx, _)| {
cx.render(rsx! { cx.render(rsx! {
div { div {
title: "About W3Schools" title: "About W3Schools"
@@ -309,14 +324,14 @@ mod tests {
}) })
}; };
static NESTED_APP: FC<()> = |(cx, props)| { static NESTED_APP: FC<()> = |(cx, _)| {
cx.render(rsx!( cx.render(rsx!(
div { div {
SIMPLE_APP {} SIMPLE_APP {}
} }
)) ))
}; };
static FRAGMENT_APP: FC<()> = |(cx, props)| { static FRAGMENT_APP: FC<()> = |(cx, _)| {
cx.render(rsx!( cx.render(rsx!(
div { "f1" } div { "f1" }
div { "f2" } div { "f2" }
@@ -372,7 +387,7 @@ mod tests {
#[test] #[test]
fn styles() { fn styles() {
static STLYE_APP: FC<()> = |(cx, props)| { static STLYE_APP: FC<()> = |(cx, _)| {
cx.render(rsx! { cx.render(rsx! {
div { style: { color: "blue", font_size: "46px" } } div { style: { color: "blue", font_size: "46px" } }
}) })
@@ -385,17 +400,34 @@ mod tests {
#[test] #[test]
fn lazy() { fn lazy() {
let p1 = SsrRenderer::new().render_lazy(rsx! { let p1 = SsrRenderer::new(|c| c).render_lazy(rsx! {
div { div { "ello" }
"ello"
}
}); });
let p2 = render_lazy!(rsx! { let p2 = render_lazy(rsx! {
div { div {
"ello" "ello"
} }
}); });
assert_eq!(p1, p2); assert_eq!(p1, p2);
} }
#[test]
fn big_lazy() {
let s = render_lazy(rsx! {
div {
div {
div {
h1 { "ello world" }
h1 { "ello world" }
h1 { "ello world" }
h1 { "ello world" }
h1 { "ello world" }
}
}
}
});
dbg!(s);
}
} }