diff --git a/packages/core/benches/create.rs b/packages/core/benches/create.rs index 2f614ac0e..f328e4d9d 100644 --- a/packages/core/benches/create.rs +++ b/packages/core/benches/create.rs @@ -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() {} diff --git a/packages/core/benches/jsframework.rs b/packages/core/benches/jsframework.rs index 91b97dd74..b9dff6bb6 100644 --- a/packages/core/benches/jsframework.rs +++ b/packages/core/benches/jsframework.rs @@ -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", diff --git a/packages/core/examples/alternative.rs b/packages/core/examples/alternative.rs index fc30beb62..177eca78f 100644 --- a/packages/core/examples/alternative.rs +++ b/packages/core/examples/alternative.rs @@ -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, ) })) diff --git a/packages/core/examples/fragment.rs b/packages/core/examples/fragment.rs deleted file mode 100644 index 3d4b97b8b..000000000 --- a/packages/core/examples/fragment.rs +++ /dev/null @@ -1,18 +0,0 @@ - - -fn main() { - - // let g = rsx! { - // Fragment { - // // div {} - // // div {} - // // div {} - // // div {} - // // div {} - // // div {} - // // div {} - // // div {} - // // div {} - // } - // }; -} diff --git a/packages/core/examples/fragment_from_iter.rs b/packages/core/examples/fragment_from_iter.rs index 08018c68c..58c4df70f 100644 --- a/packages/core/examples/fragment_from_iter.rs +++ b/packages/core/examples/fragment_from_iter.rs @@ -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, ) })) diff --git a/packages/core/examples/macrosrc.rs b/packages/core/examples/macrosrc.rs deleted file mode 100644 index 2a8dfe4cd..000000000 --- a/packages/core/examples/macrosrc.rs +++ /dev/null @@ -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! {
"success!"
}, -// Err(_) => html! {
"failure :("
}, -// } -// }); - -// // todo!() -// cx.render(html! { -//
-//

"Products"

-// -// // Subnodes can even be suspended -// // When completely rendered, they won't cause the component itself to re-render, just their slot - -// //

{ product_list }

-//
-// }) -// } - -// 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! { -//
-// // your template goes here -// // feel free to directly use "name" -//
-// }) -// } - -// fn FullySuspended<'a>(cx: &'a Context) -> 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! {
}, -// 2 => html! {
}, -// _ => html! {
}, -// }; - -// if cx.cx.blah { -// html! {
} -// } else { -// tex -// } -// }) -// } - -// /// An example of a datafetching service -// async fn fetch_data() -> Result { -// todo!() -// } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 539c6986f..0702830a3 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -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 _) } } diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 00b4f251f..61945156a 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -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 diff --git a/packages/core/tests/create_iterative.rs b/packages/core/tests/create_iterative.rs index 519e97141..cbd4068a0 100644 --- a/packages/core/tests/create_iterative.rs +++ b/packages/core/tests/create_iterative.rs @@ -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(); diff --git a/packages/core/tests/diff_iterative.rs b/packages/core/tests/diff_iterative.rs index f5fba7852..be1c02992 100644 --- a/packages/core/tests/diff_iterative.rs +++ b/packages/core/tests/diff_iterative.rs @@ -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); diff --git a/packages/core/tests/diffing.rs b/packages/core/tests/diffing.rs index b1511c926..bd71bc6d7 100644 --- a/packages/core/tests/diffing.rs +++ b/packages/core/tests/diffing.rs @@ -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] diff --git a/packages/core/tests/test_logging.rs b/packages/core/tests/test_logging.rs index 41498a9e9..aafb831e2 100644 --- a/packages/core/tests/test_logging.rs +++ b/packages/core/tests/test_logging.rs @@ -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) diff --git a/packages/html/src/attrval.s b/packages/html/src/attrval.rs similarity index 86% rename from packages/html/src/attrval.s rename to packages/html/src/attrval.rs index cb081f4cb..9ac1f1373 100644 --- a/packages/html/src/attrval.s +++ b/packages/html/src/attrval.rs @@ -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>; diff --git a/packages/ssr/examples/basic.rs b/packages/ssr/examples/basic.rs new file mode 100644 index 000000000..b3bfb6ca5 --- /dev/null +++ b/packages/ssr/examples/basic.rs @@ -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!" + } + )) +}; diff --git a/packages/ssr/examples/tofile.rs b/packages/ssr/examples/tofile.rs index 35ce6db89..09b710486 100644 --- a/packages/ssr/examples/tofile.rs +++ b/packages/ssr/examples/tofile.rs @@ -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!( "{}", diff --git a/packages/ssr/src/lib.rs b/packages/ssr/src/lib.rs index 80a4c17ef..866acf0a9 100644 --- a/packages/ssr/src/lib.rs +++ b/packages/ssr/src/lib.rs @@ -46,7 +46,7 @@ pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option { /// ```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)); } } diff --git a/packages/web/examples/basic.rs b/packages/web/examples/basic.rs index 518115f7c..35d82daae 100644 --- a/packages/web/examples/basic.rs +++ b/packages/web/examples/basic.rs @@ -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 { diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index e3a4e2c53..2aa57ea5b 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -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::().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::().unwrap(); + let el = old.dyn_ref::().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.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 diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 9ff8906e9..2adfb6b1c 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -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( ) -> 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( Ok(()) } - -struct HydrationNode { - id: usize, - node: Node, -}