feat: components pass thru children
This commit is contained in:
1 parent
b6c0bce89c
commit
c6a6c3d0f8
17 files changed
+581
-298
No files matched your search
@@ -39,19 +39,6 @@ pub fn rsx(s: TokenStream) -> TokenStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A version of the rsx! macro that does not use templates. Used for testing diffing
|
|
||||||
#[proc_macro]
|
|
||||||
pub fn rsx_without_templates(s: TokenStream) -> TokenStream {
|
|
||||||
match syn::parse::<rsx::CallBody>(s) {
|
|
||||||
Err(err) => err.to_compile_error().into(),
|
|
||||||
Ok(body) => {
|
|
||||||
let mut tokens = proc_macro2::TokenStream::new();
|
|
||||||
body.to_tokens_without_template(&mut tokens);
|
|
||||||
tokens.into()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The render! macro makes it easy for developers to write jsx-style markup in their components.
|
/// The render! macro makes it easy for developers to write jsx-style markup in their components.
|
||||||
///
|
///
|
||||||
/// The render macro automatically renders rsx - making it unhygenic.
|
/// The render macro automatically renders rsx - making it unhygenic.
|
||||||
@@ -64,18 +51,10 @@ pub fn rsx_without_templates(s: TokenStream) -> TokenStream {
|
|||||||
pub fn render(s: TokenStream) -> TokenStream {
|
pub fn render(s: TokenStream) -> TokenStream {
|
||||||
match syn::parse::<rsx::CallBody>(s) {
|
match syn::parse::<rsx::CallBody>(s) {
|
||||||
Err(err) => err.to_compile_error().into(),
|
Err(err) => err.to_compile_error().into(),
|
||||||
Ok(body) => {
|
Ok(mut body) => {
|
||||||
let mut inner = proc_macro2::TokenStream::new();
|
body.inline_cx = true;
|
||||||
body.to_tokens_without_lazynodes(&mut inner);
|
body.into_token_stream().into()
|
||||||
quote::quote! {
|
|
||||||
{
|
|
||||||
let __cx = NodeFactory::new(&cx.scope);
|
|
||||||
Some(#inner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.into_token_stream()
|
|
||||||
.into(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+31
-14
@@ -3,7 +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;
|
use crate::{AttributeValue, TemplateAttribute};
|
||||||
|
|
||||||
impl VirtualDom {
|
impl VirtualDom {
|
||||||
/// Create this template and write its mutations
|
/// Create this template and write its mutations
|
||||||
@@ -52,25 +52,41 @@ impl VirtualDom {
|
|||||||
TemplateNode::DynamicText { .. } => 1,
|
TemplateNode::DynamicText { .. } => 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let mut cur_route = None;
|
||||||
|
|
||||||
// we're on top of a node that has a dynamic attribute for a descendant
|
// we're on top of a node that has a dynamic attribute for a descendant
|
||||||
// Set that attribute now before the stack gets in a weird state
|
// Set that attribute now before the stack gets in a weird state
|
||||||
while let Some(loc) = dynamic_attrs.next_if(|a| a.path[0] == root_idx as u8) {
|
while let Some(attr) = dynamic_attrs.next_if(|a| a.path[0] == root_idx as u8) {
|
||||||
|
if cur_route.is_none() {
|
||||||
|
cur_route = Some((self.next_element(template), &attr.path[1..]));
|
||||||
|
}
|
||||||
|
|
||||||
// Attach all the elementIDs to the nodes with dynamic content
|
// Attach all the elementIDs to the nodes with dynamic content
|
||||||
let id = self.next_element(template);
|
let (id, path) = cur_route.unwrap();
|
||||||
|
|
||||||
mutations.push(AssignId {
|
mutations.push(AssignId { path, id });
|
||||||
path: &loc.path[1..],
|
attr.mounted_element.set(id);
|
||||||
id,
|
|
||||||
});
|
|
||||||
|
|
||||||
loc.mounted_element.set(id);
|
match attr.value {
|
||||||
|
AttributeValue::Text(value) => {
|
||||||
|
mutations.push(SetAttribute {
|
||||||
|
name: attr.name,
|
||||||
|
value,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
for attr in loc.attrs.iter() {
|
AttributeValue::Listener(_) => {
|
||||||
mutations.push(SetAttribute {
|
//
|
||||||
name: attr.name,
|
}
|
||||||
value: attr.value,
|
|
||||||
id,
|
AttributeValue::Float(_) => todo!(),
|
||||||
});
|
AttributeValue::Int(_) => todo!(),
|
||||||
|
AttributeValue::Bool(_) => todo!(),
|
||||||
|
AttributeValue::Any(_) => todo!(),
|
||||||
|
|
||||||
|
// Optional attributes
|
||||||
|
AttributeValue::None => todo!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +110,7 @@ impl VirtualDom {
|
|||||||
node: &'a TemplateNode<'static>,
|
node: &'a TemplateNode<'static>,
|
||||||
) {
|
) {
|
||||||
match *node {
|
match *node {
|
||||||
|
// Todo: create the children's template
|
||||||
TemplateNode::Dynamic(_) => mutations.push(CreatePlaceholder),
|
TemplateNode::Dynamic(_) => mutations.push(CreatePlaceholder),
|
||||||
TemplateNode::Text(value) => mutations.push(CreateText { value }),
|
TemplateNode::Text(value) => mutations.push(CreateText { value }),
|
||||||
TemplateNode::DynamicText { .. } => mutations.push(CreateText {
|
TemplateNode::DynamicText { .. } => mutations.push(CreateText {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::{any::Any, cell::Cell};
|
use std::{any::Any, cell::Cell};
|
||||||
|
|
||||||
use crate::{arena::ElementId, nodes::Listener, scopes::ScopeId, virtualdom::VirtualDom};
|
use crate::{arena::ElementId, scopes::ScopeId, virtualdom::VirtualDom, Attribute, AttributeValue};
|
||||||
|
|
||||||
/// User Events are events that are shuttled from the renderer into the [`VirtualDom`] through the scheduler channel.
|
/// User Events are events that are shuttled from the renderer into the [`VirtualDom`] through the scheduler channel.
|
||||||
///
|
///
|
||||||
@@ -106,12 +106,12 @@ impl VirtualDom {
|
|||||||
|
|
||||||
let location = unsafe { &mut *path.template }
|
let location = unsafe { &mut *path.template }
|
||||||
.dynamic_attrs
|
.dynamic_attrs
|
||||||
.iter_mut()
|
.iter()
|
||||||
.position(|attr| attr.mounted_element.get() == event.element)?;
|
.position(|attr| attr.mounted_element.get() == event.element)?;
|
||||||
|
|
||||||
let mut index = Some((path.template, location));
|
let mut index = Some((path.template, location));
|
||||||
|
|
||||||
let mut listeners = Vec::<&mut Listener>::new();
|
let mut listeners = Vec::<&Attribute>::new();
|
||||||
|
|
||||||
while let Some((raw_parent, dyn_index)) = index {
|
while let Some((raw_parent, dyn_index)) = index {
|
||||||
let parent = unsafe { &mut *raw_parent };
|
let parent = unsafe { &mut *raw_parent };
|
||||||
@@ -120,17 +120,18 @@ impl VirtualDom {
|
|||||||
listeners.extend(
|
listeners.extend(
|
||||||
parent
|
parent
|
||||||
.dynamic_attrs
|
.dynamic_attrs
|
||||||
.iter_mut()
|
.iter()
|
||||||
.filter(|attr| is_path_ascendant(attr.path, path))
|
.filter(|attr| is_path_ascendant(attr.path, path))
|
||||||
.map(|f| f.listeners.iter_mut().filter(|f| f.name == event.name))
|
.filter(|attr| attr.name == event.name),
|
||||||
.flatten(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
index = parent.parent;
|
index = parent.parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
for listener in listeners {
|
for listener in listeners {
|
||||||
(listener.callback)(&event.event);
|
if let AttributeValue::Listener(listener) = &listener.value {
|
||||||
|
(listener.borrow_mut())(&event.event)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(())
|
Some(())
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
use std::{cell::Cell, fmt::Arguments};
|
use std::{cell::Cell, fmt::Arguments};
|
||||||
|
|
||||||
|
use bumpalo::Bump;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
any_props::{AnyProps, VComponentProps},
|
||||||
arena::ElementId,
|
arena::ElementId,
|
||||||
innerlude::{DynamicNode, DynamicNodeKind},
|
innerlude::{DynamicNode, DynamicNodeKind},
|
||||||
LazyNodes, ScopeState, VNode,
|
Attribute, AttributeValue, Element, LazyNodes, Properties, Scope, ScopeState, VNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl ScopeState {
|
impl ScopeState {
|
||||||
@@ -50,6 +53,56 @@ impl ScopeState {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a new [`Attribute`]
|
||||||
|
pub fn attr<'a>(
|
||||||
|
&'a self,
|
||||||
|
name: &'static str,
|
||||||
|
val: impl IntoAttributeValue<'a>,
|
||||||
|
namespace: Option<&'static str>,
|
||||||
|
is_volatile: bool,
|
||||||
|
) -> Attribute<'a> {
|
||||||
|
Attribute {
|
||||||
|
name,
|
||||||
|
namespace,
|
||||||
|
mounted_element: Cell::new(ElementId(0)),
|
||||||
|
path: &[0],
|
||||||
|
value: val.into_value(self.bump()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new [`VNode::Component`]
|
||||||
|
pub fn component<'a, P>(
|
||||||
|
&'a self,
|
||||||
|
component: fn(Scope<'a, P>) -> Element,
|
||||||
|
props: P,
|
||||||
|
fn_name: &'static str,
|
||||||
|
) -> DynamicNode<'a>
|
||||||
|
where
|
||||||
|
P: Properties + 'a,
|
||||||
|
{
|
||||||
|
let props = self.bump().alloc(props);
|
||||||
|
let detached = unsafe { std::mem::transmute(component) };
|
||||||
|
let vcomp = VComponentProps::new(detached, P::memoize, props);
|
||||||
|
let as_dyn = self.bump().alloc(vcomp) as &mut dyn AnyProps;
|
||||||
|
let detached_dyn: *mut dyn AnyProps = unsafe { std::mem::transmute(as_dyn) };
|
||||||
|
|
||||||
|
// todo: clean up borrowed props
|
||||||
|
// if !P::IS_STATIC {
|
||||||
|
// let vcomp = &*vcomp;
|
||||||
|
// let vcomp = unsafe { std::mem::transmute(vcomp) };
|
||||||
|
// self.scope.items.borrow_mut().borrowed_props.push(vcomp);
|
||||||
|
// }
|
||||||
|
|
||||||
|
DynamicNode {
|
||||||
|
path: &[],
|
||||||
|
kind: DynamicNodeKind::Component {
|
||||||
|
name: fn_name,
|
||||||
|
can_memoize: P::IS_STATIC,
|
||||||
|
props: detached_dyn,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IntoVnode<'a, A = ()> {
|
pub trait IntoVnode<'a, A = ()> {
|
||||||
@@ -61,3 +114,56 @@ impl<'a, 'b> IntoVnode<'a> for LazyNodes<'a, 'b> {
|
|||||||
self.call(cx)
|
self.call(cx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<'a, 'b> IntoVnode<'a> for VNode<'a> {
|
||||||
|
fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a> {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, 'b> IntoVnode<'a> for &'a VNode<'a> {
|
||||||
|
fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a> {
|
||||||
|
VNode {
|
||||||
|
node_id: self.node_id.clone(),
|
||||||
|
parent: self.parent,
|
||||||
|
template: self.template,
|
||||||
|
root_ids: self.root_ids,
|
||||||
|
dynamic_nodes: self.dynamic_nodes,
|
||||||
|
dynamic_attrs: self.dynamic_attrs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A value that can be converted into an attribute value
|
||||||
|
pub trait IntoAttributeValue<'a> {
|
||||||
|
/// Convert into an attribute value
|
||||||
|
fn into_value(self, bump: &'a Bump) -> AttributeValue<'a>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> IntoAttributeValue<'a> for &'a str {
|
||||||
|
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||||
|
AttributeValue::Text(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> IntoAttributeValue<'a> for f32 {
|
||||||
|
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||||
|
AttributeValue::Float(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> IntoAttributeValue<'a> for i32 {
|
||||||
|
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||||
|
AttributeValue::Int(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> IntoAttributeValue<'a> for bool {
|
||||||
|
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||||
|
AttributeValue::Bool(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
|
||||||
|
fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
|
||||||
|
use bumpalo::core_alloc::fmt::Write;
|
||||||
|
let mut str_buf = bumpalo::collections::String::new_in(bump);
|
||||||
|
str_buf.write_fmt(self).unwrap();
|
||||||
|
AttributeValue::Text(str_buf.into_bump_str())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ mod garbage;
|
|||||||
mod lazynodes;
|
mod lazynodes;
|
||||||
mod mutations;
|
mod mutations;
|
||||||
mod nodes;
|
mod nodes;
|
||||||
|
mod properties;
|
||||||
mod scope_arena;
|
mod scope_arena;
|
||||||
mod scopes;
|
mod scopes;
|
||||||
mod virtualdom;
|
mod virtualdom;
|
||||||
@@ -20,10 +21,10 @@ pub(crate) mod innerlude {
|
|||||||
pub use crate::element::Element;
|
pub use crate::element::Element;
|
||||||
pub use crate::events::*;
|
pub use crate::events::*;
|
||||||
pub use crate::future_container::*;
|
pub use crate::future_container::*;
|
||||||
|
pub use crate::lazynodes::*;
|
||||||
pub use crate::mutations::*;
|
pub use crate::mutations::*;
|
||||||
pub use crate::nodes::*;
|
pub use crate::nodes::*;
|
||||||
// pub use crate::properties::*;
|
pub use crate::properties::*;
|
||||||
pub use crate::lazynodes::*;
|
|
||||||
pub use crate::scopes::*;
|
pub use crate::scopes::*;
|
||||||
pub use crate::virtualdom::*;
|
pub use crate::virtualdom::*;
|
||||||
|
|
||||||
@@ -66,6 +67,7 @@ pub(crate) mod innerlude {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub use crate::innerlude::{
|
pub use crate::innerlude::{
|
||||||
|
fc_to_builder,
|
||||||
// AnyAttributeValue, AnyEvent, Attribute, AttributeValue, Component, Element, ElementId,
|
// AnyAttributeValue, AnyEvent, Attribute, AttributeValue, Component, Element, ElementId,
|
||||||
Attribute,
|
Attribute,
|
||||||
AttributeValue,
|
AttributeValue,
|
||||||
@@ -74,8 +76,8 @@ pub use crate::innerlude::{
|
|||||||
Element,
|
Element,
|
||||||
EventPriority,
|
EventPriority,
|
||||||
LazyNodes,
|
LazyNodes,
|
||||||
Listener,
|
|
||||||
NodeFactory,
|
NodeFactory,
|
||||||
|
Properties,
|
||||||
Scope,
|
Scope,
|
||||||
ScopeId,
|
ScopeId,
|
||||||
ScopeState,
|
ScopeState,
|
||||||
@@ -96,9 +98,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, DynamicNode, DynamicNodeKind, Element, EventPriority, LazyNodes, Listener,
|
fc_to_builder, Attribute, DynamicNode, DynamicNodeKind, Element, EventPriority, LazyNodes,
|
||||||
NodeFactory, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute, TemplateNode,
|
NodeFactory, Properties, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute,
|
||||||
UiEvent, VNode, VirtualDom,
|
TemplateNode, UiEvent, VNode, VirtualDom,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ pub struct Renderer<'a> {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Mutation<'a> {
|
pub enum Mutation<'a> {
|
||||||
SetAttribute {
|
SetAttribute {
|
||||||
name: &'static str,
|
name: &'a str,
|
||||||
value: &'a str,
|
value: &'a str,
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
},
|
},
|
||||||
|
|||||||
+21
-20
@@ -1,5 +1,9 @@
|
|||||||
use crate::{any_props::AnyProps, arena::ElementId};
|
use crate::{any_props::AnyProps, arena::ElementId};
|
||||||
use std::{any::Any, cell::Cell, hash::Hasher};
|
use std::{
|
||||||
|
any::Any,
|
||||||
|
cell::{Cell, RefCell},
|
||||||
|
hash::Hasher,
|
||||||
|
};
|
||||||
|
|
||||||
pub type TemplateId = &'static str;
|
pub type TemplateId = &'static str;
|
||||||
|
|
||||||
@@ -8,17 +12,16 @@ pub struct VNode<'a> {
|
|||||||
// The ID assigned for the root of this template
|
// The ID assigned for the root of this template
|
||||||
pub node_id: Cell<ElementId>,
|
pub node_id: Cell<ElementId>,
|
||||||
|
|
||||||
// When rendered, this template will be linked to its parent
|
// When rendered, this template will be linked to its parent manually
|
||||||
pub parent: Option<(*mut VNode<'static>, usize)>,
|
pub parent: Option<(*mut VNode<'static>, usize)>,
|
||||||
|
|
||||||
pub template: Template<'static>,
|
pub template: Template<'static>,
|
||||||
|
|
||||||
pub root_ids: &'a [Cell<ElementId>],
|
pub root_ids: &'a [Cell<ElementId>],
|
||||||
|
|
||||||
/// All the dynamic nodes for a template
|
pub dynamic_nodes: &'a [DynamicNode<'a>],
|
||||||
pub dynamic_nodes: &'a mut [DynamicNode<'a>],
|
|
||||||
|
|
||||||
pub dynamic_attrs: &'a mut [AttributeLocation<'a>],
|
pub dynamic_attrs: &'a [Attribute<'a>],
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
@@ -75,6 +78,7 @@ pub enum DynamicNodeKind<'a> {
|
|||||||
// IE in caps or with underscores
|
// IE in caps or with underscores
|
||||||
Component {
|
Component {
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
|
can_memoize: bool,
|
||||||
props: *mut dyn AnyProps,
|
props: *mut dyn AnyProps,
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -104,18 +108,12 @@ pub enum TemplateAttribute<'a> {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct AttributeLocation<'a> {
|
|
||||||
pub mounted_element: Cell<ElementId>,
|
|
||||||
pub attrs: &'a mut [Attribute<'a>],
|
|
||||||
pub listeners: &'a mut [Listener<'a>],
|
|
||||||
pub path: &'static [u8],
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Attribute<'a> {
|
pub struct Attribute<'a> {
|
||||||
pub name: &'static str,
|
pub name: &'a str,
|
||||||
pub value: &'a str,
|
pub value: AttributeValue<'a>,
|
||||||
pub namespace: Option<&'static str>,
|
pub namespace: Option<&'static str>,
|
||||||
|
pub mounted_element: Cell<ElementId>,
|
||||||
|
pub path: &'static [u8],
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum AttributeValue<'a> {
|
pub enum AttributeValue<'a> {
|
||||||
@@ -123,7 +121,15 @@ pub enum AttributeValue<'a> {
|
|||||||
Float(f32),
|
Float(f32),
|
||||||
Int(i32),
|
Int(i32),
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
|
Listener(RefCell<&'a mut dyn FnMut(&dyn Any)>),
|
||||||
Any(&'a dyn AnyValue),
|
Any(&'a dyn AnyValue),
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> AttributeValue<'a> {
|
||||||
|
fn is_listener(&self) -> bool {
|
||||||
|
matches!(self, AttributeValue::Listener(_))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait AnyValue {
|
pub trait AnyValue {
|
||||||
@@ -142,11 +148,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Listener<'a> {
|
|
||||||
pub name: &'static str,
|
|
||||||
pub callback: &'a mut dyn FnMut(&dyn Any),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn what_are_the_sizes() {
|
fn what_are_the_sizes() {
|
||||||
dbg!(std::mem::size_of::<VNode>());
|
dbg!(std::mem::size_of::<VNode>());
|
||||||
|
|||||||
@@ -0,0 +1,170 @@
|
|||||||
|
use crate::innerlude::*;
|
||||||
|
|
||||||
|
pub struct FragmentProps<'a>(Element<'a>);
|
||||||
|
pub struct FragmentBuilder<'a, const BUILT: bool>(Element<'a>);
|
||||||
|
impl<'a> FragmentBuilder<'a, false> {
|
||||||
|
pub fn children(self, children: Element<'a>) -> FragmentBuilder<'a, true> {
|
||||||
|
FragmentBuilder(children)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a, const A: bool> FragmentBuilder<'a, A> {
|
||||||
|
pub fn build(self) -> FragmentProps<'a> {
|
||||||
|
FragmentProps(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Access the children elements passed into the component
|
||||||
|
///
|
||||||
|
/// This enables patterns where a component is passed children from its parent.
|
||||||
|
///
|
||||||
|
/// ## Details
|
||||||
|
///
|
||||||
|
/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions
|
||||||
|
/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure
|
||||||
|
/// on the props that takes Context.
|
||||||
|
///
|
||||||
|
/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other
|
||||||
|
/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's
|
||||||
|
/// props are valid for the static lifetime.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// fn App(cx: Scope) -> Element {
|
||||||
|
/// cx.render(rsx!{
|
||||||
|
/// CustomCard {
|
||||||
|
/// h1 {}
|
||||||
|
/// p {}
|
||||||
|
/// }
|
||||||
|
/// })
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// #[derive(PartialEq, Props)]
|
||||||
|
/// struct CardProps {
|
||||||
|
/// children: Element
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn CustomCard(cx: Scope<CardProps>) -> Element {
|
||||||
|
/// cx.render(rsx!{
|
||||||
|
/// div {
|
||||||
|
/// h1 {"Title card"}
|
||||||
|
/// {cx.props.children}
|
||||||
|
/// }
|
||||||
|
/// })
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
impl<'a> Properties for FragmentProps<'a> {
|
||||||
|
type Builder = FragmentBuilder<'a, false>;
|
||||||
|
const IS_STATIC: bool = false;
|
||||||
|
fn builder() -> Self::Builder {
|
||||||
|
FragmentBuilder(None)
|
||||||
|
}
|
||||||
|
unsafe fn memoize(&self, _other: &Self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// /// Create inline fragments using Component syntax.
|
||||||
|
// ///
|
||||||
|
// /// ## Details
|
||||||
|
// ///
|
||||||
|
// /// Fragments capture a series of children without rendering extra nodes.
|
||||||
|
// ///
|
||||||
|
// /// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and
|
||||||
|
// /// a key is needed to identify each item.
|
||||||
|
// ///
|
||||||
|
// /// ## Example
|
||||||
|
// ///
|
||||||
|
// /// ```rust, ignore
|
||||||
|
// /// rsx!{
|
||||||
|
// /// Fragment { key: "abc" }
|
||||||
|
// /// }
|
||||||
|
// /// ```
|
||||||
|
// ///
|
||||||
|
// /// ## Usage
|
||||||
|
// ///
|
||||||
|
// /// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
|
||||||
|
// /// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments.
|
||||||
|
// ///
|
||||||
|
// /// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
|
||||||
|
// ///
|
||||||
|
// /// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
|
||||||
|
// #[allow(non_upper_case_globals, non_snake_case)]
|
||||||
|
// pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
|
||||||
|
// let i = cx.props.0.as_ref().map(|f| f.decouple());
|
||||||
|
// cx.render(LazyNodes::new(|f| f.fragment_from_iter(i)))
|
||||||
|
// }
|
||||||
|
|
||||||
|
/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
|
||||||
|
/// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the
|
||||||
|
/// derive macro to implement the `Properties` trait automatically as guarantee that your memoization strategy is safe.
|
||||||
|
///
|
||||||
|
/// If your props are 'static, then Dioxus will require that they also be PartialEq for the derived memoize strategy. However,
|
||||||
|
/// if your props borrow data, then the memoization strategy will simply default to "false" and the PartialEq will be ignored.
|
||||||
|
/// This tends to be useful when props borrow something that simply cannot be compared (IE a reference to a closure);
|
||||||
|
///
|
||||||
|
/// By default, the memoization strategy is very conservative, but can be tuned to be more aggressive manually. However,
|
||||||
|
/// this is only safe if the props are 'static - otherwise you might borrow references after-free.
|
||||||
|
///
|
||||||
|
/// We strongly suggest that any changes to memoization be done at the "PartialEq" level for 'static props. Additionally,
|
||||||
|
/// we advise the use of smart pointers in cases where memoization is important.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// For props that are 'static:
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// #[derive(Props, PartialEq)]
|
||||||
|
/// struct MyProps {
|
||||||
|
/// data: String
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// For props that borrow:
|
||||||
|
///
|
||||||
|
/// ```rust, ignore
|
||||||
|
/// #[derive(Props)]
|
||||||
|
/// struct MyProps<'a >{
|
||||||
|
/// data: &'a str
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub trait Properties: Sized {
|
||||||
|
/// The type of the builder for this component.
|
||||||
|
/// Used to create "in-progress" versions of the props.
|
||||||
|
type Builder;
|
||||||
|
|
||||||
|
/// An indication if these props are can be memoized automatically.
|
||||||
|
const IS_STATIC: bool;
|
||||||
|
|
||||||
|
/// Create a builder for this component.
|
||||||
|
fn builder() -> Self::Builder;
|
||||||
|
|
||||||
|
/// Memoization can only happen if the props are valid for the 'static lifetime
|
||||||
|
///
|
||||||
|
/// # Safety
|
||||||
|
/// The user must know if their props are static, but if they make a mistake, UB happens
|
||||||
|
/// Therefore it's unsafe to memoize.
|
||||||
|
unsafe fn memoize(&self, other: &Self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Properties for () {
|
||||||
|
type Builder = EmptyBuilder;
|
||||||
|
const IS_STATIC: bool = true;
|
||||||
|
fn builder() -> Self::Builder {
|
||||||
|
EmptyBuilder {}
|
||||||
|
}
|
||||||
|
unsafe fn memoize(&self, _other: &Self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We allow components to use the () generic parameter if they have no props. This impl enables the "build" method
|
||||||
|
// that the macros use to anonymously complete prop construction.
|
||||||
|
pub struct EmptyBuilder;
|
||||||
|
impl EmptyBuilder {
|
||||||
|
pub fn build(self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern
|
||||||
|
/// to initialize a component's props.
|
||||||
|
pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder {
|
||||||
|
T::builder()
|
||||||
|
}
|
||||||
@@ -16,10 +16,7 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element {
|
|||||||
p { "you're great!" }
|
p { "you're great!" }
|
||||||
div { background_color: "red",
|
div { background_color: "red",
|
||||||
h1 { "var" }
|
h1 { "var" }
|
||||||
div {
|
div { b { "asd" } "not great" }
|
||||||
b { "asd" }
|
|
||||||
"not great"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
p { "you're great!" }
|
p { "you're great!" }
|
||||||
}
|
}
|
||||||
@@ -30,16 +27,17 @@ 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! {
|
(0..2).map(|i| rsx! { div { "asd {i}" } })
|
||||||
div { "asd" }
|
basic_child { }
|
||||||
}),
|
|
||||||
(0..2).map(|i| rsx! {
|
|
||||||
div { "asd" }
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A beautiful component
|
||||||
|
fn basic_child(cx: Scope) -> Element {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn basic_prints() {
|
fn basic_prints() {
|
||||||
let mut dom = VirtualDom::new(basic_template);
|
let mut dom = VirtualDom::new(basic_template);
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ pub mod on {
|
|||||||
factory: NodeFactory<'a>,
|
factory: NodeFactory<'a>,
|
||||||
mut callback: impl FnMut($wrapper) + 'a,
|
mut callback: impl FnMut($wrapper) + 'a,
|
||||||
// mut callback: impl FnMut(UiEvent<$data>) + 'a,
|
// mut callback: impl FnMut(UiEvent<$data>) + 'a,
|
||||||
) -> Listener<'a>
|
) -> Attribute<'a>
|
||||||
{
|
{
|
||||||
// let bump = &factory.bump();
|
// let bump = &factory.bump();
|
||||||
|
|
||||||
|
|||||||
@@ -117,8 +117,6 @@ impl ToTokens for Component {
|
|||||||
let name = &self.name;
|
let name = &self.name;
|
||||||
let prop_gen_args = &self.prop_gen_args;
|
let prop_gen_args = &self.prop_gen_args;
|
||||||
|
|
||||||
let mut has_key = None;
|
|
||||||
|
|
||||||
let builder = match &self.manual_props {
|
let builder = match &self.manual_props {
|
||||||
Some(manual_props) => {
|
Some(manual_props) => {
|
||||||
let mut toks = quote! {
|
let mut toks = quote! {
|
||||||
@@ -126,7 +124,7 @@ impl ToTokens for Component {
|
|||||||
};
|
};
|
||||||
for field in &self.fields {
|
for field in &self.fields {
|
||||||
if field.name == "key" {
|
if field.name == "key" {
|
||||||
has_key = Some(field);
|
// skip keys
|
||||||
} else {
|
} else {
|
||||||
let name = &field.name;
|
let name = &field.name;
|
||||||
let val = &field.content;
|
let val = &field.content;
|
||||||
@@ -149,18 +147,22 @@ impl ToTokens for Component {
|
|||||||
};
|
};
|
||||||
for field in &self.fields {
|
for field in &self.fields {
|
||||||
match field.name.to_string().as_str() {
|
match field.name.to_string().as_str() {
|
||||||
"key" => {
|
"key" => {}
|
||||||
//
|
|
||||||
has_key = Some(field);
|
|
||||||
}
|
|
||||||
_ => toks.append_all(quote! {#field}),
|
_ => toks.append_all(quote! {#field}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.children.is_empty() {
|
if !self.children.is_empty() {
|
||||||
let childs = &self.children;
|
let renderer = TemplateRenderer {
|
||||||
|
roots: &self.children,
|
||||||
|
};
|
||||||
|
|
||||||
toks.append_all(quote! {
|
toks.append_all(quote! {
|
||||||
.children(__cx.create_children([ #( #childs ),* ]))
|
.children(
|
||||||
|
Some({
|
||||||
|
#renderer
|
||||||
|
})
|
||||||
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,25 +173,12 @@ impl ToTokens for Component {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let key_token = match has_key {
|
|
||||||
Some(field) => {
|
|
||||||
let inners = &field.content;
|
|
||||||
if let ContentField::Formatted(ifmt) = inners {
|
|
||||||
quote! { Some(#ifmt) }
|
|
||||||
} else {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => quote! { None },
|
|
||||||
};
|
|
||||||
|
|
||||||
let fn_name = self.name.segments.last().unwrap().ident.to_string();
|
let fn_name = self.name.segments.last().unwrap().ident.to_string();
|
||||||
|
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
__cx.component(
|
__cx.component(
|
||||||
#name,
|
#name,
|
||||||
#builder,
|
#builder,
|
||||||
#key_token,
|
|
||||||
#fn_name
|
#fn_name
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
+136
-164
@@ -70,147 +70,14 @@ impl Parse for CallBody {
|
|||||||
/// Serialize the same way, regardless of flavor
|
/// Serialize the same way, regardless of flavor
|
||||||
impl ToTokens for CallBody {
|
impl ToTokens for CallBody {
|
||||||
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
|
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
|
||||||
// As we print out the dynamic nodes, we want to keep track of them in a linear fashion
|
let body = TemplateRenderer { roots: &self.roots };
|
||||||
// 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: 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: HashMap::new(),
|
|
||||||
current_path: vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
fn render_static_node<'a>(root: &'a BodyNode, cx: &mut DynamicContext<'a>) -> TokenStream2 {
|
|
||||||
match root {
|
|
||||||
BodyNode::Element(el) => {
|
|
||||||
let el_name = &el.name;
|
|
||||||
|
|
||||||
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| {
|
|
||||||
//
|
|
||||||
match &attr.attr {
|
|
||||||
ElementAttr::AttrText { name, value } if value.is_static() => {
|
|
||||||
let value = value.source.as_ref().unwrap();
|
|
||||||
Some(quote! {
|
|
||||||
::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,
|
|
||||||
value: #value,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
ElementAttr::CustomAttrText { name, value } if value.is_static() => {
|
|
||||||
let value = value.source.as_ref().unwrap();
|
|
||||||
Some(quote! {
|
|
||||||
::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,
|
|
||||||
value: #value,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
ElementAttr::AttrExpression { .. }
|
|
||||||
| ElementAttr::AttrText { .. }
|
|
||||||
| ElementAttr::CustomAttrText { .. }
|
|
||||||
| ElementAttr::CustomAttrExpression { .. } => {
|
|
||||||
// todo!();
|
|
||||||
let ct = cx.dynamic_attributes.len();
|
|
||||||
// cx.dynamic_attributes.push(attr);
|
|
||||||
// quote! {}
|
|
||||||
// None
|
|
||||||
Some(quote! { ::dioxus::core::TemplateAttribute::Dynamic {
|
|
||||||
name: "asd",
|
|
||||||
index: #ct
|
|
||||||
} })
|
|
||||||
}
|
|
||||||
|
|
||||||
ElementAttr::EventTokens { .. } => {
|
|
||||||
// todo!();
|
|
||||||
// let ct = cx.dynamic_listeners.len();
|
|
||||||
// cx.dynamic_listeners.push(attr);
|
|
||||||
// quote! {}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
quote! {
|
|
||||||
::dioxus::core::TemplateNode::Element {
|
|
||||||
tag: dioxus_elements::#el_name::TAG_NAME,
|
|
||||||
namespace: dioxus_elements::#el_name::NAME_SPACE,
|
|
||||||
attrs: &[ #(#attrs),* ],
|
|
||||||
children: &[ #children ],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BodyNode::Text(text) if text.is_static() => {
|
|
||||||
let text = text.source.as_ref().unwrap();
|
|
||||||
quote! { ::dioxus::core::TemplateNode::Text(#text) }
|
|
||||||
}
|
|
||||||
|
|
||||||
BodyNode::RawExpr(_) | BodyNode::Component(_) | BodyNode::Text(_) => {
|
|
||||||
let ct = cx.dynamic_nodes.len();
|
|
||||||
cx.dynamic_nodes.push(root);
|
|
||||||
quote! { ::dioxus::core::TemplateNode::Dynamic(#ct) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 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([]),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if self.inline_cx {
|
if self.inline_cx {
|
||||||
out_tokens.append_all(quote! {
|
out_tokens.append_all(quote! {
|
||||||
{
|
Some({
|
||||||
let __cx = cx;
|
let __cx = cx;
|
||||||
#body
|
#body
|
||||||
}
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
out_tokens.append_all(quote! {
|
out_tokens.append_all(quote! {
|
||||||
@@ -222,42 +89,147 @@ impl ToTokens for CallBody {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CallBody {
|
pub struct TemplateRenderer<'a> {
|
||||||
pub fn to_tokens_without_template(&self, out_tokens: &mut TokenStream2) {
|
pub roots: &'a [BodyNode],
|
||||||
|
}
|
||||||
|
|
||||||
// let children = &self.roots;
|
impl<'a> ToTokens for TemplateRenderer<'a> {
|
||||||
// let inner = if children.len() == 1 {
|
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
|
||||||
// let inner = &self.roots[0];
|
let mut context = DynamicContext {
|
||||||
// quote! { #inner }
|
dynamic_nodes: vec![],
|
||||||
// } else {
|
dynamic_attributes: vec![],
|
||||||
// quote! { __cx.fragment_root([ #(#children),* ]) }
|
current_path: vec![],
|
||||||
// };
|
};
|
||||||
|
|
||||||
// out_tokens.append_all(quote! {
|
let root_printer = self.roots.iter().enumerate().map(|(idx, root)| {
|
||||||
// LazyNodes::new(move |__cx: NodeFactory| -> VNode {
|
context.current_path.push(idx as u8);
|
||||||
// use dioxus_elements::{GlobalAttributes, SvgAttributes};
|
let out = context.render_static_node(root);
|
||||||
// #inner
|
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 dyn_attr_printer = &context.dynamic_attributes;
|
||||||
|
|
||||||
pub fn to_tokens_without_lazynodes(&self, out_tokens: &mut TokenStream2) {
|
|
||||||
out_tokens.append_all(quote! {
|
out_tokens.append_all(quote! {
|
||||||
static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {
|
static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {
|
||||||
id: ::dioxus::core::get_line_num!(),
|
id: ::dioxus::core::get_line_num!(),
|
||||||
roots: &[]
|
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([ #( #dyn_attr_printer ),* ]),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// As we print out the dynamic nodes, we want to keep track of them in a linear fashion
|
||||||
|
// We'll use the size of the vecs to determine the index of the dynamic node in the final
|
||||||
|
pub struct DynamicContext<'a> {
|
||||||
|
dynamic_nodes: Vec<&'a BodyNode>,
|
||||||
|
dynamic_attributes: Vec<&'a ElementAttrNamed>,
|
||||||
|
current_path: Vec<u8>,
|
||||||
|
}
|
||||||
|
|
||||||
LazyNodes::new( move | __cx: ::dioxus::core::NodeFactory| -> ::dioxus::core::VNode {
|
impl<'a> DynamicContext<'a> {
|
||||||
::dioxus::core::VNode {
|
fn render_static_node(&mut self, root: &'a BodyNode) -> TokenStream2 {
|
||||||
node_id: Default::default(),
|
match root {
|
||||||
parent: None,
|
BodyNode::Element(el) => {
|
||||||
template: &TEMPLATE,
|
let el_name = &el.name;
|
||||||
root_ids: __cx.bump().alloc([]),
|
|
||||||
dynamic_nodes: __cx.bump().alloc([]),
|
// dynamic attributes
|
||||||
dynamic_attrs: __cx.bump().alloc([]),
|
// [0]
|
||||||
|
// [0, 1]
|
||||||
|
// [0, 1]
|
||||||
|
// [0, 1]
|
||||||
|
// [0, 1, 2]
|
||||||
|
// [0, 2]
|
||||||
|
// [0, 2, 1]
|
||||||
|
|
||||||
|
let static_attrs = el.attributes.iter().filter_map(|attr| match &attr.attr {
|
||||||
|
ElementAttr::AttrText { name, value } if value.is_static() => {
|
||||||
|
let value = value.source.as_ref().unwrap();
|
||||||
|
Some(quote! {
|
||||||
|
::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,
|
||||||
|
value: #value,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ElementAttr::CustomAttrText { name, value } if value.is_static() => {
|
||||||
|
let value = value.source.as_ref().unwrap();
|
||||||
|
Some(quote! {
|
||||||
|
::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,
|
||||||
|
value: #value,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
ElementAttr::AttrExpression { .. }
|
||||||
|
| ElementAttr::AttrText { .. }
|
||||||
|
| ElementAttr::CustomAttrText { .. }
|
||||||
|
| ElementAttr::CustomAttrExpression { .. } => {
|
||||||
|
let ct = self.dynamic_attributes.len();
|
||||||
|
self.dynamic_attributes.push(attr);
|
||||||
|
Some(quote! { ::dioxus::core::TemplateAttribute::Dynamic {
|
||||||
|
name: "asd",
|
||||||
|
index: #ct
|
||||||
|
} })
|
||||||
|
}
|
||||||
|
|
||||||
|
ElementAttr::EventTokens { .. } => {
|
||||||
|
let ct = self.dynamic_attributes.len();
|
||||||
|
self.dynamic_attributes.push(attr);
|
||||||
|
Some(quote! { ::dioxus::core::TemplateAttribute::Dynamic {
|
||||||
|
name: "asd",
|
||||||
|
index: #ct
|
||||||
|
} })
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let attrs = quote! { #(#static_attrs),*};
|
||||||
|
|
||||||
|
let children = el.children.iter().enumerate().map(|(idx, root)| {
|
||||||
|
self.current_path.push(idx as u8);
|
||||||
|
let out = self.render_static_node(root);
|
||||||
|
self.current_path.pop();
|
||||||
|
out
|
||||||
|
});
|
||||||
|
|
||||||
|
let children = quote! { #(#children),* };
|
||||||
|
|
||||||
|
quote! {
|
||||||
|
::dioxus::core::TemplateNode::Element {
|
||||||
|
tag: dioxus_elements::#el_name::TAG_NAME,
|
||||||
|
namespace: dioxus_elements::#el_name::NAME_SPACE,
|
||||||
|
attrs: &[ #attrs ],
|
||||||
|
children: &[ #children ],
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
})
|
|
||||||
|
BodyNode::Text(text) if text.is_static() => {
|
||||||
|
let text = text.source.as_ref().unwrap();
|
||||||
|
quote! { ::dioxus::core::TemplateNode::Text(#text) }
|
||||||
|
}
|
||||||
|
|
||||||
|
BodyNode::RawExpr(_) | BodyNode::Text(_) | BodyNode::Component(_) => {
|
||||||
|
let ct = self.dynamic_nodes.len();
|
||||||
|
self.dynamic_nodes.push(root);
|
||||||
|
quote! { ::dioxus::core::TemplateNode::Dynamic(#ct) }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,17 +51,18 @@ impl Parse for BodyNode {
|
|||||||
// - one ident
|
// - one ident
|
||||||
// - followed by `{`
|
// - followed by `{`
|
||||||
// - 1st char is lowercase
|
// - 1st char is lowercase
|
||||||
|
// - no underscores (reserved for components)
|
||||||
//
|
//
|
||||||
// example:
|
// example:
|
||||||
// div {}
|
// div {}
|
||||||
if let Some(ident) = path.get_ident() {
|
if let Some(ident) = path.get_ident() {
|
||||||
|
let el_name = ident.to_string();
|
||||||
|
|
||||||
|
let first_char = el_name.chars().next().unwrap();
|
||||||
|
|
||||||
if body_stream.peek(token::Brace)
|
if body_stream.peek(token::Brace)
|
||||||
&& ident
|
&& first_char.is_ascii_lowercase()
|
||||||
.to_string()
|
&& !el_name.contains('_')
|
||||||
.chars()
|
|
||||||
.next()
|
|
||||||
.unwrap()
|
|
||||||
.is_ascii_lowercase()
|
|
||||||
{
|
{
|
||||||
return Ok(BodyNode::Element(stream.parse::<Element>()?));
|
return Ok(BodyNode::Element(stream.parse::<Element>()?));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#![doc = include_str!("../README.md")]
|
#![doc = include_str!("../README.md")]
|
||||||
|
|
||||||
use std::fmt::{Display, Formatter, Write};
|
use std::fmt::{Display, Formatter, Write};
|
||||||
mod template;
|
pub mod template;
|
||||||
|
|
||||||
use dioxus_core::exports::bumpalo;
|
use dioxus_core::exports::bumpalo;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use dioxus_core::prelude::*;
|
use dioxus_core::{prelude::*, AttributeValue};
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::HashMap;
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ struct StringChain {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum Segment {
|
enum Segment {
|
||||||
Attr(usize),
|
Attr(usize),
|
||||||
Dyn(usize),
|
Node(usize),
|
||||||
PreRendered(String),
|
PreRendered(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,8 +89,9 @@ impl StringCache {
|
|||||||
cur_path.pop();
|
cur_path.pop();
|
||||||
}
|
}
|
||||||
TemplateNode::Text(text) => write!(chain, "{}", text)?,
|
TemplateNode::Text(text) => write!(chain, "{}", text)?,
|
||||||
TemplateNode::Dynamic(idx) => chain.segments.push(Segment::Dyn(*idx)),
|
TemplateNode::Dynamic(idx) | TemplateNode::DynamicText(idx) => {
|
||||||
TemplateNode::DynamicText(idx) => chain.segments.push(Segment::Dyn(*idx)),
|
chain.segments.push(Segment::Node(*idx))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -98,7 +99,7 @@ impl StringCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SsrRender {
|
impl SsrRender {
|
||||||
fn render_vdom(&mut self, dom: &VirtualDom) -> String {
|
pub fn render_vdom(&mut self, dom: &VirtualDom) -> String {
|
||||||
let scope = dom.base_scope();
|
let scope = dom.base_scope();
|
||||||
let root = scope.root_node();
|
let root = scope.root_node();
|
||||||
|
|
||||||
@@ -119,14 +120,17 @@ impl SsrRender {
|
|||||||
for segment in entry.segments.iter() {
|
for segment in entry.segments.iter() {
|
||||||
match segment {
|
match segment {
|
||||||
Segment::Attr(idx) => {
|
Segment::Attr(idx) => {
|
||||||
todo!("properly implement attrs in the macro");
|
let attr = &template.dynamic_attrs[*idx];
|
||||||
// let loc = &template.dynamic_attrs[*idx];
|
match attr.value {
|
||||||
// for attr in loc.attrs.iter() {
|
AttributeValue::Text(value) => write!(buf, " {}=\"{}\"", attr.name, value)?,
|
||||||
// write!(buf, " {}=\"{}\"", attr.name, attr.value)?;
|
_ => {}
|
||||||
// }
|
};
|
||||||
}
|
}
|
||||||
Segment::Dyn(idx) => match &template.dynamic_nodes[*idx].kind {
|
Segment::Node(idx) => match &template.dynamic_nodes[*idx].kind {
|
||||||
DynamicNodeKind::Text { value, .. } => write!(buf, "{}", value)?,
|
DynamicNodeKind::Text { value, .. } => {
|
||||||
|
// todo: escape the text
|
||||||
|
write!(buf, "{}", value)?
|
||||||
|
}
|
||||||
DynamicNodeKind::Fragment { children } => {
|
DynamicNodeKind::Fragment { children } => {
|
||||||
for child in *children {
|
for child in *children {
|
||||||
self.render_template(buf, child)?;
|
self.render_template(buf, child)?;
|
||||||
@@ -152,20 +156,21 @@ fn to_string_works() {
|
|||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
let dynamic = 123;
|
let dynamic = 123;
|
||||||
|
let dyn2 = "</diiiiiiiiv>"; // todo: escape this
|
||||||
|
|
||||||
cx.render(rsx! {
|
render! {
|
||||||
div { class: "asdasdasd", class: "asdasdasd",
|
div { class: "asdasdasd", class: "asdasdasd", id: "id-{dynamic}",
|
||||||
"Hello world 1 -->"
|
"Hello world 1 -->"
|
||||||
"{dynamic}"
|
"{dynamic}"
|
||||||
"<-- Hello world 2"
|
"<-- Hello world 2"
|
||||||
div { "nest 1" }
|
div { "nest 1" }
|
||||||
div {}
|
div {}
|
||||||
div { "nest 2" }
|
div { "nest 2" }
|
||||||
(0..5).map(|i| rsx! {
|
"{dyn2}"
|
||||||
div { "finalize {i}" }
|
|
||||||
})
|
(0..5).map(|i| rsx! { div { "finalize {i}" } })
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
let mut dom = VirtualDom::new(app);
|
let mut dom = VirtualDom::new(app);
|
||||||
|
|
||||||
@@ -173,10 +178,52 @@ fn to_string_works() {
|
|||||||
dom.rebuild(&mut mutations);
|
dom.rebuild(&mut mutations);
|
||||||
|
|
||||||
let cache = StringCache::from_template(&dom.base_scope().root_node()).unwrap();
|
let cache = StringCache::from_template(&dom.base_scope().root_node()).unwrap();
|
||||||
|
|
||||||
dbg!(cache.segments);
|
dbg!(cache.segments);
|
||||||
|
|
||||||
let mut renderer = SsrRender::default();
|
let mut renderer = SsrRender::default();
|
||||||
|
|
||||||
dbg!(renderer.render_vdom(&dom));
|
dbg!(renderer.render_vdom(&dom));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn children_processes_properly() {
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
fn app(cx: Scope) -> Element {
|
||||||
|
render! {
|
||||||
|
div {
|
||||||
|
"yo!"
|
||||||
|
"yo!"
|
||||||
|
"yo!"
|
||||||
|
"yo!"
|
||||||
|
"yo!"
|
||||||
|
Child {}
|
||||||
|
Child {}
|
||||||
|
Child {}
|
||||||
|
Child {}
|
||||||
|
ChildWithChildren {
|
||||||
|
"hii"
|
||||||
|
}
|
||||||
|
(0..10).map(|f| rsx! {
|
||||||
|
"div {f}"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Yo its the child component!
|
||||||
|
fn Child(cx: Scope) -> Element {
|
||||||
|
render! ( div { "child" } )
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline_props]
|
||||||
|
fn ChildWithChildren<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
|
||||||
|
render! {
|
||||||
|
div {
|
||||||
|
"div"
|
||||||
|
children
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dom = VirtualDom::new(app);
|
||||||
|
}
|
||||||
File renamed without changes.
File renamed without changes.
Reference in new issue
Block a user