diff --git a/examples/rsx_compile_fail.rs b/examples/rsx_compile_fail.rs index 6e02c66a3..500cdad84 100644 --- a/examples/rsx_compile_fail.rs +++ b/examples/rsx_compile_fail.rs @@ -2,13 +2,13 @@ //! It also proves that lifetimes work properly, especially when used with use_ref use dioxus::prelude::*; -use dioxus_ssr::config::SsrConfig; +use dioxus_ssr::config::Config; fn main() { let mut vdom = VirtualDom::new(example); _ = vdom.rebuild(); - let out = dioxus_ssr::render_vdom_cfg(&vdom, SsrConfig::default().newline(true).indent(true)); + let out = dioxus_ssr::render_vdom_cfg(&vdom, Config::default().newline(true).indent(true)); println!("{}", out); } diff --git a/examples/ssr.rs b/examples/ssr.rs index e1e5a3ad8..0bdebc419 100644 --- a/examples/ssr.rs +++ b/examples/ssr.rs @@ -5,7 +5,7 @@ use std::fmt::Write; use dioxus::prelude::*; -use dioxus_ssr::config::SsrConfig; +use dioxus_ssr::config::Config; fn main() { // We can render VirtualDoms @@ -26,7 +26,7 @@ fn main() { // We can configure the SSR rendering to add ids for rehydration println!( "{}", - dioxus_ssr::render_vdom_cfg(&vdom, SsrConfig::default().pre_render(true)) + dioxus_ssr::render_vdom_cfg(&vdom, Config::default().pre_render(true)) ); // We can even render as a writer diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index b41bacb7c..cc63a0e8b 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -307,7 +307,8 @@ impl<'b> VirtualDom { let unbounded_props = unsafe { std::mem::transmute(props) }; - let scope = self.new_scope(unbounded_props).id; + let scope = self.new_scope(unbounded_props, component.name); + let scope = scope.id; component.scope.set(Some(scope)); let return_nodes = unsafe { self.run_scope(scope).extend_lifetime_ref() }; diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index 68aa13450..af36f1aa6 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -17,7 +17,11 @@ use std::{ }; impl VirtualDom { - pub(super) fn new_scope(&mut self, props: Box>) -> &ScopeState { + pub(super) fn new_scope( + &mut self, + props: Box>, + name: &'static str, + ) -> &ScopeState { let parent = self.acquire_current_scope_raw(); let entry = self.scopes.vacant_entry(); let height = unsafe { parent.map(|f| (*f).height + 1).unwrap_or(0) }; @@ -28,6 +32,7 @@ impl VirtualDom { id, height, props: Some(props), + name, placeholder: Default::default(), node_arena_1: BumpFrame::new(50), node_arena_2: BumpFrame::new(50), diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 79ce6f64a..b1bbb42fa 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -68,6 +68,7 @@ pub struct ScopeId(pub usize); /// This struct exists to provide a common interface for all scopes without relying on generics. pub struct ScopeState { pub(crate) render_cnt: Cell, + pub(crate) name: &'static str, pub(crate) node_arena_1: BumpFrame, pub(crate) node_arena_2: BumpFrame, @@ -115,6 +116,11 @@ impl<'src> ScopeState { } } + /// Get the name of this component + pub fn name(&self) -> &str { + self.name + } + /// Get the current render since the inception of this component /// /// This can be used as a helpful diagnostic when debugging hooks/renders, etc diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 57d7c10d4..25feddbea 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -239,11 +239,10 @@ impl VirtualDom { mutations: Mutations::default(), }; - let root = dom.new_scope(Box::new(VProps::new( - root, - |_, _| unreachable!(), - root_props, - ))); + let root = dom.new_scope( + Box::new(VProps::new(root, |_, _| unreachable!(), root_props)), + "app", + ); // The root component is always a suspense boundary for any async children // This could be unexpected, so we might rethink this behavior later diff --git a/packages/core/tests/context_api.rs b/packages/core/tests/context_api.rs index 39cdf4843..696810cb0 100644 --- a/packages/core/tests/context_api.rs +++ b/packages/core/tests/context_api.rs @@ -29,17 +29,11 @@ fn state_shares() { dom.mark_dirty(ScopeId(0)); _ = dom.render_immediate(); - assert_eq!( - dom.base_scope().consume_context::().unwrap().as_ref(), - &1 - ); + assert_eq!(dom.base_scope().consume_context::().unwrap(), 1); dom.mark_dirty(ScopeId(0)); _ = dom.render_immediate(); - assert_eq!( - dom.base_scope().consume_context::().unwrap().as_ref(), - &2 - ); + assert_eq!(dom.base_scope().consume_context::().unwrap(), 2); dom.mark_dirty(ScopeId(2)); assert_eq!( diff --git a/packages/core/tests/safety.rs b/packages/core/tests/safety.rs index 564f21eb6..8c125f4c6 100644 --- a/packages/core/tests/safety.rs +++ b/packages/core/tests/safety.rs @@ -1,5 +1,7 @@ //! Tests related to safety of the library. +use std::rc::Rc; + use dioxus::prelude::*; use dioxus_core::SuspenseContext; @@ -18,5 +20,5 @@ fn root_node_isnt_null() { // There should be a default suspense context // todo: there should also be a default error boundary - assert!(scope.has_context::().is_some()); + assert!(scope.has_context::>().is_some()); } diff --git a/packages/core/tests/suspense.rs b/packages/core/tests/suspense.rs index 47c617085..9fac13937 100644 --- a/packages/core/tests/suspense.rs +++ b/packages/core/tests/suspense.rs @@ -2,6 +2,7 @@ use dioxus::core::ElementId; use dioxus::core::{Mutation::*, SuspenseContext}; use dioxus::prelude::*; use std::future::IntoFuture; +use std::rc::Rc; use std::time::Duration; #[tokio::test] @@ -51,11 +52,11 @@ fn app(cx: Scope) -> Element { fn suspense_boundary(cx: Scope) -> Element { cx.use_hook(|| { - cx.provide_context(SuspenseContext::new(cx.scope_id())); + cx.provide_context(Rc::new(SuspenseContext::new(cx.scope_id()))); }); // Ensure the right types are found - cx.has_context::().unwrap(); + cx.has_context::>().unwrap(); cx.render(rsx!(async_child {})) } diff --git a/packages/ssr/src/config.rs b/packages/ssr/src/config.rs index 73d21f4d4..8b1378917 100644 --- a/packages/ssr/src/config.rs +++ b/packages/ssr/src/config.rs @@ -1,38 +1 @@ -#[derive(Clone, Debug, Default)] -pub struct SsrConfig { - /// currently not supported - control if we indent the HTML output - indent: bool, - /// Control if elements are written onto a new line - newline: bool, - - /// Choose to write ElementIDs into elements so the page can be re-hydrated later on - pre_render: bool, - - // Currently not implemented - // Don't proceed onto new components. Instead, put the name of the component. - // TODO: components don't have names :( - skip_components: bool, -} - -impl SsrConfig { - pub fn indent(mut self, a: bool) -> Self { - self.indent = a; - self - } - - pub fn newline(mut self, a: bool) -> Self { - self.newline = a; - self - } - - pub fn pre_render(mut self, a: bool) -> Self { - self.pre_render = a; - self - } - - pub fn skip_components(mut self, a: bool) -> Self { - self.skip_components = a; - self - } -} diff --git a/packages/ssr/src/helpers.rs b/packages/ssr/src/helpers.rs deleted file mode 100644 index ed8449ea0..000000000 --- a/packages/ssr/src/helpers.rs +++ /dev/null @@ -1,50 +0,0 @@ -use std::fmt::Write; - -use dioxus_core::{LazyNodes, ScopeId, VirtualDom}; - -use crate::config::SsrConfig; - -pub fn pre_render(dom: &VirtualDom) -> String { - todo!() -} - -pub fn pre_render_to(dom: &VirtualDom, write: impl Write) { - todo!() -} - -pub fn render_vdom(dom: &VirtualDom) -> String { - todo!() - // format!("{:}", TextRenderer::from_vdom(dom, SsrConfig::default())) -} - -pub fn pre_render_vdom(dom: &VirtualDom) -> String { - todo!() - // format!( - // "{:}", - // TextRenderer::from_vdom(dom, SsrConfig::default().pre_render(true)) - // ) -} - -pub fn render_vdom_cfg(dom: &VirtualDom, cfg: SsrConfig) -> String { - todo!() - // format!( - // "{:}", - // TextRenderer::from_vdom(dom, cfg(SsrConfig::default())) - // ) -} - -pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option { - todo!() - // Some(format!( - // "{:}", - // TextRenderer { - // cfg: SsrConfig::default(), - // root: vdom.get_scope(scope).unwrap().root_node(), - // vdom: Some(vdom), - // } - // )) -} - -pub fn render_lazy<'a, 'b>(f: LazyNodes<'a, 'b>) -> String { - todo!() -} diff --git a/packages/ssr/src/lib.rs b/packages/ssr/src/lib.rs index ee3608ab7..f9e1ca832 100644 --- a/packages/ssr/src/lib.rs +++ b/packages/ssr/src/lib.rs @@ -2,8 +2,45 @@ mod cache; pub mod config; -pub mod helpers; pub mod renderer; pub mod template; -pub use helpers::*; -pub use template::SsrRender; +use dioxus_core::{Element, LazyNodes, Scope, VirtualDom}; +use std::cell::Cell; + +use crate::renderer::Renderer; + +/// A convenience function to render an `rsx!` call to a string +/// +/// For advanced rendering, create a new `SsrRender`. +pub fn render_lazy<'a, 'b>(f: LazyNodes<'a, 'b>) -> String { + // We need to somehow get the lazy call into the virtualdom even with the lifetime + // Since the lazy lifetime is valid for this function, we can just transmute it to static temporarily + // This is okay since we're returning an owned value + struct RootProps<'a, 'b> { + caller: Cell>>, + } + + fn lazy_app<'a>(cx: Scope<'a, RootProps<'static, 'static>>) -> Element<'a> { + let lazy = cx.props.caller.take().unwrap(); + let lazy: LazyNodes = unsafe { std::mem::transmute(lazy) }; + Ok(lazy.call(cx)) + } + + let props: RootProps = unsafe { + std::mem::transmute(RootProps { + caller: Cell::new(Some(f)), + }) + }; + + let mut dom = VirtualDom::new_with_props(lazy_app, props); + _ = dom.rebuild(); + + Renderer::new().render(&dom) +} + +/// A convenience function to render an existing VirtualDom to a string +/// +/// We generally recommend creating a new `Renderer` to take advantage of template caching. +pub fn render(dom: &VirtualDom) -> String { + Renderer::new().render(dom) +} diff --git a/packages/ssr/src/renderer.rs b/packages/ssr/src/renderer.rs index c41e6ec0a..51a5763bb 100644 --- a/packages/ssr/src/renderer.rs +++ b/packages/ssr/src/renderer.rs @@ -1,146 +1,181 @@ -// use dioxus_core::VirtualDom; +use super::cache::Segment; +use crate::cache::StringCache; +use dioxus_core::{prelude::*, AttributeValue, DynamicNode, RenderReturn}; +use std::collections::HashMap; +use std::fmt::Write; +use std::rc::Rc; -// use crate::config::SsrConfig; +/// A virtualdom renderer that caches the templates it has seen for faster rendering +#[derive(Default)] +pub struct Renderer { + /// should we do our best to prettify the output? + pub pretty: bool, -// pub struct SsrRenderer { -// vdom: VirtualDom, -// cfg: SsrConfig, -// } + /// Control if elements are written onto a new line + pub newline: bool, -// impl Default for SsrRenderer { -// fn default() -> Self { -// Self::new(SsrConfig::default()) -// } -// } + /// Should we sanitize text nodes? (escape HTML) + pub sanitize: bool, -// impl SsrRenderer { -// pub fn new(cfg: SsrConfig) -> Self { -// Self { -// vdom: VirtualDom::new(app), -// cfg, -// } -// } + /// Choose to write ElementIDs into elements so the page can be re-hydrated later on + pub pre_render: bool, -// pub fn render_lazy<'a>(&'a mut self, f: LazyNodes<'a, '_>) -> String { -// let scope = self.vdom.base_scope(); -// let root = f.call(scope); -// format!( -// "{:}", -// TextRenderer { -// cfg: self.cfg.clone(), -// root: &root, -// vdom: Some(&self.vdom), -// } -// ) -// } + // Currently not implemented + // Don't proceed onto new components. Instead, put the name of the component. + pub skip_components: bool, -// fn html_render( -// &self, -// node: &VNode, -// f: &mut impl Write, -// il: u16, -// last_node_was_text: &mut bool, -// ) -> std::fmt::Result { -// // match &node { -// // VNode::Text(text) => { -// // if *last_node_was_text { -// // write!(f, "")?; -// // } + /// A cache of templates that have been rendered + template_cache: HashMap<&'static str, Rc>, +} -// // if self.cfg.indent { -// // for _ in 0..il { -// // write!(f, " ")?; -// // } -// // } +impl Renderer { + pub fn new() -> Self { + Self::default() + } -// // *last_node_was_text = true; + pub fn render(&mut self, dom: &VirtualDom) -> String { + let mut buf = String::new(); + self.render_to(&mut buf, dom).unwrap(); + buf + } -// // write!(f, "{}", text.text)? -// // } -// // VNode::Element(el) => { -// // *last_node_was_text = false; + pub fn render_to(&mut self, buf: &mut impl Write, dom: &VirtualDom) -> std::fmt::Result { + self.render_scope(buf, dom, ScopeId(0)) + } -// // if self.cfg.indent { -// // for _ in 0..il { -// // write!(f, " ")?; -// // } -// // } + pub fn render_scope( + &mut self, + buf: &mut impl Write, + dom: &VirtualDom, + scope: ScopeId, + ) -> std::fmt::Result { + // We should never ever run into async or errored nodes in SSR + // Error boundaries and suspense boundaries will convert these to sync + if let RenderReturn::Sync(Ok(node)) = dom.get_scope(scope).unwrap().root_node() { + self.render_template(buf, dom, node)? + }; -// // write!(f, "<{}", el.tag)?; + Ok(()) + } -// // let inner_html = render_attributes(el.attributes.iter(), f)?; + fn render_template( + &mut self, + buf: &mut impl Write, + dom: &VirtualDom, + template: &VNode, + ) -> std::fmt::Result { + let entry = self + .template_cache + .entry(template.template.name) + .or_insert_with(|| Rc::new(StringCache::from_template(template).unwrap())) + .clone(); -// // match self.cfg.newline { -// // true => writeln!(f, ">")?, -// // false => write!(f, ">")?, -// // } + for segment in entry.segments.iter() { + match segment { + Segment::Attr(idx) => { + let attr = &template.dynamic_attrs[*idx]; + match attr.value { + AttributeValue::Text(value) => write!(buf, " {}=\"{}\"", attr.name, value)?, + AttributeValue::Bool(value) => write!(buf, " {}={}", attr.name, value)?, + _ => {} + }; + } + Segment::Node(idx) => match &template.dynamic_nodes[*idx] { + DynamicNode::Component(node) => { + if self.skip_components { + write!(buf, "<{}><{}/>", node.name, node.name)?; + } else { + let id = node.scope.get().unwrap(); + let scope = dom.get_scope(id).unwrap(); + let node = scope.root_node(); + match node { + RenderReturn::Sync(Ok(node)) => { + self.render_template(buf, dom, node)? + } + _ => todo!( + "generally, scopes should be sync, only if being traversed" + ), + } + } + } + DynamicNode::Text(text) => { + // in SSR, we are concerned that we can't hunt down the right text node since they might get merged + if self.pre_render { + write!(buf, "")?; + } -// // if let Some(inner_html) = inner_html { -// // write!(f, "{}", inner_html)?; -// // } else { -// // let mut last_node_was_text = false; -// // for child in el.children { -// // self.html_render(child, f, il + 1, &mut last_node_was_text)?; -// // } -// // } + // todo: escape the text + write!(buf, "{}", text.value)?; -// // if self.cfg.newline { -// // writeln!(f)?; -// // } -// // if self.cfg.indent { -// // for _ in 0..il { -// // write!(f, " ")?; -// // } -// // } + if self.pre_render { + write!(buf, "")?; + } + } + DynamicNode::Fragment(nodes) => { + for child in *nodes { + self.render_template(buf, dom, child)?; + } + } -// // write!(f, "", el.tag)?; -// // if self.cfg.newline { -// // writeln!(f)?; -// // } -// // } -// // VNode::Fragment(frag) => match frag.children.len() { -// // 0 => { -// // *last_node_was_text = false; -// // if self.cfg.indent { -// // for _ in 0..il { -// // write!(f, " ")?; -// // } -// // } -// // write!(f, "")?; -// // } -// // _ => { -// // for child in frag.children { -// // self.html_render(child, f, il + 1, last_node_was_text)?; -// // } -// // } -// // }, -// // VNode::Component(vcomp) => { -// // let idx = vcomp.scope.get().unwrap(); + DynamicNode::Placeholder(_el) => { + if self.pre_render { + write!(buf, "
")?;
+                        }
+                    }
+                },
 
-//         //         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, last_node_was_text)?;
-//         //         } else {
-//         //         }
-//         //     }
-//         //     VNode::Template(t) => {
-//         //         if let Some(vdom) = self.vdom {
-//         //             todo!()
-//         //         } else {
-//         //             panic!("Cannot render template without vdom");
-//         //         }
-//         //     }
-//         //     VNode::Placeholder(_) => {
-//         //         todo!()
-//         //     }
-//         // }
-//         Ok(())
-//     }
-// }
+                Segment::PreRendered(contents) => write!(buf, "{}", contents)?,
+            }
+        }
 
-// impl<'a: 'c, 'c> Display for SsrRenderer<'a, '_, 'c> {
-//     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-//         let mut last_node_was_text = false;
-//         self.html_render(self.root, f, 0, &mut last_node_was_text)
-//     }
-// }
+        Ok(())
+    }
+}
+
+#[test]
+fn to_string_works() {
+    use dioxus::prelude::*;
+
+    fn app(cx: Scope) -> Element {
+        let dynamic = 123;
+        let dyn2 = ""; // todo: escape this
+
+        render! {
+            div { class: "asdasdasd", class: "asdasdasd", id: "id-{dynamic}",
+                "Hello world 1 -->" "{dynamic}" "<-- Hello world 2"
+                div { "nest 1" }
+                div {}
+                div { "nest 2" }
+                "{dyn2}"
+                (0..5).map(|i| rsx! { div { "finalize {i}" } })
+            }
+        }
+    }
+
+    let mut dom = VirtualDom::new(app);
+    _ = dom.rebuild();
+
+    let mut renderer = Renderer::new();
+
+    let out = renderer.render(&dom);
+
+    use Segment::*;
+    assert_eq!(
+        renderer.template_cache.iter().next().unwrap().1.segments,
+        vec![
+            PreRendered("
Hello world 1 -->".into(),), + Node(0,), + PreRendered("<-- Hello world 2
nest 1
nest 2
".into(),), + Node(1,), + Node(2,), + PreRendered("
".into(),), + ] + ); + + assert_eq!( + out, + "
Hello world 1 -->123<-- Hello world 2
nest 1
nest 2
finalize 0
finalize 1
finalize 2
finalize 3
finalize 4
" + ); +} diff --git a/packages/ssr/src/template.rs b/packages/ssr/src/template.rs index 8039a774c..8b1378917 100644 --- a/packages/ssr/src/template.rs +++ b/packages/ssr/src/template.rs @@ -1,140 +1 @@ -use super::cache::Segment; -use dioxus_core::{prelude::*, AttributeValue, DynamicNode, RenderReturn, VText}; -use std::collections::HashMap; -use std::fmt::Write; -use std::rc::Rc; -use crate::cache::StringCache; - -/// A virtualdom renderer that caches the templates it has seen for faster rendering -#[derive(Default)] -pub struct SsrRender { - template_cache: HashMap<&'static str, Rc>, -} - -impl SsrRender { - pub fn render_vdom(&mut self, dom: &VirtualDom) -> String { - let scope = dom.base_scope(); - let root = scope.root_node(); - - let mut out = String::new(); - - match root { - RenderReturn::Sync(Ok(node)) => self.render_template(&mut out, dom, node).unwrap(), - _ => {} - }; - - out - } - - fn render_template( - &mut self, - buf: &mut String, - dom: &VirtualDom, - template: &VNode, - ) -> std::fmt::Result { - let entry = self - .template_cache - .entry(template.template.name) - .or_insert_with(|| Rc::new(StringCache::from_template(template).unwrap())) - .clone(); - - for segment in entry.segments.iter() { - match segment { - Segment::Attr(idx) => { - let attr = &template.dynamic_attrs[*idx]; - match attr.value { - AttributeValue::Text(value) => write!(buf, " {}=\"{}\"", attr.name, value)?, - AttributeValue::Bool(value) => write!(buf, " {}={}", attr.name, value)?, - _ => {} - }; - } - Segment::Node(idx) => match &template.dynamic_nodes[*idx] { - DynamicNode::Component(node) => { - let id = node.scope.get().unwrap(); - let scope = dom.get_scope(id).unwrap(); - let node = scope.root_node(); - match node { - RenderReturn::Sync(Ok(node)) => self.render_template(buf, dom, node)?, - _ => todo!(), - } - } - DynamicNode::Text(text) => { - // in SSR, we are concerned that we can't hunt down the right text node since they might get merged - // if !*inner { - // write!(buf, "")?; - // } - - // todo: escape the text - write!(buf, "{}", text.value)?; - - // if !*inner { - // write!(buf, "")?; - // } - } - DynamicNode::Fragment(nodes) => { - for child in *nodes { - self.render_template(buf, dom, child)?; - } - } - - DynamicNode::Placeholder(_el) => { - // todo write a placeholder if in pre-render mode - // write!(buf, "")?; - } - }, - - Segment::PreRendered(contents) => buf.push_str(contents), - } - } - - Ok(()) - } -} - -#[test] -fn to_string_works() { - use dioxus::prelude::*; - - fn app(cx: Scope) -> Element { - let dynamic = 123; - let dyn2 = ""; // todo: escape this - - render! { - div { class: "asdasdasd", class: "asdasdasd", id: "id-{dynamic}", - "Hello world 1 -->" "{dynamic}" "<-- Hello world 2" - div { "nest 1" } - div {} - div { "nest 2" } - "{dyn2}" - (0..5).map(|i| rsx! { div { "finalize {i}" } }) - } - } - } - - let mut dom = VirtualDom::new(app); - dom.rebuild(); - - use Segment::*; - - // assert_eq!( - // StringCache::from_template(&dom.base_scope().root_node()) - // .unwrap() - // .segments, - // vec![ - // PreRendered("
Hello world 1 -->".into(),), - // Node(0,), - // PreRendered("<-- Hello world 2
nest 1
nest 2
".into(),), - // Node(1,), - // Node(2,), - // PreRendered("
".into(),), - // ] - // ); - - // assert_eq!( - // SsrRender::default().render_vdom(&dom), - // "
Hello world 1 -->123<-- Hello world 2
nest 1
nest 2
finalize 0
finalize 1
finalize 2
finalize 3
finalize 4
" - // ); -} diff --git a/packages/ssr/tests.old/renders.rs b/packages/ssr/tests.old/renders.rs deleted file mode 100644 index 64a760e6a..000000000 --- a/packages/ssr/tests.old/renders.rs +++ /dev/null @@ -1,144 +0,0 @@ -use dioxus::prelude::*; -use dioxus_ssr::{render_lazy, render_vdom, render_vdom_cfg, SsrConfig, SsrRenderer, TextRenderer}; - -static SIMPLE_APP: Component = |cx| { - cx.render(rsx!(div { - "hello world!" - })) -}; - -static SLIGHTLY_MORE_COMPLEX: Component = |cx| { - cx.render(rsx! { - div { title: "About W3Schools", - (0..20).map(|f| rsx!{ - div { - title: "About W3Schools", - style: "color:blue;text-align:center", - class: "About W3Schools", - p { - title: "About W3Schools", - "Hello world!: {f}" - } - } - }) - } - }) -}; - -static NESTED_APP: Component = |cx| { - cx.render(rsx!( - div { - SIMPLE_APP {} - } - )) -}; -static FRAGMENT_APP: Component = |cx| { - cx.render(rsx!( - div { "f1" } - div { "f2" } - div { "f3" } - div { "f4" } - )) -}; - -#[test] -fn to_string_works() { - let mut dom = VirtualDom::new(SIMPLE_APP); - dom.rebuild(); - dbg!(render_vdom(&dom)); -} - -#[test] -fn hydration() { - let mut dom = VirtualDom::new(NESTED_APP); - dom.rebuild(); - dbg!(render_vdom_cfg(&dom, |c| c.pre_render(true))); -} - -#[test] -fn nested() { - let mut dom = VirtualDom::new(NESTED_APP); - dom.rebuild(); - dbg!(render_vdom(&dom)); -} - -#[test] -fn fragment_app() { - let mut dom = VirtualDom::new(FRAGMENT_APP); - dom.rebuild(); - dbg!(render_vdom(&dom)); -} - -#[test] -fn write_to_file() { - use std::fs::File; - use std::io::Write; - - let mut file = File::create("index.html").unwrap(); - - let mut dom = VirtualDom::new(SLIGHTLY_MORE_COMPLEX); - dom.rebuild(); - - file.write_fmt(format_args!( - "{}", - TextRenderer::from_vdom(&dom, SsrConfig::default()) - )) - .unwrap(); -} - -#[test] -fn styles() { - static STLYE_APP: Component = |cx| { - cx.render(rsx! { - div { color: "blue", font_size: "46px" } - }) - }; - - let mut dom = VirtualDom::new(STLYE_APP); - dom.rebuild(); - dbg!(render_vdom(&dom)); -} - -#[test] -fn lazy() { - let p1 = SsrRenderer::new(|c| c).render_lazy(rsx! { - div { "ello" } - }); - - let p2 = render_lazy(rsx! { - div { - "ello" - } - }); - 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); -} - -#[test] -fn inner_html() { - let s = render_lazy(rsx! { - div { - dangerous_inner_html: "
ack
" - } - }); - - dbg!(s); -} diff --git a/packages/ssr/tests/simple.rs b/packages/ssr/tests/simple.rs index 5739c8c61..63137aeae 100644 --- a/packages/ssr/tests/simple.rs +++ b/packages/ssr/tests/simple.rs @@ -7,66 +7,43 @@ fn simple() { } let mut dom = VirtualDom::new(app); - dom.rebuild(); + _ = dom.rebuild(); + + assert_eq!(dioxus_ssr::render(&dom), "
hello!
"); assert_eq!( - dioxus_ssr::SsrRender::default().render_vdom(&dom), + dioxus_ssr::render_lazy(rsx!( div {"hello!"} )), "
hello!
" ); } #[test] fn lists() { - fn app(cx: Scope) -> Element { - render! { + assert_eq!( + dioxus_ssr::render_lazy(rsx! { ul { (0..5).map(|i| rsx! { li { "item {i}" } }) } - } - } - - let mut dom = VirtualDom::new(app); - dom.rebuild(); - - assert_eq!( - dioxus_ssr::SsrRender::default().render_vdom(&dom), + }), "
  • item 0
  • item 1
  • item 2
  • item 3
  • item 4
" ); } #[test] fn dynamic() { - fn app(cx: Scope) -> Element { - let dynamic = 123; - - render! { - div { "Hello world 1 -->" "{dynamic}" "<-- Hello world 2" } - } - } - - let mut dom = VirtualDom::new(app); - dom.rebuild(); - + let dynamic = 123; assert_eq!( - dioxus_ssr::SsrRender::default().render_vdom(&dom), + dioxus_ssr::render_lazy(rsx! { + div { "Hello world 1 -->" "{dynamic}" "<-- Hello world 2" } + }), "
Hello world 1 -->123<-- Hello world 2
" ); } #[test] fn components() { - fn app(cx: Scope) -> Element { - render! { - div { - (0..5).map(|name| rsx! { - my_component { name: name } - }) - } - } - } - #[inline_props] fn my_component(cx: Scope, name: i32) -> Element { render! { @@ -74,11 +51,26 @@ fn components() { } } - let mut dom = VirtualDom::new(app); - dom.rebuild(); - assert_eq!( - dioxus_ssr::SsrRender::default().render_vdom(&dom), + dioxus_ssr::render_lazy(rsx! { + div { + (0..5).map(|name| rsx! { + my_component { name: name } + }) + } + }), "
component 0
component 1
component 2
component 3
component 4
" ); } + +#[test] +fn fragments() { + assert_eq!( + dioxus_ssr::render_lazy(rsx! { + div { + (0..5).map(|_| rsx! (())) + } + }), + "
" + ); +}