Feat: more updates to hooks
This commit is contained in:
1 parent
4d01436c3f
commit
2c2882c9a2
9 files changed
+286
-84
No files matched your search
Vendored
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"rust-analyzer.inlayHints.enable": false
|
"rust-analyzer.inlayHints.enable": true
|
||||||
}
|
}
|
||||||
@@ -11,7 +11,8 @@ description = "Core functionality for Dioxus - a concurrent renderer-agnostic Vi
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# dodrio-derive = { path = "../html-macro-2", version = "0.1.0" }
|
# dodrio-derive = { path = "../html-macro-2", version = "0.1.0" }
|
||||||
dioxus-html-macro = { path = "../html-macro", version = "0.1.0" }
|
# dioxus-html-macro = { path = "../html-macro", version = "0.1.0" }
|
||||||
|
dioxus-html-2 = { path = "../html-macro-2", version = "0.1.0" }
|
||||||
dioxus-core-macro = { path = "../core-macro" }
|
dioxus-core-macro = { path = "../core-macro" }
|
||||||
# Backs some static data
|
# Backs some static data
|
||||||
once_cell = "1.5.2"
|
once_cell = "1.5.2"
|
||||||
|
|||||||
@@ -55,27 +55,29 @@ impl<'src> Properties for Props<'src> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn component<'a>(ctx: &'a Context<Props>) -> VNode<'a> {
|
fn component<'a>(ctx: Context<'a, Props>) -> VNode<'a> {
|
||||||
// Write asynchronous rendering code that immediately returns a "suspended" VNode
|
// Write asynchronous rendering code that immediately returns a "suspended" VNode
|
||||||
// The concurrent API will then progress this component when the future finishes
|
// The concurrent API will then progress this component when the future finishes
|
||||||
// You can suspend the entire component, or just parts of it
|
// You can suspend the entire component, or just parts of it
|
||||||
let product_list = ctx.suspend(async {
|
let product_list = ctx.suspend(async {
|
||||||
// Suspend the rendering that completes when the future is done
|
// Suspend the rendering that completes when the future is done
|
||||||
match fetch_data().await {
|
match fetch_data().await {
|
||||||
Ok(data) => html! { <div> </div>},
|
Ok(data) => html! { <div> "success!" </div>},
|
||||||
Err(_) => html! { <div> </div>},
|
Err(_) => html! { <div> "failure :(" </div>},
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
todo!()
|
// todo!()
|
||||||
// ctx.view(html! {
|
ctx.view(html! {
|
||||||
// <div>
|
<div>
|
||||||
// // <h1> "Products" </h1>
|
<h1> "Products" </h1>
|
||||||
// // // Subnodes can even be suspended
|
<button> "hello!" </button>
|
||||||
// // // When completely rendered, they won't cause the component itself to re-render, just their slot
|
// Subnodes can even be suspended
|
||||||
// // <p> { product_list } </p>
|
// When completely rendered, they won't cause the component itself to re-render, just their slot
|
||||||
// </div>
|
|
||||||
// })
|
// <p> { product_list } </p>
|
||||||
|
</div>
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn BuilderComp<'a>(ctx: &'a Context<'a, Props>) -> VNode<'a> {
|
fn BuilderComp<'a>(ctx: &'a Context<'a, Props>) -> VNode<'a> {
|
||||||
|
|||||||
@@ -1,43 +1,160 @@
|
|||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use dioxus_core::prelude::{Context, VNode};
|
use dioxus_core::{
|
||||||
use std::{any::Any, cell::RefCell, rc::Rc};
|
prelude::{Context, VElement, VNode, FC},
|
||||||
|
virtual_dom::{Properties, Scope},
|
||||||
|
};
|
||||||
|
use std::{
|
||||||
|
any::Any,
|
||||||
|
borrow::BorrowMut,
|
||||||
|
cell::RefCell,
|
||||||
|
mem::swap,
|
||||||
|
ops::{Deref, DerefMut},
|
||||||
|
rc::Rc,
|
||||||
|
};
|
||||||
use std::{borrow::Borrow, sync::atomic::AtomicUsize};
|
use std::{borrow::Borrow, sync::atomic::AtomicUsize};
|
||||||
use typed_arena::Arena;
|
use typed_arena::Arena;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let ar = Arena::new();
|
let mut scope = Scope::new(component);
|
||||||
|
|
||||||
(0..5).for_each(|f| {
|
(0..5).for_each(|f| {
|
||||||
// Create the temporary context obect
|
let ctx = scope.create_context();
|
||||||
let c = Context {
|
component(ctx);
|
||||||
_p: std::marker::PhantomData {},
|
|
||||||
props: (),
|
|
||||||
idx: 0.into(),
|
|
||||||
arena: &ar,
|
|
||||||
hooks: RefCell::new(Vec::new()),
|
|
||||||
};
|
|
||||||
|
|
||||||
component(c);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// we need to do something about props and context being borrowed from different sources....
|
// we need to do something about props and context being borrowed from different sources....
|
||||||
// kinda anooying
|
// kinda anooying
|
||||||
/// use_ref creates a new value when the component is created and then borrows that value on every render
|
/// use_ref creates a new value when the component is created and then borrows that value on every render
|
||||||
fn component(ctx: Context<()>) {
|
fn component(ctx: Context<()>) -> VNode {
|
||||||
(0..10).for_each(|f| {
|
(0..10).for_each(|f| {
|
||||||
let r = use_ref(&ctx, move || f);
|
let r = use_ref(&ctx, move || f);
|
||||||
assert_eq!(*r, f);
|
|
||||||
});
|
});
|
||||||
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct UseRef<T: 'static> {
|
||||||
|
current: RefCell<T>,
|
||||||
|
}
|
||||||
|
impl<T: 'static> UseRef<T> {
|
||||||
|
fn new(val: T) -> Self {
|
||||||
|
Self {
|
||||||
|
current: RefCell::new(val),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn modify(&self, modifier: impl FnOnce(&mut T)) {
|
||||||
|
let mut val = self.current.borrow_mut();
|
||||||
|
let val_as_ref = val.deref_mut();
|
||||||
|
modifier(val_as_ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Store a mutable value between renders!
|
||||||
|
/// To read the value, borrow the ref.
|
||||||
|
/// To change it, use modify.
|
||||||
|
/// Modifications to this value do not cause updates to the component
|
||||||
pub fn use_ref<'a, P, T: 'static>(
|
pub fn use_ref<'a, P, T: 'static>(
|
||||||
ctx: &'a Context<'a, P>,
|
ctx: &'a Context<'a, P>,
|
||||||
initial_state_fn: impl FnOnce() -> T + 'static,
|
initial_state_fn: impl FnOnce() -> T + 'static,
|
||||||
) -> &'a T {
|
) -> &'a UseRef<T> {
|
||||||
ctx.use_hook(
|
ctx.use_hook(
|
||||||
|| initial_state_fn(), // initializer
|
|| UseRef::new(initial_state_fn()),
|
||||||
|state| state, // runner, borrows the internal value
|
|state, _| &*state,
|
||||||
|b| {}, // tear down
|
|_| {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct UseState<T> {
|
||||||
|
new_val: Rc<RefCell<Option<T>>>,
|
||||||
|
current_val: T,
|
||||||
|
caller: Box<dyn Fn(T) + 'static>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Store state between component renders!
|
||||||
|
/// When called, this hook retrives a stored value and provides a setter to update that value.
|
||||||
|
/// When the setter is called, the component is re-ran with the new value.
|
||||||
|
///
|
||||||
|
/// This is behaves almost exactly the same way as React's "use_state".
|
||||||
|
///
|
||||||
|
/// Usage:
|
||||||
|
/// ```ignore
|
||||||
|
/// static Example: FC<()> = |ctx| {
|
||||||
|
/// let (counter, set_counter) = use_state(ctx, || 0);
|
||||||
|
/// let increment = || set_couter(counter + 1);
|
||||||
|
/// let decrement = || set_couter(counter + 1);
|
||||||
|
///
|
||||||
|
/// html! {
|
||||||
|
/// <div>
|
||||||
|
/// <h1>"Counter: {counter}" </h1>
|
||||||
|
/// <button onclick={increment}> "Increment" </button>
|
||||||
|
/// <button onclick={decrement}> "Decrement" </button>
|
||||||
|
/// </div>
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn use_state<'a, P: Properties + 'static, T: 'static, F: FnOnce() -> T + 'static>(
|
||||||
|
ctx: &'a Context<'a, P>,
|
||||||
|
initial_state_fn: F,
|
||||||
|
) -> (&T, &impl Fn(T)) {
|
||||||
|
ctx.use_hook(
|
||||||
|
move || UseState {
|
||||||
|
new_val: Rc::new(RefCell::new(None)),
|
||||||
|
current_val: initial_state_fn(),
|
||||||
|
caller: Box::new(|_| println!("setter called!")),
|
||||||
|
},
|
||||||
|
move |hook, updater| {
|
||||||
|
let inner = hook.new_val.clone();
|
||||||
|
|
||||||
|
// todo: swap out the caller with a subscription call and an internal update
|
||||||
|
hook.caller = Box::new(move |new_val| {
|
||||||
|
// update the setter with the new value
|
||||||
|
let mut new_inner = inner.as_ref().borrow_mut();
|
||||||
|
*new_inner = Some(new_val);
|
||||||
|
});
|
||||||
|
|
||||||
|
// box gets derefed into a ref which is then taken as ref with the hook
|
||||||
|
(&hook.current_val, &hook.caller)
|
||||||
|
},
|
||||||
|
|_| {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_use_state(ctx: Context<()>) -> VNode {
|
||||||
|
let (val, set_val) = use_state(&ctx, || 10);
|
||||||
|
|
||||||
|
// gloriousness!
|
||||||
|
// closures are taken with ref to ctx :)
|
||||||
|
// Can freely use hooks
|
||||||
|
let handler_0: _ = || set_val(val + 1);
|
||||||
|
let handler_1: _ = || set_val(val + 10);
|
||||||
|
let handler_2: _ = || set_val(val + 100);
|
||||||
|
|
||||||
|
// these fns are captured, boxed into the bump arena, and then attached to the listeners
|
||||||
|
// the vnodes share the lifetime of these closures (and the hook data)
|
||||||
|
// whenever a listener wakes up, we take the reference directly from the bump arena and, with a small bit
|
||||||
|
// of unsafe code, execute the associated closure / listener function
|
||||||
|
// Those vnodes are then tossed out and new ones are installed, meaning and old references (potentially bad)
|
||||||
|
// are removed and UB is prevented from affecting the program
|
||||||
|
{
|
||||||
|
VNode::Element(VElement::new("button"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn test_use_state_2(ctx: Context<()>) -> VNode {
|
||||||
|
let (val, set_val) = use_state(&ctx, || 0);
|
||||||
|
|
||||||
|
let incr = || set_val(val + 1);
|
||||||
|
let decr = || set_val(val - 1);
|
||||||
|
|
||||||
|
todo!()
|
||||||
|
// html! {
|
||||||
|
// <div>
|
||||||
|
// <nav class="menu">
|
||||||
|
// <button onclick=incr> { "Increment" } </button>
|
||||||
|
// <button onclick=decr> { "Decrement" } </button>
|
||||||
|
// </nav>
|
||||||
|
// <p> <b>{ "Current value: {val}" }</b> </p>
|
||||||
|
// </div>
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -76,9 +76,6 @@ pub mod builder {
|
|||||||
|
|
||||||
/// Re-export common types for ease of development use.
|
/// Re-export common types for ease of development use.
|
||||||
/// Essential when working with the html! macro
|
/// Essential when working with the html! macro
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
use crate::nodes;
|
use crate::nodes;
|
||||||
pub use crate::virtual_dom::{Context, VirtualDom};
|
pub use crate::virtual_dom::{Context, VirtualDom};
|
||||||
@@ -86,7 +83,8 @@ pub mod prelude {
|
|||||||
// pub use nodes::iterables::IterableNodes;
|
// pub use nodes::iterables::IterableNodes;
|
||||||
|
|
||||||
/// This type alias is an internal way of abstracting over the static functions that represent components.
|
/// This type alias is an internal way of abstracting over the static functions that represent components.
|
||||||
pub type FC<P> = for<'a> fn(&'a Context<P>) -> VNode<'a>;
|
pub type FC<P> = for<'a> fn(Context<'a, P>) -> VNode<'a>;
|
||||||
|
// pub type FC<P> = for<'a> fn(Context<'a, P>) -> VNode<'a>;
|
||||||
|
|
||||||
// TODO @Jon, fix this
|
// TODO @Jon, fix this
|
||||||
// hack the VNode type until VirtualNode is fixed in the macro crate
|
// hack the VNode type until VirtualNode is fixed in the macro crate
|
||||||
@@ -100,7 +98,7 @@ pub mod prelude {
|
|||||||
|
|
||||||
// Re-export the FC macro
|
// Re-export the FC macro
|
||||||
pub use dioxus_core_macro::fc;
|
pub use dioxus_core_macro::fc;
|
||||||
pub use dioxus_html_macro::html;
|
pub use dioxus_html_2::html;
|
||||||
|
|
||||||
pub use crate as dioxus;
|
pub use crate as dioxus;
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ use std::{
|
|||||||
any::TypeId,
|
any::TypeId,
|
||||||
cell::{RefCell, UnsafeCell},
|
cell::{RefCell, UnsafeCell},
|
||||||
future::Future,
|
future::Future,
|
||||||
|
marker::PhantomData,
|
||||||
sync::atomic::AtomicUsize,
|
sync::atomic::AtomicUsize,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -245,15 +246,18 @@ mod fc_test {
|
|||||||
// g
|
// g
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_component<'a>(ctx: &'a Context<()>) -> VNode<'a> {
|
fn test_component(ctx: Context<()>) -> VNode {
|
||||||
// todo: helper should be part of html! macro
|
// todo: helper should be part of html! macro
|
||||||
todo!()
|
todo!()
|
||||||
// ctx.view(|bump| html! {bump, <div> </div> })
|
// ctx.view(|bump| html! {bump, <div> </div> })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_component2<'a>(ctx: &'a Context<()>) -> VNode<'a> {
|
fn test_component2(ctx: Context<()>) -> VNode {
|
||||||
ctx.view(|bump: &Bump| VNode::text("blah"))
|
ctx.view(|bump: &Bump| VNode::text("blah"))
|
||||||
}
|
}
|
||||||
|
// fn test_component2<'a>(ctx: &'a Context<()>) -> VNode<'a> {
|
||||||
|
// ctx.view(|bump: &Bump| VNode::text("blah"))
|
||||||
|
// }
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ensure_types_work() {
|
fn ensure_types_work() {
|
||||||
@@ -272,32 +276,41 @@ mod fc_test {
|
|||||||
/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components
|
/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components
|
||||||
/// The actualy contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
|
/// The actualy contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
|
||||||
pub struct Scope {
|
pub struct Scope {
|
||||||
hook_idx: i32,
|
arena: typed_arena::Arena<Hook>,
|
||||||
hooks: Vec<OLDHookState>,
|
hooks: RefCell<Vec<*mut Hook>>,
|
||||||
props_type: TypeId,
|
props_type: TypeId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scope {
|
impl Scope {
|
||||||
// create a new scope from a function
|
// create a new scope from a function
|
||||||
fn new<T: 'static>(f: FC<T>) -> Self {
|
pub fn new<T: 'static>(f: FC<T>) -> Self {
|
||||||
// Capture the props type
|
// Capture the props type
|
||||||
let props_type = TypeId::of::<T>();
|
let props_type = TypeId::of::<T>();
|
||||||
|
let arena = typed_arena::Arena::new();
|
||||||
|
let hooks = RefCell::new(Vec::new());
|
||||||
|
|
||||||
// Obscure the function
|
|
||||||
Self {
|
Self {
|
||||||
hook_idx: 0,
|
arena,
|
||||||
hooks: vec![],
|
hooks,
|
||||||
props_type,
|
props_type,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_context<T: Properties>(&mut self) -> Context<T> {
|
||||||
|
Context {
|
||||||
|
_p: PhantomData {},
|
||||||
|
arena: &self.arena,
|
||||||
|
hooks: &self.hooks,
|
||||||
|
idx: 0.into(),
|
||||||
|
props: T::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new context and run the component with references from the Virtual Dom
|
/// Create a new context and run the component with references from the Virtual Dom
|
||||||
/// This function downcasts the function pointer based on the stored props_type
|
/// This function downcasts the function pointer based on the stored props_type
|
||||||
fn run() {}
|
fn run() {}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct OLDHookState {}
|
|
||||||
|
|
||||||
/// Components in Dioxus use the "Context" object to interact with their lifecycle.
|
/// Components in Dioxus use the "Context" object to interact with their lifecycle.
|
||||||
/// This lets components schedule updates, integrate hooks, and expose their context via the context api.
|
/// This lets components schedule updates, integrate hooks, and expose their context via the context api.
|
||||||
///
|
///
|
||||||
@@ -322,8 +335,13 @@ pub struct Context<'src, T> {
|
|||||||
/// Direct access to the properties used to create this component.
|
/// Direct access to the properties used to create this component.
|
||||||
pub props: T,
|
pub props: T,
|
||||||
pub idx: AtomicUsize,
|
pub idx: AtomicUsize,
|
||||||
pub arena: &'src typed_arena::Arena<Hook>,
|
|
||||||
pub hooks: RefCell<Vec<*mut Hook>>,
|
// Borrowed from scope
|
||||||
|
arena: &'src typed_arena::Arena<Hook>,
|
||||||
|
hooks: &'src RefCell<Vec<*mut Hook>>,
|
||||||
|
|
||||||
|
// holder for the src lifetime
|
||||||
|
// todo @jon remove this
|
||||||
pub _p: std::marker::PhantomData<&'src ()>,
|
pub _p: std::marker::PhantomData<&'src ()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,16 +388,16 @@ impl<'a, T> Context<'a, T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// use_hook provides a way to store data between renders for functional components.
|
/// use_hook provides a way to store data between renders for functional components.
|
||||||
pub fn use_hook<'comp, InternalHookState: 'static, Output: 'static>(
|
pub fn use_hook<'comp, InternalHookState: 'static, Output: 'comp>(
|
||||||
&'comp self,
|
&'comp self,
|
||||||
// The closure that builds the hook state
|
// The closure that builds the hook state
|
||||||
initializer: impl FnOnce() -> InternalHookState,
|
initializer: impl FnOnce() -> InternalHookState,
|
||||||
// The closure that takes the hookstate and returns some value
|
// The closure that takes the hookstate and returns some value
|
||||||
runner: impl for<'b> FnOnce(&'comp mut InternalHookState) -> &'comp Output,
|
runner: impl FnOnce(&'comp mut InternalHookState, ()) -> Output,
|
||||||
// The closure that cleans up whatever mess is left when the component gets torn down
|
// The closure that cleans up whatever mess is left when the component gets torn down
|
||||||
// TODO: add this to the "clean up" group for when the component is dropped
|
// TODO: add this to the "clean up" group for when the component is dropped
|
||||||
tear_down: impl FnOnce(InternalHookState),
|
cleanup: impl FnOnce(InternalHookState),
|
||||||
) -> &'comp Output {
|
) -> Output {
|
||||||
let raw_hook = {
|
let raw_hook = {
|
||||||
let idx = self.idx.load(std::sync::atomic::Ordering::Relaxed);
|
let idx = self.idx.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
|
|
||||||
@@ -408,7 +426,7 @@ impl<'a, T> Context<'a, T> {
|
|||||||
Here, we dereference a raw pointer. Normally, we aren't guaranteed that this is okay.
|
Here, we dereference a raw pointer. Normally, we aren't guaranteed that this is okay.
|
||||||
|
|
||||||
However, typed-arena gives a mutable reference to the stored data which is stable for any inserts
|
However, typed-arena gives a mutable reference to the stored data which is stable for any inserts
|
||||||
into the arena. During the first call of the function, we need to add the mutable reference given to use by
|
into the arena. During the first call of the function, we need to add the mutable reference given to us by
|
||||||
the arena into our list of hooks. The arena provides stability of the &mut references and is only deallocated
|
the arena into our list of hooks. The arena provides stability of the &mut references and is only deallocated
|
||||||
when the component itself is deallocated.
|
when the component itself is deallocated.
|
||||||
|
|
||||||
@@ -417,6 +435,7 @@ impl<'a, T> Context<'a, T> {
|
|||||||
- Usage of the raw hooks is tied behind the Vec refcell
|
- Usage of the raw hooks is tied behind the Vec refcell
|
||||||
- Output is static, meaning it can't take a reference to the data
|
- Output is static, meaning it can't take a reference to the data
|
||||||
- We don't expose the raw hook pointer outside of the scope of use_hook
|
- We don't expose the raw hook pointer outside of the scope of use_hook
|
||||||
|
- The reference is tied to context, meaning it can only be used while ctx is around to free it
|
||||||
*/
|
*/
|
||||||
let borrowed_hook: &'comp mut _ = unsafe { raw_hook.as_mut().unwrap() };
|
let borrowed_hook: &'comp mut _ = unsafe { raw_hook.as_mut().unwrap() };
|
||||||
|
|
||||||
@@ -425,7 +444,10 @@ impl<'a, T> Context<'a, T> {
|
|||||||
.downcast_mut::<InternalHookState>()
|
.downcast_mut::<InternalHookState>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
runner(internal_state)
|
// todo: set up an updater with the subscription API
|
||||||
|
let updater = ();
|
||||||
|
|
||||||
|
runner(internal_state, updater)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "dodrio-derive"
|
name = "dioxus-html-2"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Richard Dodd <richard.o.dodd@gmail.com>"]
|
authors = ["Jonathan Kelley <jkelleyrtp@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
@@ -15,3 +15,7 @@ quote = "1.0.3"
|
|||||||
proc-macro-hack = "0.5.15"
|
proc-macro-hack = "0.5.15"
|
||||||
proc-macro2 = "1.0.10"
|
proc-macro2 = "1.0.10"
|
||||||
style-shared = { git = "https://github.com/derekdreery/style" }
|
style-shared = { git = "https://github.com/derekdreery/style" }
|
||||||
|
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
bumpalo = "*"
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
use bumpalo::Bump;
|
||||||
|
use dioxus_html_2::html;
|
||||||
|
|
||||||
|
mod dioxus {
|
||||||
|
pub use bumpalo;
|
||||||
|
pub mod builder {
|
||||||
|
use bumpalo::Bump;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
/*
|
||||||
|
Th below code is not meant to compile, but it is meant to expand properly
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
// let l = html! {
|
||||||
|
// <div>
|
||||||
|
// <div>
|
||||||
|
// <h1>"asdl"</h1>
|
||||||
|
// <h1>"asdl"</h1>
|
||||||
|
// <h1>"asdl"</h1>
|
||||||
|
// <h1>"asdl"</h1>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// };
|
||||||
|
|
||||||
|
// let l = move |bump| {
|
||||||
|
// dioxus::builder::div(bump)
|
||||||
|
// .children([dioxus::builder::div(bump)
|
||||||
|
// .children([
|
||||||
|
// dioxus::builder::h1(bump)
|
||||||
|
// .children([dioxus::builder::text("asdl")])
|
||||||
|
// .finish(),
|
||||||
|
// dioxus::builder::h1(bump)
|
||||||
|
// .children([dioxus::builder::text("asdl")])
|
||||||
|
// .finish(),
|
||||||
|
// dioxus::builder::h1(bump)
|
||||||
|
// .children([dioxus::builder::text("asdl")])
|
||||||
|
// .finish(),
|
||||||
|
// dioxus::builder::h1(bump)
|
||||||
|
// .children([dioxus::builder::text("asdl")])
|
||||||
|
// .finish(),
|
||||||
|
// ])
|
||||||
|
// .finish()])
|
||||||
|
// .finish()
|
||||||
|
// };
|
||||||
|
}
|
||||||
@@ -21,14 +21,14 @@ pub fn html(s: TokenStream) -> TokenStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct HtmlRender {
|
struct HtmlRender {
|
||||||
ctx: Ident,
|
// ctx: Ident,
|
||||||
kind: NodeOrList,
|
kind: NodeOrList,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for HtmlRender {
|
impl Parse for HtmlRender {
|
||||||
fn parse(s: ParseStream) -> Result<Self> {
|
fn parse(s: ParseStream) -> Result<Self> {
|
||||||
let ctx: Ident = s.parse()?;
|
// let ctx: Ident = s.parse()?;
|
||||||
s.parse::<Token![,]>()?;
|
// s.parse::<Token![,]>()?;
|
||||||
// if elements are in an array, return a bumpalo::collections::Vec rather than a Node.
|
// if elements are in an array, return a bumpalo::collections::Vec rather than a Node.
|
||||||
let kind = if s.peek(token::Bracket) {
|
let kind = if s.peek(token::Bracket) {
|
||||||
let nodes_toks;
|
let nodes_toks;
|
||||||
@@ -42,13 +42,20 @@ impl Parse for HtmlRender {
|
|||||||
} else {
|
} else {
|
||||||
NodeOrList::Node(s.parse()?)
|
NodeOrList::Node(s.parse()?)
|
||||||
};
|
};
|
||||||
Ok(HtmlRender { ctx, kind })
|
Ok(HtmlRender { kind })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for HtmlRender {
|
impl ToTokens for HtmlRender {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
|
||||||
ToToksCtx::new(&self.ctx, &self.kind).to_tokens(tokens)
|
let new_toks = ToToksCtx::new(&self.kind).to_token_stream();
|
||||||
|
|
||||||
|
// create a lazy tree that accepts a bump allocator
|
||||||
|
let final_tokens = quote! {
|
||||||
|
move |bump| { #new_toks }
|
||||||
|
};
|
||||||
|
|
||||||
|
final_tokens.to_tokens(out_tokens);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +64,7 @@ enum NodeOrList {
|
|||||||
List(NodeList),
|
List(NodeList),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for ToToksCtx<'_, &NodeOrList> {
|
impl ToTokens for ToToksCtx<&NodeOrList> {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
match self.inner {
|
match self.inner {
|
||||||
NodeOrList::Node(node) => self.recurse(node).to_tokens(tokens),
|
NodeOrList::Node(node) => self.recurse(node).to_tokens(tokens),
|
||||||
@@ -68,12 +75,12 @@ impl ToTokens for ToToksCtx<'_, &NodeOrList> {
|
|||||||
|
|
||||||
struct NodeList(Vec<MaybeExpr<Node>>);
|
struct NodeList(Vec<MaybeExpr<Node>>);
|
||||||
|
|
||||||
impl ToTokens for ToToksCtx<'_, &NodeList> {
|
impl ToTokens for ToToksCtx<&NodeList> {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
let ctx = &self.ctx;
|
// let ctx = &self.ctx;
|
||||||
let nodes = self.inner.0.iter().map(|node| self.recurse(node));
|
let nodes = self.inner.0.iter().map(|node| self.recurse(node));
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
dioxus::bumpalo::vec![in #ctx.bump;
|
dioxus::bumpalo::vec![in bump;
|
||||||
#(#nodes),*
|
#(#nodes),*
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -85,7 +92,7 @@ enum Node {
|
|||||||
Text(TextNode),
|
Text(TextNode),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for ToToksCtx<'_, &Node> {
|
impl ToTokens for ToToksCtx<&Node> {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
match &self.inner {
|
match &self.inner {
|
||||||
Node::Element(el) => self.recurse(el).to_tokens(tokens),
|
Node::Element(el) => self.recurse(el).to_tokens(tokens),
|
||||||
@@ -116,12 +123,12 @@ struct Element {
|
|||||||
children: MaybeExpr<Vec<Node>>,
|
children: MaybeExpr<Vec<Node>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for ToToksCtx<'_, &Element> {
|
impl ToTokens for ToToksCtx<&Element> {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
let ctx = self.ctx;
|
// let ctx = self.ctx;
|
||||||
let name = &self.inner.name;
|
let name = &self.inner.name;
|
||||||
tokens.append_all(quote! {
|
tokens.append_all(quote! {
|
||||||
dioxus::builder::#name(&#ctx)
|
dioxus::builder::#name(bump)
|
||||||
});
|
});
|
||||||
for attr in self.inner.attrs.iter() {
|
for attr in self.inner.attrs.iter() {
|
||||||
self.recurse(attr).to_tokens(tokens);
|
self.recurse(attr).to_tokens(tokens);
|
||||||
@@ -249,7 +256,7 @@ impl Parse for Attr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for ToToksCtx<'_, &Attr> {
|
impl ToTokens for ToToksCtx<&Attr> {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
let name = self.inner.name.to_string();
|
let name = self.inner.name.to_string();
|
||||||
let mut attr_stream = TokenStream2::new();
|
let mut attr_stream = TokenStream2::new();
|
||||||
@@ -283,7 +290,7 @@ impl Parse for TextNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for ToToksCtx<'_, &TextNode> {
|
impl ToTokens for ToToksCtx<&TextNode> {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
let mut token_stream = TokenStream2::new();
|
let mut token_stream = TokenStream2::new();
|
||||||
self.recurse(&self.inner.0).to_tokens(&mut token_stream);
|
self.recurse(&self.inner.0).to_tokens(&mut token_stream);
|
||||||
@@ -310,10 +317,10 @@ impl<T: Parse> Parse for MaybeExpr<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> ToTokens for ToToksCtx<'a, &'a MaybeExpr<T>>
|
impl<'a, T> ToTokens for ToToksCtx<&'a MaybeExpr<T>>
|
||||||
where
|
where
|
||||||
T: 'a,
|
T: 'a,
|
||||||
ToToksCtx<'a, &'a T>: ToTokens,
|
ToToksCtx<&'a T>: ToTokens,
|
||||||
{
|
{
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
match &self.inner {
|
match &self.inner {
|
||||||
@@ -324,25 +331,28 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// ToTokens context
|
/// ToTokens context
|
||||||
struct ToToksCtx<'a, T> {
|
struct ToToksCtx<T> {
|
||||||
|
// struct ToToksCtx<'a, T> {
|
||||||
inner: T,
|
inner: T,
|
||||||
ctx: &'a Ident,
|
// ctx: &'a Ident,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> ToToksCtx<'a, T> {
|
impl<'a, T> ToToksCtx<T> {
|
||||||
fn new(ctx: &'a Ident, inner: T) -> Self {
|
fn new(inner: T) -> Self {
|
||||||
ToToksCtx { ctx, inner }
|
// fn new(ctx: &'a Ident, inner: T) -> Self {
|
||||||
|
ToToksCtx { inner }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recurse<U>(&self, inner: U) -> ToToksCtx<'a, U> {
|
fn recurse<U>(&self, inner: U) -> ToToksCtx<U> {
|
||||||
|
// fn recurse<U>(&self, inner: U) -> ToToksCtx<'a, U> {
|
||||||
ToToksCtx {
|
ToToksCtx {
|
||||||
ctx: &self.ctx,
|
// ctx: &self.ctx,
|
||||||
inner,
|
inner,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ToTokens for ToToksCtx<'_, &LitStr> {
|
impl ToTokens for ToToksCtx<&LitStr> {
|
||||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||||
self.inner.to_tokens(tokens)
|
self.inner.to_tokens(tokens)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user