mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-23 12:43:08 +00:00
feat: support cached ssr
This commit is contained in:
parent
4a31b29703
commit
b6c0bce89c
11 changed files with 433 additions and 178 deletions
|
@ -3,6 +3,7 @@ use crate::mutations::Mutation::*;
|
|||
use crate::nodes::VNode;
|
||||
use crate::nodes::{DynamicNode, DynamicNodeKind, TemplateNode};
|
||||
use crate::virtualdom::VirtualDom;
|
||||
use crate::TemplateAttribute;
|
||||
|
||||
impl VirtualDom {
|
||||
/// Create this template and write its mutations
|
||||
|
@ -112,10 +113,11 @@ impl VirtualDom {
|
|||
id,
|
||||
});
|
||||
|
||||
mutations.extend(attrs.into_iter().map(|attr| SetAttribute {
|
||||
name: attr.name,
|
||||
value: attr.value,
|
||||
id,
|
||||
mutations.extend(attrs.into_iter().filter_map(|attr| match attr {
|
||||
TemplateAttribute::Static { name, value, .. } => {
|
||||
Some(SetAttribute { name, value, id })
|
||||
}
|
||||
_ => None,
|
||||
}));
|
||||
|
||||
children
|
||||
|
|
|
@ -1,19 +1,36 @@
|
|||
use std::fmt::Arguments;
|
||||
use std::{cell::Cell, fmt::Arguments};
|
||||
|
||||
use crate::{innerlude::DynamicNode, LazyNodes, ScopeState, VNode};
|
||||
use crate::{
|
||||
arena::ElementId,
|
||||
innerlude::{DynamicNode, DynamicNodeKind},
|
||||
LazyNodes, ScopeState, VNode,
|
||||
};
|
||||
|
||||
impl ScopeState {
|
||||
/// Create some text that's allocated along with the other vnodes
|
||||
///
|
||||
pub fn text(&self, args: Arguments) -> DynamicNode {
|
||||
// let (text, _is_static) = self.raw_text(args);
|
||||
pub fn text<'a>(&'a self, args: Arguments) -> DynamicNode<'a> {
|
||||
let (text, _) = self.raw_text(args);
|
||||
|
||||
// VNode::Text(self.bump.alloc(VText {
|
||||
// text,
|
||||
// id: Default::default(),
|
||||
// }))
|
||||
DynamicNode {
|
||||
kind: DynamicNodeKind::Text {
|
||||
id: Cell::new(ElementId(0)),
|
||||
value: text,
|
||||
},
|
||||
path: &[0],
|
||||
}
|
||||
}
|
||||
|
||||
todo!()
|
||||
pub fn raw_text<'a>(&'a self, args: Arguments) -> (&'a str, bool) {
|
||||
match args.as_str() {
|
||||
Some(static_str) => (static_str, true),
|
||||
None => {
|
||||
use bumpalo::core_alloc::fmt::Write;
|
||||
let mut str_buf = bumpalo::collections::String::new_in(self.bump());
|
||||
str_buf.write_fmt(args).unwrap();
|
||||
(str_buf.into_bump_str(), false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fragment_from_iter<'a, I, F: IntoVnode<'a, I>>(
|
||||
|
|
|
@ -69,6 +69,8 @@ pub use crate::innerlude::{
|
|||
// AnyAttributeValue, AnyEvent, Attribute, AttributeValue, Component, Element, ElementId,
|
||||
Attribute,
|
||||
AttributeValue,
|
||||
DynamicNode,
|
||||
DynamicNodeKind,
|
||||
Element,
|
||||
EventPriority,
|
||||
LazyNodes,
|
||||
|
@ -94,8 +96,9 @@ pub use crate::innerlude::{
|
|||
/// This includes types like [`Scope`], [`Element`], and [`Component`].
|
||||
pub mod prelude {
|
||||
pub use crate::innerlude::{
|
||||
Attribute, Element, EventPriority, LazyNodes, Listener, NodeFactory, Scope, ScopeId,
|
||||
ScopeState, TaskId, Template, TemplateAttribute, TemplateNode, UiEvent, VNode, VirtualDom,
|
||||
Attribute, DynamicNode, DynamicNodeKind, Element, EventPriority, LazyNodes, Listener,
|
||||
NodeFactory, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute, TemplateNode,
|
||||
UiEvent, VNode, VirtualDom,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -34,8 +34,8 @@ pub enum Mutation<'a> {
|
|||
},
|
||||
|
||||
ReplacePlaceholder {
|
||||
path: &'static [u8],
|
||||
m: usize,
|
||||
path: &'static [u8],
|
||||
},
|
||||
|
||||
AssignId {
|
||||
|
|
|
@ -1,9 +1,5 @@
|
|||
use crate::{any_props::AnyProps, arena::ElementId};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::Cell,
|
||||
num::NonZeroUsize,
|
||||
};
|
||||
use std::{any::Any, cell::Cell, hash::Hasher};
|
||||
|
||||
pub type TemplateId = &'static str;
|
||||
|
||||
|
@ -15,7 +11,7 @@ pub struct VNode<'a> {
|
|||
// When rendered, this template will be linked to its parent
|
||||
pub parent: Option<(*mut VNode<'static>, usize)>,
|
||||
|
||||
pub template: Template,
|
||||
pub template: Template<'static>,
|
||||
|
||||
pub root_ids: &'a [Cell<ElementId>],
|
||||
|
||||
|
@ -26,15 +22,38 @@ pub struct VNode<'a> {
|
|||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Template {
|
||||
pub id: &'static str,
|
||||
pub roots: &'static [TemplateNode<'static>],
|
||||
pub struct Template<'a> {
|
||||
pub id: &'a str,
|
||||
pub roots: &'a [TemplateNode<'a>],
|
||||
}
|
||||
|
||||
impl<'a> std::hash::Hash for Template<'a> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.id.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Template<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.id == other.id
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Template<'_> {}
|
||||
impl PartialOrd for Template<'_> {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
self.id.partial_cmp(other.id)
|
||||
}
|
||||
}
|
||||
impl Ord for Template<'_> {
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
self.id.cmp(other.id)
|
||||
}
|
||||
}
|
||||
|
||||
/// A weird-ish variant of VNodes with way more limited types
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum TemplateNode<'a> {
|
||||
/// A simple element
|
||||
Element {
|
||||
tag: &'a str,
|
||||
namespace: Option<&'a str>,
|
||||
|
@ -62,7 +81,7 @@ pub enum DynamicNodeKind<'a> {
|
|||
// Comes in with string interpolation or from format_args, include_str, etc
|
||||
Text {
|
||||
id: Cell<ElementId>,
|
||||
value: &'static str,
|
||||
value: &'a str,
|
||||
},
|
||||
|
||||
// Anything that's coming in as an iterator
|
||||
|
@ -72,11 +91,17 @@ pub enum DynamicNodeKind<'a> {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TemplateAttribute<'a> {
|
||||
pub name: &'static str,
|
||||
pub value: &'a str,
|
||||
pub namespace: Option<&'static str>,
|
||||
pub volatile: bool,
|
||||
pub enum TemplateAttribute<'a> {
|
||||
Static {
|
||||
name: &'static str,
|
||||
value: &'a str,
|
||||
namespace: Option<&'static str>,
|
||||
volatile: bool,
|
||||
},
|
||||
Dynamic {
|
||||
name: &'static str,
|
||||
index: usize,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct AttributeLocation<'a> {
|
||||
|
|
|
@ -15,7 +15,7 @@ use slab::Slab;
|
|||
use std::collections::{BTreeSet, HashMap};
|
||||
|
||||
pub struct VirtualDom {
|
||||
pub(crate) templates: HashMap<TemplateId, Template>,
|
||||
pub(crate) templates: HashMap<TemplateId, Template<'static>>,
|
||||
pub(crate) elements: Slab<ElementPath>,
|
||||
pub(crate) scopes: Slab<ScopeState>,
|
||||
pub(crate) scope_stack: Vec<ScopeId>,
|
||||
|
@ -82,6 +82,14 @@ impl VirtualDom {
|
|||
///
|
||||
/// This is cancel safe, so if the future is dropped, you can push events into the virtualdom
|
||||
pub async fn wait_for_work(&mut self) {}
|
||||
|
||||
pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
|
||||
self.scopes.get(id.0)
|
||||
}
|
||||
|
||||
pub fn base_scope(&self) -> &ScopeState {
|
||||
self.scopes.get(0).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for VirtualDom {
|
||||
|
|
|
@ -30,6 +30,9 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element {
|
|||
fn basic_template(cx: Scope) -> Element {
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
(0..2).map(|i| rsx! {
|
||||
div { "asd" }
|
||||
}),
|
||||
(0..2).map(|i| rsx! {
|
||||
div { "asd" }
|
||||
})
|
||||
|
@ -42,7 +45,10 @@ fn basic_prints() {
|
|||
let mut dom = VirtualDom::new(basic_template);
|
||||
|
||||
let mut edits = Vec::new();
|
||||
dom.rebuild(&mut edits);
|
||||
dbg!(edits);
|
||||
|
||||
let mut edits = Vec::new();
|
||||
dom.rebuild(&mut edits);
|
||||
|
||||
dbg!(edits);
|
||||
|
|
|
@ -21,6 +21,8 @@ mod ifmt;
|
|||
mod node;
|
||||
mod template;
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
// Re-export the namespaces into each other
|
||||
pub use component::*;
|
||||
pub use element::*;
|
||||
|
@ -39,6 +41,9 @@ use syn::{
|
|||
#[derive(Default)]
|
||||
pub struct CallBody {
|
||||
pub roots: Vec<BodyNode>,
|
||||
|
||||
// set this after
|
||||
pub inline_cx: bool,
|
||||
}
|
||||
|
||||
impl Parse for CallBody {
|
||||
|
@ -55,7 +60,10 @@ impl Parse for CallBody {
|
|||
roots.push(node);
|
||||
}
|
||||
|
||||
Ok(Self { roots })
|
||||
Ok(Self {
|
||||
roots,
|
||||
inline_cx: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,14 +74,20 @@ impl ToTokens for CallBody {
|
|||
// We'll use the size of the vecs to determine the index of the dynamic node in the final
|
||||
struct DynamicContext<'a> {
|
||||
dynamic_nodes: Vec<&'a BodyNode>,
|
||||
dynamic_attributes: Vec<&'a ElementAttrNamed>,
|
||||
dynamic_listeners: Vec<&'a ElementAttrNamed>,
|
||||
dynamic_attributes: HashMap<Vec<u8>, AttrLocation<'a>>,
|
||||
current_path: Vec<u8>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct AttrLocation<'a> {
|
||||
attrs: Vec<&'a ElementAttrNamed>,
|
||||
listeners: Vec<&'a ElementAttrNamed>,
|
||||
}
|
||||
|
||||
let mut context = DynamicContext {
|
||||
dynamic_nodes: vec![],
|
||||
dynamic_attributes: vec![],
|
||||
dynamic_listeners: vec![],
|
||||
dynamic_attributes: HashMap::new(),
|
||||
current_path: vec![],
|
||||
};
|
||||
|
||||
fn render_static_node<'a>(root: &'a BodyNode, cx: &mut DynamicContext<'a>) -> TokenStream2 {
|
||||
|
@ -81,10 +95,14 @@ impl ToTokens for CallBody {
|
|||
BodyNode::Element(el) => {
|
||||
let el_name = &el.name;
|
||||
|
||||
let children = {
|
||||
let children = el.children.iter().map(|root| render_static_node(root, cx));
|
||||
quote! { #(#children),* }
|
||||
};
|
||||
let children = el.children.iter().enumerate().map(|(idx, root)| {
|
||||
cx.current_path.push(idx as u8);
|
||||
let out = render_static_node(root, cx);
|
||||
cx.current_path.pop();
|
||||
out
|
||||
});
|
||||
|
||||
let children = quote! { #(#children),* };
|
||||
|
||||
let attrs = el.attributes.iter().filter_map(|attr| {
|
||||
//
|
||||
|
@ -92,7 +110,7 @@ impl ToTokens for CallBody {
|
|||
ElementAttr::AttrText { name, value } if value.is_static() => {
|
||||
let value = value.source.as_ref().unwrap();
|
||||
Some(quote! {
|
||||
::dioxus::core::TemplateAttribute {
|
||||
::dioxus::core::TemplateAttribute::Static {
|
||||
name: dioxus_elements::#el_name::#name.0,
|
||||
namespace: dioxus_elements::#el_name::#name.1,
|
||||
volatile: dioxus_elements::#el_name::#name.2,
|
||||
|
@ -104,7 +122,7 @@ impl ToTokens for CallBody {
|
|||
ElementAttr::CustomAttrText { name, value } if value.is_static() => {
|
||||
let value = value.source.as_ref().unwrap();
|
||||
Some(quote! {
|
||||
::dioxus::core::TemplateAttribute {
|
||||
::dioxus::core::TemplateAttribute::Static {
|
||||
name: dioxus_elements::#el_name::#name.0,
|
||||
namespace: dioxus_elements::#el_name::#name.1,
|
||||
volatile: dioxus_elements::#el_name::#name.2,
|
||||
|
@ -117,16 +135,21 @@ impl ToTokens for CallBody {
|
|||
| ElementAttr::AttrText { .. }
|
||||
| ElementAttr::CustomAttrText { .. }
|
||||
| ElementAttr::CustomAttrExpression { .. } => {
|
||||
// todo!();
|
||||
let ct = cx.dynamic_attributes.len();
|
||||
cx.dynamic_attributes.push(attr);
|
||||
// cx.dynamic_attributes.push(attr);
|
||||
// quote! {}
|
||||
None
|
||||
// quote! { ::dioxus::core::TemplateAttribute::Dynamic(#ct) }
|
||||
// None
|
||||
Some(quote! { ::dioxus::core::TemplateAttribute::Dynamic {
|
||||
name: "asd",
|
||||
index: #ct
|
||||
} })
|
||||
}
|
||||
|
||||
ElementAttr::EventTokens { .. } => {
|
||||
let ct = cx.dynamic_listeners.len();
|
||||
cx.dynamic_listeners.push(attr);
|
||||
// todo!();
|
||||
// let ct = cx.dynamic_listeners.len();
|
||||
// cx.dynamic_listeners.push(attr);
|
||||
// quote! {}
|
||||
None
|
||||
}
|
||||
|
@ -156,55 +179,46 @@ impl ToTokens for CallBody {
|
|||
}
|
||||
}
|
||||
|
||||
let root_printer = self
|
||||
.roots
|
||||
.iter()
|
||||
.map(|root| render_static_node(root, &mut context));
|
||||
let root_printer = self.roots.iter().enumerate().map(|(idx, root)| {
|
||||
context.current_path.push(idx as u8);
|
||||
let out = render_static_node(root, &mut context);
|
||||
context.current_path.pop();
|
||||
out
|
||||
});
|
||||
|
||||
// Render and release the mutable borrow on context
|
||||
let roots = quote! { #( #root_printer ),* };
|
||||
|
||||
let node_printer = &context.dynamic_nodes;
|
||||
let attr_printer = context.dynamic_attributes.iter();
|
||||
let listener_printer = context.dynamic_listeners.iter();
|
||||
|
||||
out_tokens.append_all(quote! {
|
||||
// LazyNodes::new(move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
|
||||
// __cx.template_ref(
|
||||
// ::dioxus::core::Template {
|
||||
// id: ::dioxus::core::get_line_num!(),
|
||||
// roots: &[ #roots ]
|
||||
// },
|
||||
// __cx.bump().alloc([
|
||||
// #( #node_printer ),*
|
||||
// ]),
|
||||
// __cx.bump().alloc([
|
||||
// #( #attr_printer ),*
|
||||
// ]),
|
||||
// __cx.bump().alloc([
|
||||
// #( #listener_printer ),*
|
||||
// ]),
|
||||
// None
|
||||
// )
|
||||
// })
|
||||
let body = quote! {
|
||||
static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {
|
||||
id: ::dioxus::core::get_line_num!(),
|
||||
roots: &[ #roots ]
|
||||
};
|
||||
::dioxus::core::VNode {
|
||||
node_id: Default::default(),
|
||||
parent: None,
|
||||
template: TEMPLATE,
|
||||
root_ids: __cx.bump().alloc([]),
|
||||
dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]),
|
||||
dynamic_attrs: __cx.bump().alloc([]),
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
::dioxus::core::LazyNodes::new( move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
|
||||
static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {
|
||||
id: ::dioxus::core::get_line_num!(),
|
||||
roots: &[ #roots ]
|
||||
};
|
||||
|
||||
::dioxus::core::VNode {
|
||||
node_id: Default::default(),
|
||||
parent: None,
|
||||
template: TEMPLATE,
|
||||
root_ids: __cx.bump().alloc([]),
|
||||
dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]),
|
||||
dynamic_attrs: __cx.bump().alloc([]),
|
||||
if self.inline_cx {
|
||||
out_tokens.append_all(quote! {
|
||||
{
|
||||
let __cx = cx;
|
||||
#body
|
||||
}
|
||||
})
|
||||
})
|
||||
} else {
|
||||
out_tokens.append_all(quote! {
|
||||
::dioxus::core::LazyNodes::new( move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
|
||||
#body
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
|
||||
use std::fmt::{Display, Formatter, Write};
|
||||
mod template;
|
||||
|
||||
use dioxus_core::exports::bumpalo;
|
||||
use dioxus_core::IntoVNode;
|
||||
|
||||
use dioxus_core::*;
|
||||
|
||||
fn app(_cx: Scope) -> Element {
|
||||
|
@ -25,9 +26,7 @@ impl SsrRenderer {
|
|||
|
||||
pub fn render_lazy<'a>(&'a mut self, f: LazyNodes<'a, '_>) -> String {
|
||||
let scope = self.vdom.base_scope();
|
||||
let factory = NodeFactory::new(scope);
|
||||
|
||||
let root = f.into_vnode(factory);
|
||||
let root = f.call(scope);
|
||||
format!(
|
||||
"{:}",
|
||||
TextRenderer {
|
||||
|
@ -55,8 +54,7 @@ pub fn render_lazy<'a>(f: LazyNodes<'a, '_>) -> String {
|
|||
// Therefore, we cast our local bump allocator to the 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.
|
||||
let scope = unsafe { &*scope };
|
||||
|
||||
let root = f.into_vnode(NodeFactory::new(scope));
|
||||
let root = f.call(scope);
|
||||
|
||||
let vdom = Some(&vdom);
|
||||
|
||||
|
@ -148,99 +146,99 @@ impl<'a: 'c, 'c> TextRenderer<'a, '_, 'c> {
|
|||
il: u16,
|
||||
last_node_was_text: &mut bool,
|
||||
) -> std::fmt::Result {
|
||||
match &node {
|
||||
VNode::Text(text) => {
|
||||
if *last_node_was_text {
|
||||
write!(f, "<!--spacer-->")?;
|
||||
}
|
||||
// match &node {
|
||||
// VNode::Text(text) => {
|
||||
// if *last_node_was_text {
|
||||
// write!(f, "<!--spacer-->")?;
|
||||
// }
|
||||
|
||||
if self.cfg.indent {
|
||||
for _ in 0..il {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
}
|
||||
// if self.cfg.indent {
|
||||
// for _ in 0..il {
|
||||
// write!(f, " ")?;
|
||||
// }
|
||||
// }
|
||||
|
||||
*last_node_was_text = true;
|
||||
// *last_node_was_text = true;
|
||||
|
||||
write!(f, "{}", text.text)?
|
||||
}
|
||||
VNode::Element(el) => {
|
||||
*last_node_was_text = false;
|
||||
// write!(f, "{}", text.text)?
|
||||
// }
|
||||
// VNode::Element(el) => {
|
||||
// *last_node_was_text = false;
|
||||
|
||||
if self.cfg.indent {
|
||||
for _ in 0..il {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
}
|
||||
// if self.cfg.indent {
|
||||
// for _ in 0..il {
|
||||
// write!(f, " ")?;
|
||||
// }
|
||||
// }
|
||||
|
||||
write!(f, "<{}", el.tag)?;
|
||||
// write!(f, "<{}", el.tag)?;
|
||||
|
||||
let inner_html = render_attributes(el.attributes.iter(), f)?;
|
||||
// let inner_html = render_attributes(el.attributes.iter(), f)?;
|
||||
|
||||
match self.cfg.newline {
|
||||
true => writeln!(f, ">")?,
|
||||
false => write!(f, ">")?,
|
||||
}
|
||||
// match self.cfg.newline {
|
||||
// true => writeln!(f, ">")?,
|
||||
// false => write!(f, ">")?,
|
||||
// }
|
||||
|
||||
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)?;
|
||||
}
|
||||
}
|
||||
// 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)?;
|
||||
// }
|
||||
// }
|
||||
|
||||
if self.cfg.newline {
|
||||
writeln!(f)?;
|
||||
}
|
||||
if self.cfg.indent {
|
||||
for _ in 0..il {
|
||||
write!(f, " ")?;
|
||||
}
|
||||
}
|
||||
// if self.cfg.newline {
|
||||
// writeln!(f)?;
|
||||
// }
|
||||
// if self.cfg.indent {
|
||||
// for _ in 0..il {
|
||||
// write!(f, " ")?;
|
||||
// }
|
||||
// }
|
||||
|
||||
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, "<!--placeholder-->")?;
|
||||
}
|
||||
_ => {
|
||||
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();
|
||||
// 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, "<!--placeholder-->")?;
|
||||
// }
|
||||
// _ => {
|
||||
// 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();
|
||||
|
||||
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!()
|
||||
}
|
||||
}
|
||||
// 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(())
|
||||
}
|
||||
|
||||
|
|
182
packages/ssr/src/template.rs
Normal file
182
packages/ssr/src/template.rs
Normal file
|
@ -0,0 +1,182 @@
|
|||
use dioxus_core::prelude::*;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Write;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// A virtualdom renderer that caches the templates it has seen for faster rendering
|
||||
#[derive(Default)]
|
||||
pub struct SsrRender {
|
||||
template_cache: RefCell<HashMap<Template<'static>, Rc<StringCache>>>,
|
||||
}
|
||||
|
||||
struct StringCache {
|
||||
segments: Vec<Segment>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct StringChain {
|
||||
segments: Vec<Segment>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
enum Segment {
|
||||
Attr(usize),
|
||||
Dyn(usize),
|
||||
PreRendered(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Write for StringChain {
|
||||
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||
match self.segments.last_mut() {
|
||||
Some(Segment::PreRendered(s2)) => s2.push_str(s),
|
||||
_ => self.segments.push(Segment::PreRendered(s.to_string())),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl StringCache {
|
||||
fn from_template(template: &VNode) -> Result<Self, std::fmt::Error> {
|
||||
let mut chain = StringChain::default();
|
||||
|
||||
let mut cur_path = vec![];
|
||||
|
||||
for (root_idx, root) in template.template.roots.iter().enumerate() {
|
||||
Self::recurse(root, &mut cur_path, root_idx, &mut chain)?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
segments: chain.segments,
|
||||
})
|
||||
}
|
||||
|
||||
fn recurse(
|
||||
root: &TemplateNode,
|
||||
cur_path: &mut Vec<usize>,
|
||||
root_idx: usize,
|
||||
chain: &mut StringChain,
|
||||
) -> Result<(), std::fmt::Error> {
|
||||
match root {
|
||||
TemplateNode::Element {
|
||||
tag,
|
||||
attrs,
|
||||
children,
|
||||
..
|
||||
} => {
|
||||
cur_path.push(root_idx);
|
||||
write!(chain, "<{}", tag)?;
|
||||
for attr in *attrs {
|
||||
match attr {
|
||||
TemplateAttribute::Static { name, value, .. } => {
|
||||
write!(chain, " {}=\"{}\"", name, value)?;
|
||||
}
|
||||
TemplateAttribute::Dynamic { index, .. } => {
|
||||
chain.segments.push(Segment::Attr(*index))
|
||||
}
|
||||
}
|
||||
}
|
||||
if children.len() == 0 {
|
||||
write!(chain, "/>")?;
|
||||
} else {
|
||||
write!(chain, ">")?;
|
||||
for child in *children {
|
||||
Self::recurse(child, cur_path, root_idx, chain)?;
|
||||
}
|
||||
write!(chain, "</{}>", tag)?;
|
||||
}
|
||||
cur_path.pop();
|
||||
}
|
||||
TemplateNode::Text(text) => write!(chain, "{}", text)?,
|
||||
TemplateNode::Dynamic(idx) => chain.segments.push(Segment::Dyn(*idx)),
|
||||
TemplateNode::DynamicText(idx) => chain.segments.push(Segment::Dyn(*idx)),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl SsrRender {
|
||||
fn render_vdom(&mut self, dom: &VirtualDom) -> String {
|
||||
let scope = dom.base_scope();
|
||||
let root = scope.root_node();
|
||||
|
||||
let mut out = String::new();
|
||||
self.render_template(&mut out, root).unwrap();
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
fn render_template(&self, buf: &mut String, template: &VNode) -> std::fmt::Result {
|
||||
let entry = self
|
||||
.template_cache
|
||||
.borrow_mut()
|
||||
.entry(template.template)
|
||||
.or_insert_with(|| Rc::new(StringCache::from_template(template).unwrap()))
|
||||
.clone();
|
||||
|
||||
for segment in entry.segments.iter() {
|
||||
match segment {
|
||||
Segment::Attr(idx) => {
|
||||
todo!("properly implement attrs in the macro");
|
||||
// let loc = &template.dynamic_attrs[*idx];
|
||||
// for attr in loc.attrs.iter() {
|
||||
// write!(buf, " {}=\"{}\"", attr.name, attr.value)?;
|
||||
// }
|
||||
}
|
||||
Segment::Dyn(idx) => match &template.dynamic_nodes[*idx].kind {
|
||||
DynamicNodeKind::Text { value, .. } => write!(buf, "{}", value)?,
|
||||
DynamicNodeKind::Fragment { children } => {
|
||||
for child in *children {
|
||||
self.render_template(buf, child)?;
|
||||
}
|
||||
//
|
||||
}
|
||||
DynamicNodeKind::Component { .. } => {
|
||||
//
|
||||
}
|
||||
},
|
||||
|
||||
Segment::PreRendered(text) => buf.push_str(&text),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn to_string_works() {
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let dynamic = 123;
|
||||
|
||||
cx.render(rsx! {
|
||||
div { class: "asdasdasd", class: "asdasdasd",
|
||||
"Hello world 1 -->"
|
||||
"{dynamic}"
|
||||
"<-- Hello world 2"
|
||||
div { "nest 1" }
|
||||
div {}
|
||||
div { "nest 2" }
|
||||
(0..5).map(|i| rsx! {
|
||||
div { "finalize {i}" }
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
let mut dom = VirtualDom::new(app);
|
||||
|
||||
let mut mutations = Vec::new();
|
||||
dom.rebuild(&mut mutations);
|
||||
|
||||
let cache = StringCache::from_template(&dom.base_scope().root_node()).unwrap();
|
||||
|
||||
dbg!(cache.segments);
|
||||
|
||||
let mut renderer = SsrRender::default();
|
||||
|
||||
dbg!(renderer.render_vdom(&dom));
|
||||
}
|
0
packages/ssr/tests/caches.rs
Normal file
0
packages/ssr/tests/caches.rs
Normal file
Loading…
Reference in a new issue