wip: remove runner on hook and then update docs

This commit is contained in:
Jonathan Kelley committed 2022-01-02 02:15:04 -05:00
1 parent 7d41a16208
commit d1560450ba
95 files changed
+1339 -675

No files matched your search

+1 -1
View File
@@ -19,7 +19,7 @@ use quote::{quote, ToTokens, TokenStreamExt};
use syn::{
ext::IdentExt,
parse::{Parse, ParseBuffer, ParseStream},
token, Expr, ExprClosure, Ident, Result, Token,
token, Expr, Ident, Result, Token,
};
pub struct Component {
+12 -12
View File
@@ -25,16 +25,16 @@ criterion_main!(mbenches);
fn create_rows(c: &mut Criterion) {
static App: Component = |cx| {
let mut rng = SmallRng::from_entropy();
let rows = (0..10_000_usize).map(|f| {
let label = Label::new(&mut rng);
rsx!(Row {
row_id: f,
label: label
})
});
rsx!(cx, table {
tbody {
{rows}
(0..10_000_usize).map(|f| {
let label = Label::new(&mut rng);
rsx!(Row {
row_id: f,
label: label
})
})
}
})
};
@@ -58,12 +58,12 @@ fn Row(cx: Scope<RowProps>) -> Element {
cx.render(rsx! {
tr {
td { class:"col-md-1", "{cx.props.row_id}" }
td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
td { class:"col-md-1", onclick: move |_| { /* run onselect */ },
a { class: "lbl", "{adj}" "{col}" "{noun}" }
}
td { class: "col-md-1"
a { class: "remove", onclick: move |_| {/* remove */}
span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
td { class: "col-md-1",
a { class: "remove", onclick: move |_| {/* remove */},
span { class: "glyphicon glyphicon-remove remove", aria_hidden: "true" }
}
}
td { class: "col-md-6" }
+3 -3
View File
@@ -12,7 +12,7 @@ fn main() {
}
fn parent(cx: Scope) -> Element {
let value = cx.use_hook(|_| String::new(), |f| f);
let value = cx.use_hook(|_| String::new());
cx.render(rsx! {
div {
@@ -33,8 +33,8 @@ struct ChildProps<'a> {
fn child<'a>(cx: Scope<'a, ChildProps<'a>>) -> Element {
cx.render(rsx! {
div {
"it's nested {cx.props.name}"
{&cx.props.children}
"it's nested {cx.props.name}",
&cx.props.children
}
})
}
+1 -1
View File
@@ -10,7 +10,7 @@ fn main() {
}
fn parent(cx: Scope) -> Element {
let value = cx.use_hook(|_| String::new(), |f| f);
let value = cx.use_hook(|_| String::new());
cx.render(rsx! {
div {
+12 -19
View File
@@ -473,9 +473,16 @@ impl<'bump> DiffState<'bump> {
// Check the most common cases first
// these are *actual* elements, not wrappers around lists
(Text(old), Text(new)) => {
self.diff_text_nodes(old, new, old_node, new_node);
if let Some(root) = old.id.get() {
if old.text != new.text {
self.mutations.set_text(new.text, root.as_u64());
}
self.scopes.update_node(new_node, root);
new.id.set(Some(root));
}
}
(Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node),
(Placeholder(old), Placeholder(new)) => {
if let Some(root) = old.id.get() {
self.scopes.update_node(new_node, root);
@@ -483,10 +490,13 @@ impl<'bump> DiffState<'bump> {
}
}
(Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node),
// These two sets are pointers to nodes but are not actually nodes themselves
(Component(old), Component(new)) => {
self.diff_component_nodes(old_node, new_node, *old, *new)
}
(Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new),
// The normal pathway still works, but generates slightly weird instructions
@@ -506,23 +516,6 @@ impl<'bump> DiffState<'bump> {
}
}
fn diff_text_nodes(
&mut self,
old: &'bump VText<'bump>,
new: &'bump VText<'bump>,
_old_node: &'bump VNode<'bump>,
new_node: &'bump VNode<'bump>,
) {
if let Some(root) = old.id.get() {
if old.text != new.text {
self.mutations.set_text(new.text, root.as_u64());
}
self.scopes.update_node(new_node, root);
new.id.set(Some(root));
}
}
fn diff_element_nodes(
&mut self,
old: &'bump VElement<'bump>,
+4 -4
View File
@@ -65,10 +65,10 @@ pub(crate) mod innerlude {
}
pub use crate::innerlude::{
Attribute, Component, DioxusElement, DomEdit, Element, ElementId, EventHandler, EventPriority,
IntoVNode, LazyNodes, Listener, Mutations, NodeFactory, Properties, SchedulerMsg, Scope,
ScopeId, ScopeState, TaskId, UserEvent, VComponent, VElement, VFragment, VNode, VPlaceholder,
VText, VirtualDom,
Attribute, Component, DioxusElement, DomEdit, Element, ElementId, ElementIdIterator, Event,
EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, Mutations, NodeFactory,
Properties, SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, UserEvent, VComponent, VElement,
VFragment, VNode, VPlaceholder, VText, VirtualDom,
};
pub mod prelude {
+18 -12
View File
@@ -320,6 +320,18 @@ impl ScopeArena {
pub fn root_node(&self, id: ScopeId) -> &VNode {
self.fin_head(id)
}
// this is totally okay since all our nodes are always in a valid state
pub fn get_element(&self, id: ElementId) -> Option<&VNode> {
let ptr = self.nodes.borrow().get(id.0).cloned();
match ptr {
Some(ptr) => {
let node = unsafe { &*ptr };
Some(unsafe { extend_vnode(node) })
}
None => None,
}
}
}
/// Components in Dioxus use the "Context" object to interact with their lifecycle.
@@ -620,7 +632,7 @@ impl ScopeState {
/// struct SharedState(&'static str);
///
/// static App: Component = |cx| {
/// cx.use_hook(|_| cx.provide_context(SharedState("world")), |_| {}, |_| {});
/// cx.use_hook(|_| cx.provide_context(SharedState("world")));
/// rsx!(cx, Child {})
/// }
///
@@ -711,17 +723,13 @@ impl ScopeState {
/// ```ignore
/// // use_ref is the simplest way of storing a value between renders
/// fn use_ref<T: 'static>(initial_value: impl FnOnce() -> T) -> &RefCell<T> {
/// use_hook(
/// || Rc::new(RefCell::new(initial_value())),
/// |state| state,
/// )
/// use_hook(|| Rc::new(RefCell::new(initial_value())))
/// }
/// ```
pub fn use_hook<'src, State: 'static, Output: 'src>(
pub fn use_hook<'src, State: 'static>(
&'src self,
initializer: impl FnOnce(usize) -> State,
runner: impl FnOnce(&'src mut State) -> Output,
) -> Output {
) -> &'src mut State {
let mut vals = self.hook_vals.borrow_mut();
let hook_len = vals.len();
@@ -731,7 +739,7 @@ impl ScopeState {
vals.push(self.hook_arena.alloc(initializer(hook_len)));
}
let state = vals
vals
.get(cur_idx)
.and_then(|inn| {
self.hook_idx.set(cur_idx + 1);
@@ -746,9 +754,7 @@ impl ScopeState {
You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
Functions prefixed with "use" should never be called conditionally.
"###,
);
runner(state)
)
}
/// The "work in progress frame" represents the frame that is currently being worked on.
+104
View File
@@ -260,6 +260,10 @@ impl VirtualDom {
self.channel.0.clone()
}
pub fn get_element(&self, id: ElementId) -> Option<&VNode> {
self.scopes.get_element(id)
}
/// Add a new message to the scheduler queue directly.
///
///
@@ -1003,3 +1007,103 @@ impl EmptyBuilder {
pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder {
T::builder()
}
pub struct Event<T> {
data: Arc<T>,
_cancel: std::rc::Rc<std::cell::Cell<bool>>,
}
impl<T> std::ops::Deref for Event<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.data.as_ref()
}
}
impl<T> Event<T> {
pub fn cancel(&self) {}
pub fn timestamp(&self) {}
pub fn triggered_element(&self) -> Option<Element> {
None
}
}
pub struct ElementIdIterator<'a> {
vdom: &'a VirtualDom,
// Heuristcally we should never bleed into 5 completely nested fragments/components
// Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 5]>,
}
impl<'a> ElementIdIterator<'a> {
pub fn new(vdom: &'a VirtualDom, node: &'a VNode<'a>) -> Self {
Self {
vdom,
stack: smallvec::smallvec![(0, node)],
}
}
}
impl<'a> Iterator for ElementIdIterator<'a> {
type Item = &'a VNode<'a>;
fn next(&mut self) -> Option<&'a VNode<'a>> {
let mut should_pop = false;
let mut returned_node = None;
let mut should_push = None;
while returned_node.is_none() {
if let Some((count, node)) = self.stack.last_mut() {
match node {
// We can only exit our looping when we get "real" nodes
VNode::Element(_) | VNode::Text(_) | VNode::Placeholder(_) => {
// We've recursed INTO an element/text
// We need to recurse *out* of it and move forward to the next
// println!("Found element! Returning it!");
should_pop = true;
returned_node = Some(&**node);
}
// If we get a fragment we push the next child
VNode::Fragment(frag) => {
let _count = *count as usize;
if _count >= frag.children.len() {
should_pop = true;
} else {
should_push = Some(&frag.children[_count]);
}
}
// For components, we load their root and push them onto the stack
VNode::Component(sc) => {
// todo!();
let scope = self.vdom.get_scope(sc.scope.get().unwrap()).unwrap();
// Simply swap the current node on the stack with the root of the component
*node = scope.root_node();
}
}
} else {
// If there's no more items on the stack, we're done!
return None;
}
if should_pop {
self.stack.pop();
if let Some((id, _)) = self.stack.last_mut() {
*id += 1;
}
should_pop = false;
}
if let Some(push) = should_push {
self.stack.push((0, push));
should_push = None;
}
}
returned_node
}
}
+1 -1
View File
@@ -11,7 +11,7 @@ fn test_borrowed_state() {
}
fn Parent(cx: Scope) -> Element {
let value = cx.use_hook(|_| String::new(), |f| &*f);
let value = cx.use_hook(|_| String::new());
cx.render(rsx! {
div {
+1 -1
View File
@@ -25,7 +25,7 @@ fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
#[test]
fn test_early_abort() {
const app: Component = |cx| {
let val = cx.use_hook(|_| 0, |f| f);
let val = cx.use_hook(|_| 0);
*val += 1;
+3 -3
View File
@@ -46,7 +46,7 @@ fn manual_diffing() {
#[test]
fn events_generate() {
static App: Component = |cx| {
let count = cx.use_hook(|_| 0, |f| f);
let count = cx.use_hook(|_| 0);
let inner = match *count {
0 => {
@@ -105,7 +105,7 @@ fn events_generate() {
#[test]
fn components_generate() {
static App: Component = |cx| {
let render_phase = cx.use_hook(|_| 0, |f| f);
let render_phase = cx.use_hook(|_| 0);
*render_phase += 1;
cx.render(match *render_phase {
@@ -221,7 +221,7 @@ fn components_generate() {
#[test]
fn component_swap() {
static App: Component = |cx| {
let render_phase = cx.use_hook(|_| 0, |f| f);
let render_phase = cx.use_hook(|_| 0);
*render_phase += 1;
cx.render(match *render_phase {
+6 -6
View File
@@ -29,7 +29,7 @@ fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
#[test]
fn test_memory_leak() {
fn app(cx: Scope) -> Element {
let val = cx.use_hook(|_| 0, |f| f);
let val = cx.use_hook(|_| 0);
*val += 1;
@@ -37,7 +37,7 @@ fn test_memory_leak() {
return None;
}
let name = cx.use_hook(|_| String::from("asd"), |f| f);
let name = cx.use_hook(|_| String::from("asd"));
cx.render(rsx!(
div { "Hello, world!" }
@@ -86,7 +86,7 @@ fn test_memory_leak() {
#[test]
fn memo_works_properly() {
fn app(cx: Scope) -> Element {
let val = cx.use_hook(|_| 0, |f| f);
let val = cx.use_hook(|_| 0);
*val += 1;
@@ -94,7 +94,7 @@ fn memo_works_properly() {
return None;
}
let name = cx.use_hook(|_| String::from("asd"), |f| f);
let name = cx.use_hook(|_| String::from("asd"));
cx.render(rsx!(
div { "Hello, world!" }
@@ -204,7 +204,7 @@ fn free_works_on_root_hooks() {
}
fn app(cx: Scope) -> Element {
let name = cx.use_hook(|_| Droppable(String::from("asd")), |f| f);
let name = cx.use_hook(|_| Droppable(String::from("asd")));
rsx!(cx, div { "{name.0}" })
}
@@ -216,7 +216,7 @@ fn free_works_on_root_hooks() {
fn old_props_arent_stale() {
fn app(cx: Scope) -> Element {
dbg!("rendering parent");
let cnt = cx.use_hook(|_| 0, |f| f);
let cnt = cx.use_hook(|_| 0);
*cnt += 1;
if *cnt == 1 {
+1 -8
View File
@@ -59,14 +59,7 @@ pub struct WebviewWindowProps<'a> {
pub fn WebviewWindow(cx: Scope<WebviewWindowProps>) -> Element {
let dtcx = cx.consume_state::<RefCell<DesktopContext>>()?;
cx.use_hook(
|_| {
//
},
|state| {
//
},
);
cx.use_hook(|_| {});
// render the children directly
todo!()
+2 -11
View File
@@ -4,17 +4,8 @@
<html>
<head>
<!-- enable for mobile support -->
<meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0 user-scalable=no"
charset="utf-8" />
<!-- <style>
body,
html {
position: fixed;
}
</style> -->
<link rel="stylesheet" href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css">
<script src="https://cdn.plot.ly/plotly-1.52.3.min.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0" charset="utf-8" />
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0 maximum-scale=1.0 user-scalable=no" charset="utf-8" /> -->
</head>
+12 -15
View File
@@ -54,6 +54,11 @@ function serialize_event(event) {
case "submit": {
let target = event.target;
let value = target.value ?? target.textContent;
if (target.type == "checkbox") {
value = target.checked ? "true" : "false";
}
return {
value: value
};
@@ -310,32 +315,24 @@ class Interpreter {
element.setAttribute(`dioxus-event-${event_name}`, `${scope}.${mounted_node_id}`);
if (this.listeners[event_name] === undefined) {
this.listeners[event_name] = "bla";
this.listeners[event_name] = true;
this.root.addEventListener(event_name, (event) => {
// console.log("CLICKED");
console.log("handling event", event);
const target = event.target;
const real_id = target.getAttribute(`dioxus-id`);
if (real_id == null) {
alert("no id");
return;
}
// const fields = val.split(".");
// const scope_id = parseInt(fields[0]);
// const real_id = parseInt(fields[1]);
// // console.log(`parsed event with scope_id ${scope_id} and real_id ${real_id}`);
// console.log("message fired");
let contents = serialize_event(event);
let evt = {
rpc.call('user_event', {
event: event_name,
// scope: scope_id,
mounted_dom_id: parseInt(real_id),
contents: contents,
};
contents: serialize_event(event),
});
rpc.call('user_event', evt);
});
}
}
+33 -40
View File
@@ -60,37 +60,33 @@ impl<T> ProvidedStateInner<T> {
///
///
pub fn use_context<'a, T: 'static>(cx: &'a ScopeState) -> Option<UseSharedState<'a, T>> {
cx.use_hook(
|_| {
let scope_id = cx.scope_id();
let root = cx.consume_context::<ProvidedState<T>>();
let state = cx.use_hook(|_| {
let scope_id = cx.scope_id();
let root = cx.consume_context::<ProvidedState<T>>();
if let Some(root) = root.as_ref() {
root.borrow_mut().consumers.insert(scope_id);
}
if let Some(root) = root.as_ref() {
root.borrow_mut().consumers.insert(scope_id);
}
let value = root.as_ref().map(|f| f.borrow().value.clone());
SharedStateInner {
root,
value,
scope_id,
needs_notification: Cell::new(false),
}
},
|f| {
//
f.needs_notification.set(false);
match (&f.value, &f.root) {
(Some(value), Some(root)) => Some(UseSharedState {
cx,
value,
root,
needs_notification: &f.needs_notification,
}),
_ => None,
}
},
)
let value = root.as_ref().map(|f| f.borrow().value.clone());
SharedStateInner {
root,
value,
scope_id,
needs_notification: Cell::new(false),
}
});
state.needs_notification.set(false);
match (&state.value, &state.root) {
(Some(value), Some(root)) => Some(UseSharedState {
cx,
value,
root,
needs_notification: &state.needs_notification,
}),
_ => None,
}
}
struct SharedStateInner<T: 'static> {
@@ -176,15 +172,12 @@ where
///
///
pub fn use_context_provider<'a, T: 'static>(cx: &'a ScopeState, f: impl FnOnce() -> T) {
cx.use_hook(
|_| {
let state: ProvidedState<T> = RefCell::new(ProvidedStateInner {
value: Rc::new(RefCell::new(f())),
notify_any: cx.schedule_update_any(),
consumers: HashSet::new(),
});
cx.provide_context(state)
},
|_inner| {},
)
cx.use_hook(|_| {
let state: ProvidedState<T> = RefCell::new(ProvidedStateInner {
value: Rc::new(RefCell::new(f())),
notify_any: cx.schedule_update_any(),
consumers: HashSet::new(),
});
cx.provide_context(state)
});
}
+40 -43
View File
@@ -1,6 +1,6 @@
use dioxus_core::{ScopeState, TaskId};
use std::future::Future;
use std::{cell::Cell, pin::Pin, rc::Rc};
use std::{cell::Cell, rc::Rc};
/*
@@ -22,61 +22,58 @@ pub fn use_coroutine<'a, F>(
where
F: Future<Output = ()> + 'static,
{
cx.use_hook(
move |_| {
let f = create_future();
let id = cx.push_future(f);
State {
let state = cx.use_hook(move |_| {
let f = create_future();
let id = cx.push_future(f);
State {
running: Default::default(),
id
_id: id
// pending_fut: Default::default(),
// running_fut: Default::default(),
}
},
|state| {
// state.pending_fut.set(Some(Box::pin(f)));
});
// if let Some(fut) = state.running_fut.as_mut() {
// cx.push_future(fut);
// }
// state.pending_fut.set(Some(Box::pin(f)));
// if let Some(fut) = state.running_fut.take() {
// state.running.set(true);
// fut.resume();
// }
// if let Some(fut) = state.running_fut.as_mut() {
// cx.push_future(fut);
// }
// let submit: Box<dyn FnOnce() + 'a> = Box::new(move || {
// let g = async move {
// running.set(true);
// create_future().await;
// running.set(false);
// };
// let p: Pin<Box<dyn Future<Output = ()>>> = Box::pin(g);
// fut_slot
// .borrow_mut()
// .replace(unsafe { std::mem::transmute(p) });
// });
// if let Some(fut) = state.running_fut.take() {
// state.running.set(true);
// fut.resume();
// }
// let submit = unsafe { std::mem::transmute(submit) };
// state.submit.get_mut().replace(submit);
// let submit: Box<dyn FnOnce() + 'a> = Box::new(move || {
// let g = async move {
// running.set(true);
// create_future().await;
// running.set(false);
// };
// let p: Pin<Box<dyn Future<Output = ()>>> = Box::pin(g);
// fut_slot
// .borrow_mut()
// .replace(unsafe { std::mem::transmute(p) });
// });
// if state.running.get() {
// // let mut fut = state.fut.borrow_mut();
// // cx.push_task(|| fut.as_mut().unwrap().as_mut());
// } else {
// // make sure to drop the old future
// if let Some(fut) = state.fut.borrow_mut().take() {
// drop(fut);
// }
// }
CoroutineHandle { cx, inner: state }
},
)
// let submit = unsafe { std::mem::transmute(submit) };
// state.submit.get_mut().replace(submit);
// if state.running.get() {
// // let mut fut = state.fut.borrow_mut();
// // cx.push_task(|| fut.as_mut().unwrap().as_mut());
// } else {
// // make sure to drop the old future
// if let Some(fut) = state.fut.borrow_mut().take() {
// drop(fut);
// }
// }
CoroutineHandle { cx, inner: state }
}
struct State {
running: Rc<Cell<bool>>,
id: TaskId,
_id: TaskId,
// the way this is structure, you can toggle the coroutine without re-rendering the comppnent
// this means every render *generates* the future, which is a bit of a waste
// todo: allocate pending futures in the bump allocator and then have a true promotion
+30 -32
View File
@@ -5,40 +5,38 @@ pub fn use_future<'a, T: 'static, F: Future<Output = T> + 'static>(
cx: &'a ScopeState,
f: impl FnOnce() -> F,
) -> (Option<&T>, FutureHandle<'a, T>) {
cx.use_hook(
|_| {
//
let fut = f();
let slot = Rc::new(Cell::new(None));
let updater = cx.schedule_update();
let state = cx.use_hook(|_| {
//
let fut = f();
let slot = Rc::new(Cell::new(None));
let updater = cx.schedule_update();
let _slot = slot.clone();
let new_fut = async move {
let res = fut.await;
_slot.set(Some(res));
updater();
};
let task = cx.push_future(new_fut);
let _slot = slot.clone();
let new_fut = async move {
let res = fut.await;
_slot.set(Some(res));
updater();
};
let task = cx.push_future(new_fut);
UseFutureInner {
needs_regen: true,
slot,
value: None,
task: Some(task),
}
},
|state| {
if let Some(value) = state.slot.take() {
state.value = Some(value);
state.task = None;
}
(
state.value.as_ref(),
FutureHandle {
cx,
value: Cell::new(None),
},
)
UseFutureInner {
needs_regen: true,
slot,
value: None,
task: Some(task),
}
});
if let Some(value) = state.slot.take() {
state.value = Some(value);
state.task = None;
}
(
state.value.as_ref(),
FutureHandle {
cx,
value: Cell::new(None),
},
)
}
+13 -27
View File
@@ -14,18 +14,15 @@ use std::{
};
pub fn use_model<'a, T: 'static>(cx: &'a ScopeState, f: impl FnOnce() -> T) -> UseModel<'a, T> {
cx.use_hook(
|_| UseModelInner {
update_scheduled: Cell::new(false),
update_callback: cx.schedule_update(),
value: RefCell::new(f()),
// tasks: RefCell::new(Vec::new()),
},
|inner| {
inner.update_scheduled.set(false);
UseModel { inner }
},
)
let inner = cx.use_hook(|_| UseModelInner {
update_scheduled: Cell::new(false),
update_callback: cx.schedule_update(),
value: RefCell::new(f()),
// tasks: RefCell::new(Vec::new()),
});
inner.update_scheduled.set(false);
UseModel { inner }
}
pub struct UseModel<'a, T> {
@@ -81,21 +78,10 @@ pub fn use_model_coroutine<'a, T, F: Future<Output = ()> + 'static>(
_model: UseModel<T>,
_f: impl FnOnce(AppModels) -> F,
) -> UseModelCoroutine {
cx.use_hook(
|_| {
//
UseModelTaskInner {
task: Default::default(),
}
},
|inner| {
// if let Some(task) = inner.task.get_mut() {
// cx.push_task(|| task);
// }
//
todo!()
},
)
cx.use_hook(|_| UseModelTaskInner {
task: Default::default(),
});
todo!()
}
impl<T> Copy for UseModel<'_, T> {}
+8 -11
View File
@@ -6,17 +6,14 @@ use std::{
use dioxus_core::ScopeState;
pub fn use_ref<'a, T: 'static>(cx: &'a ScopeState, f: impl FnOnce() -> T) -> UseRef<'a, T> {
cx.use_hook(
|_| UseRefInner {
update_scheduled: Cell::new(false),
update_callback: cx.schedule_update(),
value: RefCell::new(f()),
},
|inner| {
inner.update_scheduled.set(false);
UseRef { inner }
},
)
let inner = cx.use_hook(|_| UseRefInner {
update_scheduled: Cell::new(false),
update_callback: cx.schedule_update(),
value: RefCell::new(f()),
});
inner.update_scheduled.set(false);
UseRef { inner }
}
pub struct UseRef<'a, T> {
+20 -24
View File
@@ -51,32 +51,28 @@ pub fn use_state<'a, T: 'static>(
cx: &'a ScopeState,
initial_state_fn: impl FnOnce() -> T,
) -> UseState<'a, T> {
cx.use_hook(
move |_| {
let first_val = initial_state_fn();
UseStateInner {
current_val: Rc::new(first_val),
update_callback: cx.schedule_update(),
wip: Rc::new(RefCell::new(None)),
update_scheuled: Cell::new(false),
}
},
move |hook| {
hook.update_scheuled.set(false);
let hook = cx.use_hook(move |_| {
let first_val = initial_state_fn();
UseStateInner {
current_val: Rc::new(first_val),
update_callback: cx.schedule_update(),
wip: Rc::new(RefCell::new(None)),
update_scheuled: Cell::new(false),
}
});
let mut new_val = hook.wip.borrow_mut();
if new_val.is_some() {
// if there's only one reference (weak or otherwise), we can just swap the values
if let Some(val) = Rc::get_mut(&mut hook.current_val) {
*val = new_val.take().unwrap();
} else {
hook.current_val = Rc::new(new_val.take().unwrap());
}
}
hook.update_scheuled.set(false);
let mut new_val = hook.wip.borrow_mut();
if new_val.is_some() {
// if there's only one reference (weak or otherwise), we can just swap the values
if let Some(val) = Rc::get_mut(&mut hook.current_val) {
*val = new_val.take().unwrap();
} else {
hook.current_val = Rc::new(new_val.take().unwrap());
}
}
UseState { inner: &*hook }
},
)
UseState { inner: &*hook }
}
struct UseStateInner<T: 'static> {
current_val: Rc<T>,
+22 -25
View File
@@ -7,34 +7,31 @@ pub fn use_suspense<R: 'static, F: Future<Output = R> + 'static>(
create_future: impl FnOnce() -> F,
render: impl FnOnce(&R) -> Element,
) -> Element {
cx.use_hook(
|_| {
let fut = create_future();
let sus = cx.use_hook(|_| {
let fut = create_future();
let wip_value: Rc<Cell<Option<R>>> = Default::default();
let wip_value: Rc<Cell<Option<R>>> = Default::default();
let wip = wip_value.clone();
let new_fut = async move {
let val = fut.await;
wip.set(Some(val));
};
let wip = wip_value.clone();
let new_fut = async move {
let val = fut.await;
wip.set(Some(val));
};
let task = cx.push_future(new_fut);
SuspenseInner {
task,
value: None,
wip_value,
}
},
|sus| {
if let Some(value) = sus.value.as_ref() {
render(&value)
} else {
// generate a placeholder node if the future isnt ready
None
}
},
)
let task = cx.push_future(new_fut);
SuspenseInner {
task,
value: None,
wip_value,
}
});
if let Some(value) = sus.value.as_ref() {
render(&value)
} else {
// generate a placeholder node if the future isnt ready
None
}
}
struct SuspenseInner<R> {
+2 -3
View File
@@ -281,8 +281,8 @@ builder_constructors! {
/// ```
///
/// ## References:
/// - https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div
/// - https://www.w3schools.com/tags/tag_div.asp
/// - <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/div>
/// - <https://www.w3schools.com/tags/tag_div.asp>
div {};
/// Build a
@@ -883,7 +883,6 @@ builder_constructors! {
src: Uri,
step: String,
tabindex: usize,
width: isize,
// Manual implementations below...
+6 -6
View File
@@ -21,12 +21,12 @@ pub mod on {
$(
$(#[$method_attr])*
pub fn $name<'a, F>(
c: NodeFactory<'a>,
factory: NodeFactory<'a>,
mut callback: F,
) -> Listener<'a>
where F: FnMut(Arc<$wrapper>) + 'a
{
let bump = &c.bump();
let bump = &factory.bump();
// we can't allocate unsized in bumpalo's box, so we need to craft the box manually
// safety: this is essentially the same as calling Box::new() but manually
@@ -48,7 +48,7 @@ pub mod on {
callback: bump.alloc(std::cell::RefCell::new(Some(callback))),
};
c.listener(shortname, handler)
factory.listener(shortname, handler)
}
)*
)*
@@ -179,8 +179,8 @@ pub mod on {
/// ```
///
/// ## Reference
/// - https://www.w3schools.com/tags/ev_onclick.asp
/// - https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event
/// - <https://www.w3schools.com/tags/ev_onclick.asp>
/// - <https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event>
onclick
/// oncontextmenu
@@ -430,7 +430,7 @@ pub mod on {
/// Get the key code as an enum Variant.
///
/// This is intended for things like arrow keys, escape keys, function keys, and other non-international keys.
/// To match on unicode sequences, use the [`key`] method - this will return a string identifier instead of a limited enum.
/// To match on unicode sequences, use the [`KeyboardEvent::key`] method - this will return a string identifier instead of a limited enum.
///
///
/// ## Example
+3 -2
View File
@@ -399,7 +399,7 @@ pub trait GlobalAttributes {
height: "height",
/// Specifies how flex items are aligned along the main axis of the flex container after any flexible lengths and auto margins have been resolved.
justify_content: "auto margins have been resolved.",
justify_content: "justify-content",
/// Specify the location of the left edge of the positioned element.
left: "left",
@@ -535,6 +535,7 @@ pub trait GlobalAttributes {
/// Sets the horizontal alignment of inline content.
text_align: "text-align",
/// Specifies how the last line of a block or a line right before a forced line break is aligned when is justify.",
text_align_last: "text-align-last",
@@ -614,7 +615,7 @@ pub trait GlobalAttributes {
word_wrap: "word-wrap",
/// Specifies a layering or stacking order for positioned elements.
z_index : "z-index ",
z_index : "z-index",
}
aria_trait_methods! {
+11 -12
View File
@@ -49,7 +49,8 @@ impl<R: Routable> RouterService<R> {
}
pub fn use_router_service<R: Routable>(cx: &ScopeState) -> Option<&Rc<RouterService<R>>> {
cx.use_hook(|_| cx.consume_state::<RouterService<R>>(), |f| f.as_ref())
cx.use_hook(|_| cx.consume_state::<RouterService<R>>())
.as_ref()
}
/// This hould only be used once per app
@@ -57,7 +58,7 @@ pub fn use_router_service<R: Routable>(cx: &ScopeState) -> Option<&Rc<RouterServ
/// You can manually parse the route if you want, but the derived `parse` method on `Routable` will also work just fine
pub fn use_router<R: Routable>(cx: &ScopeState, mut parse: impl FnMut(&str) -> R + 'static) -> &R {
// for the web, attach to the history api
cx.use_hook(
let state = cx.use_hook(
#[cfg(not(feature = "web"))]
|_| {},
#[cfg(feature = "web")]
@@ -112,15 +113,13 @@ pub fn use_router<R: Routable>(cx: &ScopeState, mut parse: impl FnMut(&str) -> R
service
},
|state| {
let base = state.base_ur.borrow();
if let Some(base) = base.as_ref() {
//
let path = format!("{}{}", base, state.get_current_route());
}
let history = state.history_service.borrow();
);
state.historic_routes.last().unwrap()
},
)
let base = state.base_ur.borrow();
if let Some(base) = base.as_ref() {
let path = format!("{}{}", base, state.get_current_route());
}
let history = state.history_service.borrow();
state.historic_routes.last().unwrap()
}
+19 -22
View File
@@ -4,30 +4,27 @@ use crate::{Routable, RouterCfg, RouterService};
use dioxus_core::ScopeState;
/// Initialize the app's router service and provide access to `Link` components
pub fn use_router<'a, R: Routable>(cx: &'a ScopeState, cfg: impl FnOnce(&mut RouterCfg)) -> &'a R {
cx.use_hook(
|_| {
let svc: RouterService<R> = RouterService {
regen_route: cx.schedule_update(),
pending_routes: RefCell::new(Vec::new()),
};
let first_path = R::default();
cx.provide_context(svc);
UseRouterInner {
svc: cx.consume_context::<RouterService<R>>().unwrap(),
history: vec![first_path],
}
},
|f| {
let mut pending_routes = f.svc.pending_routes.borrow_mut();
pub fn use_router<'a, R: Routable>(cx: &'a ScopeState, _cfg: impl FnOnce(&mut RouterCfg)) -> &'a R {
let router = cx.use_hook(|_| {
let svc: RouterService<R> = RouterService {
regen_route: cx.schedule_update(),
pending_routes: RefCell::new(Vec::new()),
};
let first_path = R::default();
cx.provide_context(svc);
UseRouterInner {
svc: cx.consume_context::<RouterService<R>>().unwrap(),
history: vec![first_path],
}
});
for route in pending_routes.drain(..) {
f.history.push(route);
}
let mut pending_routes = router.svc.pending_routes.borrow_mut();
f.history.last().unwrap()
},
)
for route in pending_routes.drain(..) {
router.history.push(route);
}
router.history.last().unwrap()
}
struct UseRouterInner<R: Routable> {
+72 -9
View File
@@ -1,24 +1,87 @@
# Dioxus SSR
<div align="center">
<h1>Dioxus Server-Side Rendering (SSR)</h1>
<p>
<strong>Render Dioxus to valid html.</strong>
</p>
</div>
Render a Dioxus VirtualDOM to a string.
## Resources
This crate is a part of the broader Dioxus ecosystem. For more resources about Dioxus, check out:
- [Getting Started](https://dioxuslabs.com/getting-started)
- [Book](https://dioxuslabs.com/book)
- [Reference](https://dioxuslabs.com/reference)
- [Community Examples](https://github.com/DioxusLabs/community-examples)
## Overview
Dioxus SSR provides utilities to render Dioxus components to valid HTML. Once rendered, the HTML can be rehydrated client side or served from your web-server of choice.
```rust
// Our app:
const App: Component = |cx| rsx!(cx, div {"hello world!"});
let app: Component = |cx| cx.render(rsx!(div {"hello world!"}));
// Build the virtualdom from our app
let mut vdom = VirtualDOM::new(App);
// This runs components, lifecycles, etc. without needing a physical dom. Some features (like noderef) won't work.
let mut vdom = VirtualDom::new(app);
let _ = vdom.rebuild();
// Render the entire virtualdom from the root
let text = dioxus::ssr::render_vdom(&vdom);
assert_eq!(text, "<div>hello world!</div>")
```
## Basic Usage
The simplest example is to simply render some `rsx!` nodes to html. This can be done with the [`render_lazy`] api.
```rust
let content = dioxus::ssr::render(rsx!{
div {
(0..5).map(|i| rsx!(
"Number: {i}"
))
}
});
```
## Rendering a VirtualDom
```rust
let mut dom = VirtualDom::new(app);
let _ = dom.rebuild();
let content = dioxus::ssr::render_vdom(&dom);
```
## Configuring output
It's possible to configure the output of the generated HTML.
```rust
let content = dioxus::ssr::render_vdom(&dom, |config| config.pretty(true).prerender(true));
```
## Usage as a writer
We provide the basic `SsrFormatter` object that implements `Display`, so you can integrate SSR into an existing string, or write directly to a file.
```rust
use std::fmt::{Error, Write};
let mut buf = String::new();
let dom = VirtualDom::new(app);
let _ = dom.rebuild();
let args = dioxus::ssr::formatter(dom, |config| config);
buf.write_fmt!(format_args!("{}", args));
```
## Configuration
## Usage in pre-rendering
This crate is particularly useful in pre-generating pages server-side and then selectively loading dioxus client-side to pick up the reactive elements.
+6 -16
View File
@@ -1,14 +1,4 @@
//!
//!
//!
//!
//! This crate demonstrates how to implement a custom renderer for Dioxus VNodes via the `TextRenderer` renderer.
//! The `TextRenderer` consumes a Dioxus Virtual DOM, progresses its event queue, and renders the VNodes to a String.
//!
//! While `VNode` supports "to_string" directly, it renders child components as the RSX! macro tokens. For custom components,
//! an external renderer is needed to progress the component lifecycles. The `TextRenderer` shows how to use the Virtual DOM
//! API to progress these lifecycle events to generate a fully-mounted Virtual DOM instance which can be renderer in the
//! `render` method.
#![doc = include_str!("../README.md")]
use std::fmt::{Display, Formatter};
@@ -17,7 +7,6 @@ use dioxus_core::exports::bumpalo::Bump;
use dioxus_core::IntoVNode;
use dioxus_core::*;
/// A memory pool for rendering
pub struct SsrRenderer {
inner: bumpalo::Bump,
cfg: SsrConfig,
@@ -34,6 +23,7 @@ impl SsrRenderer {
pub fn render_lazy<'a>(&'a mut self, f: LazyNodes<'a, '_>) -> String {
let bump = &mut self.inner as *mut _;
let s = self.render_inner(f);
// reuse the bump's memory
unsafe { (&mut *bump as &mut bumpalo::Bump).reset() };
s
@@ -63,13 +53,12 @@ pub fn render_lazy<'a>(f: LazyNodes<'a, '_>) -> String {
// regular component usage. The <'a> lifetime is used to enforce that all calls of IntoVnode use the same allocator.
//
// When LazyNodes are provided, they are FnOnce, but do not come with a allocator selected to borrow from. The <'a>
// lifetime is therefore longer than the lifetime of the allocator which doesn't exist yet.
// lifetime is therefore longer than the lifetime of the allocator which doesn't exist... yet.
//
// Therefore, we cast our local bump alloactor into 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.
// Therefore, we know that no references are leaking.
let _b: &'a Bump = unsafe { std::mem::transmute(borrowed) };
// is *definitely* shorter than the <'a> lifetime, and we return *owned* data - not borrowed data.
let _b = unsafe { std::mem::transmute::<&Bump, &'a Bump>(borrowed) };
let root = f.into_vnode(NodeFactory::new(_b));
format!(
@@ -85,6 +74,7 @@ pub fn render_lazy<'a>(f: LazyNodes<'a, '_>) -> String {
pub fn render_vdom(dom: &VirtualDom) -> String {
format!("{:}", TextRenderer::from_vdom(dom, SsrConfig::default()))
}
pub fn render_vdom_cfg(dom: &VirtualDom, cfg: impl FnOnce(SsrConfig) -> SsrConfig) -> String {
format!(
"{:}",