From a5f05d73acc0e47b05cff64a373482519414bc7c Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 19 Nov 2021 00:49:04 -0500 Subject: [PATCH] wip: docs and router --- Cargo.toml | 4 + docs/guide/src/concepts/10-concurrent-mode.md | 4 +- docs/guide/src/concepts/11-arena-memo.md | 6 +- docs/guide/src/concepts/12-signals.md | 6 +- .../src/concepts/conditional_rendering.md | 2 +- docs/guide/src/hello_world.md | 2 +- examples/async.rs | 2 +- examples/calculator.rs | 6 +- examples/core/alternative.rs | 2 +- examples/core/syntax.rs | 2 +- examples/crm.rs | 2 +- examples/desktop/crm.rs | 2 +- examples/desktop/todomvc.rs | 2 +- examples/framework_benchmark.rs | 180 ++++---- examples/pattern_reducer.rs | 2 +- examples/web/basic.rs | 4 +- examples/web/crm2.rs | 2 +- notes/Parity.md | 2 +- notes/SOLVEDPROBLEMS.md | 10 +- packages/core-macro/src/lib.rs | 2 +- packages/core-macro/src/rsxtemplate.rs | 2 +- packages/core/Cargo.toml | 11 +- packages/core/benches/jsframework.rs | 19 +- packages/core/examples/rows.rs | 120 +++++ packages/core/flamegraph.svg | 412 ++++++++++++++++++ packages/core/src/diff.rs | 73 +++- packages/core/src/scope.rs | 2 +- packages/core/src/scopearena.rs | 16 +- packages/core/src/virtual_dom.rs | 2 +- packages/desktop/README.md | 14 +- packages/desktop/src/index.html | 5 +- packages/desktop/src/lib.rs | 6 + packages/hooks/src/usecollection.rs | 2 +- packages/hooks/src/useref.rs | 5 + packages/hooks/src/usestate.rs | 2 +- packages/html/src/elements.rs | 21 +- packages/router/Cargo.toml | 6 + packages/router/README.md | 40 ++ packages/router/examples/simple.rs | 45 ++ packages/router/src/lib.rs | 36 +- packages/ssr/README.md | 2 +- packages/ssr/src/lib.rs | 2 +- packages/web/Cargo.toml | 22 +- packages/web/examples/js_bench.rs | 243 +++++++++++ packages/web/examples/simple.rs | 46 ++ packages/web/src/cache.rs | 5 + packages/web/src/cfg.rs | 2 +- packages/web/src/dom.rs | 68 ++- packages/web/src/lib.rs | 15 +- src/lib.rs | 9 +- 50 files changed, 1263 insertions(+), 234 deletions(-) create mode 100644 packages/core/examples/rows.rs create mode 100644 packages/core/flamegraph.svg create mode 100644 packages/router/README.md create mode 100644 packages/router/examples/simple.rs create mode 100644 packages/web/examples/js_bench.rs create mode 100644 packages/web/examples/simple.rs diff --git a/Cargo.toml b/Cargo.toml index 4c1863dc..bbf4243e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,3 +91,7 @@ path = "./examples/webview.rs" required-features = ["desktop"] name = "tailwind" path = "./examples/tailwind.rs" + + +[patch.crates-io] +wasm-bindgen = { path = "../Tinkering/wasm-bindgen" } diff --git a/docs/guide/src/concepts/10-concurrent-mode.md b/docs/guide/src/concepts/10-concurrent-mode.md index 821f94e1..76e443ab 100644 --- a/docs/guide/src/concepts/10-concurrent-mode.md +++ b/docs/guide/src/concepts/10-concurrent-mode.md @@ -50,8 +50,8 @@ async fn ExampleLoader(cx: Context<()>) -> Vnode { This API stores the result on the Context object, so the loaded data is taken as reference. */ let name: &Result = use_fetch_data("http://example.com/json", ()) - .place_holder(|(cx, props)|rsx!{
"loading..."
}) - .delayed_place_holder(1000, |(cx, props)|rsx!{
"still loading..."
}) + .place_holder(|cx, props|rsx!{
"loading..."
}) + .delayed_place_holder(1000, |cx, props|rsx!{
"still loading..."
}) .await; match name { diff --git a/docs/guide/src/concepts/11-arena-memo.md b/docs/guide/src/concepts/11-arena-memo.md index dce655bc..28eccda2 100644 --- a/docs/guide/src/concepts/11-arena-memo.md +++ b/docs/guide/src/concepts/11-arena-memo.md @@ -21,9 +21,9 @@ fn test() -> DomTree { } } -static TestComponent: FC<()> = |(cx, props)|html!{
"Hello world"
}; +static TestComponent: FC<()> = |cx, props|html!{
"Hello world"
}; -static TestComponent: FC<()> = |(cx, props)|{ +static TestComponent: FC<()> = |cx, props|{ let g = "BLAH"; html! {
"Hello world"
@@ -31,7 +31,7 @@ static TestComponent: FC<()> = |(cx, props)|{ }; #[functional_component] -static TestComponent: FC<{ name: String }> = |(cx, props)|html! {
"Hello {name}"
}; +static TestComponent: FC<{ name: String }> = |cx, props|html! {
"Hello {name}"
}; ``` ## Why this behavior? diff --git a/docs/guide/src/concepts/12-signals.md b/docs/guide/src/concepts/12-signals.md index d64a7617..5000faeb 100644 --- a/docs/guide/src/concepts/12-signals.md +++ b/docs/guide/src/concepts/12-signals.md @@ -96,7 +96,7 @@ Sometimes you want a signal to propagate across your app, either through far-awa ```rust const TITLE: Atom = || "".to_string(); -const Provider: FC<()> = |(cx, props)|{ +const Provider: FC<()> = |cx, props|{ let title = use_signal(&cx, &TITLE); rsx!(cx, input { value: title }) }; @@ -105,7 +105,7 @@ const Provider: FC<()> = |(cx, props)|{ If we use the `TITLE` atom in another component, we can cause updates to flow between components without calling render or diffing either component trees: ```rust -const Receiver: FC<()> = |(cx, props)|{ +const Receiver: FC<()> = |cx, props|{ let title = use_signal(&cx, &TITLE); log::info!("This will only be called once!"); rsx!(cx, @@ -132,7 +132,7 @@ Dioxus automatically understands how to use your signals when mixed with iterato ```rust const DICT: AtomFamily = |_| {}; -const List: FC<()> = |(cx, props)|{ +const List: FC<()> = |cx, props|{ let dict = use_signal(&cx, &DICT); cx.render(rsx!( ul { diff --git a/docs/guide/src/concepts/conditional_rendering.md b/docs/guide/src/concepts/conditional_rendering.md index 977f6659..cfbb8add 100644 --- a/docs/guide/src/concepts/conditional_rendering.md +++ b/docs/guide/src/concepts/conditional_rendering.md @@ -83,7 +83,7 @@ fn App((cx, props): Scope<()>) -> Element { This syntax even enables us to write a one-line component: ```rust -static App: Fc<()> = |(cx, props)| rsx!(cx, "hello world!"); +static App: Fc<()> = |cx, props| rsx!(cx, "hello world!"); ``` Alternatively, for match statements, we can just return the builder itself and pass it into a final, single call to `cx.render`: diff --git a/docs/guide/src/hello_world.md b/docs/guide/src/hello_world.md index 7c703b03..89c785a4 100644 --- a/docs/guide/src/hello_world.md +++ b/docs/guide/src/hello_world.md @@ -143,7 +143,7 @@ fn App<'a>(cx: Component<'a, ()>) -> Element<'a> { Writing `fn App((cx, props): Component<()>) -> Element {` might become tedious. Rust will also let you write functions as static closures, but these types of Components cannot have props that borrow data. ```rust -static App: Fc<()> = |(cx, props)| { +static App: Fc<()> = |cx, props| { cx.render(rsx! { div { "Hello, world!" } }) diff --git a/examples/async.rs b/examples/async.rs index b0ea0a7b..bb05669e 100644 --- a/examples/async.rs +++ b/examples/async.rs @@ -11,7 +11,7 @@ async fn main() { dioxus::desktop::launch(App, |c| c); } -pub static App: FC<()> = |(cx, _)| { +pub static App: FC<()> = |cx, _| { let count = use_state(cx, || 0); let mut direction = use_state(cx, || 1); diff --git a/examples/calculator.rs b/examples/calculator.rs index 3fc110b6..0b1b4f1e 100644 --- a/examples/calculator.rs +++ b/examples/calculator.rs @@ -10,7 +10,7 @@ fn main() { dioxus::desktop::launch(APP, |cfg| cfg); } -const APP: FC<()> = |(cx, _)| { +const APP: FC<()> = |cx, _| { let cur_val = use_state(cx, || 0.0_f64); let operator = use_state(cx, || None as Option<&'static str>); let display_value = use_state(cx, || String::from("")); @@ -114,10 +114,10 @@ const APP: FC<()> = |(cx, _)| { struct CalculatorKeyProps<'a> { name: &'static str, onclick: &'a dyn Fn(MouseEvent), - children: ScopeChildren<'a>, + children: Element, } -fn CalculatorKey<'a>((cx, props): Scope<'a, CalculatorKeyProps<'a>>) -> Element { +fn CalculatorKey<'a>(cx: Context, props: &CalculatorKeyProps) -> Element { rsx!(cx, button { class: "calculator-key {props.name}" onclick: {props.onclick} diff --git a/examples/core/alternative.rs b/examples/core/alternative.rs index 3f2fa979..6c20e2ad 100644 --- a/examples/core/alternative.rs +++ b/examples/core/alternative.rs @@ -9,7 +9,7 @@ fn main() { println!("{}", dom); } -pub static EXAMPLE: FC<()> = |(cx, _)| { +pub static EXAMPLE: FC<()> = |cx, _| { let list = (0..10).map(|_f| { rsx! { "{_f}" diff --git a/examples/core/syntax.rs b/examples/core/syntax.rs index 5eec9d18..9a21bc7e 100644 --- a/examples/core/syntax.rs +++ b/examples/core/syntax.rs @@ -31,7 +31,7 @@ fn html_usage() { // let p = rsx!(div { {f} }); } -static App2: FC<()> = |(cx, _)| cx.render(rsx!("hello world!")); +static App2: FC<()> = |cx, _| cx.render(rsx!("hello world!")); static App: FC<()> = |cx, props| { let name = cx.use_state(|| 0); diff --git a/examples/crm.rs b/examples/crm.rs index e0c87322..3de5b7e3 100644 --- a/examples/crm.rs +++ b/examples/crm.rs @@ -19,7 +19,7 @@ pub struct Client { pub description: String, } -static App: FC<()> = |(cx, _)| { +static App: FC<()> = |cx, _| { let mut clients = use_ref(cx, || vec![] as Vec); let mut scene = use_state(cx, || Scene::ClientsList); diff --git a/examples/desktop/crm.rs b/examples/desktop/crm.rs index efb7efed..8cc5b298 100644 --- a/examples/desktop/crm.rs +++ b/examples/desktop/crm.rs @@ -21,7 +21,7 @@ pub struct Client { pub description: String, } -static App: FC<()> = |(cx, _)| { +static App: FC<()> = |cx, _| { let mut scene = use_state(cx, || Scene::ClientsList); let clients = use_ref(cx, || vec![] as Vec); diff --git a/examples/desktop/todomvc.rs b/examples/desktop/todomvc.rs index 6e85b87c..d8edf40e 100644 --- a/examples/desktop/todomvc.rs +++ b/examples/desktop/todomvc.rs @@ -32,7 +32,7 @@ pub struct TodoItem { } pub type Todos = HashMap; -pub static App: FC<()> = |(cx, _)| { +pub static App: FC<()> = |cx, _| { // Share our TodoList to the todos themselves use_provide_state(cx, Todos::new); diff --git a/examples/framework_benchmark.rs b/examples/framework_benchmark.rs index 6dd9a907..782081c4 100644 --- a/examples/framework_benchmark.rs +++ b/examples/framework_benchmark.rs @@ -1,50 +1,38 @@ -use dioxus::{events::MouseEvent, prelude::*}; -use fxhash::FxBuildHasher; -use std::rc::Rc; +use dioxus::prelude::*; +use rand::prelude::*; fn main() { - dioxus::desktop::launch(App, |c| c); + dioxus::web::launch(App, |c| c); + // dioxus::desktop::launch(App, |c| c); } -// We use a special immutable hashmap to make hashmap operations efficient -type RowList = im_rc::HashMap, FxBuildHasher>; +#[derive(Clone, PartialEq)] +struct Label { + key: usize, + labels: [&'static str; 3], +} -static App: FC<()> = |(cx, _props)| { - let mut items = use_state(cx, || RowList::default()); - - let create_rendered_rows = move |from, num| move |_| items.set(create_row_list(from, num)); - - let mut append_1_000_rows = - move |_| items.set(create_row_list(items.len(), 1000).union((*items).clone())); - - let update_every_10th_row = move |_| { - let mut new_items = (*items).clone(); - let mut small_rng = SmallRng::from_entropy(); - new_items.iter_mut().step_by(10).for_each(|(_, val)| { - *val = create_new_row_label(&mut String::with_capacity(30), &mut small_rng) - }); - items.set(new_items); - }; - let clear_rows = move |_| items.set(RowList::default()); - - let swap_rows = move |_| { - // this looks a bit ugly because we're using a hashmap instead of a vec - if items.len() > 998 { - let mut new_items = (*items).clone(); - let a = new_items.get(&0).unwrap().clone(); - *new_items.get_mut(&0).unwrap() = new_items.get(&998).unwrap().clone(); - *new_items.get_mut(&998).unwrap() = a; - items.set(new_items); +impl Label { + fn new_list(num: usize) -> Vec { + let mut rng = SmallRng::from_entropy(); + let mut labels = Vec::with_capacity(num); + for _ in 0..num { + labels.push(Label { + key: 0, + labels: [ + ADJECTIVES.choose(&mut rng).unwrap(), + COLOURS.choose(&mut rng).unwrap(), + NOUNS.choose(&mut rng).unwrap(), + ], + }); } - }; + labels + } +} - let rows = items.iter().map(|(key, value)| { - rsx!(Row { - key: "{key}", - row_id: *key as usize, - label: value.clone(), - }) - }); +static App: FC<()> = |cx, _props| { + let mut items = use_ref(cx, || vec![]); + let mut selected = use_state(cx, || None); cx.render(rsx! { div { class: "container" @@ -53,22 +41,49 @@ static App: FC<()> = |(cx, _props)| { div { class: "col-md-6", h1 { "Dioxus" } } div { class: "col-md-6" div { class: "row" - ActionButton { name: "Create 1,000 rows", id: "run", onclick: {create_rendered_rows(0, 1_000)} } - ActionButton { name: "Create 10,000 rows", id: "runlots", onclick: {create_rendered_rows(0, 10_000)} } - ActionButton { name: "Append 1,000 rows", id: "add", onclick: {append_1_000_rows} } - ActionButton { name: "Update every 10th row", id: "update", onclick: {update_every_10th_row} } - ActionButton { name: "Clear", id: "clear", onclick: {clear_rows} } - ActionButton { name: "Swap rows", id: "swaprows", onclick: {swap_rows} } + ActionButton { name: "Create 1,000 rows", id: "run", + onclick: move || items.set(Label::new_list(1_000)), + } + ActionButton { name: "Create 10,000 rows", id: "runlots", + onclick: move || items.set(Label::new_list(10_000)), + } + ActionButton { name: "Append 1,000 rows", id: "add", + onclick: move || items.write().extend(Label::new_list(1_000)), + } + ActionButton { name: "Update every 10th row", id: "update", + onclick: move || items.write().iter_mut().step_by(10).for_each(|item| item.labels[2] = "!!!"), + } + ActionButton { name: "Clear", id: "clear", + onclick: move || items.write().clear(), + } + ActionButton { name: "Swap rows", id: "swaprows", + onclick: move || items.write().swap(0, 998), + } } } } } table { tbody { - {rows} + {items.read().iter().enumerate().map(|(id, item)| { + let is_in_danger = if (*selected).map(|s| s == id).unwrap_or(false) {"danger"} else {""}; + rsx!(tr { class: "{is_in_danger}" + td { class:"col-md-1" } + td { class:"col-md-1", "{item.key}" } + td { class:"col-md-1", onclick: move |_| selected.set(Some(id)), + a { class: "lbl", {item.labels} } + } + td { class: "col-md-1" + a { class: "remove", onclick: move |_| { items.write().remove(id); }, + span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" } + } + } + td { class: "col-md-6" } + }) + })} } } - span {} + // span { class: "preloadicon glyphicon glyphicon-remove" aria_hidden: "true" } } }) }; @@ -77,59 +92,17 @@ static App: FC<()> = |(cx, _props)| { struct ActionButtonProps<'a> { name: &'static str, id: &'static str, - onclick: &'a dyn Fn(MouseEvent), + onclick: &'a dyn Fn(), } -fn ActionButton<'a>((cx, props): Scope<'a, ActionButtonProps>) -> Element<'a> { +fn ActionButton(cx: Context, props: &ActionButtonProps) -> Element { rsx!(cx, div { class: "col-sm-6 smallpad" - button { class:"btn btn-primary btn-block", r#type: "button", id: "{props.id}", onclick: {props.onclick}, + button { class:"btn btn-primary btn-block", r#type: "button", id: "{props.id}", onclick: move |_| (props.onclick)(), "{props.name}" } }) } -#[derive(PartialEq, Props)] -struct RowProps { - row_id: usize, - label: Rc, -} -fn Row((cx, props): Scope) -> Element { - rsx!(cx, tr { - td { class:"col-md-1", "{props.row_id}" } - td { class:"col-md-1", onclick: move |_| { /* run onselect */ } - a { class: "lbl", "{props.label}" } - } - 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" } - }) -} - -use rand::prelude::*; -fn create_new_row_label(label: &mut String, rng: &mut SmallRng) -> Rc { - label.push_str(ADJECTIVES.choose(rng).unwrap()); - label.push(' '); - label.push_str(COLOURS.choose(rng).unwrap()); - label.push(' '); - label.push_str(NOUNS.choose(rng).unwrap()); - Rc::from(label.as_ref()) -} - -fn create_row_list(from: usize, num: usize) -> RowList { - let mut small_rng = SmallRng::from_entropy(); - let mut buf = String::with_capacity(35); - (from..num + from) - .map(|f| { - let o = (f, create_new_row_label(&mut buf, &mut small_rng)); - buf.clear(); - o - }) - .collect::() -} - static ADJECTIVES: &[&str] = &[ "pretty", "large", @@ -167,3 +140,24 @@ static NOUNS: &[&str] = &[ "table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger", "pizza", "mouse", "keyboard", ]; + +// #[derive(PartialEq, Props)] +// struct RowProps<'a> { +// row_id: usize, +// label: &'a Label, +// } + +// fn Row(cx: Context, props: &RowProps) -> Element { +// rsx!(cx, tr { +// td { class:"col-md-1", "{props.row_id}" } +// td { class:"col-md-1", onclick: move |_| { /* run onselect */ } +// a { class: "lbl", {props.label.labels} } +// } +// 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" } +// }) +// } diff --git a/examples/pattern_reducer.rs b/examples/pattern_reducer.rs index df28fdcf..221e8eb0 100644 --- a/examples/pattern_reducer.rs +++ b/examples/pattern_reducer.rs @@ -11,7 +11,7 @@ fn main() { dioxus::desktop::launch(App, |c| c); } -pub static App: FC<()> = |(cx, _)| { +pub static App: FC<()> = |cx, _| { let state = use_state(cx, PlayerState::new); let is_playing = state.is_playing(); diff --git a/examples/web/basic.rs b/examples/web/basic.rs index 699305df..4c4d52ce 100644 --- a/examples/web/basic.rs +++ b/examples/web/basic.rs @@ -14,7 +14,7 @@ fn main() { dioxus_web::launch(APP, |c| c) } -static APP: FC<()> = |(cx, _)| { +static APP: FC<()> = |cx, _| { let mut count = use_state(cx, || 3); let content = use_state(cx, || String::from("h1")); let text_content = use_state(cx, || String::from("Hello, world!")); @@ -86,4 +86,4 @@ fn render_list(cx: Context, count: usize) -> Element { rsx!(cx, ul { {items} }) } -static CHILD: FC<()> = |(cx, _)| rsx!(cx, div {"hello child"}); +static CHILD: FC<()> = |cx, _| rsx!(cx, div {"hello child"}); diff --git a/examples/web/crm2.rs b/examples/web/crm2.rs index 0645f05f..4dfe9db3 100644 --- a/examples/web/crm2.rs +++ b/examples/web/crm2.rs @@ -28,7 +28,7 @@ pub struct Client { pub description: String, } -static App: FC<()> = |(cx, _)| { +static App: FC<()> = |cx, _| { let scene = use_state(cx, || Scene::ClientsList); let clients = use_ref(cx, || vec![] as Vec); diff --git a/notes/Parity.md b/notes/Parity.md index 9842b796..5cc0851c 100644 --- a/notes/Parity.md +++ b/notes/Parity.md @@ -12,7 +12,7 @@ https://github.com/rustwasm/gloo For example, resize observer would function like this: ```rust -pub static Example: FC<()> = |(cx, props)|{ +pub static Example: FC<()> = |cx, props|{ let observer = use_resize_observer(); cx.render(rsx!( diff --git a/notes/SOLVEDPROBLEMS.md b/notes/SOLVEDPROBLEMS.md index d7bc6034..3c42094e 100644 --- a/notes/SOLVEDPROBLEMS.md +++ b/notes/SOLVEDPROBLEMS.md @@ -153,13 +153,13 @@ Notice that LiveComponent receivers (the client-side interpretation of a LiveCom The `VNodeTree` type is a very special type that allows VNodes to be created using a pluggable allocator. The html! macro creates something that looks like: ```rust -pub static Example: FC<()> = |(cx, props)|{ +pub static Example: FC<()> = |cx, props|{ html! {
"blah"
} }; // expands to... -pub static Example: FC<()> = |(cx, props)|{ +pub static Example: FC<()> = |cx, props|{ // This function converts a Fn(allocator) -> DomTree closure to a VNode struct that will later be evaluated. html_macro_to_vnodetree(move |allocator| { let mut node0 = allocator.alloc(VElement::div); @@ -313,7 +313,7 @@ Here's how react does it: Any "dirty" node causes an entire subtree render. Calling "setState" at the very top will cascade all the way down. This is particularly bad for this component design: ```rust -static APP: FC<()> = |(cx, props)|{ +static APP: FC<()> = |cx, props|{ let title = use_context(Title); cx.render(html!{
@@ -334,7 +334,7 @@ static APP: FC<()> = |(cx, props)|{
}) }; -static HEAVY_LIST: FC<()> = |(cx, props)|{ +static HEAVY_LIST: FC<()> = |cx, props|{ cx.render({ {0.100.map(i => )} }) @@ -378,7 +378,7 @@ struct Props { } -static Component: FC = |(cx, props)|{ +static Component: FC = |cx, props|{ } ``` diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs index a218ccc4..6e754a16 100644 --- a/packages/core-macro/src/lib.rs +++ b/packages/core-macro/src/lib.rs @@ -30,7 +30,7 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token /// /// ## Complete Reference Guide: /// ``` -/// const Example: FC<()> = |(cx, props)|{ +/// const Example: FC<()> = |cx, props|{ /// let formatting = "formatting!"; /// let formatting_tuple = ("a", "b"); /// let lazy_fmt = format_args!("lazily formatted text"); diff --git a/packages/core-macro/src/rsxtemplate.rs b/packages/core-macro/src/rsxtemplate.rs index e228929c..1f712997 100644 --- a/packages/core-macro/src/rsxtemplate.rs +++ b/packages/core-macro/src/rsxtemplate.rs @@ -72,7 +72,7 @@ impl ToTokens for RsxTemplate { // // create a lazy tree that accepts a bump allocator // let final_tokens = quote! { - // dioxus::prelude::LazyNodes::new(move |(cx, props)|{ + // dioxus::prelude::LazyNodes::new(move |cx, props|{ // let bump = &cx.bump(); // #new_toks diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index 8619c7e7..7ea455d3 100644 --- a/packages/core/Cargo.toml +++ b/packages/core/Cargo.toml @@ -49,8 +49,7 @@ rand = { version = "0.8.4", features = ["small_rng"] } simple_logger = "1.13.0" dioxus-core-macro = { path = "../core-macro", version = "0.1.2" } dioxus-hooks = { path = "../hooks" } -# async-std = { version = "1.9.0", features = ["attributes"] } -# criterion = "0.3.5" +criterion = "0.3.5" [features] default = [] @@ -64,3 +63,11 @@ harness = false [[bench]] name = "jsframework" harness = false + +[[example]] +name = "rows" +path = "./examples/rows.rs" + + +[profile.release] +debug = true diff --git a/packages/core/benches/jsframework.rs b/packages/core/benches/jsframework.rs index a36d4a1e..4f09f020 100644 --- a/packages/core/benches/jsframework.rs +++ b/packages/core/benches/jsframework.rs @@ -19,7 +19,6 @@ use dioxus_core_macro::*; use dioxus_html as dioxus_elements; use rand::prelude::*; -fn main() {} criterion_group!(mbenches, create_rows); criterion_main!(mbenches); @@ -28,18 +27,14 @@ fn create_rows(c: &mut Criterion) { 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!(Row { + row_id: f, + label: label + }) }); - cx.render(rsx! { - table { - tbody { - {rows} - } + rsx!(cx, table { + tbody { + {rows} } }) }; diff --git a/packages/core/examples/rows.rs b/packages/core/examples/rows.rs new file mode 100644 index 00000000..7506c418 --- /dev/null +++ b/packages/core/examples/rows.rs @@ -0,0 +1,120 @@ +#![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 +//! to be made, but the change application phase will be just as performant as the vanilla wasm_bindgen code. In essence, +//! we are measuring the overhead of Dioxus, not the performance of the "apply" phase. +//! +//! On my MBP 2019: +//! - Dioxus takes 3ms to create 1_000 rows +//! - Dioxus takes 30ms to create 10_000 rows +//! +//! 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 criterion::{criterion_group, criterion_main, Criterion}; +use dioxus_core as dioxus; +use dioxus_core::prelude::*; +use dioxus_core_macro::*; +use dioxus_html as dioxus_elements; +use rand::prelude::*; + +fn main() { + static App: FC<()> = |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 + } + } + }); + cx.render(rsx! { + table { + tbody { + {rows} + } + } + }) + }; + + let mut dom = VirtualDom::new(App); + let g = dom.rebuild(); + assert!(g.edits.len() > 1); +} + +#[derive(PartialEq, Props)] +struct RowProps { + row_id: usize, + label: Label, +} +fn Row(cx: Context, props: &RowProps) -> Element { + let [adj, col, noun] = props.label.0; + cx.render(rsx! { + tr { + td { class:"col-md-1", "{props.row_id}" } + 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-6" } + } + }) +} + +#[derive(PartialEq)] +struct Label([&'static str; 3]); + +impl Label { + fn new(rng: &mut SmallRng) -> Self { + Label([ + ADJECTIVES.choose(rng).unwrap(), + COLOURS.choose(rng).unwrap(), + NOUNS.choose(rng).unwrap(), + ]) + } +} + +static ADJECTIVES: &[&str] = &[ + "pretty", + "large", + "big", + "small", + "tall", + "short", + "long", + "handsome", + "plain", + "quaint", + "clean", + "elegant", + "easy", + "angry", + "crazy", + "helpful", + "mushy", + "odd", + "unsightly", + "adorable", + "important", + "inexpensive", + "cheap", + "expensive", + "fancy", +]; + +static COLOURS: &[&str] = &[ + "red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black", + "orange", +]; + +static NOUNS: &[&str] = &[ + "table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger", + "pizza", "mouse", "keyboard", +]; diff --git a/packages/core/flamegraph.svg b/packages/core/flamegraph.svg new file mode 100644 index 00000000..0699a198 --- /dev/null +++ b/packages/core/flamegraph.svg @@ -0,0 +1,412 @@ +Flame Graph Reset ZoomSearch jsframework-a8f4acf5955e8e7f`core::ptr::drop_in_place<dioxus_core::virtual_dom::VirtualDom> (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`core::ptr::drop_in_place<alloc::boxed::Box<dioxus_core::scopearena::ScopeArena>> (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`<bumpalo::Bump as core::ops::drop::Drop>::drop (1 samples, 1.82%)j..libsystem_malloc.dylib`free_medium (1 samples, 1.82%)l..libsystem_kernel.dylib`madvise (1 samples, 1.82%)l..libsystem_malloc.dylib`_malloc_zone_malloc (2 samples, 3.64%)libs..libsystem_malloc.dylib`szone_malloc_should_clear (2 samples, 3.64%)libs..libsystem_malloc.dylib`tiny_malloc_should_clear (2 samples, 3.64%)libs..libsystem_malloc.dylib`tiny_malloc_from_free_list (1 samples, 1.82%)l..libsystem_malloc.dylib`tiny_free_list_add_ptr (1 samples, 1.82%)l..jsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::create_element_node (7 samples, 12.73%)jsframework-a8f4acf..jsframework-a8f4acf5955e8e7f`alloc::raw_vec::RawVec<T,A>::reserve::do_reserve_and_handle (3 samples, 5.45%)jsframe..jsframework-a8f4acf5955e8e7f`alloc::raw_vec::finish_grow (3 samples, 5.45%)jsframe..libsystem_malloc.dylib`realloc (1 samples, 1.82%)l..libsystem_malloc.dylib`malloc_zone_realloc (1 samples, 1.82%)l..libsystem_malloc.dylib`szone_realloc (1 samples, 1.82%)l..libsystem_platform.dylib`_platform_memmove$VARIANT$Haswell (1 samples, 1.82%)l..jsframework-a8f4acf5955e8e7f`dioxus_core::scopearena::ScopeArena::fin_head (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`<bumpalo::Bump as core::default::Default>::default (4 samples, 7.27%)jsframewor..jsframework-a8f4acf5955e8e7f`<bumpalo::Bump as core::ops::drop::Drop>::drop (1 samples, 1.82%)j..libsystem_malloc.dylib`free_tiny (1 samples, 1.82%)l..libsystem_malloc.dylib`tiny_free_no_lock (1 samples, 1.82%)l..libsystem_malloc.dylib`tiny_free_list_add_ptr (1 samples, 1.82%)l..libsystem_malloc.dylib`malloc_zone_realloc (1 samples, 1.82%)l..libsystem_platform.dylib`DYLD-STUB$$_platform_memmove (1 samples, 1.82%)l..jsframework-a8f4acf5955e8e7f`alloc::raw_vec::RawVec<T,A>::reserve::do_reserve_and_handle (2 samples, 3.64%)jsfr..jsframework-a8f4acf5955e8e7f`alloc::raw_vec::finish_grow (2 samples, 3.64%)jsfr..libsystem_malloc.dylib`realloc (2 samples, 3.64%)libs..libsystem_malloc.dylib`szone_size (1 samples, 1.82%)l..jsframework-a8f4acf5955e8e7f`bumpalo::Bump::with_capacity (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`bumpalo::Bump::with_capacity (2 samples, 3.64%)jsfr..jsframework-a8f4acf5955e8e7f`dioxus_core::scope::BumpFrame::new (3 samples, 5.45%)jsframe..libsystem_malloc.dylib`_malloc_zone_malloc (1 samples, 1.82%)l..libsystem_malloc.dylib`szone_malloc_should_clear (1 samples, 1.82%)l..libsystem_malloc.dylib`tiny_malloc_should_clear (1 samples, 1.82%)l..libsystem_malloc.dylib`tiny_malloc_from_free_list (1 samples, 1.82%)l..jsframework-a8f4acf5955e8e7f`dioxus_core::scopearena::ScopeArena::new_with_key (14 samples, 25.45%)jsframework-a8f4acf5955e8e7f`dioxus_core:..jsframework-a8f4acf5955e8e7f`hashbrown::raw::RawTable<T,A>::insert (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::element (6 samples, 10.91%)jsframework-a8f4..jsframework-a8f4acf5955e8e7f`bumpalo::Bump::alloc_layout_slow (6 samples, 10.91%)jsframework-a8f4..libsystem_malloc.dylib`_malloc_zone_malloc (2 samples, 3.64%)libs..libsystem_malloc.dylib`szone_malloc_should_clear (2 samples, 3.64%)libs..libsystem_malloc.dylib`tiny_malloc_should_clear (2 samples, 3.64%)libs..libsystem_malloc.dylib`set_tiny_meta_header_in_use (1 samples, 1.82%)l..jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::raw_element (12 samples, 21.82%)jsframework-a8f4acf5955e8e7f`dioxu..jsframework-a8f4acf5955e8e7f`bumpalo::Bump::alloc_layout_slow (12 samples, 21.82%)jsframework-a8f4acf5955e8e7f`bumpa..libsystem_malloc.dylib`_malloc_zone_malloc (1 samples, 1.82%)l..libsystem_malloc.dylib`szone_malloc_should_clear (1 samples, 1.82%)l..libsystem_malloc.dylib`small_malloc_should_clear (1 samples, 1.82%)l..jsframework-a8f4acf5955e8e7f`<&T as core::fmt::Display>::fmt (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`bumpalo::collections::string::String::into_bump_str (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::text (3 samples, 5.45%)jsframe..jsframework-a8f4acf5955e8e7f`core::fmt::write (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`<&mut W as core::fmt::Write>::write_str (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::call (22 samples, 40.00%)jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::c..jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::new::_{{closure}} (22 samples, 40.00%)jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::n..jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::empty_cell (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`dioxus_core::scope::Scope::render (23 samples, 41.82%)jsframework-a8f4acf5955e8e7f`dioxus_core::scope::Scope::renderlibsystem_platform.dylib`_platform_memmove$VARIANT$Haswell (1 samples, 1.82%)l..jsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::create_node (40 samples, 72.73%)jsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::create_nodejsframework-a8f4acf5955e8e7f`dioxus_core::scopearena::ScopeArena::run_scope (24 samples, 43.64%)jsframework-a8f4acf5955e8e7f`dioxus_core::scopearena::ScopeArena::run_s..jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::component::_{{closure}} (24 samples, 43.64%)jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::component..jsframework-a8f4acf5955e8e7f`jsframework::Row (24 samples, 43.64%)jsframework-a8f4acf5955e8e7f`jsframework::Rowlibsystem_platform.dylib`_platform_memmove$VARIANT$Haswell (1 samples, 1.82%)l..jsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::work (48 samples, 87.27%)jsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::workjsframework-a8f4acf5955e8e7f`dioxus_core::diff::DiffState::mount (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`<core::option::Option<dioxus_core::lazynodes::LazyNodes> as dioxus_core::nodes::IntoVNode>::into_vnode (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::call (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::new::_{{closure}} (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::component (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`main (51 samples, 92.73%)jsframework-a8f4acf5955e8e7f`mainjsframework-a8f4acf5955e8e7f`std::rt::lang_start_internal (51 samples, 92.73%)jsframework-a8f4acf5955e8e7f`std::rt::lang_start_internaljsframework-a8f4acf5955e8e7f`std::rt::lang_start::_{{closure}} (51 samples, 92.73%)jsframework-a8f4acf5955e8e7f`std::rt::lang_start::_{{closure}}jsframework-a8f4acf5955e8e7f`std::sys_common::backtrace::__rust_begin_short_backtrace (51 samples, 92.73%)jsframework-a8f4acf5955e8e7f`std::sys_common::backtrace::__rust_begin_short_backtracejsframework-a8f4acf5955e8e7f`jsframework::main (51 samples, 92.73%)jsframework-a8f4acf5955e8e7f`jsframework::mainjsframework-a8f4acf5955e8e7f`criterion::Criterion<M>::bench_function (51 samples, 92.73%)jsframework-a8f4acf5955e8e7f`criterion::Criterion<M>::bench_functionjsframework-a8f4acf5955e8e7f`criterion::benchmark_group::BenchmarkGroup<M>::bench_function (51 samples, 92.73%)jsframework-a8f4acf5955e8e7f`criterion::benchmark_group::BenchmarkGroup<M>::bench_functionjsframework-a8f4acf5955e8e7f`criterion::routine::Routine::test (51 samples, 92.73%)jsframework-a8f4acf5955e8e7f`criterion::routine::Routine::testjsframework-a8f4acf5955e8e7f`<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter (51 samples, 92.73%)jsframework-a8f4acf5955e8e7f`<alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iterjsframework-a8f4acf5955e8e7f`criterion::bencher::Bencher<M>::iter (51 samples, 92.73%)jsframework-a8f4acf5955e8e7f`criterion::bencher::Bencher<M>::iterjsframework-a8f4acf5955e8e7f`dioxus_core::virtual_dom::VirtualDom::rebuild (50 samples, 90.91%)jsframework-a8f4acf5955e8e7f`dioxus_core::virtual_dom::VirtualDom::rebuildjsframework-a8f4acf5955e8e7f`dioxus_core::scopearena::ScopeArena::run_scope (2 samples, 3.64%)jsfr..jsframework-a8f4acf5955e8e7f`dioxus_core::virtual_dom::VirtualDom::new_with_props_and_scheduler::_{{closure}} (2 samples, 3.64%)jsfr..jsframework-a8f4acf5955e8e7f`core::ops::function::FnOnce::call_once (2 samples, 3.64%)jsfr..jsframework-a8f4acf5955e8e7f`dioxus_core::scope::Scope::render (2 samples, 3.64%)jsfr..jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::call (2 samples, 3.64%)jsfr..jsframework-a8f4acf5955e8e7f`dioxus_core::lazynodes::LazyNodes::new::_{{closure}} (2 samples, 3.64%)jsfr..jsframework-a8f4acf5955e8e7f`dioxus_core::nodes::NodeFactory::fragment_from_iter (2 samples, 3.64%)jsfr..jsframework-a8f4acf5955e8e7f`core::ops::function::impls::_<impl core::ops::function::FnOnce<A> for &mut F>::call_once (1 samples, 1.82%)j..jsframework-a8f4acf5955e8e7f`rand::rng::Rng::gen_range (1 samples, 1.82%)j..all (55 samples, 100%)0x1 (55 samples, 100.00%)0x1libdyld.dylib`start (55 samples, 100.00%)libdyld.dylib`startlibsystem_kernel.dylib`__exit (4 samples, 7.27%)libsystem_.. \ No newline at end of file diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 8c6831ca..f4bebc8d 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -434,8 +434,15 @@ impl<'bump> DiffState<'bump> { } if !children.is_empty() { - self.stack.create_children(children, MountType::Append); + if children.len() == 1 { + if let VNode::Text(vtext) = children[0] { + self.mutations.set_text(vtext.text, real_id.as_u64()); + return; + } + } } + + self.stack.create_children(children, MountType::Append); } fn create_fragment_node(&mut self, frag: &'bump VFragment<'bump>) { @@ -645,17 +652,59 @@ impl<'bump> DiffState<'bump> { } } - if old.children.is_empty() && !new.children.is_empty() { - self.mutations.edits.push(PushRoot { - root: root.as_u64(), - }); - self.stack.element_stack.push(root); - self.stack.instructions.push(DiffInstruction::PopElement); - self.stack.create_children(new.children, MountType::Append); - } else { - self.stack.element_stack.push(root); - self.stack.instructions.push(DiffInstruction::PopElement); - self.diff_children(old.children, new.children); + match (old.children.len(), new.children.len()) { + (0, 0) => {} + (1, 1) => { + let old1 = &old.children[0]; + let new1 = &new.children[0]; + + match (old1, new1) { + (VNode::Text(old_text), VNode::Text(new_text)) => { + if old_text.text != new_text.text { + self.mutations.set_text(new_text.text, root.as_u64()); + } + } + (VNode::Text(old_text), _) => { + self.stack.element_stack.push(root); + self.stack.instructions.push(DiffInstruction::PopElement); + self.stack.create_node(new1, MountType::Append); + } + (_, VNode::Text(new_text)) => { + self.remove_nodes([old1], false); + self.mutations.set_text(new_text.text, root.as_u64()); + } + _ => { + self.stack.element_stack.push(root); + self.stack.instructions.push(DiffInstruction::PopElement); + self.diff_children(old.children, new.children); + } + } + } + (0, 1) => { + if let VNode::Text(text) = &new.children[0] { + self.mutations.set_text(text.text, root.as_u64()); + } else { + self.stack.element_stack.push(root); + self.stack.instructions.push(DiffInstruction::PopElement); + } + } + (0, _) => { + self.mutations.edits.push(PushRoot { + root: root.as_u64(), + }); + self.stack.element_stack.push(root); + self.stack.instructions.push(DiffInstruction::PopElement); + self.stack.create_children(new.children, MountType::Append); + } + (_, 0) => { + self.remove_nodes(old.children, false); + self.mutations.set_text("", root.as_u64()); + } + (_, _) => { + self.stack.element_stack.push(root); + self.stack.instructions.push(DiffInstruction::PopElement); + self.diff_children(old.children, new.children); + } } } diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 4a2eabd6..398c8f20 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -196,7 +196,7 @@ impl Scope { let chan = self.sender.clone(); let id = self.scope_id(); Rc::new(move || { - log::debug!("set on channel an update for scope {:?}", id); + // log::debug!("set on channel an update for scope {:?}", id); let _ = chan.unbounded_send(SchedulerMsg::Immediate(id)); }) } diff --git a/packages/core/src/scopearena.rs b/packages/core/src/scopearena.rs index dd1f555c..ea6f0c5b 100644 --- a/packages/core/src/scopearena.rs +++ b/packages/core/src/scopearena.rs @@ -92,15 +92,15 @@ impl ScopeArena { let new_scope_id = ScopeId(self.scope_counter.get()); self.scope_counter.set(self.scope_counter.get() + 1); - log::debug!("new scope {:?} with parent {:?}", new_scope_id, container); + // log::debug!("new scope {:?} with parent {:?}", new_scope_id, container); if let Some(old_scope) = self.free_scopes.borrow_mut().pop() { let scope = unsafe { &mut *old_scope }; - log::debug!( - "reusing scope {:?} as {:?}", - scope.our_arena_idx, - new_scope_id - ); + // log::debug!( + // "reusing scope {:?} as {:?}", + // scope.our_arena_idx, + // new_scope_id + // ); scope.caller = caller; scope.parent_scope = parent_scope; @@ -202,7 +202,7 @@ impl ScopeArena { pub fn try_remove(&self, id: &ScopeId) -> Option<()> { self.ensure_drop_safety(id); - log::debug!("removing scope {:?}", id); + // log::debug!("removing scope {:?}", id); // Safety: // - ensure_drop_safety ensures that no references to this scope are in use @@ -311,7 +311,7 @@ impl ScopeArena { let scope = unsafe { &mut *self.get_scope_mut(id).expect("could not find scope") }; - log::debug!("found scope, about to run: {:?}", id); + // log::debug!("found scope, about to run: {:?}", id); // Safety: // - We dropped the listeners, so no more &mut T can be used while these are held diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index fe1fcac2..fc85ef0a 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -352,7 +352,7 @@ impl VirtualDom { let mut committed_mutations = vec![]; while !self.dirty_scopes.is_empty() { - log::debug!("working with deadline"); + // log::debug!("working with deadline"); let scopes = &self.scopes; let mut diff_state = DiffState::new(scopes); diff --git a/packages/desktop/README.md b/packages/desktop/README.md index 2888ae22..0c166348 100644 --- a/packages/desktop/README.md +++ b/packages/desktop/README.md @@ -7,7 +7,7 @@ fn main() { dioxus::desktop::launch(App, |c| c) } -static App: FC<()> = |(cx, props)| { +static App: FC<()> = |cx, props| { let (count, set_count) = use_state(cx, || 0); cx.render(rsx!( @@ -34,7 +34,7 @@ Window management, system trays, notifications, and other desktop-related functi Managing windows is done by simply rendering content into a `WebviewWindow` component. ```rust -static App: FC<()> = |(cx, props)| { +static App: FC<()> = |cx, props| { rsx!(cx, WebviewWindow { "hello world" } ) } ``` @@ -46,7 +46,7 @@ Notifications also use a declarative approach. Sending a notification has never The api has been somewhat modeled after https://github.com/mikaelbr/node-notifier ```rust -static Notifications: FC<()> = |(cx, props)| { +static Notifications: FC<()> = |cx, props| { cx.render(rsx!( Notification { title: "title" @@ -78,7 +78,7 @@ static Notifications: FC<()> = |(cx, props)| { Dioxus Desktop supports app trays, which can be built with native menu groups or with a custom window. ```rust -static Tray: FC<()> = |(cx, props)| { +static Tray: FC<()> = |cx, props| { cx.render(rsx!( GlobalTray { MenuGroup { @@ -90,7 +90,7 @@ static Tray: FC<()> = |(cx, props)| { }; // using a builder -static Tray: FC<()> = |(cx, props)| { +static Tray: FC<()> = |cx, props| { let menu = MenuGroup::builder(cx) .with_items([ MenuGroupItem::builder() @@ -107,7 +107,7 @@ static Tray: FC<()> = |(cx, props)| { } // or with a custom window -static Tray: FC<()> = |(cx, props)| { +static Tray: FC<()> = |cx, props| { rsx!(cx, GlobalTray { div { "custom buttons here" } }) }; ``` @@ -116,7 +116,7 @@ static Tray: FC<()> = |(cx, props)| { Declaring menus is convenient and cross-platform. ```rust -static Menu: FC<()> = |(cx, props)| { +static Menu: FC<()> = |cx, props| { cx.render(rsx!( MenuBarMajorItem { title: "File" MenuGroup { diff --git a/packages/desktop/src/index.html b/packages/desktop/src/index.html index 428a3b8d..df3bd643 100644 --- a/packages/desktop/src/index.html +++ b/packages/desktop/src/index.html @@ -19,10 +19,9 @@ -
+
- + diff --git a/packages/desktop/src/lib.rs b/packages/desktop/src/lib.rs index 817e2049..1e429059 100644 --- a/packages/desktop/src/lib.rs +++ b/packages/desktop/src/lib.rs @@ -90,6 +90,8 @@ pub fn run( let edit_queue = Arc::new(RwLock::new(VecDeque::new())); let is_ready: Arc = Default::default(); + let mut frame = 0; + event_loop.run(move |window_event, event_loop, control_flow| { *control_flow = ControlFlow::Wait; @@ -145,6 +147,9 @@ pub fn run( view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit)) .unwrap(); } + } else { + println!("waiting for onload {:?}", frame); + frame += 1; } } Event::Resumed => {} @@ -258,6 +263,7 @@ fn create_webview( // always driven through eval None }) + // .with_initialization_script(include_str!("./index.js")) // Any content that that uses the `wry://` scheme will be shuttled through this handler as a "special case" // For now, we only serve two pieces of content which get included as bytes into the final binary. .with_custom_protocol("wry".into(), move |request| { diff --git a/packages/hooks/src/usecollection.rs b/packages/hooks/src/usecollection.rs index c8227729..d0b3b71e 100644 --- a/packages/hooks/src/usecollection.rs +++ b/packages/hooks/src/usecollection.rs @@ -36,7 +36,7 @@ uses the same memoization on top of the use_context API. Here's a fully-functional todo app using the use_map API: ```rust -static TodoList: FC<()> = |(cx, props)|{ +static TodoList: FC<()> = |cx, props|{ let todos = use_map(cx, || HashMap::new()); let input = use_ref(|| None); diff --git a/packages/hooks/src/useref.rs b/packages/hooks/src/useref.rs index 75ae9c82..9ebfbfeb 100644 --- a/packages/hooks/src/useref.rs +++ b/packages/hooks/src/useref.rs @@ -33,6 +33,11 @@ impl<'a, T> UseRef<'a, T> { self.inner.value.borrow() } + pub fn set(&self, new: T) { + *self.inner.value.borrow_mut() = new; + self.needs_update(); + } + pub fn read_write(&self) -> (Ref<'_, T>, &Self) { (self.read(), self) } diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs index 83db764e..fb8d3e4a 100644 --- a/packages/hooks/src/usestate.rs +++ b/packages/hooks/src/usestate.rs @@ -35,7 +35,7 @@ use std::{ /// /// Usage: /// ```ignore -/// const Example: FC<()> = |(cx, props)|{ +/// const Example: FC<()> = |cx, props|{ /// let counter = use_state(cx, || 0); /// let increment = |_| counter += 1; /// let decrement = |_| counter += 1; diff --git a/packages/html/src/elements.rs b/packages/html/src/elements.rs index 38797ed6..db4b3442 100644 --- a/packages/html/src/elements.rs +++ b/packages/html/src/elements.rs @@ -717,8 +717,7 @@ builder_constructors! { nonce: Nonce, src: Uri, text: String, - r#async: Bool, - r#type: String, // TODO could be an enum + }; @@ -823,7 +822,6 @@ builder_constructors! { formnovalidate: Bool, formtarget: Target, name: Id, - r#type: ButtonType, value: String, }; @@ -1064,6 +1062,23 @@ impl input { volatile attributes */ +impl script { + // r#async: Bool, + // r#type: String, // TODO could be an enum + pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { + cx.attr("type", val, None, false) + } + pub fn r#script<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { + cx.attr("script", val, None, false) + } +} + +impl button { + pub fn r#type<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { + cx.attr("type", val, None, false) + } +} + impl select { pub fn value<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { cx.attr("value", val, None, true) diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index 93a811a7..3f699237 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -37,3 +37,9 @@ default = ["web"] web = ["web-sys"] desktop = [] mobile = [] + +[dev-dependencies] +console_error_panic_hook = "0.1.7" +dioxus-web = { path = "../web" } +log = "0.4.14" +wasm-logger = "0.2.0" diff --git a/packages/router/README.md b/packages/router/README.md new file mode 100644 index 00000000..902bfdb1 --- /dev/null +++ b/packages/router/README.md @@ -0,0 +1,40 @@ +# Router hook for Dioxus apps + +Dioxus-router provides a use_router hook that returns a different value depending on the route. +The router is generic over any value, however it makes sense to return a different set of VNodes +and feed them into the App's return VNodes. + +Using the router should feel similar to tide's routing framework where an "address" book is assembled at the head of the app. + +Here's an example of how to use the router hook: + +```rust +#[derive(Clone, Routable)] +enum AppRoute { + Home, + Posts, + NotFound +} + +static App: FC<()> = |cx, props| { + let route = use_router(cx, AppRoute::parse); + + match route { + AppRoute::Home => rsx!(cx, Home {}) + AppRoute::Posts => rsx!(cx, Posts {}) + AppRoute::Notfound => rsx!(cx, Notfound {}) + } +}; +``` + +Adding links into your app: + +```rust +static Leaf: FC<()> = |cx, props| { + rsx!(cx, div { + Link { to: AppRoute::Home } + }) +} +``` + +Currently, the router is only supported in a web environment, but we plan to add 1st-party support via the context API when new renderers are available. diff --git a/packages/router/examples/simple.rs b/packages/router/examples/simple.rs new file mode 100644 index 00000000..43ddaa2d --- /dev/null +++ b/packages/router/examples/simple.rs @@ -0,0 +1,45 @@ +use dioxus_core::prelude::*; +use dioxus_core_macro::*; +use dioxus_html as dioxus_elements; +use dioxus_router::*; + +fn main() { + console_error_panic_hook::set_once(); + dioxus_web::launch(App, |c| c); +} + +#[derive(Clone, Debug, PartialEq)] +enum Route { + Home, + About, + NotFound, +} + +static App: FC<()> = |cx, props| { + let route = use_router(cx, Route::parse); + + match route { + Route::Home => rsx!(cx, div { "Home" }), + Route::About => rsx!(cx, div { "About" }), + Route::NotFound => rsx!(cx, div { "NotFound" }), + } +}; + +impl ToString for Route { + fn to_string(&self) -> String { + match self { + Route::Home => "/".to_string(), + Route::About => "/about".to_string(), + Route::NotFound => "/404".to_string(), + } + } +} +impl Route { + fn parse(s: &str) -> Self { + match s { + "/" => Route::Home, + "/about" => Route::About, + _ => Route::NotFound, + } + } +} diff --git a/packages/router/src/lib.rs b/packages/router/src/lib.rs index 3aee5a68..948cb4ef 100644 --- a/packages/router/src/lib.rs +++ b/packages/router/src/lib.rs @@ -11,14 +11,18 @@ use web_sys::Event; use crate::utils::fetch_base_url; +pub trait Routable: 'static + Send + Clone + ToString + PartialEq {} +impl Routable for T where T: 'static + Send + Clone + ToString + PartialEq {} + pub struct RouterService { - history: RefCell>, + historic_routes: RefCell>, + history_service: web_sys::History, base_ur: RefCell>, } impl RouterService { fn push_route(&self, r: R) { - self.history.borrow_mut().push(r); + self.historic_routes.borrow_mut().push(r); } fn get_current_route(&self) -> &str { @@ -61,7 +65,7 @@ impl RouterService { /// This hould only be used once per app /// /// 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(cx: Context, cfg: impl FnOnce(&str) -> R) -> Option<&R> { +pub fn use_router(cx: Context, parse: impl FnMut(&str) -> R) -> &R { // for the web, attach to the history api cx.use_hook( |f| { @@ -71,10 +75,13 @@ pub fn use_router(cx: Context, cfg: impl FnOnce(&str) -> R) -> Opti let base_url = fetch_base_url(); let service: RouterService = RouterService { - history: RefCell::new(vec![]), + historic_routes: RefCell::new(vec![]), + history_service: web_sys::window().unwrap().history().expect("no history"), base_ur: RefCell::new(base_url), }; + // service.history_service.push_state(data, title); + cx.provide_state(service); let regenerate = cx.schedule_update(); @@ -105,30 +112,13 @@ pub struct LinkProps { children: Element, } -pub fn Link<'a, R: Routable>(cx: Context, props: &LinkProps) -> Element { +pub fn Link(cx: Context, props: &LinkProps) -> Element { let service = use_router_service::(cx)?; cx.render(rsx! { a { - href: format_args!("{}", props.to.to_path()), + href: format_args!("{}", props.to.to_string()), onclick: move |_| service.push_route(props.to.clone()), {&props.children}, } }) } - -pub trait Routable: Sized + Clone + 'static { - /// Converts path to an instance of the routes enum. - fn from_path(path: &str, params: &HashMap<&str, &str>) -> Option; - - /// Converts the route to a string that can passed to the history API. - fn to_path(&self) -> String; - - /// Lists all the available routes - fn routes() -> Vec<&'static str>; - - /// The route to redirect to on 404 - fn not_found_route() -> Option; - - /// Match a route based on the path - fn recognize(pathname: &str) -> Option; -} diff --git a/packages/ssr/README.md b/packages/ssr/README.md index 7efb5ca9..4d47b426 100644 --- a/packages/ssr/README.md +++ b/packages/ssr/README.md @@ -5,7 +5,7 @@ Render a Dioxus VirtualDOM to a string. ```rust // Our app: -const App: FC<()> = |(cx, props)| rsx!(cx, div {"hello world!"}); +const App: FC<()> = |cx, props| rsx!(cx, div {"hello world!"}); // Build the virtualdom from our app let mut vdom = VirtualDOM::new(App); diff --git a/packages/ssr/src/lib.rs b/packages/ssr/src/lib.rs index 4fc696c1..a04271bb 100644 --- a/packages/ssr/src/lib.rs +++ b/packages/ssr/src/lib.rs @@ -110,7 +110,7 @@ pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option { /// /// ## Example /// ```ignore -/// static App: FC<()> = |(cx, props)|cx.render(rsx!(div { "hello world" })); +/// static App: FC<()> = |cx, props|cx.render(rsx!(div { "hello world" })); /// let mut vdom = VirtualDom::new(App); /// vdom.rebuild(); /// diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 05c8ce4e..043e4e7d 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -11,10 +11,17 @@ license = "MIT/Apache-2.0" dioxus-core = { path = "../core", version = "0.1.2" } dioxus-html = { path = "../html" } js-sys = "0.3" +# wasm-bindgen-shared = { path = "../../../Tinkering/wasm-bindgen/crates/shared" } +# wasm-bindgen-macro-support = { path = "../../../Tinkering/wasm-bindgen/crates/macro-support" } +# wasm-bindgen = { features = [ + + wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] } +# wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] } +# wasm-bindgen = { version = "0.2.78", features = ["enable-interning"] } lazy_static = "1.4.0" wasm-bindgen-futures = "0.4.20" -log = "0.4.14" +log = { version = "0.4.14", features = ["release_max_level_off"] } fxhash = "0.2.1" wasm-logger = "0.2.0" console_error_panic_hook = "0.1.6" @@ -24,6 +31,11 @@ async-channel = "1.6.1" anyhow = "1.0" gloo-timers = { version = "0.2.1", features = ["futures"] } futures-util = "0.3.15" +smallstr = "0.2.0" + +[patch.crates-io] +wasm-bindgen = { path = "../../../Tinkering/wasm-bindgen/" } + [dependencies.web-sys] version = "0.3.51" @@ -65,6 +77,7 @@ features = [ "IdleDeadline", ] + [lib] crate-type = ["cdylib", "rlib"] @@ -76,7 +89,12 @@ serde = { version = "1.0.126", features = ["derive"] } reqwest = { version = "0.11", features = ["json"] } dioxus-hooks = { path = "../hooks" } dioxus-core-macro = { path = "../core-macro" } -# rand = { version="0.8.4", features=["small_rng"] } +rand = { version = "0.8.4", features = ["small_rng"] } + +[dev-dependencies.getrandom] +version = "0.2" +features = ["js"] + # surf = { version = "2.3.1", default-features = false, features = [ # "wasm-client", # ] } diff --git a/packages/web/examples/js_bench.rs b/packages/web/examples/js_bench.rs new file mode 100644 index 00000000..9ce90d84 --- /dev/null +++ b/packages/web/examples/js_bench.rs @@ -0,0 +1,243 @@ +use std::cell::Cell; + +use dioxus::prelude::*; +use dioxus_core as dioxus; +use dioxus_core_macro::*; +use dioxus_hooks::{use_ref, use_state}; +use dioxus_html as dioxus_elements; +use dioxus_web; +use gloo_timers::future::TimeoutFuture; +use rand::prelude::*; + +fn main() { + console_error_panic_hook::set_once(); + if cfg!(debug_assertions) { + wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); + log::debug!("hello world"); + } + + for a in ADJECTIVES { + wasm_bindgen::intern(*a); + } + for a in COLOURS { + wasm_bindgen::intern(*a); + } + for a in NOUNS { + wasm_bindgen::intern(*a); + } + for a in [ + "container", + "jumbotron", + "row", + "Dioxus", + "col-md-6", + "col-md-1", + "Create 1,000 rows", + "run", + "Create 10,000 rows", + "runlots", + "Append 1,000 rows", + "add", + "Update every 10th row", + "update", + "Clear", + "clear", + "Swap rows", + "swaprows", + "preloadicon glyphicon glyphicon-remove", // + "aria-hidden", + "onclick", + "true", + "false", + "danger", + "type", + "id", + "class", + "glyphicon glyphicon-remove remove", + "dioxus-id", + "dioxus-event-click", + "dioxus", + "click", + "1.10", + "lbl", + "remove", + "dioxus-event", + "col-sm-6 smallpad", + "btn btn-primary btn-block", + "", + " ", + ] { + wasm_bindgen::intern(a); + } + for x in 0..100_000 { + wasm_bindgen::intern(&x.to_string()); + } + + dioxus_web::launch(App, |c| c.rootname("main")); +} + +#[derive(Clone, PartialEq, Copy)] +struct Label { + key: usize, + labels: [&'static str; 3], +} + +static mut Counter: Cell = Cell::new(1); + +impl Label { + fn new_list(num: usize) -> Vec { + let mut rng = SmallRng::from_entropy(); + let mut labels = Vec::with_capacity(num); + + let offset = unsafe { Counter.get() }; + unsafe { Counter.set(offset + num) }; + + for k in offset..(offset + num) { + labels.push(Label { + key: k, + labels: [ + ADJECTIVES.choose(&mut rng).unwrap(), + COLOURS.choose(&mut rng).unwrap(), + NOUNS.choose(&mut rng).unwrap(), + ], + }); + } + + labels + } +} + +static App: FC<()> = |cx, _props| { + let mut items = use_ref(cx, || vec![]); + let mut selected = use_state(cx, || None); + + cx.render(rsx! { + div { class: "container" + div { class: "jumbotron" + div { class: "row" + div { class: "col-md-6", h1 { "Dioxus" } } + div { class: "col-md-6" + div { class: "row" + ActionButton { name: "Create 1,000 rows", id: "run", + onclick: move || items.set(Label::new_list(1_000)), + } + ActionButton { name: "Create 10,000 rows", id: "runlots", + onclick: move || items.set(Label::new_list(10_000)), + } + ActionButton { name: "Append 1,000 rows", id: "add", + onclick: move || items.write().extend(Label::new_list(1_000)), + } + ActionButton { name: "Update every 10th row", id: "update", + onclick: move || items.write().iter_mut().step_by(10).for_each(|item| item.labels[2] = "!!!"), + } + ActionButton { name: "Clear", id: "clear", + onclick: move || items.write().clear(), + } + ActionButton { name: "Swap Rows", id: "swaprows", + onclick: move || items.write().swap(0, 998), + } + } + } + } + } + table { class: "table table-hover table-striped test-data" + tbody { id: "tbody" + {items.read().iter().enumerate().map(|(id, item)| { + let [adj, col, noun] = item.labels; + let is_in_danger = if (*selected).map(|s| s == id).unwrap_or(false) {"danger"} else {""}; + rsx!(tr { + class: "{is_in_danger}", + key: "{id}", + td { class:"col-md-1" } + td { class:"col-md-1", "{item.key}" } + td { class:"col-md-1", onclick: move |_| selected.set(Some(id)), + a { class: "lbl", "{adj} {col} {noun}" } + } + td { class: "col-md-1" + a { class: "remove", onclick: move |_| { items.write().remove(id); }, + span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" } + } + } + td { class: "col-md-6" } + }) + })} + } + } + span { class: "preloadicon glyphicon glyphicon-remove" aria_hidden: "true" } + } + }) +}; + +#[derive(Props)] +struct ActionButtonProps<'a> { + name: &'static str, + id: &'static str, + onclick: &'a dyn Fn(), +} + +fn ActionButton(cx: Context, props: &ActionButtonProps) -> Element { + rsx!(cx, div { class: "col-sm-6 smallpad" + button { class:"btn btn-primary btn-block", r#type: "button", id: "{props.id}", onclick: move |_| (props.onclick)(), + "{props.name}" + } + }) +} + +static ADJECTIVES: &[&str] = &[ + "pretty", + "large", + "big", + "small", + "tall", + "short", + "long", + "handsome", + "plain", + "quaint", + "clean", + "elegant", + "easy", + "angry", + "crazy", + "helpful", + "mushy", + "odd", + "unsightly", + "adorable", + "important", + "inexpensive", + "cheap", + "expensive", + "fancy", +]; + +static COLOURS: &[&str] = &[ + "red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black", + "orange", +]; + +static NOUNS: &[&str] = &[ + "table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger", + "pizza", "mouse", "keyboard", +]; + +// #[derive(PartialEq, Props)] +// struct RowProps<'a> { +// row_id: usize, +// label: &'a Label, +// } + +// fn Row(cx: Context, props: &RowProps) -> Element { +// rsx!(cx, tr { +// td { class:"col-md-1", "{props.row_id}" } +// td { class:"col-md-1", onclick: move |_| { /* run onselect */ } +// a { class: "lbl", {props.label.labels} } +// } +// 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" } +// }) +// } diff --git a/packages/web/examples/simple.rs b/packages/web/examples/simple.rs new file mode 100644 index 00000000..f6a397e0 --- /dev/null +++ b/packages/web/examples/simple.rs @@ -0,0 +1,46 @@ +//! Example: README.md showcase +//! +//! The example from the README.md. + +use dioxus::prelude::*; +use dioxus_core as dioxus; +use dioxus_core_macro::*; +use dioxus_hooks::use_state; +use dioxus_html as dioxus_elements; +use dioxus_web; +use gloo_timers::future::TimeoutFuture; + +fn main() { + wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); + dioxus_web::launch(App, |c| c); +} + +static App: FC<()> = |cx, props| { + let show = use_state(cx, || true); + + let inner = match *show { + true => { + rsx!( div { + "hello world" + }) + } + false => { + rsx!( div { + // h1 { + "bello world" + // } + }) + } + }; + + rsx!(cx, div { + button { + "toggle" + onclick: move |_| { + let cur = *show; + show.set(!cur); + } + } + {inner} + }) +}; diff --git a/packages/web/src/cache.rs b/packages/web/src/cache.rs index 1cdba795..61339f91 100644 --- a/packages/web/src/cache.rs +++ b/packages/web/src/cache.rs @@ -7,6 +7,11 @@ /// Eventually we might want to procedurally generate these strings for common words, phrases, and values. pub(crate) fn intern_cached_strings() { let cached_words = [ + // Important tags to dioxus + "dioxus-id", + "dioxus", + "dioxus-event-click", // todo: more events + "click", // All the HTML Tags "a", "abbr", diff --git a/packages/web/src/cfg.rs b/packages/web/src/cfg.rs index 287e8185..09c48802 100644 --- a/packages/web/src/cfg.rs +++ b/packages/web/src/cfg.rs @@ -17,7 +17,7 @@ impl Default for WebConfig { fn default() -> Self { Self { hydrate: false, - rootname: "dioxusroot".to_string(), + rootname: "main".to_string(), } } } diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 630cd2b3..c92dd340 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -213,7 +213,7 @@ impl WebsysDom { fn create_placeholder(&mut self, id: u64) { self.create_element("pre", None, id); - // self.set_attribute("hidden", "", None); + self.set_attribute("hidden", "", None, id); } fn create_text_node(&mut self, text: &str, id: u64) { @@ -246,8 +246,15 @@ impl WebsysDom { .unwrap(), }; + use smallstr; + use smallstr::SmallString; + use std::fmt::Write; + + let mut s: SmallString<[u8; 8]> = smallstr::SmallString::new(); + write!(s, "{}", id).unwrap(); + let el2 = el.dyn_ref::().unwrap(); - el2.set_attribute("dioxus-id", &format!("{}", id)).unwrap(); + el2.set_attribute("dioxus-id", s.as_str()).unwrap(); self.stack.push(el.clone()); self.nodes[(id as usize)] = Some(el); @@ -263,18 +270,21 @@ impl WebsysDom { let el = self.stack.top(); - let el = el - .dyn_ref::() - .expect(&format!("not an element: {:?}", el)); + let el = el.dyn_ref::().unwrap(); + // let el = el.dyn_ref::().unwrap(); + // .expect(&format!("not an element: {:?}", el)); // let scope_id = scope.data().as_ffi(); - let scope_id = scope.0 as u64; + // let scope_id = scope.0 as u64; + // "dioxus-event-click", + // "1.10" + // &format!("", scope_id, real_id), + // &format!("dioxus-event-{}", event), + // &format!("{}.{}", scope_id, real_id), + // &format!("dioxus-event-{}", event), + // &format!("{}.{}", scope_id, real_id), - el.set_attribute( - &format!("dioxus-event-{}", event), - &format!("{}.{}", scope_id, real_id), - ) - .unwrap(); + el.set_attribute("dioxus-event", event).unwrap(); // el.set_attribute(&format!("dioxus-event"), &format!("{}", event)) // .unwrap(); @@ -488,6 +498,8 @@ unsafe impl Sync for DioxusWebsysEvent {} fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc { use dioxus_html::on::*; use dioxus_html::KeyCode; + // event.prevent_default(); + // use dioxus_core::events::on::*; match event.type_().as_str() { "copy" | "cut" | "paste" => Arc::new(ClipboardEvent {}), @@ -682,30 +694,40 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result { use anyhow::Context; + let element_id = target + .get_attribute("dioxus-id") + .context("Could not find element id on event target")? + .parse()?; + // The error handling here is not very descriptive and needs to be replaced with a zero-cost error system let val: String = target - .get_attribute(&format!("dioxus-event-{}", typ)) + .get_attribute("dioxus-event") .context(format!("wrong format - received {:#?}", typ))?; + // .get_attribute(&format!("dioxus-event-{}", typ)) + // .context(format!("wrong format - received {:#?}", typ))?; let mut fields = val.splitn(3, "."); - let gi_id = fields - .next() - .and_then(|f| f.parse::().ok()) - .context("failed to parse gi id")?; + // let gi_id = fields + // .next() + // .and_then(|f| f.parse::().ok()) + // .context("failed to parse gi id")?; - let real_id = fields - .next() - .and_then(|raw_id| raw_id.parse::().ok()) - .context("failed to parse real id")?; + // let real_id = fields + // .next() + // .and_then(|raw_id| raw_id.parse::().ok()) + // .context("failed to parse real id")?; - let triggered_scope = gi_id; + // let triggered_scope = gi_id; Ok(UserEvent { name: event_name_from_typ(&typ), data: virtual_event_from_websys_event(event.clone()), - element: Some(ElementId(real_id as usize)), - scope_id: Some(ScopeId(triggered_scope as usize)), + element: Some(ElementId(element_id)), + scope_id: None, + // scope_id: Some(ScopeId(triggered_scope as usize)), + // element: Some(ElementId(real_id as usize)), + // scope_id: Some(ScopeId(triggered_scope as usize)), priority: dioxus_core::EventPriority::Medium, }) } diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 4e5f5fef..d9e14b6c 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -85,7 +85,7 @@ mod ric_raf; /// dioxus_web::launch(App, |c| c); /// } /// -/// static App: FC<()> = |(cx, props)| { +/// static App: FC<()> = |cx, props| { /// rsx!(cx, div {"hello world"}) /// } /// ``` @@ -109,7 +109,7 @@ pub fn launch(root_component: FC<()>, configuration: impl FnOnce(WebConfig) -> W /// name: String /// } /// -/// static App: FC = |(cx, props)| { +/// static App: FC = |cx, props| { /// rsx!(cx, div {"hello {props.name}"}) /// } /// ``` @@ -155,7 +155,7 @@ pub async fn run_with_props(root: FC, root_props: T, cfg: // 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 !should_hydrate { - log::info!("Applying rebuild edits..., {:?}", mutations); + // log::info!("Applying rebuild edits..., {:?}", mutations); websys_dom.process_edits(&mut mutations.edits); } @@ -166,17 +166,20 @@ pub async fn run_with_props(root: FC, root_props: T, cfg: // if there is work then this future resolves immediately. dom.wait_for_work().await; - // wait for the mainthread to schedule us in - let mut deadline = work_loop.wait_for_idle_time().await; + // // wait for the mainthread to schedule us in + // let mut deadline = work_loop.wait_for_idle_time().await; // run the virtualdom work phase until the frame deadline is reached - let mutations = dom.work_with_deadline(|| (&mut deadline).now_or_never().is_some()); + let mutations = dom.work_with_deadline(|| false); + // // run the virtualdom work phase until the frame deadline is reached + // let mutations = dom.work_with_deadline(|| (&mut deadline).now_or_never().is_some()); // wait for the animation frame to fire so we can apply our changes work_loop.wait_for_raf().await; for mut edit in mutations { // actually apply our changes during the animation frame + // log::info!("Applying change edits..., {:?}", edit); websys_dom.process_edits(&mut edit.edits); } } diff --git a/src/lib.rs b/src/lib.rs index 93c7d33b..999fff41 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,7 +93,7 @@ //! Dioxus uses hooks for state management. Hooks are a form of state persisted between calls of the function component. //! //! ``` -//! pub pub static Example: FC<()> = |(cx, props)|{ +//! pub pub static Example: FC<()> = |cx, props|{ //! let (val, set_val) = use_state(cx, || 0); //! cx.render(rsx!( //! button { onclick: move |_| set_val(val + 1) } @@ -156,7 +156,7 @@ //! dioxus::web::launch(Example); //! } //! -//! pub pub static Example: FC<()> = |(cx, props)|{ +//! pub pub static Example: FC<()> = |cx, props|{ //! cx.render(rsx! { //! div { "Hello World!" } //! }) @@ -190,6 +190,11 @@ pub use dioxus_router as router; pub mod debug {} +pub mod events { + #[cfg(feature = "html")] + pub use dioxus_html::{on::*, KeyCode}; +} + pub mod prelude { //! A glob import that includes helper types like FC, rsx!, html!, and required traits pub use dioxus_core::prelude::*;