feat: support cached ssr
This commit is contained in:
1 parent
4a31b29703
commit
b6c0bce89c
11 files changed
+433
-178
No files matched your search
@@ -3,6 +3,7 @@ use crate::mutations::Mutation::*;
|
|||||||
use crate::nodes::VNode;
|
use crate::nodes::VNode;
|
||||||
use crate::nodes::{DynamicNode, DynamicNodeKind, TemplateNode};
|
use crate::nodes::{DynamicNode, DynamicNodeKind, TemplateNode};
|
||||||
use crate::virtualdom::VirtualDom;
|
use crate::virtualdom::VirtualDom;
|
||||||
|
use crate::TemplateAttribute;
|
||||||
|
|
||||||
impl VirtualDom {
|
impl VirtualDom {
|
||||||
/// Create this template and write its mutations
|
/// Create this template and write its mutations
|
||||||
@@ -112,10 +113,11 @@ impl VirtualDom {
|
|||||||
id,
|
id,
|
||||||
});
|
});
|
||||||
|
|
||||||
mutations.extend(attrs.into_iter().map(|attr| SetAttribute {
|
mutations.extend(attrs.into_iter().filter_map(|attr| match attr {
|
||||||
name: attr.name,
|
TemplateAttribute::Static { name, value, .. } => {
|
||||||
value: attr.value,
|
Some(SetAttribute { name, value, id })
|
||||||
id,
|
}
|
||||||
|
_ => None,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
children
|
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 {
|
impl ScopeState {
|
||||||
/// Create some text that's allocated along with the other vnodes
|
/// Create some text that's allocated along with the other vnodes
|
||||||
///
|
///
|
||||||
pub fn text(&self, args: Arguments) -> DynamicNode {
|
pub fn text<'a>(&'a self, args: Arguments) -> DynamicNode<'a> {
|
||||||
// let (text, _is_static) = self.raw_text(args);
|
let (text, _) = self.raw_text(args);
|
||||||
|
|
||||||
// VNode::Text(self.bump.alloc(VText {
|
DynamicNode {
|
||||||
// text,
|
kind: DynamicNodeKind::Text {
|
||||||
// id: Default::default(),
|
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>>(
|
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,
|
// AnyAttributeValue, AnyEvent, Attribute, AttributeValue, Component, Element, ElementId,
|
||||||
Attribute,
|
Attribute,
|
||||||
AttributeValue,
|
AttributeValue,
|
||||||
|
DynamicNode,
|
||||||
|
DynamicNodeKind,
|
||||||
Element,
|
Element,
|
||||||
EventPriority,
|
EventPriority,
|
||||||
LazyNodes,
|
LazyNodes,
|
||||||
@@ -94,8 +96,9 @@ pub use crate::innerlude::{
|
|||||||
/// This includes types like [`Scope`], [`Element`], and [`Component`].
|
/// This includes types like [`Scope`], [`Element`], and [`Component`].
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::innerlude::{
|
pub use crate::innerlude::{
|
||||||
Attribute, Element, EventPriority, LazyNodes, Listener, NodeFactory, Scope, ScopeId,
|
Attribute, DynamicNode, DynamicNodeKind, Element, EventPriority, LazyNodes, Listener,
|
||||||
ScopeState, TaskId, Template, TemplateAttribute, TemplateNode, UiEvent, VNode, VirtualDom,
|
NodeFactory, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute, TemplateNode,
|
||||||
|
UiEvent, VNode, VirtualDom,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ pub enum Mutation<'a> {
|
|||||||
},
|
},
|
||||||
|
|
||||||
ReplacePlaceholder {
|
ReplacePlaceholder {
|
||||||
path: &'static [u8],
|
|
||||||
m: usize,
|
m: usize,
|
||||||
|
path: &'static [u8],
|
||||||
},
|
},
|
||||||
|
|
||||||
AssignId {
|
AssignId {
|
||||||
|
|||||||
+41
-16
@@ -1,9 +1,5 @@
|
|||||||
use crate::{any_props::AnyProps, arena::ElementId};
|
use crate::{any_props::AnyProps, arena::ElementId};
|
||||||
use std::{
|
use std::{any::Any, cell::Cell, hash::Hasher};
|
||||||
any::{Any, TypeId},
|
|
||||||
cell::Cell,
|
|
||||||
num::NonZeroUsize,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub type TemplateId = &'static str;
|
pub type TemplateId = &'static str;
|
||||||
|
|
||||||
@@ -15,7 +11,7 @@ pub struct VNode<'a> {
|
|||||||
// When rendered, this template will be linked to its parent
|
// When rendered, this template will be linked to its parent
|
||||||
pub parent: Option<(*mut VNode<'static>, usize)>,
|
pub parent: Option<(*mut VNode<'static>, usize)>,
|
||||||
|
|
||||||
pub template: Template,
|
pub template: Template<'static>,
|
||||||
|
|
||||||
pub root_ids: &'a [Cell<ElementId>],
|
pub root_ids: &'a [Cell<ElementId>],
|
||||||
|
|
||||||
@@ -26,15 +22,38 @@ pub struct VNode<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Template {
|
pub struct Template<'a> {
|
||||||
pub id: &'static str,
|
pub id: &'a str,
|
||||||
pub roots: &'static [TemplateNode<'static>],
|
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
|
/// A weird-ish variant of VNodes with way more limited types
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum TemplateNode<'a> {
|
pub enum TemplateNode<'a> {
|
||||||
/// A simple element
|
|
||||||
Element {
|
Element {
|
||||||
tag: &'a str,
|
tag: &'a str,
|
||||||
namespace: Option<&'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
|
// Comes in with string interpolation or from format_args, include_str, etc
|
||||||
Text {
|
Text {
|
||||||
id: Cell<ElementId>,
|
id: Cell<ElementId>,
|
||||||
value: &'static str,
|
value: &'a str,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Anything that's coming in as an iterator
|
// Anything that's coming in as an iterator
|
||||||
@@ -72,11 +91,17 @@ pub enum DynamicNodeKind<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TemplateAttribute<'a> {
|
pub enum TemplateAttribute<'a> {
|
||||||
pub name: &'static str,
|
Static {
|
||||||
pub value: &'a str,
|
name: &'static str,
|
||||||
pub namespace: Option<&'static str>,
|
value: &'a str,
|
||||||
pub volatile: bool,
|
namespace: Option<&'static str>,
|
||||||
|
volatile: bool,
|
||||||
|
},
|
||||||
|
Dynamic {
|
||||||
|
name: &'static str,
|
||||||
|
index: usize,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AttributeLocation<'a> {
|
pub struct AttributeLocation<'a> {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ use slab::Slab;
|
|||||||
use std::collections::{BTreeSet, HashMap};
|
use std::collections::{BTreeSet, HashMap};
|
||||||
|
|
||||||
pub struct VirtualDom {
|
pub struct VirtualDom {
|
||||||
pub(crate) templates: HashMap<TemplateId, Template>,
|
pub(crate) templates: HashMap<TemplateId, Template<'static>>,
|
||||||
pub(crate) elements: Slab<ElementPath>,
|
pub(crate) elements: Slab<ElementPath>,
|
||||||
pub(crate) scopes: Slab<ScopeState>,
|
pub(crate) scopes: Slab<ScopeState>,
|
||||||
pub(crate) scope_stack: Vec<ScopeId>,
|
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
|
/// 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 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 {
|
impl Drop for VirtualDom {
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element {
|
|||||||
fn basic_template(cx: Scope) -> Element {
|
fn basic_template(cx: Scope) -> Element {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
|
(0..2).map(|i| rsx! {
|
||||||
|
div { "asd" }
|
||||||
|
}),
|
||||||
(0..2).map(|i| rsx! {
|
(0..2).map(|i| rsx! {
|
||||||
div { "asd" }
|
div { "asd" }
|
||||||
})
|
})
|
||||||
@@ -42,7 +45,10 @@ fn basic_prints() {
|
|||||||
let mut dom = VirtualDom::new(basic_template);
|
let mut dom = VirtualDom::new(basic_template);
|
||||||
|
|
||||||
let mut edits = Vec::new();
|
let mut edits = Vec::new();
|
||||||
|
dom.rebuild(&mut edits);
|
||||||
|
dbg!(edits);
|
||||||
|
|
||||||
|
let mut edits = Vec::new();
|
||||||
dom.rebuild(&mut edits);
|
dom.rebuild(&mut edits);
|
||||||
|
|
||||||
dbg!(edits);
|
dbg!(edits);
|
||||||
|
|||||||
+71
-57
@@ -21,6 +21,8 @@ mod ifmt;
|
|||||||
mod node;
|
mod node;
|
||||||
mod template;
|
mod template;
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
// Re-export the namespaces into each other
|
// Re-export the namespaces into each other
|
||||||
pub use component::*;
|
pub use component::*;
|
||||||
pub use element::*;
|
pub use element::*;
|
||||||
@@ -39,6 +41,9 @@ use syn::{
|
|||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct CallBody {
|
pub struct CallBody {
|
||||||
pub roots: Vec<BodyNode>,
|
pub roots: Vec<BodyNode>,
|
||||||
|
|
||||||
|
// set this after
|
||||||
|
pub inline_cx: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for CallBody {
|
impl Parse for CallBody {
|
||||||
@@ -55,7 +60,10 @@ impl Parse for CallBody {
|
|||||||
roots.push(node);
|
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
|
// We'll use the size of the vecs to determine the index of the dynamic node in the final
|
||||||
struct DynamicContext<'a> {
|
struct DynamicContext<'a> {
|
||||||
dynamic_nodes: Vec<&'a BodyNode>,
|
dynamic_nodes: Vec<&'a BodyNode>,
|
||||||
dynamic_attributes: Vec<&'a ElementAttrNamed>,
|
dynamic_attributes: HashMap<Vec<u8>, AttrLocation<'a>>,
|
||||||
dynamic_listeners: Vec<&'a ElementAttrNamed>,
|
current_path: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct AttrLocation<'a> {
|
||||||
|
attrs: Vec<&'a ElementAttrNamed>,
|
||||||
|
listeners: Vec<&'a ElementAttrNamed>,
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut context = DynamicContext {
|
let mut context = DynamicContext {
|
||||||
dynamic_nodes: vec![],
|
dynamic_nodes: vec![],
|
||||||
dynamic_attributes: vec![],
|
dynamic_attributes: HashMap::new(),
|
||||||
dynamic_listeners: vec![],
|
current_path: vec![],
|
||||||
};
|
};
|
||||||
|
|
||||||
fn render_static_node<'a>(root: &'a BodyNode, cx: &mut DynamicContext<'a>) -> TokenStream2 {
|
fn render_static_node<'a>(root: &'a BodyNode, cx: &mut DynamicContext<'a>) -> TokenStream2 {
|
||||||
@@ -81,10 +95,14 @@ impl ToTokens for CallBody {
|
|||||||
BodyNode::Element(el) => {
|
BodyNode::Element(el) => {
|
||||||
let el_name = &el.name;
|
let el_name = &el.name;
|
||||||
|
|
||||||
let children = {
|
let children = el.children.iter().enumerate().map(|(idx, root)| {
|
||||||
let children = el.children.iter().map(|root| render_static_node(root, cx));
|
cx.current_path.push(idx as u8);
|
||||||
quote! { #(#children),* }
|
let out = render_static_node(root, cx);
|
||||||
};
|
cx.current_path.pop();
|
||||||
|
out
|
||||||
|
});
|
||||||
|
|
||||||
|
let children = quote! { #(#children),* };
|
||||||
|
|
||||||
let attrs = el.attributes.iter().filter_map(|attr| {
|
let attrs = el.attributes.iter().filter_map(|attr| {
|
||||||
//
|
//
|
||||||
@@ -92,7 +110,7 @@ impl ToTokens for CallBody {
|
|||||||
ElementAttr::AttrText { name, value } if value.is_static() => {
|
ElementAttr::AttrText { name, value } if value.is_static() => {
|
||||||
let value = value.source.as_ref().unwrap();
|
let value = value.source.as_ref().unwrap();
|
||||||
Some(quote! {
|
Some(quote! {
|
||||||
::dioxus::core::TemplateAttribute {
|
::dioxus::core::TemplateAttribute::Static {
|
||||||
name: dioxus_elements::#el_name::#name.0,
|
name: dioxus_elements::#el_name::#name.0,
|
||||||
namespace: dioxus_elements::#el_name::#name.1,
|
namespace: dioxus_elements::#el_name::#name.1,
|
||||||
volatile: dioxus_elements::#el_name::#name.2,
|
volatile: dioxus_elements::#el_name::#name.2,
|
||||||
@@ -104,7 +122,7 @@ impl ToTokens for CallBody {
|
|||||||
ElementAttr::CustomAttrText { name, value } if value.is_static() => {
|
ElementAttr::CustomAttrText { name, value } if value.is_static() => {
|
||||||
let value = value.source.as_ref().unwrap();
|
let value = value.source.as_ref().unwrap();
|
||||||
Some(quote! {
|
Some(quote! {
|
||||||
::dioxus::core::TemplateAttribute {
|
::dioxus::core::TemplateAttribute::Static {
|
||||||
name: dioxus_elements::#el_name::#name.0,
|
name: dioxus_elements::#el_name::#name.0,
|
||||||
namespace: dioxus_elements::#el_name::#name.1,
|
namespace: dioxus_elements::#el_name::#name.1,
|
||||||
volatile: dioxus_elements::#el_name::#name.2,
|
volatile: dioxus_elements::#el_name::#name.2,
|
||||||
@@ -117,16 +135,21 @@ impl ToTokens for CallBody {
|
|||||||
| ElementAttr::AttrText { .. }
|
| ElementAttr::AttrText { .. }
|
||||||
| ElementAttr::CustomAttrText { .. }
|
| ElementAttr::CustomAttrText { .. }
|
||||||
| ElementAttr::CustomAttrExpression { .. } => {
|
| ElementAttr::CustomAttrExpression { .. } => {
|
||||||
|
// todo!();
|
||||||
let ct = cx.dynamic_attributes.len();
|
let ct = cx.dynamic_attributes.len();
|
||||||
cx.dynamic_attributes.push(attr);
|
// cx.dynamic_attributes.push(attr);
|
||||||
// quote! {}
|
// quote! {}
|
||||||
None
|
// None
|
||||||
// quote! { ::dioxus::core::TemplateAttribute::Dynamic(#ct) }
|
Some(quote! { ::dioxus::core::TemplateAttribute::Dynamic {
|
||||||
|
name: "asd",
|
||||||
|
index: #ct
|
||||||
|
} })
|
||||||
}
|
}
|
||||||
|
|
||||||
ElementAttr::EventTokens { .. } => {
|
ElementAttr::EventTokens { .. } => {
|
||||||
let ct = cx.dynamic_listeners.len();
|
// todo!();
|
||||||
cx.dynamic_listeners.push(attr);
|
// let ct = cx.dynamic_listeners.len();
|
||||||
|
// cx.dynamic_listeners.push(attr);
|
||||||
// quote! {}
|
// quote! {}
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
@@ -156,55 +179,46 @@ impl ToTokens for CallBody {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let root_printer = self
|
let root_printer = self.roots.iter().enumerate().map(|(idx, root)| {
|
||||||
.roots
|
context.current_path.push(idx as u8);
|
||||||
.iter()
|
let out = render_static_node(root, &mut context);
|
||||||
.map(|root| render_static_node(root, &mut context));
|
context.current_path.pop();
|
||||||
|
out
|
||||||
|
});
|
||||||
|
|
||||||
// Render and release the mutable borrow on context
|
// Render and release the mutable borrow on context
|
||||||
let roots = quote! { #( #root_printer ),* };
|
let roots = quote! { #( #root_printer ),* };
|
||||||
|
|
||||||
let node_printer = &context.dynamic_nodes;
|
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! {
|
let body = quote! {
|
||||||
// LazyNodes::new(move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
|
static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {
|
||||||
// __cx.template_ref(
|
id: ::dioxus::core::get_line_num!(),
|
||||||
// ::dioxus::core::Template {
|
roots: &[ #roots ]
|
||||||
// id: ::dioxus::core::get_line_num!(),
|
};
|
||||||
// roots: &[ #roots ]
|
::dioxus::core::VNode {
|
||||||
// },
|
node_id: Default::default(),
|
||||||
// __cx.bump().alloc([
|
parent: None,
|
||||||
// #( #node_printer ),*
|
template: TEMPLATE,
|
||||||
// ]),
|
root_ids: __cx.bump().alloc([]),
|
||||||
// __cx.bump().alloc([
|
dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]),
|
||||||
// #( #attr_printer ),*
|
dynamic_attrs: __cx.bump().alloc([]),
|
||||||
// ]),
|
}
|
||||||
// __cx.bump().alloc([
|
};
|
||||||
// #( #listener_printer ),*
|
|
||||||
// ]),
|
|
||||||
// None
|
|
||||||
// )
|
|
||||||
// })
|
|
||||||
|
|
||||||
|
if self.inline_cx {
|
||||||
::dioxus::core::LazyNodes::new( move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
|
out_tokens.append_all(quote! {
|
||||||
static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {
|
{
|
||||||
id: ::dioxus::core::get_line_num!(),
|
let __cx = cx;
|
||||||
roots: &[ #roots ]
|
#body
|
||||||
};
|
|
||||||
|
|
||||||
::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([]),
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
} else {
|
||||||
|
out_tokens.append_all(quote! {
|
||||||
|
::dioxus::core::LazyNodes::new( move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
|
||||||
|
#body
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+86
-88
@@ -1,9 +1,10 @@
|
|||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
use std::fmt::{Display, Formatter, Write};
|
use std::fmt::{Display, Formatter, Write};
|
||||||
|
mod template;
|
||||||
|
|
||||||
use dioxus_core::exports::bumpalo;
|
use dioxus_core::exports::bumpalo;
|
||||||
use dioxus_core::IntoVNode;
|
|
||||||
use dioxus_core::*;
|
use dioxus_core::*;
|
||||||
|
|
||||||
fn app(_cx: Scope) -> Element {
|
fn app(_cx: Scope) -> Element {
|
||||||
@@ -25,9 +26,7 @@ impl SsrRenderer {
|
|||||||
|
|
||||||
pub fn render_lazy<'a>(&'a mut self, f: LazyNodes<'a, '_>) -> String {
|
pub fn render_lazy<'a>(&'a mut self, f: LazyNodes<'a, '_>) -> String {
|
||||||
let scope = self.vdom.base_scope();
|
let scope = self.vdom.base_scope();
|
||||||
let factory = NodeFactory::new(scope);
|
let root = f.call(scope);
|
||||||
|
|
||||||
let root = f.into_vnode(factory);
|
|
||||||
format!(
|
format!(
|
||||||
"{:}",
|
"{:}",
|
||||||
TextRenderer {
|
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
|
// 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.
|
// arena is *definitely* shorter than the <'a> lifetime, and we return *owned* data - not borrowed data.
|
||||||
let scope = unsafe { &*scope };
|
let scope = unsafe { &*scope };
|
||||||
|
let root = f.call(scope);
|
||||||
let root = f.into_vnode(NodeFactory::new(scope));
|
|
||||||
|
|
||||||
let vdom = Some(&vdom);
|
let vdom = Some(&vdom);
|
||||||
|
|
||||||
@@ -148,99 +146,99 @@ impl<'a: 'c, 'c> TextRenderer<'a, '_, 'c> {
|
|||||||
il: u16,
|
il: u16,
|
||||||
last_node_was_text: &mut bool,
|
last_node_was_text: &mut bool,
|
||||||
) -> std::fmt::Result {
|
) -> std::fmt::Result {
|
||||||
match &node {
|
// match &node {
|
||||||
VNode::Text(text) => {
|
// VNode::Text(text) => {
|
||||||
if *last_node_was_text {
|
// if *last_node_was_text {
|
||||||
write!(f, "<!--spacer-->")?;
|
// write!(f, "<!--spacer-->")?;
|
||||||
}
|
// }
|
||||||
|
|
||||||
if self.cfg.indent {
|
// if self.cfg.indent {
|
||||||
for _ in 0..il {
|
// for _ in 0..il {
|
||||||
write!(f, " ")?;
|
// write!(f, " ")?;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
*last_node_was_text = true;
|
// *last_node_was_text = true;
|
||||||
|
|
||||||
write!(f, "{}", text.text)?
|
// write!(f, "{}", text.text)?
|
||||||
}
|
// }
|
||||||
VNode::Element(el) => {
|
// VNode::Element(el) => {
|
||||||
*last_node_was_text = false;
|
// *last_node_was_text = false;
|
||||||
|
|
||||||
if self.cfg.indent {
|
// if self.cfg.indent {
|
||||||
for _ in 0..il {
|
// for _ in 0..il {
|
||||||
write!(f, " ")?;
|
// 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 {
|
// match self.cfg.newline {
|
||||||
true => writeln!(f, ">")?,
|
// true => writeln!(f, ">")?,
|
||||||
false => write!(f, ">")?,
|
// false => write!(f, ">")?,
|
||||||
}
|
// }
|
||||||
|
|
||||||
if let Some(inner_html) = inner_html {
|
// if let Some(inner_html) = inner_html {
|
||||||
write!(f, "{}", inner_html)?;
|
// write!(f, "{}", inner_html)?;
|
||||||
} else {
|
// } else {
|
||||||
let mut last_node_was_text = false;
|
// let mut last_node_was_text = false;
|
||||||
for child in el.children {
|
// for child in el.children {
|
||||||
self.html_render(child, f, il + 1, &mut last_node_was_text)?;
|
// self.html_render(child, f, il + 1, &mut last_node_was_text)?;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
if self.cfg.newline {
|
// if self.cfg.newline {
|
||||||
writeln!(f)?;
|
// writeln!(f)?;
|
||||||
}
|
// }
|
||||||
if self.cfg.indent {
|
// if self.cfg.indent {
|
||||||
for _ in 0..il {
|
// for _ in 0..il {
|
||||||
write!(f, " ")?;
|
// write!(f, " ")?;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
write!(f, "</{}>", el.tag)?;
|
// write!(f, "</{}>", el.tag)?;
|
||||||
if self.cfg.newline {
|
// if self.cfg.newline {
|
||||||
writeln!(f)?;
|
// writeln!(f)?;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
VNode::Fragment(frag) => match frag.children.len() {
|
// VNode::Fragment(frag) => match frag.children.len() {
|
||||||
0 => {
|
// 0 => {
|
||||||
*last_node_was_text = false;
|
// *last_node_was_text = false;
|
||||||
if self.cfg.indent {
|
// if self.cfg.indent {
|
||||||
for _ in 0..il {
|
// for _ in 0..il {
|
||||||
write!(f, " ")?;
|
// write!(f, " ")?;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
write!(f, "<!--placeholder-->")?;
|
// write!(f, "<!--placeholder-->")?;
|
||||||
}
|
// }
|
||||||
_ => {
|
// _ => {
|
||||||
for child in frag.children {
|
// for child in frag.children {
|
||||||
self.html_render(child, f, il + 1, last_node_was_text)?;
|
// self.html_render(child, f, il + 1, last_node_was_text)?;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
VNode::Component(vcomp) => {
|
// VNode::Component(vcomp) => {
|
||||||
let idx = vcomp.scope.get().unwrap();
|
// let idx = vcomp.scope.get().unwrap();
|
||||||
|
|
||||||
if let (Some(vdom), false) = (self.vdom, self.cfg.skip_components) {
|
// 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, last_node_was_text)?;
|
// self.html_render(new_node, f, il + 1, last_node_was_text)?;
|
||||||
} else {
|
// } else {
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
VNode::Template(t) => {
|
// VNode::Template(t) => {
|
||||||
if let Some(vdom) = self.vdom {
|
// if let Some(vdom) = self.vdom {
|
||||||
todo!()
|
// todo!()
|
||||||
} else {
|
// } else {
|
||||||
panic!("Cannot render template without vdom");
|
// panic!("Cannot render template without vdom");
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
VNode::Placeholder(_) => {
|
// VNode::Placeholder(_) => {
|
||||||
todo!()
|
// todo!()
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
Whitespace-only changes.
Reference in new issue
Block a user