wip: it works but the page is backwards
This commit is contained in:
1 parent
a0a977a12d
commit
cdcd8611e8
19 files changed
+239
-349
No files matched your search
@@ -1,16 +1 @@
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
|
||||
fn fibonacci(n: u64) -> u64 {
|
||||
match n {
|
||||
0 => 1,
|
||||
1 => 1,
|
||||
n => fibonacci(n - 1) + fibonacci(n - 2),
|
||||
}
|
||||
}
|
||||
|
||||
fn criterion_benchmark(c: &mut Criterion) {
|
||||
c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20))));
|
||||
}
|
||||
|
||||
criterion_group!(benches, criterion_benchmark);
|
||||
criterion_main!(benches);
|
||||
fn main() {}
|
||||
@@ -1,3 +1,4 @@
|
||||
#![allow(non_snake_case, non_upper_case_globals)]
|
||||
//! This benchmark tests just the overhead of Dioxus itself.
|
||||
//!
|
||||
//! For the JS Framework Benchmark, both the framework and the browser is benchmarked together. Dioxus prepares changes
|
||||
@@ -8,12 +9,10 @@
|
||||
//! - Dioxus takes 3ms to create 1_000 rows
|
||||
//! - Dioxus takes 30ms to create 10_000 rows
|
||||
//!
|
||||
//! As pure "overhead", these are really really good numbers, mostly slowed down by hitting the global allocator.
|
||||
//! As pure "overhead", these are amazing good numbers, mostly slowed down by hitting the global allocator.
|
||||
//! These numbers don't represent Dioxus with the heuristic engine installed, so I assume it'll be even faster.
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use criterion::{black_box, criterion_group, criterion_main, Criterion};
|
||||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_core::prelude::*;
|
||||
use dioxus_html as dioxus_elements;
|
||||
@@ -58,11 +57,12 @@ struct RowProps {
|
||||
label: Label,
|
||||
}
|
||||
fn Row<'a>(cx: Context<'a, RowProps>) -> DomTree {
|
||||
let [adj, col, noun] = cx.label.0;
|
||||
cx.render(rsx! {
|
||||
tr {
|
||||
td { class:"col-md-1", "{cx.row_id}" }
|
||||
td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
|
||||
a { class: "lbl", "{cx.label}" }
|
||||
a { class: "lbl", "{adj}" "{col}" "{noun}" }
|
||||
}
|
||||
td { class: "col-md-1"
|
||||
a { class: "remove", onclick: move |_| {/* remove */}
|
||||
@@ -86,11 +86,6 @@ impl Label {
|
||||
])
|
||||
}
|
||||
}
|
||||
impl Display for Label {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} {} {}", self.0[0], self.0[1], self.0[2])
|
||||
}
|
||||
}
|
||||
|
||||
static ADJECTIVES: &[&str] = &[
|
||||
"pretty",
|
||||
|
||||
@@ -6,17 +6,16 @@ pub static Example: FC<()> = |cx| {
|
||||
let list = (0..10).map(|f| LazyNodes::new(move |f| todo!()));
|
||||
|
||||
cx.render(LazyNodes::new(move |cx| {
|
||||
let bump = cx.bump();
|
||||
cx.raw_element(
|
||||
"div",
|
||||
None,
|
||||
&mut [],
|
||||
&mut [],
|
||||
cx.bump().alloc([
|
||||
[],
|
||||
[],
|
||||
[
|
||||
cx.text(format_args!("hello")),
|
||||
cx.text(format_args!("hello")),
|
||||
cx.fragment_from_iter(list),
|
||||
]),
|
||||
],
|
||||
None,
|
||||
)
|
||||
}))
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
|
||||
|
||||
fn main() {
|
||||
|
||||
// let g = rsx! {
|
||||
// Fragment {
|
||||
// // div {}
|
||||
// // div {}
|
||||
// // div {}
|
||||
// // div {}
|
||||
// // div {}
|
||||
// // div {}
|
||||
// // div {}
|
||||
// // div {}
|
||||
// // div {}
|
||||
// }
|
||||
// };
|
||||
}
|
||||
@@ -1,31 +1,27 @@
|
||||
fn main() {}
|
||||
|
||||
use dioxus_core::prelude::*;
|
||||
|
||||
fn App(cx: Context<()>) -> DomTree {
|
||||
//
|
||||
fn main() {}
|
||||
|
||||
fn app(cx: Context<()>) -> DomTree {
|
||||
let vak = use_suspense(
|
||||
cx,
|
||||
|| async {},
|
||||
|c, res| {
|
||||
//
|
||||
c.render(LazyNodes::new(move |f| f.text(format_args!(""))))
|
||||
},
|
||||
|c, _res| c.render(LazyNodes::new(move |f| f.text(format_args!("")))),
|
||||
);
|
||||
|
||||
let d1 = cx.render(LazyNodes::new(move |f| {
|
||||
f.raw_element(
|
||||
"div",
|
||||
None,
|
||||
&mut [],
|
||||
&[],
|
||||
f.bump().alloc([
|
||||
[],
|
||||
[],
|
||||
[
|
||||
f.fragment_from_iter(vak),
|
||||
f.text(format_args!("")),
|
||||
f.text(format_args!("")),
|
||||
f.text(format_args!("")),
|
||||
f.text(format_args!("")),
|
||||
]),
|
||||
],
|
||||
None,
|
||||
)
|
||||
}));
|
||||
@@ -34,15 +30,15 @@ fn App(cx: Context<()>) -> DomTree {
|
||||
f.raw_element(
|
||||
"div",
|
||||
None,
|
||||
&mut [],
|
||||
&[],
|
||||
f.bump().alloc([
|
||||
[],
|
||||
[],
|
||||
[
|
||||
f.text(format_args!("")),
|
||||
f.text(format_args!("")),
|
||||
f.text(format_args!("")),
|
||||
f.text(format_args!("")),
|
||||
f.fragment_from_iter(d1),
|
||||
]),
|
||||
d1.unwrap(),
|
||||
],
|
||||
None,
|
||||
)
|
||||
}))
|
||||
|
||||
@@ -1,137 +0,0 @@
|
||||
// #![allow(unused, non_upper_case_globals, non_snake_case)]
|
||||
// use bumpalo::Bump;
|
||||
// use dioxus_core::nodebuilder::*;
|
||||
// use dioxus_core::prelude::*;
|
||||
// use std::{collections::HashMap, future::Future, marker::PhantomData};
|
||||
|
||||
fn main() {}
|
||||
// fn main() {
|
||||
// let mut vdom = VirtualDom::new_with_props(
|
||||
// component,
|
||||
// Props {
|
||||
// blah: false,
|
||||
// text: "blah".into(),
|
||||
// },
|
||||
// );
|
||||
|
||||
// vdom.progress();
|
||||
|
||||
// let somet = String::from("asd");
|
||||
// let text = somet.as_str();
|
||||
|
||||
// /*
|
||||
// this could be auto-generated via the macro
|
||||
// this props is allocated in this
|
||||
// but the component and props would like need to be cached
|
||||
// we could box this fn, abstracting away the props requirement and just keep the entrance and allocator requirement
|
||||
// How do we keep cached things around?
|
||||
// Need some sort of caching mechanism
|
||||
|
||||
// how do we enter into a childscope from a parent scope?
|
||||
|
||||
// Problems:
|
||||
// 1: Comp props need to be stored somewhere so we can re-evalute components when they receive updates
|
||||
// 2: Trees are not evaluated
|
||||
|
||||
// */
|
||||
// let example_caller = move |cx: &Bump| {
|
||||
// todo!()
|
||||
// // let p = Props { blah: true, text };
|
||||
// // let c = Context { props: &p };
|
||||
// // let r = component(&c);
|
||||
// };
|
||||
|
||||
// // check the edit list
|
||||
// }
|
||||
|
||||
// // ~~~ Text shared between components via props can be done with lifetimes! ~~~
|
||||
// // Super duper efficient :)
|
||||
// struct Props {
|
||||
// blah: bool,
|
||||
// text: String,
|
||||
// // text: &'src str,
|
||||
// }
|
||||
// impl Properties for Props {
|
||||
// fn new() -> Self {
|
||||
// todo!()
|
||||
// }
|
||||
// }
|
||||
|
||||
// fn component<'a>(cx: Context<'a, Props>) -> DomTree<'a> {
|
||||
// // Write asynchronous rendering code that immediately returns a "suspended" VNode
|
||||
// // The concurrent API will then progress this component when the future finishes
|
||||
// // You can suspend the entire component, or just parts of it
|
||||
// let product_list = cx.suspend(async {
|
||||
// // Suspend the rendering that completes when the future is done
|
||||
// match fetch_data().await {
|
||||
// Ok(data) => html! { <div> "success!" </div>},
|
||||
// Err(_) => html! { <div> "failure :(" </div>},
|
||||
// }
|
||||
// });
|
||||
|
||||
// // todo!()
|
||||
// cx.render(html! {
|
||||
// <div>
|
||||
// <h1> "Products" </h1>
|
||||
// <button> "hello!" </button>
|
||||
// // Subnodes can even be suspended
|
||||
// // When completely rendered, they won't cause the component itself to re-render, just their slot
|
||||
|
||||
// // <p> { product_list } </p>
|
||||
// </div>
|
||||
// })
|
||||
// }
|
||||
|
||||
// fn BuilderComp<'a>(cx: Context<'a, Props>) -> DomTree<'a> {
|
||||
// // VNodes can be constructed via a builder or the html! macro
|
||||
// // However, both of these are "lazy" - they need to be evaluated (aka, "viewed")
|
||||
// // We can "view" them with Context for ultimate speed while inside components
|
||||
// cx.render(|bump| {
|
||||
// div(bump)
|
||||
// .attr("class", "edit")
|
||||
// .child(text("Hello"))
|
||||
// .child(text(cx.cx.text.as_ref()))
|
||||
// .finish()
|
||||
// })
|
||||
// }
|
||||
|
||||
// #[fc]
|
||||
// fn EffcComp(cx: Context, name: &str) -> DomTree {
|
||||
// // VNodes can be constructed via a builder or the html! macro
|
||||
// // However, both of these are "lazy" - they need to be evaluated (aka, "viewed")
|
||||
// // We can "view" them with Context for ultimate speed while inside components
|
||||
// // use "phase" style allocation;
|
||||
|
||||
// cx.render(html! {
|
||||
// <div>
|
||||
// // your template goes here
|
||||
// // feel free to directly use "name"
|
||||
// </div>
|
||||
// })
|
||||
// }
|
||||
|
||||
// fn FullySuspended<'a>(cx: &'a Context<Props>) -> DomTree<'a> {
|
||||
// cx.suspend(async {
|
||||
// let i: i32 = 0;
|
||||
|
||||
// // full suspended works great with just returning VNodes!
|
||||
// // Feel free to capture the html! macro directly
|
||||
// // Anything returned here is automatically viewed
|
||||
// let tex = match i {
|
||||
// 1 => html! { <div> </div> },
|
||||
// 2 => html! { <div> </div> },
|
||||
// _ => html! { <div> </div> },
|
||||
// };
|
||||
|
||||
// if cx.cx.blah {
|
||||
// html! { <div> </div> }
|
||||
// } else {
|
||||
// tex
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
// /// An example of a datafetching service
|
||||
// async fn fetch_data() -> Result<String, ()> {
|
||||
// todo!()
|
||||
// }
|
||||
+19
-21
@@ -89,7 +89,6 @@
|
||||
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
|
||||
|
||||
use crate::{arena::SharedResources, innerlude::*};
|
||||
use futures_util::{Future, FutureExt};
|
||||
use fxhash::{FxHashMap, FxHashSet};
|
||||
use DomEdit::*;
|
||||
|
||||
@@ -281,19 +280,20 @@ impl<'bump> DiffMachine<'bump> {
|
||||
} = element;
|
||||
|
||||
let real_id = self.vdom.reserve_node();
|
||||
dom_id.set(Some(real_id));
|
||||
|
||||
self.mutations.create_element(tag_name, *namespace, real_id);
|
||||
|
||||
self.stack.add_child_count(1);
|
||||
|
||||
dom_id.set(Some(real_id));
|
||||
|
||||
let cur_scope = self.stack.current_scope().unwrap();
|
||||
let cur_scope_id = self.stack.current_scope().unwrap();
|
||||
let scope = self.vdom.get_scope(cur_scope_id).unwrap();
|
||||
|
||||
listeners.iter().for_each(|listener| {
|
||||
self.fix_listener(listener);
|
||||
self.attach_listener_to_scope(listener, scope);
|
||||
listener.mounted_node.set(Some(real_id));
|
||||
self.mutations
|
||||
.new_event_listener(listener, cur_scope.clone());
|
||||
.new_event_listener(listener, cur_scope_id.clone());
|
||||
});
|
||||
|
||||
for attr in *attributes {
|
||||
@@ -466,16 +466,19 @@ impl<'bump> DiffMachine<'bump> {
|
||||
// We also need to make sure that all listeners are properly attached to the parent scope (fix_listener)
|
||||
//
|
||||
// TODO: take a more efficient path than this
|
||||
let cur_scope = self.stack.current_scope().unwrap();
|
||||
|
||||
let cur_scope_id = self.stack.current_scope().unwrap();
|
||||
let scope = self.vdom.get_scope(cur_scope_id).unwrap();
|
||||
|
||||
if old.listeners.len() == new.listeners.len() {
|
||||
for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
|
||||
if old_l.event != new_l.event {
|
||||
please_commit(&mut self.mutations.edits);
|
||||
self.mutations.remove_event_listener(old_l.event);
|
||||
self.mutations.new_event_listener(new_l, cur_scope);
|
||||
self.mutations.new_event_listener(new_l, cur_scope_id);
|
||||
}
|
||||
new_l.mounted_node.set(old_l.mounted_node.get());
|
||||
self.fix_listener(new_l);
|
||||
self.attach_listener_to_scope(new_l, scope);
|
||||
}
|
||||
} else {
|
||||
please_commit(&mut self.mutations.edits);
|
||||
@@ -484,10 +487,8 @@ impl<'bump> DiffMachine<'bump> {
|
||||
}
|
||||
for listener in new.listeners {
|
||||
listener.mounted_node.set(Some(root));
|
||||
self.mutations.new_event_listener(listener, cur_scope);
|
||||
|
||||
// Make sure the listener gets attached to the scope list
|
||||
self.fix_listener(listener);
|
||||
self.mutations.new_event_listener(listener, cur_scope_id);
|
||||
self.attach_listener_to_scope(listener, scope);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1082,13 +1083,10 @@ impl<'bump> DiffMachine<'bump> {
|
||||
}
|
||||
}
|
||||
|
||||
fn fix_listener<'a>(&mut self, listener: &'a Listener<'a>) {
|
||||
let scope_id = self.stack.current_scope();
|
||||
if let Some(scope_id) = scope_id {
|
||||
let scope = self.vdom.get_scope(scope_id).unwrap();
|
||||
let mut queue = scope.listeners.borrow_mut();
|
||||
let long_listener: &'a Listener<'static> = unsafe { std::mem::transmute(listener) };
|
||||
queue.push(long_listener as *const _)
|
||||
}
|
||||
/// Adds a listener closure to a scope during diff.
|
||||
fn attach_listener_to_scope<'a>(&mut self, listener: &'a Listener<'a>, scope: &Scope) {
|
||||
let mut queue = scope.listeners.borrow_mut();
|
||||
let long_listener: &'a Listener<'static> = unsafe { std::mem::transmute(listener) };
|
||||
queue.push(long_listener as *const _)
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,6 @@ impl Scope {
|
||||
child_nodes: ScopeChildren,
|
||||
) {
|
||||
self.caller = caller;
|
||||
// let child_nodes = unsafe { std::mem::transmute(child_nodes) };
|
||||
let child_nodes = unsafe { child_nodes.extend_lifetime() };
|
||||
self.child_nodes = child_nodes;
|
||||
}
|
||||
@@ -237,8 +236,8 @@ impl Scope {
|
||||
}
|
||||
|
||||
pub fn consume_garbage(&self) -> Vec<&VNode> {
|
||||
let mut garbage = self.pending_garbage.borrow_mut();
|
||||
garbage
|
||||
self.pending_garbage
|
||||
.borrow_mut()
|
||||
.drain(..)
|
||||
.map(|node| {
|
||||
// safety: scopes cannot cycle without their garbage being collected. these nodes are safe
|
||||
|
||||
@@ -7,6 +7,8 @@ use dioxus_core as dioxus;
|
||||
use dioxus_html as dioxus_elements;
|
||||
use DomEdit::*;
|
||||
|
||||
const LOGGING_ENABLED: bool = false;
|
||||
|
||||
#[test]
|
||||
fn test_original_diff() {
|
||||
static App: FC<()> = |cx| {
|
||||
@@ -57,7 +59,7 @@ async fn create() {
|
||||
})
|
||||
};
|
||||
|
||||
test_logging::set_up_logging();
|
||||
test_logging::set_up_logging(LOGGING_ENABLED);
|
||||
let mut dom = VirtualDom::new(App);
|
||||
let mutations = dom.rebuild_async().await.unwrap();
|
||||
assert_eq!(
|
||||
@@ -98,7 +100,7 @@ async fn create_list() {
|
||||
})
|
||||
};
|
||||
|
||||
test_logging::set_up_logging();
|
||||
test_logging::set_up_logging(LOGGING_ENABLED);
|
||||
|
||||
let mut dom = VirtualDom::new(App);
|
||||
let mutations = dom.rebuild_async().await.unwrap();
|
||||
@@ -141,7 +143,7 @@ async fn create_simple() {
|
||||
})
|
||||
};
|
||||
|
||||
test_logging::set_up_logging();
|
||||
test_logging::set_up_logging(LOGGING_ENABLED);
|
||||
|
||||
let mut dom = VirtualDom::new(App);
|
||||
let mutations = dom.rebuild_async().await.unwrap();
|
||||
@@ -177,7 +179,7 @@ async fn create_components() {
|
||||
})
|
||||
};
|
||||
|
||||
test_logging::set_up_logging();
|
||||
test_logging::set_up_logging(LOGGING_ENABLED);
|
||||
|
||||
let mut dom = VirtualDom::new(App);
|
||||
let mutations = dom.rebuild_async().await.unwrap();
|
||||
@@ -223,7 +225,7 @@ async fn anchors() {
|
||||
})
|
||||
};
|
||||
|
||||
test_logging::set_up_logging();
|
||||
test_logging::set_up_logging(LOGGING_ENABLED);
|
||||
|
||||
let mut dom = VirtualDom::new(App);
|
||||
let mutations = dom.rebuild_async().await.unwrap();
|
||||
@@ -249,7 +251,7 @@ async fn suspended() {
|
||||
cx.render(rsx! { {val} })
|
||||
};
|
||||
|
||||
test_logging::set_up_logging();
|
||||
test_logging::set_up_logging(LOGGING_ENABLED);
|
||||
|
||||
let mut dom = VirtualDom::new(App);
|
||||
let mutations = dom.rebuild_async().await.unwrap();
|
||||
|
||||
@@ -6,6 +6,8 @@ mod test_logging;
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_html as dioxus_elements;
|
||||
|
||||
const LOGGING_ENABLED: bool = false;
|
||||
|
||||
#[async_std::test]
|
||||
async fn test_iterative_create_components() {
|
||||
static App: FC<()> = |cx| {
|
||||
@@ -34,7 +36,7 @@ async fn test_iterative_create_components() {
|
||||
})
|
||||
}
|
||||
|
||||
test_logging::set_up_logging();
|
||||
test_logging::set_up_logging(LOGGING_ENABLED);
|
||||
|
||||
let mut dom = VirtualDom::new(App);
|
||||
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
//! Diffing Tests
|
||||
//! -------------
|
||||
//!
|
||||
//! These should always compile and run, but the result is not validat root: (), m: () ed for each test.
|
||||
//! TODO: Validate the results beyond visual inspection.
|
||||
|
||||
use bumpalo::Bump;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use dioxus::{
|
||||
arena::SharedResources, diff::DiffMachine, prelude::*, DiffInstruction, DomEdit, MountType,
|
||||
};
|
||||
@@ -18,7 +13,7 @@ use DomEdit::*;
|
||||
|
||||
// logging is wired up to the test harness
|
||||
// feel free to enable while debugging
|
||||
const IS_LOGGING_ENABLED: bool = true;
|
||||
const IS_LOGGING_ENABLED: bool = false;
|
||||
|
||||
struct TestDom {
|
||||
bump: Bump,
|
||||
@@ -26,9 +21,7 @@ struct TestDom {
|
||||
}
|
||||
impl TestDom {
|
||||
fn new() -> TestDom {
|
||||
if IS_LOGGING_ENABLED {
|
||||
test_logging::set_up_logging();
|
||||
}
|
||||
test_logging::set_up_logging(IS_LOGGING_ENABLED);
|
||||
let bump = Bump::new();
|
||||
let resources = SharedResources::new();
|
||||
TestDom { bump, resources }
|
||||
@@ -82,8 +75,6 @@ impl TestDom {
|
||||
|
||||
let new = self.bump.alloc(self.render(right));
|
||||
|
||||
// let mut create_edits = Vec::new();
|
||||
|
||||
let mut machine = DiffMachine::new_headless(&self.resources);
|
||||
|
||||
machine.stack.create_node(old, MountType::Append);
|
||||
@@ -343,16 +334,31 @@ fn two_fragments_with_differrent_elements_are_differet() {
|
||||
let dom = TestDom::new();
|
||||
|
||||
let left = rsx!(
|
||||
{ (0..2).map(|f| rsx! { div { }} ) }
|
||||
{ (0..2).map(|_| rsx! { div { }} ) }
|
||||
p {}
|
||||
);
|
||||
let right = rsx!(
|
||||
{ (0..5).map(|f| rsx! (h1 { }) ) }
|
||||
{ (0..5).map(|_| rsx! (h1 { }) ) }
|
||||
p {}
|
||||
);
|
||||
|
||||
let edits = dom.lazy_diff(left, right);
|
||||
log::debug!("{:#?}", &edits);
|
||||
let (create, changes) = dom.lazy_diff(left, right);
|
||||
log::debug!("{:#?}", &changes);
|
||||
assert_eq!(
|
||||
changes.edits,
|
||||
[
|
||||
// create the new h1s
|
||||
CreateElement { tag: "h1", id: 3 },
|
||||
CreateElement { tag: "h1", id: 4 },
|
||||
CreateElement { tag: "h1", id: 5 },
|
||||
InsertAfter { root: 1, n: 3 },
|
||||
// replace the divs with new h1s
|
||||
CreateElement { tag: "h1", id: 6 },
|
||||
ReplaceWith { root: 0, m: 1 },
|
||||
CreateElement { tag: "h1", id: 7 },
|
||||
ReplaceWith { root: 1, m: 1 },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/// Should result in multiple nodes destroyed - with changes to the first nodes
|
||||
@@ -452,14 +458,6 @@ fn keyed_diffing_order() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fragment_keys() {
|
||||
let r = 1;
|
||||
let p = rsx! {
|
||||
Fragment { key: "asd {r}" }
|
||||
};
|
||||
}
|
||||
|
||||
/// Should result in moves, but not removals or additions
|
||||
#[test]
|
||||
fn keyed_diffing_out_of_order() {
|
||||
@@ -477,8 +475,12 @@ fn keyed_diffing_out_of_order() {
|
||||
})
|
||||
});
|
||||
|
||||
let edits = dom.lazy_diff(left, right);
|
||||
log::debug!("{:?}", &edits.1);
|
||||
let (_, changes) = dom.lazy_diff(left, right);
|
||||
log::debug!("{:?}", &changes);
|
||||
assert_eq!(
|
||||
changes.edits,
|
||||
[PushRoot { id: 6 }, InsertBefore { root: 4, n: 1 }]
|
||||
);
|
||||
}
|
||||
|
||||
/// Should result in moves only
|
||||
@@ -655,21 +657,24 @@ fn keyed_diffing_additions_and_moves_on_ends() {
|
||||
|
||||
let right = rsx!({
|
||||
[/**/ 7, 4, 5, 6, 11, 12 /**/].iter().map(|f| {
|
||||
// [/**/ 8, 7, 4, 5, 6, 9, 10 /**/].iter().map(|f| {
|
||||
rsx! { div { key: "{f}" }}
|
||||
})
|
||||
});
|
||||
|
||||
let (_, change) = dom.lazy_diff(left, right);
|
||||
log::debug!("{:?}", change);
|
||||
// assert_eq!(
|
||||
// change.edits,
|
||||
// [
|
||||
// CreateElement { id: 5, tag: "div" },
|
||||
// CreateElement { id: 6, tag: "div" },
|
||||
// InsertAfter { n: 2, root: 4 }
|
||||
// ]
|
||||
// );
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[
|
||||
// create 11, 12
|
||||
CreateElement { tag: "div", id: 4 },
|
||||
CreateElement { tag: "div", id: 5 },
|
||||
InsertAfter { root: 2, n: 2 },
|
||||
// move 7 to the front
|
||||
PushRoot { id: 3 },
|
||||
InsertBefore { root: 0, n: 1 }
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -688,16 +693,25 @@ fn keyed_diffing_additions_and_moves_in_middle() {
|
||||
})
|
||||
});
|
||||
|
||||
// LIS: 4, 5, 6
|
||||
let (_, change) = dom.lazy_diff(left, right);
|
||||
log::debug!("{:?}", change);
|
||||
// assert_eq!(
|
||||
// change.edits,
|
||||
// [
|
||||
// CreateElement { id: 5, tag: "div" },
|
||||
// CreateElement { id: 6, tag: "div" },
|
||||
// InsertAfter { n: 2, root: 4 }
|
||||
// ]
|
||||
// );
|
||||
log::debug!("{:#?}", change);
|
||||
assert_eq!(
|
||||
change.edits,
|
||||
[
|
||||
// create 13, 17
|
||||
CreateElement { tag: "div", id: 4 },
|
||||
CreateElement { tag: "div", id: 5 },
|
||||
InsertBefore { root: 1, n: 2 },
|
||||
// create 11, 12
|
||||
CreateElement { tag: "div", id: 6 },
|
||||
CreateElement { tag: "div", id: 7 },
|
||||
InsertBefore { root: 2, n: 2 },
|
||||
// move 7
|
||||
PushRoot { id: 3 },
|
||||
InsertBefore { root: 0, n: 1 }
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
pub fn set_up_logging() {
|
||||
pub fn set_up_logging(enabled: bool) {
|
||||
use fern::colors::{Color, ColoredLevelConfig};
|
||||
use log::debug;
|
||||
|
||||
if !enabled {
|
||||
return;
|
||||
}
|
||||
|
||||
// configure colors for the whole line
|
||||
let colors_line = ColoredLevelConfig::new()
|
||||
.error(Color::Red)
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
//! This module is not included anywhere.
|
||||
//!
|
||||
//! It is a prototype for a system that supports non-string attribute values.
|
||||
|
||||
trait AsAttributeValue: Sized {
|
||||
fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a>;
|
||||
@@ -0,0 +1,25 @@
|
||||
use dioxus::virtual_dom::VirtualDom;
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_core::prelude::*;
|
||||
use dioxus_html as dioxus_elements;
|
||||
|
||||
fn main() {
|
||||
let mut dom = VirtualDom::new(App);
|
||||
dom.rebuild().unwrap();
|
||||
println!(
|
||||
"{}",
|
||||
dioxus_ssr::render_vdom(&dom, |c| c.newline(true).indent(true))
|
||||
)
|
||||
}
|
||||
|
||||
pub static App: FC<()> = |cx| {
|
||||
cx.render(rsx!(
|
||||
div {
|
||||
class: "overflow-hidden"
|
||||
ul {
|
||||
{(0..10).map(|i| rsx!{ li { class: "flex flex-col", "entry: {i}"}})}
|
||||
}
|
||||
"hello world!"
|
||||
}
|
||||
))
|
||||
};
|
||||
@@ -13,7 +13,7 @@ fn main() {
|
||||
let mut file = File::create("example.html").unwrap();
|
||||
|
||||
let mut dom = VirtualDom::new(App);
|
||||
dom.rebuild_in_place().expect("failed to run virtualdom");
|
||||
dom.rebuild().expect("failed to run virtualdom");
|
||||
|
||||
file.write_fmt(format_args!(
|
||||
"{}",
|
||||
|
||||
@@ -46,7 +46,7 @@ pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
|
||||
/// ```ignore
|
||||
/// static App: FC<()> = |cx| cx.render(rsx!(div { "hello world" }));
|
||||
/// let mut vdom = VirtualDom::new(App);
|
||||
/// vdom.rebuild_in_place();
|
||||
/// vdom.rebuild();
|
||||
///
|
||||
/// let renderer = TextRenderer::new(&vdom);
|
||||
/// let output = format!("{}", renderer);
|
||||
@@ -74,7 +74,7 @@ impl<'a> TextRenderer<'a> {
|
||||
}
|
||||
|
||||
fn html_render(&self, node: &VNode, f: &mut std::fmt::Formatter, il: u16) -> std::fmt::Result {
|
||||
match &node.kind {
|
||||
match &node {
|
||||
VNode::Text(text) => {
|
||||
if self.cfg.indent {
|
||||
for _ in 0..il {
|
||||
@@ -286,28 +286,28 @@ mod tests {
|
||||
#[test]
|
||||
fn to_string_works() {
|
||||
let mut dom = VirtualDom::new(SIMPLE_APP);
|
||||
dom.rebuild_in_place().expect("failed to run virtualdom");
|
||||
dom.rebuild().expect("failed to run virtualdom");
|
||||
dbg!(render_vdom(&dom, |c| c));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hydration() {
|
||||
let mut dom = VirtualDom::new(NESTED_APP);
|
||||
dom.rebuild_in_place().expect("failed to run virtualdom");
|
||||
dom.rebuild().expect("failed to run virtualdom");
|
||||
dbg!(render_vdom(&dom, |c| c.pre_render(true)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested() {
|
||||
let mut dom = VirtualDom::new(NESTED_APP);
|
||||
dom.rebuild_in_place().expect("failed to run virtualdom");
|
||||
dom.rebuild().expect("failed to run virtualdom");
|
||||
dbg!(render_vdom(&dom, |c| c));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fragment_app() {
|
||||
let mut dom = VirtualDom::new(FRAGMENT_APP);
|
||||
dom.rebuild_in_place().expect("failed to run virtualdom");
|
||||
dom.rebuild().expect("failed to run virtualdom");
|
||||
dbg!(render_vdom(&dom, |c| c));
|
||||
}
|
||||
|
||||
@@ -319,7 +319,7 @@ mod tests {
|
||||
let mut file = File::create("index.html").unwrap();
|
||||
|
||||
let mut dom = VirtualDom::new(SLIGHTLY_MORE_COMPLEX);
|
||||
dom.rebuild_in_place().expect("failed to run virtualdom");
|
||||
dom.rebuild().expect("failed to run virtualdom");
|
||||
|
||||
file.write_fmt(format_args!(
|
||||
"{}",
|
||||
@@ -337,7 +337,7 @@ mod tests {
|
||||
};
|
||||
|
||||
let mut dom = VirtualDom::new(STLYE_APP);
|
||||
dom.rebuild_in_place().expect("failed to run virtualdom");
|
||||
dom.rebuild().expect("failed to run virtualdom");
|
||||
dbg!(render_vdom(&dom, |c| c));
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@ fn main() {
|
||||
}
|
||||
|
||||
static App: FC<()> = |cx| {
|
||||
let mut count = use_state(cx, || 0);
|
||||
let mut count = use_state(cx, || 3);
|
||||
|
||||
cx.render(rsx! {
|
||||
div {
|
||||
|
||||
+20
-20
@@ -84,8 +84,8 @@ impl WebsysDom {
|
||||
DomEdit::PushRoot { id: root } => self.push(root),
|
||||
DomEdit::PopRoot => self.pop(),
|
||||
DomEdit::AppendChildren { many } => self.append_children(many),
|
||||
DomEdit::ReplaceWith { n, m } => self.replace_with(n, m),
|
||||
DomEdit::Remove => self.remove(),
|
||||
DomEdit::ReplaceWith { m, root } => self.replace_with(m, root),
|
||||
DomEdit::Remove { root } => self.remove(root),
|
||||
DomEdit::RemoveAllChildren => self.remove_all_children(),
|
||||
DomEdit::CreateTextNode { text, id } => self.create_text_node(text, id),
|
||||
DomEdit::CreateElement { tag, id } => self.create_element(tag, None, id),
|
||||
@@ -102,8 +102,8 @@ impl WebsysDom {
|
||||
DomEdit::SetAttribute { field, value, ns } => self.set_attribute(field, value, ns),
|
||||
DomEdit::RemoveAttribute { name } => self.remove_attribute(name),
|
||||
|
||||
DomEdit::InsertAfter { n } => self.insert_after(n),
|
||||
DomEdit::InsertBefore { n } => self.insert_before(n),
|
||||
DomEdit::InsertAfter { n, root } => self.insert_after(n, root),
|
||||
DomEdit::InsertBefore { n, root } => self.insert_before(n, root),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,7 +154,7 @@ impl WebsysDom {
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_with(&mut self, n: u32, m: u32) {
|
||||
fn replace_with(&mut self, m: u32, root: u64) {
|
||||
log::debug!("Called [`replace_with`]");
|
||||
|
||||
let mut new_nodes = vec![];
|
||||
@@ -162,19 +162,11 @@ impl WebsysDom {
|
||||
new_nodes.push(self.stack.pop());
|
||||
}
|
||||
|
||||
let mut old_nodes = vec![];
|
||||
for _ in 0..n {
|
||||
old_nodes.push(self.stack.pop());
|
||||
}
|
||||
|
||||
for node in &old_nodes[1..] {
|
||||
node.dyn_ref::<Element>().unwrap().remove();
|
||||
}
|
||||
|
||||
let old = old_nodes[0].clone();
|
||||
let old = self.nodes[root as usize].as_ref().unwrap();
|
||||
let arr: js_sys::Array = new_nodes.iter().collect();
|
||||
let el = old.dyn_into::<Element>().unwrap();
|
||||
let el = old.dyn_ref::<Element>().unwrap();
|
||||
el.replace_with_with_node(&arr).unwrap();
|
||||
|
||||
// let arr = js_sys::Array::from();
|
||||
|
||||
// TODO: use different-sized replace withs
|
||||
@@ -205,9 +197,17 @@ impl WebsysDom {
|
||||
// self.stack.push(new_node);
|
||||
}
|
||||
|
||||
fn remove(&mut self) {
|
||||
fn remove(&mut self, root: u64) {
|
||||
log::debug!("Called [`remove`]");
|
||||
todo!()
|
||||
|
||||
let node = self.nodes[root as usize].as_ref().unwrap();
|
||||
if let Some(element) = node.dyn_ref::<Element>() {
|
||||
element.remove();
|
||||
} else {
|
||||
if let Some(parent) = node.parent_node() {
|
||||
parent.remove_child(&node).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_all_children(&mut self) {
|
||||
@@ -361,7 +361,7 @@ impl WebsysDom {
|
||||
}
|
||||
}
|
||||
|
||||
fn insert_after(&mut self, n: u32) {
|
||||
fn insert_after(&mut self, n: u32, root: u64) {
|
||||
let mut new_nodes = vec![];
|
||||
for _ in 0..n {
|
||||
new_nodes.push(self.stack.pop());
|
||||
@@ -380,7 +380,7 @@ impl WebsysDom {
|
||||
// let el = self.stack.top();
|
||||
}
|
||||
|
||||
fn insert_before(&mut self, n: u32) {
|
||||
fn insert_before(&mut self, n: u32, root: u64) {
|
||||
let n = n as usize;
|
||||
let root = self
|
||||
.stack
|
||||
|
||||
+64
-41
@@ -1,30 +1,56 @@
|
||||
//! Dioxus WebSys
|
||||
//! --------------
|
||||
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys.
|
||||
|
||||
/*
|
||||
From Google's guide on rAF and rIC:
|
||||
--------
|
||||
|
||||
If the callback is fired at the end of the frame, it will be scheduled to go after the current frame has been committed,
|
||||
which means that style changes will have been applied, and, importantly, layout calculated. If we make DOM changes inside
|
||||
of the idle callback, those layout calculations will be invalidated. If there are any kind of layout reads in the next
|
||||
frame, e.g. getBoundingClientRect, clientWidth, etc, the browser will have to perform a Forced Synchronous Layout,
|
||||
which is a potential performance bottleneck.
|
||||
|
||||
Another reason not trigger DOM changes in the idle callback is that the time impact of changing the DOM is unpredictable,
|
||||
and as such we could easily go past the deadline the browser provided.
|
||||
|
||||
The best practice is to only make DOM changes inside of a requestAnimationFrame callback, since it is scheduled by the
|
||||
browser with that type of work in mind. That means that our code will need to use a document fragment, which can then
|
||||
be appended in the next requestAnimationFrame callback. If you are using a VDOM library, you would use requestIdleCallback
|
||||
to make changes, but you would apply the DOM patches in the next requestAnimationFrame callback, not the idle callback.
|
||||
|
||||
Essentially:
|
||||
------------
|
||||
- Do the VDOM work during the idlecallback
|
||||
- Do DOM work in the next requestAnimationFrame callback
|
||||
*/
|
||||
//!
|
||||
//! ## Overview
|
||||
//! ------------
|
||||
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using WebSys. This web render for
|
||||
//! Dioxus is one of the more advanced renderers, supporting:
|
||||
//! - idle work
|
||||
//! - animations
|
||||
//! - jank-free rendering
|
||||
//! - noderefs
|
||||
//! - controlled components
|
||||
//! - re-hydration
|
||||
//! - and more.
|
||||
//!
|
||||
//! The actual implementation is farily thin, with the heavy lifting happening inside the Dioxus Core crate.
|
||||
//!
|
||||
//! To purview the examples, check of the root Dioxus crate - the examples in this crate are mostly meant to provide
|
||||
//! validation of websys-specific features and not the general use of Dioxus.
|
||||
//!
|
||||
//! ## RequestAnimationFrame and RequestIdleCallback
|
||||
//! ------------------------------------------------
|
||||
//! React implements "jank free rendering" by deliberately not blocking the browser's main thread. For large diffs, long
|
||||
//! running work, and integration with things like React-Three-Fiber, it's extremeley important to avoid blocking the
|
||||
//! main thread.
|
||||
//!
|
||||
//! React solves this problem by breaking up the rendering process into a "diff" phase and a "render" phase. In Dioxus,
|
||||
//! the diff phase is non-blocking, using "yield_now" to allow the browser to process other events. When the diff phase
|
||||
//! is finally complete, the VirtualDOM will return a set of "Mutations" for this crate to apply.
|
||||
//!
|
||||
//! Here, we schedule the "diff" phase during the browser's idle period, achieved by calling RequestIdleCallback and then
|
||||
//! setting a timeout from the that completes when the idleperiod is over. Then, we call requestAnimationFrame
|
||||
//!
|
||||
//! From Google's guide on rAF and rIC:
|
||||
//! -----------------------------------
|
||||
//!
|
||||
//! If the callback is fired at the end of the frame, it will be scheduled to go after the current frame has been committed,
|
||||
//! which means that style changes will have been applied, and, importantly, layout calculated. If we make DOM changes inside
|
||||
//! of the idle callback, those layout calculations will be invalidated. If there are any kind of layout reads in the next
|
||||
//! frame, e.g. getBoundingClientRect, clientWidth, etc, the browser will have to perform a Forced Synchronous Layout,
|
||||
//! which is a potential performance bottleneck.
|
||||
//!
|
||||
//! Another reason not trigger DOM changes in the idle callback is that the time impact of changing the DOM is unpredictable,
|
||||
//! and as such we could easily go past the deadline the browser provided.
|
||||
//!
|
||||
//! The best practice is to only make DOM changes inside of a requestAnimationFrame callback, since it is scheduled by the
|
||||
//! browser with that type of work in mind. That means that our code will need to use a document fragment, which can then
|
||||
//! be appended in the next requestAnimationFrame callback. If you are using a VDOM library, you would use requestIdleCallback
|
||||
//! to make changes, but you would apply the DOM patches in the next requestAnimationFrame callback, not the idle callback.
|
||||
//!
|
||||
//! Essentially:
|
||||
//! ------------
|
||||
//! - Do the VDOM work during the idlecallback
|
||||
//! - Do DOM work in the next requestAnimationFrame callback
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
@@ -103,20 +129,22 @@ pub async fn run_with_props<T: Properties + 'static>(
|
||||
) -> Result<()> {
|
||||
let mut dom = VirtualDom::new_with_props(root, root_props);
|
||||
|
||||
let hydrating = cfg.hydrate;
|
||||
|
||||
let root_el = load_document().get_element_by_id(&cfg.rootname).unwrap();
|
||||
|
||||
let tasks = dom.get_event_sender();
|
||||
let sender_callback = Rc::new(move |event| tasks.unbounded_send(event).unwrap());
|
||||
|
||||
// initialize the virtualdom first
|
||||
// if cfg.hydrate {
|
||||
// dom.rebuild_in_place()?;
|
||||
// }
|
||||
let mut websys_dom = dom::WebsysDom::new(root_el, cfg, sender_callback);
|
||||
|
||||
let mut websys_dom = dom::WebsysDom::new(
|
||||
root_el,
|
||||
cfg,
|
||||
Rc::new(move |event| tasks.unbounded_send(event).unwrap()),
|
||||
);
|
||||
let mut mutations = dom.rebuild().unwrap();
|
||||
log::info!("Mutations: {:#?}", mutations);
|
||||
|
||||
// hydrating is simply running the dom for a single render. If the page is already written, then the corresponding
|
||||
// ElementIds should already line up because the web_sys dom has already loaded elements with the DioxusID into memory
|
||||
if !hydrating {
|
||||
websys_dom.process_edits(&mut mutations.edits);
|
||||
}
|
||||
|
||||
loop {
|
||||
let deadline = gloo_timers::future::TimeoutFuture::new(16);
|
||||
@@ -127,8 +155,3 @@ pub async fn run_with_props<T: Properties + 'static>(
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
struct HydrationNode {
|
||||
id: usize,
|
||||
node: Node,
|
||||
}
|
||||
Reference in new issue
Block a user