wip: remove runner on hook and then update docs
This commit is contained in:
1 parent
7d41a16208
commit
d1560450ba
95 files changed
+1339
-675
No files matched your search
@@ -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 {
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -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
@@ -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>,
|
||||
|
||||
@@ -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
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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!()
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
});
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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> {}
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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...
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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! {
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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!(
|
||||
"{:}",
|
||||
|
||||
Reference in new issue
Block a user