mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
wip: making progress on diffing and hydration
This commit is contained in:
parent
e5c88fe3a4
commit
49856ccd68
34 changed files with 1023 additions and 750 deletions
23
Cargo.toml
23
Cargo.toml
|
@ -19,7 +19,7 @@ dioxus-mobile = { path = "./packages/mobile", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# core
|
# core
|
||||||
default = ["core", "ssr", "desktop"]
|
default = ["core", "ssr"]
|
||||||
core = ["macro", "hooks", "html"]
|
core = ["macro", "hooks", "html"]
|
||||||
macro = ["dioxus-core-macro"]
|
macro = ["dioxus-core-macro"]
|
||||||
hooks = ["dioxus-hooks"]
|
hooks = ["dioxus-hooks"]
|
||||||
|
@ -37,18 +37,31 @@ mobile = ["dioxus-mobile"]
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
futures = "0.3.15"
|
futures-util = "0.3.16"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
num-format = "0.4.0"
|
num-format = "0.4.0"
|
||||||
separator = "0.4.1"
|
separator = "0.4.1"
|
||||||
serde = { version = "1.0.126", features = ["derive"] }
|
serde = { version = "1.0.126", features = ["derive"] }
|
||||||
surf = "2.2.0"
|
im-rc = "15.0.0"
|
||||||
|
fxhash = "0.2.1"
|
||||||
|
anyhow = "1.0.42"
|
||||||
|
|
||||||
|
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
|
||||||
|
argh = "0.1.5"
|
||||||
env_logger = "*"
|
env_logger = "*"
|
||||||
async-std = { version = "1.9.0", features = ["attributes"] }
|
async-std = { version = "1.9.0", features = ["attributes"] }
|
||||||
im-rc = "15.0.0"
|
|
||||||
rand = { version = "0.8.4", features = ["small_rng"] }
|
rand = { version = "0.8.4", features = ["small_rng"] }
|
||||||
fxhash = "0.2.1"
|
surf = {version = "2.2.0", git = "https://github.com/jkelleyrtp/surf/", branch = "jk/fix-the-wasm"}
|
||||||
|
|
||||||
|
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
|
||||||
gloo-timers = "0.2.1"
|
gloo-timers = "0.2.1"
|
||||||
|
surf = {version = "2.2.0", default-features = false, features = ["wasm-client"], git = "https://github.com/jkelleyrtp/surf/", branch = "jk/fix-the-wasm"}
|
||||||
|
|
||||||
|
|
||||||
|
[dependencies.getrandom]
|
||||||
|
version = "0.2"
|
||||||
|
features = ["js"]
|
||||||
|
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
|
|
|
@ -166,7 +166,7 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an
|
||||||
| Custom elements | ✅ | ✅ | Define new element primitives |
|
| Custom elements | ✅ | ✅ | Define new element primitives |
|
||||||
| Suspense | ✅ | ✅ | schedule future render from future/promise |
|
| Suspense | ✅ | ✅ | schedule future render from future/promise |
|
||||||
| Integrated error handling | ✅ | ✅ | Gracefully handle errors with ? syntax |
|
| Integrated error handling | ✅ | ✅ | Gracefully handle errors with ? syntax |
|
||||||
| Re-hydration | 🛠 | ✅ | Pre-render to HTML to speed up first contentful paint |
|
| Re-hydration | ✅ | ✅ | Pre-render to HTML to speed up first contentful paint |
|
||||||
| Cooperative Scheduling | 🛠 | ✅ | Prioritize important events over non-important events |
|
| Cooperative Scheduling | 🛠 | ✅ | Prioritize important events over non-important events |
|
||||||
| Runs natively | ✅ | ❓ | runs as a portable binary w/o a runtime (Node) |
|
| Runs natively | ✅ | ❓ | runs as a portable binary w/o a runtime (Node) |
|
||||||
| 1st class global state | ✅ | ❓ | redux/recoil/mobx on top of context |
|
| 1st class global state | ✅ | ❓ | redux/recoil/mobx on top of context |
|
||||||
|
|
|
@ -12,9 +12,11 @@ use std::fs::{self, DirEntry};
|
||||||
fn main() {
|
fn main() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
dioxus::desktop::launch(App, |c| {
|
dioxus::desktop::launch(App, |c| {
|
||||||
c.with_resizable(false)
|
c.with_window(|w| {
|
||||||
|
w.with_resizable(false)
|
||||||
.with_inner_size(LogicalSize::new(800.0, 400.0))
|
.with_inner_size(LogicalSize::new(800.0, 400.0))
|
||||||
})
|
})
|
||||||
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
33
examples/hydration.rs
Normal file
33
examples/hydration.rs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
//! Example: realworld usage of hydration
|
||||||
|
//! ------------------------------------
|
||||||
|
//!
|
||||||
|
//! This example shows how to pre-render a page using dioxus SSR and then how to rehydrate it on the client side.
|
||||||
|
//!
|
||||||
|
//! To accomplish hydration on the web, you'll want to set up a slightly more sophisticated build & bundle strategy. In
|
||||||
|
//! the official docs, we have a guide for using DioxusStudio as a build tool with pre-rendering and hydration.
|
||||||
|
//!
|
||||||
|
//! In this example, we pre-render the page to HTML and then pass it into the desktop configuration. This serves as a
|
||||||
|
//! proof-of-concept for the hydration feature, but you'll probably only want to use hydration for the web.
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus::ssr;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let mut vdom = VirtualDom::launch_in_place(App);
|
||||||
|
let content = ssr::render_vdom(&vdom, |f| f.pre_render(true));
|
||||||
|
|
||||||
|
dioxus::desktop::launch(App, |c| c.with_prerendered(content)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
static App: FC<()> = |cx| {
|
||||||
|
let mut val = use_state(cx, || 0);
|
||||||
|
cx.render(rsx! {
|
||||||
|
div {
|
||||||
|
h1 {"hello world. Count: {val}"}
|
||||||
|
button {
|
||||||
|
"click to increment"
|
||||||
|
onclick: move |_| val += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
|
@ -15,18 +15,20 @@
|
||||||
//! the RefCell will panic and crash. You can use `try_get_mut` or `.modify` to avoid this problem, or just not hold two
|
//! the RefCell will panic and crash. You can use `try_get_mut` or `.modify` to avoid this problem, or just not hold two
|
||||||
//! RefMuts at the same time.
|
//! RefMuts at the same time.
|
||||||
|
|
||||||
|
use dioxus::desktop::wry::application::dpi::LogicalSize;
|
||||||
use dioxus::events::on::*;
|
use dioxus::events::on::*;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_desktop::wry::application::dpi::LogicalSize;
|
|
||||||
|
|
||||||
const STYLE: &str = include_str!("./assets/calculator.css");
|
const STYLE: &str = include_str!("./assets/calculator.css");
|
||||||
fn main() {
|
fn main() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
dioxus::desktop::launch(App, |cfg| {
|
dioxus::desktop::launch(App, |cfg| {
|
||||||
cfg.with_title("Calculator Demo")
|
cfg.with_window(|w| {
|
||||||
|
w.with_title("Calculator Demo")
|
||||||
.with_resizable(false)
|
.with_resizable(false)
|
||||||
.with_inner_size(LogicalSize::new(320.0, 530.0))
|
.with_inner_size(LogicalSize::new(320.0, 530.0))
|
||||||
})
|
})
|
||||||
|
})
|
||||||
.expect("failed to launch dioxus app");
|
.expect("failed to launch dioxus app");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,8 +30,8 @@ pub static Example: FC<()> = |cx| {
|
||||||
|
|
||||||
// Tasks are 'static, so we need to copy relevant items in
|
// Tasks are 'static, so we need to copy relevant items in
|
||||||
let (async_count, dir) = (count.for_async(), *direction);
|
let (async_count, dir) = (count.for_async(), *direction);
|
||||||
|
|
||||||
let (task, result) = use_task(cx, move || async move {
|
let (task, result) = use_task(cx, move || async move {
|
||||||
// Count infinitely!
|
|
||||||
loop {
|
loop {
|
||||||
gloo_timers::future::TimeoutFuture::new(250).await;
|
gloo_timers::future::TimeoutFuture::new(250).await;
|
||||||
*async_count.get_mut() += dir;
|
*async_count.get_mut() += dir;
|
||||||
|
|
|
@ -7,7 +7,7 @@ pub static Example: FC<()> = |cx| {
|
||||||
// This is an easy/low hanging fruit to improve upon
|
// This is an easy/low hanging fruit to improve upon
|
||||||
let mut dom = VirtualDom::new(SomeApp);
|
let mut dom = VirtualDom::new(SomeApp);
|
||||||
dom.rebuild_in_place().unwrap();
|
dom.rebuild_in_place().unwrap();
|
||||||
ssr::render_vdom(&dom)
|
ssr::render_vdom(&dom, |c| c)
|
||||||
});
|
});
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
|
#![allow(non_upper_case_globals)]
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus::ssr;
|
use dioxus::ssr;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut vdom = VirtualDom::new(App);
|
let mut vdom = VirtualDom::new(App);
|
||||||
vdom.rebuild_in_place();
|
vdom.rebuild_in_place().expect("Rebuilding failed");
|
||||||
println!("{}", ssr::render_vdom(&vdom));
|
println!("{}", ssr::render_vdom(&vdom, |c| c));
|
||||||
}
|
}
|
||||||
|
|
||||||
const App: FC<()> = |cx| {
|
static App: FC<()> = |cx| {
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
div {
|
div {
|
||||||
h1 { "Title" }
|
h1 { "Title" }
|
||||||
|
|
|
@ -3,10 +3,12 @@ use dioxus::prelude::*;
|
||||||
fn main() {
|
fn main() {
|
||||||
use dioxus::desktop::wry::application::platform::macos::*;
|
use dioxus::desktop::wry::application::platform::macos::*;
|
||||||
dioxus::desktop::launch(App, |c| {
|
dioxus::desktop::launch(App, |c| {
|
||||||
c.with_fullsize_content_view(true)
|
c.with_window(|w| {
|
||||||
|
w.with_fullsize_content_view(true)
|
||||||
.with_titlebar_buttons_hidden(false)
|
.with_titlebar_buttons_hidden(false)
|
||||||
.with_titlebar_transparent(true)
|
.with_titlebar_transparent(true)
|
||||||
.with_movable_by_window_background(true)
|
.with_movable_by_window_background(true)
|
||||||
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
|
#![allow(non_upper_case_globals, non_snake_case)]
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use im_rc::HashMap;
|
use im_rc::HashMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
fn main() {
|
fn main() -> anyhow::Result<()> {
|
||||||
#[cfg(feature = "desktop")]
|
dioxus::desktop::launch(App, |c| c)
|
||||||
// #[cfg(not(target_arch = "wasm32"))]
|
|
||||||
dioxus::desktop::launch(App, |c| c);
|
|
||||||
|
|
||||||
#[cfg(feature = "desktop")]
|
|
||||||
dioxus::web::launch(App, |c| c);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
|
|
|
@ -8,12 +8,13 @@
|
||||||
//! into the native VDom instance.
|
//! into the native VDom instance.
|
||||||
//!
|
//!
|
||||||
//! Currently, NodeRefs won't work properly, but all other event functionality will.
|
//! Currently, NodeRefs won't work properly, but all other event functionality will.
|
||||||
|
#![allow(non_upper_case_globals, non_snake_case)]
|
||||||
|
|
||||||
use dioxus::{events::on::MouseEvent, prelude::*};
|
use dioxus::{events::on::MouseEvent, prelude::*};
|
||||||
|
|
||||||
fn main() {
|
fn main() -> anyhow::Result<()> {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
dioxus::desktop::launch(App, |c| c);
|
dioxus::desktop::launch(App, |c| c)
|
||||||
}
|
}
|
||||||
|
|
||||||
static App: FC<()> = |cx| {
|
static App: FC<()> = |cx| {
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#![allow(non_upper_case_globals, non_snake_case)]
|
||||||
//! Example: Webview Renderer
|
//! Example: Webview Renderer
|
||||||
//! -------------------------
|
//! -------------------------
|
||||||
//!
|
//!
|
||||||
|
@ -11,53 +12,19 @@
|
||||||
|
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
// #[cfg]
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// env_logger::init();
|
|
||||||
dioxus::web::launch(App, |c| c);
|
dioxus::web::launch(App, |c| c);
|
||||||
}
|
}
|
||||||
|
|
||||||
static App: FC<()> = |cx| {
|
static App: FC<()> = |cx| {
|
||||||
dbg!("rednering parent");
|
|
||||||
cx.render(rsx! {
|
|
||||||
div {
|
|
||||||
But {
|
|
||||||
h1 {"he"}
|
|
||||||
}
|
|
||||||
// But {
|
|
||||||
// h1 {"llo"}
|
|
||||||
// }
|
|
||||||
// But {
|
|
||||||
// h1 {"world"}
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
static But: FC<()> = |cx| {
|
|
||||||
let mut count = use_state(cx, || 0);
|
let mut count = use_state(cx, || 0);
|
||||||
|
|
||||||
// let d = Dropper { name: "asd" };
|
|
||||||
// let handler = move |_| {
|
|
||||||
// dbg!(d.name);
|
|
||||||
// };
|
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
h1 { "Hifive counter: {count}" }
|
h1 { "Hifive counter: {count}" }
|
||||||
{cx.children()}
|
{cx.children()}
|
||||||
button { onclick: move |_| count += 1, "Up high!" }
|
button { onclick: move |_| count += 1, "Up high!" }
|
||||||
button { onclick: move |_| count -= 1, "Down low!" }
|
button { onclick: move |_| count -= 1, "Down low!" }
|
||||||
// button { onclick: {handler}, "Down low!" }
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
// struct Dropper {
|
|
||||||
// name: &'static str,
|
|
||||||
// }
|
|
||||||
// impl Drop for Dropper {
|
|
||||||
// fn drop(&mut self) {
|
|
||||||
// dbg!("dropped");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
|
@ -40,6 +40,10 @@ smallvec = "1.6.1"
|
||||||
slab = "0.4.3"
|
slab = "0.4.3"
|
||||||
|
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
dioxus-html = { path = "../html" }
|
||||||
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["serialize"]
|
default = ["serialize"]
|
||||||
serialize = ["serde"]
|
serialize = ["serde"]
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
fn main() {}
|
|
||||||
|
|
||||||
use dioxus::*;
|
|
||||||
use dioxus_core as dioxus;
|
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
|
|
||||||
|
fn main() {}
|
||||||
|
|
||||||
pub static Example: FC<()> = |cx| {
|
pub static Example: FC<()> = |cx| {
|
||||||
let list = (0..10).map(|f| LazyNodes::new(move |f| todo!()));
|
let list = (0..10).map(|f| LazyNodes::new(move |f| todo!()));
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use std::cell::{RefCell, RefMut};
|
use std::cell::{RefCell, RefMut};
|
||||||
|
use std::fmt::Display;
|
||||||
use std::{cell::UnsafeCell, rc::Rc};
|
use std::{cell::UnsafeCell, rc::Rc};
|
||||||
|
|
||||||
use crate::heuristics::*;
|
use crate::heuristics::*;
|
||||||
|
@ -17,6 +18,11 @@ pub struct ScopeId(pub usize);
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
pub struct ElementId(pub usize);
|
pub struct ElementId(pub usize);
|
||||||
|
impl Display for ElementId {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ElementId {
|
impl ElementId {
|
||||||
pub fn as_u64(self) -> u64 {
|
pub fn as_u64(self) -> u64 {
|
||||||
|
|
|
@ -1,24 +1,24 @@
|
||||||
use crate::innerlude::*;
|
//! Public APIs for managing component state, tasks, and lifecycles.
|
||||||
|
|
||||||
use futures_util::FutureExt;
|
use crate::innerlude::*;
|
||||||
use std::{
|
use std::{any::TypeId, ops::Deref, rc::Rc};
|
||||||
any::{Any, TypeId},
|
|
||||||
cell::{Cell, RefCell},
|
|
||||||
future::Future,
|
|
||||||
ops::Deref,
|
|
||||||
rc::Rc,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Components in Dioxus use the "Context" object to interact with their lifecycle.
|
/// Components in Dioxus use the "Context" object to interact with their lifecycle.
|
||||||
/// This lets components schedule updates, integrate hooks, and expose their context via the context api.
|
|
||||||
///
|
///
|
||||||
/// Properties passed down from the parent component are also directly accessible via the exposed "props" field.
|
/// This lets components access props, schedule updates, integrate hooks, and expose shared state.
|
||||||
|
///
|
||||||
|
/// Note: all of these methods are *imperative* - they do not act as hooks! They are meant to be used by hooks
|
||||||
|
/// to provide complex behavior. For instance, calling "add_shared_state" on every render is considered a leak. This method
|
||||||
|
/// exists for the `use_provide_state` hook to provide a shared state object.
|
||||||
|
///
|
||||||
|
/// For the most part, the only method you should be using regularly is `render`.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
///
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// #[derive(Properties)]
|
/// #[derive(Properties)]
|
||||||
/// struct Props {
|
/// struct Props {
|
||||||
/// name: String
|
/// name: String
|
||||||
///
|
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn example(cx: Context<Props>) -> VNode {
|
/// fn example(cx: Context<Props>) -> VNode {
|
||||||
|
@ -27,16 +27,6 @@ use std::{
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
|
||||||
/// ## Available Methods:
|
|
||||||
/// - render
|
|
||||||
/// - use_hook
|
|
||||||
/// - use_task
|
|
||||||
/// - use_suspense
|
|
||||||
/// - submit_task
|
|
||||||
/// - children
|
|
||||||
/// - use_effect
|
|
||||||
///
|
|
||||||
pub struct Context<'src, T> {
|
pub struct Context<'src, T> {
|
||||||
pub props: &'src T,
|
pub props: &'src T,
|
||||||
pub scope: &'src Scope,
|
pub scope: &'src Scope,
|
||||||
|
@ -123,7 +113,8 @@ impl<'src, P> Context<'src, P> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get's this context's ScopeId
|
/// Get's this component's unique identifier.
|
||||||
|
///
|
||||||
pub fn get_scope_id(&self) -> ScopeId {
|
pub fn get_scope_id(&self) -> ScopeId {
|
||||||
self.scope.our_arena_idx.clone()
|
self.scope.our_arena_idx.clone()
|
||||||
}
|
}
|
||||||
|
@ -148,11 +139,7 @@ impl<'src, P> Context<'src, P> {
|
||||||
lazy_nodes: LazyNodes<'src, F>,
|
lazy_nodes: LazyNodes<'src, F>,
|
||||||
) -> DomTree<'src> {
|
) -> DomTree<'src> {
|
||||||
let scope_ref = self.scope;
|
let scope_ref = self.scope;
|
||||||
// let listener_id = &scope_ref.listener_idx;
|
Some(lazy_nodes.into_vnode(NodeFactory { scope: scope_ref }))
|
||||||
Some(lazy_nodes.into_vnode(NodeFactory {
|
|
||||||
scope: scope_ref,
|
|
||||||
// listener_id,
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `submit_task` will submit the future to be polled.
|
/// `submit_task` will submit the future to be polled.
|
||||||
|
@ -177,11 +164,14 @@ impl<'src, P> Context<'src, P> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a state globally accessible to child components via tree walking
|
/// Add a state globally accessible to child components via tree walking
|
||||||
pub fn add_shared_state<T: 'static>(self, val: T) -> Option<Rc<dyn Any>> {
|
pub fn add_shared_state<T: 'static>(self, val: T) {
|
||||||
self.scope
|
self.scope
|
||||||
.shared_contexts
|
.shared_contexts
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
.insert(TypeId::of::<T>(), Rc::new(val))
|
.insert(TypeId::of::<T>(), Rc::new(val))
|
||||||
|
.map(|_| {
|
||||||
|
log::warn!("A shared state was replaced with itself. This is does not result in a panic, but is probably not what you are trying to do");
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Walk the tree to find a shared state with the TypeId of the generic type
|
/// Walk the tree to find a shared state with the TypeId of the generic type
|
||||||
|
@ -212,7 +202,6 @@ impl<'src, P> Context<'src, P> {
|
||||||
.clone()
|
.clone()
|
||||||
.downcast::<T>()
|
.downcast::<T>()
|
||||||
.expect("Should not fail, already validated the type from the hashmap");
|
.expect("Should not fail, already validated the type from the hashmap");
|
||||||
|
|
||||||
par = Some(rc);
|
par = Some(rc);
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -55,7 +55,7 @@ use crate::{arena::SharedResources, innerlude::*};
|
||||||
use fxhash::{FxHashMap, FxHashSet};
|
use fxhash::{FxHashMap, FxHashSet};
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
|
||||||
use std::{any::Any, borrow::Borrow, cmp::Ordering};
|
use std::{any::Any, cmp::Ordering, process::Child};
|
||||||
|
|
||||||
/// Instead of having handles directly over nodes, Dioxus uses simple u32 as node IDs.
|
/// Instead of having handles directly over nodes, Dioxus uses simple u32 as node IDs.
|
||||||
/// The expectation is that the underlying renderer will mainain their Nodes in vec where the ids are the index. This allows
|
/// The expectation is that the underlying renderer will mainain their Nodes in vec where the ids are the index. This allows
|
||||||
|
@ -71,31 +71,20 @@ pub trait RealDom<'a> {
|
||||||
|
|
||||||
pub struct DiffMachine<'real, 'bump> {
|
pub struct DiffMachine<'real, 'bump> {
|
||||||
pub real_dom: &'real dyn RealDom<'bump>,
|
pub real_dom: &'real dyn RealDom<'bump>,
|
||||||
|
|
||||||
pub vdom: &'bump SharedResources,
|
pub vdom: &'bump SharedResources,
|
||||||
|
|
||||||
pub edits: DomEditor<'real, 'bump>,
|
pub edits: DomEditor<'real, 'bump>,
|
||||||
|
|
||||||
pub scheduled_garbage: Vec<&'bump VNode<'bump>>,
|
pub scheduled_garbage: Vec<&'bump VNode<'bump>>,
|
||||||
|
|
||||||
pub cur_idxs: SmallVec<[ScopeId; 5]>,
|
pub cur_idxs: SmallVec<[ScopeId; 5]>,
|
||||||
|
|
||||||
pub diffed: FxHashSet<ScopeId>,
|
pub diffed: FxHashSet<ScopeId>,
|
||||||
|
|
||||||
pub seen_nodes: FxHashSet<ScopeId>,
|
pub seen_nodes: FxHashSet<ScopeId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'r, 'b> DiffMachine<'r, 'b> {
|
|
||||||
pub fn get_scope_mut(&mut self, id: &ScopeId) -> Option<&'b mut Scope> {
|
|
||||||
// ensure we haven't seen this scope before
|
|
||||||
// if we have, then we're trying to alias it, which is not allowed
|
|
||||||
debug_assert!(!self.seen_nodes.contains(id));
|
|
||||||
|
|
||||||
unsafe { self.vdom.get_scope_mut(*id) }
|
|
||||||
}
|
|
||||||
pub fn get_scope(&mut self, id: &ScopeId) -> Option<&'b Scope> {
|
|
||||||
// ensure we haven't seen this scope before
|
|
||||||
// if we have, then we're trying to alias it, which is not allowed
|
|
||||||
unsafe { self.vdom.get_scope(*id) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'real, 'bump> DiffMachine<'real, 'bump> {
|
impl<'real, 'bump> DiffMachine<'real, 'bump> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
edits: &'real mut Vec<DomEdit<'bump>>,
|
edits: &'real mut Vec<DomEdit<'bump>>,
|
||||||
|
@ -121,20 +110,17 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> {
|
||||||
//
|
//
|
||||||
// each function call assumes the stack is fresh (empty).
|
// each function call assumes the stack is fresh (empty).
|
||||||
pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {
|
pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {
|
||||||
|
let root = old_node.dom_id.get();
|
||||||
|
|
||||||
match (&old_node.kind, &new_node.kind) {
|
match (&old_node.kind, &new_node.kind) {
|
||||||
// Handle the "sane" cases first.
|
// Handle the "sane" cases first.
|
||||||
// The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment".
|
// The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment".
|
||||||
// So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable.
|
// So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable.
|
||||||
(VNodeKind::Text(old), VNodeKind::Text(new)) => {
|
(VNodeKind::Text(old), VNodeKind::Text(new)) => {
|
||||||
// currently busted for components - need to fid
|
let root = root.unwrap();
|
||||||
let root = old_node.dom_id.get().expect(&format!(
|
|
||||||
"Should not be diffing old nodes that were never assigned, {:#?}",
|
|
||||||
old_node
|
|
||||||
));
|
|
||||||
|
|
||||||
if old.text != new.text {
|
if old.text != new.text {
|
||||||
self.edits.push_root(root);
|
self.edits.push_root(root);
|
||||||
log::debug!("Text has changed {}, {}", old.text, new.text);
|
|
||||||
self.edits.set_text(new.text);
|
self.edits.set_text(new.text);
|
||||||
self.edits.pop();
|
self.edits.pop();
|
||||||
}
|
}
|
||||||
|
@ -143,16 +129,12 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> {
|
||||||
}
|
}
|
||||||
|
|
||||||
(VNodeKind::Element(old), VNodeKind::Element(new)) => {
|
(VNodeKind::Element(old), VNodeKind::Element(new)) => {
|
||||||
// currently busted for components - need to fid
|
let root = root.unwrap();
|
||||||
let root = old_node.dom_id.get().expect(&format!(
|
|
||||||
"Should not be diffing old nodes that were never assigned, {:#?}",
|
|
||||||
old_node
|
|
||||||
));
|
|
||||||
|
|
||||||
// If the element type is completely different, the element needs to be re-rendered completely
|
// If the element type is completely different, the element needs to be re-rendered completely
|
||||||
// This is an optimization React makes due to how users structure their code
|
// This is an optimization React makes due to how users structure their code
|
||||||
//
|
//
|
||||||
// In Dioxus, this is less likely to occur unless through a fragment
|
// This case is rather rare (typically only in non-keyed lists)
|
||||||
if new.tag_name != old.tag_name || new.namespace != old.namespace {
|
if new.tag_name != old.tag_name || new.namespace != old.namespace {
|
||||||
self.edits.push_root(root);
|
self.edits.push_root(root);
|
||||||
let meta = self.create(new_node);
|
let meta = self.create(new_node);
|
||||||
|
@ -164,59 +146,105 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> {
|
||||||
|
|
||||||
new_node.dom_id.set(Some(root));
|
new_node.dom_id.set(Some(root));
|
||||||
|
|
||||||
// push it just in case
|
// Don't push the root if we don't have to
|
||||||
// TODO: remove this - it clogs up things and is inefficient
|
|
||||||
// self.edits.push_root(root);
|
|
||||||
|
|
||||||
let mut has_comitted = false;
|
let mut has_comitted = false;
|
||||||
self.edits.push_root(root);
|
let mut please_commit = |edits: &mut DomEditor| {
|
||||||
// dbg!("diffing listeners");
|
if !has_comitted {
|
||||||
self.diff_listeners(&mut has_comitted, old.listeners, new.listeners);
|
has_comitted = true;
|
||||||
// dbg!("diffing attrs");
|
edits.push_root(root);
|
||||||
self.diff_attr(
|
}
|
||||||
&mut has_comitted,
|
};
|
||||||
old.attributes,
|
|
||||||
new.attributes,
|
// Diff Attributes
|
||||||
new.namespace,
|
//
|
||||||
);
|
// It's extraordinarily rare to have the number/order of attributes change
|
||||||
// dbg!("diffing childrne");
|
// In these cases, we just completely erase the old set and make a new set
|
||||||
self.diff_children(&mut has_comitted, old.children, new.children);
|
//
|
||||||
|
// TODO: take a more efficient path than this
|
||||||
|
if old.attributes.len() == new.attributes.len() {
|
||||||
|
for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) {
|
||||||
|
if old_attr.value != new_attr.value {
|
||||||
|
please_commit(&mut self.edits);
|
||||||
|
self.edits.set_attribute(new_attr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: provide some sort of report on how "good" the diffing was
|
||||||
|
please_commit(&mut self.edits);
|
||||||
|
for attribute in old.attributes {
|
||||||
|
self.edits.remove_attribute(attribute);
|
||||||
|
}
|
||||||
|
for attribute in new.attributes {
|
||||||
|
self.edits.set_attribute(attribute)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diff listeners
|
||||||
|
//
|
||||||
|
// It's extraordinarily rare to have the number/order of listeners change
|
||||||
|
// In the cases where the listeners change, we completely wipe the data attributes and add new ones
|
||||||
|
//
|
||||||
|
// TODO: take a more efficient path than this
|
||||||
|
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.edits);
|
||||||
|
self.edits.remove_event_listener(old_l.event);
|
||||||
|
self.edits.new_event_listener(new_l);
|
||||||
|
}
|
||||||
|
new_l.mounted_node.set(old_l.mounted_node.get());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
please_commit(&mut self.edits);
|
||||||
|
for listener in old.listeners {
|
||||||
|
self.edits.remove_event_listener(listener.event);
|
||||||
|
}
|
||||||
|
for listener in new.listeners {
|
||||||
|
listener.mounted_node.set(Some(root));
|
||||||
|
self.edits.new_event_listener(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if has_comitted {
|
||||||
self.edits.pop();
|
self.edits.pop();
|
||||||
// if has_comitted {
|
}
|
||||||
// self.edits.pop();
|
|
||||||
// }
|
// Each child pushes its own root, so it doesn't need our current root
|
||||||
|
todo!();
|
||||||
|
// self.diff_children(old.children, new.children);
|
||||||
}
|
}
|
||||||
|
|
||||||
(VNodeKind::Component(old), VNodeKind::Component(new)) => {
|
(VNodeKind::Component(old), VNodeKind::Component(new)) => {
|
||||||
log::warn!("diffing components? {:#?}", new.user_fc);
|
let scope_addr = old.ass_scope.get().unwrap();
|
||||||
if old.user_fc == new.user_fc {
|
|
||||||
// Make sure we're dealing with the same component (by function pointer)
|
// Make sure we're dealing with the same component (by function pointer)
|
||||||
self.cur_idxs.push(old.ass_scope.get().unwrap());
|
if old.user_fc == new.user_fc {
|
||||||
|
//
|
||||||
|
self.cur_idxs.push(scope_addr);
|
||||||
|
|
||||||
// Make sure the new component vnode is referencing the right scope id
|
// Make sure the new component vnode is referencing the right scope id
|
||||||
let scope_addr = old.ass_scope.get().unwrap();
|
|
||||||
new.ass_scope.set(Some(scope_addr));
|
new.ass_scope.set(Some(scope_addr));
|
||||||
|
|
||||||
// make sure the component's caller function is up to date
|
// make sure the component's caller function is up to date
|
||||||
let scope = self.get_scope_mut(&scope_addr).unwrap();
|
let scope = self.get_scope_mut(&scope_addr).unwrap();
|
||||||
|
|
||||||
scope.caller = new.caller.clone();
|
scope
|
||||||
|
.update_scope_dependencies(new.caller.clone(), ScopeChildren(new.children));
|
||||||
// ack - this doesn't work on its own!
|
|
||||||
scope.update_children(ScopeChildren(new.children));
|
|
||||||
|
|
||||||
// React doesn't automatically memoize, but we do.
|
// React doesn't automatically memoize, but we do.
|
||||||
let are_the_same = match old.comparator {
|
let compare = old.comparator.unwrap();
|
||||||
Some(comparator) => comparator(new),
|
|
||||||
None => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
if !are_the_same {
|
match compare(new) {
|
||||||
|
true => {
|
||||||
|
// the props are the same...
|
||||||
|
}
|
||||||
|
false => {
|
||||||
|
// the props are different...
|
||||||
scope.run_scope().unwrap();
|
scope.run_scope().unwrap();
|
||||||
self.diff_node(scope.frames.wip_head(), scope.frames.fin_head());
|
self.diff_node(scope.frames.wip_head(), scope.frames.fin_head());
|
||||||
} else {
|
|
||||||
//
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.cur_idxs.pop();
|
self.cur_idxs.pop();
|
||||||
|
|
||||||
self.seen_nodes.insert(scope_addr);
|
self.seen_nodes.insert(scope_addr);
|
||||||
|
@ -254,14 +282,7 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Diff using the approach where we're looking for added or removed nodes.
|
self.diff_children(old, new, old, new_anchor)
|
||||||
if old.children.len() != new.children.len() {}
|
|
||||||
|
|
||||||
// Diff where we think the elements are the same
|
|
||||||
if old.children.len() == new.children.len() {}
|
|
||||||
|
|
||||||
let mut has_comitted = false;
|
|
||||||
self.diff_children(&mut has_comitted, old.children, new.children);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The strategy here is to pick the first possible node from the previous set and use that as our replace with root
|
// The strategy here is to pick the first possible node from the previous set and use that as our replace with root
|
||||||
|
@ -329,25 +350,7 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// When we create new nodes, we need to propagate some information back up the call chain.
|
|
||||||
// This gives the caller some information on how to handle things like insertins, appending, and subtree discarding.
|
|
||||||
pub struct CreateMeta {
|
|
||||||
pub is_static: bool,
|
|
||||||
pub added_to_stack: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CreateMeta {
|
|
||||||
fn new(is_static: bool, added_to_tack: u32) -> Self {
|
|
||||||
Self {
|
|
||||||
is_static,
|
|
||||||
added_to_stack: added_to_tack,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'real, 'bump> DiffMachine<'real, 'bump> {
|
|
||||||
// Emit instructions to create the given virtual node.
|
// Emit instructions to create the given virtual node.
|
||||||
//
|
//
|
||||||
// The change list stack may have any shape upon entering this function:
|
// The change list stack may have any shape upon entering this function:
|
||||||
|
@ -395,11 +398,10 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> {
|
||||||
};
|
};
|
||||||
node.dom_id.set(Some(real_id));
|
node.dom_id.set(Some(real_id));
|
||||||
|
|
||||||
listeners.iter().enumerate().for_each(|(idx, listener)| {
|
listeners.iter().for_each(|listener| {
|
||||||
log::info!("setting listener id to {:#?}", real_id);
|
log::info!("setting listener id to {:#?}", real_id);
|
||||||
listener.mounted_node.set(Some(real_id));
|
listener.mounted_node.set(Some(real_id));
|
||||||
self.edits
|
self.edits.new_event_listener(listener);
|
||||||
.new_event_listener(listener.event, listener.scope, idx, real_id);
|
|
||||||
|
|
||||||
// if the node has an event listener, then it must be visited ?
|
// if the node has an event listener, then it must be visited ?
|
||||||
is_static = false;
|
is_static = false;
|
||||||
|
@ -407,8 +409,7 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> {
|
||||||
|
|
||||||
for attr in *attributes {
|
for attr in *attributes {
|
||||||
is_static = is_static && attr.is_static;
|
is_static = is_static && attr.is_static;
|
||||||
self.edits
|
self.edits.set_attribute(attr);
|
||||||
.set_attribute(&attr.name, &attr.value, *namespace);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fast path: if there is a single text child, it is faster to
|
// Fast path: if there is a single text child, it is faster to
|
||||||
|
@ -526,9 +527,25 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a, 'bump> DiffMachine<'a, 'bump> {
|
fn create_children(&mut self, children: &'bump [VNode<'bump>]) -> CreateMeta {
|
||||||
|
let mut is_static = true;
|
||||||
|
let mut added_to_stack = 0;
|
||||||
|
|
||||||
|
for child in children {
|
||||||
|
let child_meta = self.create(child);
|
||||||
|
is_static = is_static && child_meta.is_static;
|
||||||
|
added_to_stack += child_meta.added_to_stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateMeta {
|
||||||
|
is_static,
|
||||||
|
added_to_stack,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn replace_vnode(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {}
|
||||||
|
|
||||||
/// Destroy a scope and all of its descendents.
|
/// Destroy a scope and all of its descendents.
|
||||||
///
|
///
|
||||||
/// Calling this will run the destuctors on all hooks in the tree.
|
/// Calling this will run the destuctors on all hooks in the tree.
|
||||||
|
@ -561,133 +578,6 @@ impl<'a, 'bump> DiffMachine<'a, 'bump> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Diff event listeners between `old` and `new`.
|
|
||||||
//
|
|
||||||
// The listeners' node must be on top of the change list stack:
|
|
||||||
//
|
|
||||||
// [... node]
|
|
||||||
//
|
|
||||||
// The change list stack is left unchanged.
|
|
||||||
fn diff_listeners(&mut self, committed: &mut bool, old: &[Listener<'_>], new: &[Listener<'_>]) {
|
|
||||||
if !old.is_empty() || !new.is_empty() {
|
|
||||||
// self.edits.commit_traversal();
|
|
||||||
}
|
|
||||||
// TODO
|
|
||||||
// what does "diffing listeners" even mean?
|
|
||||||
|
|
||||||
for (old_l, new_l) in old.iter().zip(new.iter()) {
|
|
||||||
log::info!(
|
|
||||||
"moving listener forward with event. old: {:#?}",
|
|
||||||
old_l.mounted_node.get()
|
|
||||||
);
|
|
||||||
new_l.mounted_node.set(old_l.mounted_node.get());
|
|
||||||
}
|
|
||||||
// 'outer1: for (_l_idx, new_l) in new.iter().enumerate() {
|
|
||||||
// // go through each new listener
|
|
||||||
// // find its corresponding partner in the old list
|
|
||||||
// // if any characteristics changed, remove and then re-add
|
|
||||||
|
|
||||||
// // if nothing changed, then just move on
|
|
||||||
// let _event_type = new_l.event;
|
|
||||||
|
|
||||||
// for old_l in old {
|
|
||||||
// if new_l.event == old_l.event {
|
|
||||||
// log::info!(
|
|
||||||
// "moving listener forward with event. old: {:#?}",
|
|
||||||
// old_l.mounted_node.get()
|
|
||||||
// );
|
|
||||||
// new_l.mounted_node.set(old_l.mounted_node.get());
|
|
||||||
// // if new_l.id != old_l.id {
|
|
||||||
// // self.edits.remove_event_listener(event_type);
|
|
||||||
// // // TODO! we need to mess with events and assign them by ElementId
|
|
||||||
// // // self.edits
|
|
||||||
// // // .update_event_listener(event_type, new_l.scope, new_l.id)
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// continue 'outer1;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// self.edits
|
|
||||||
// .new_event_listener(event_type, new_l.scope, new_l.id);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 'outer2: for old_l in old {
|
|
||||||
// for new_l in new {
|
|
||||||
// if new_l.event == old_l.event {
|
|
||||||
// continue 'outer2;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// self.edits.remove_event_listener(old_l.event);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Diff a node's attributes.
|
|
||||||
//
|
|
||||||
// The attributes' node must be on top of the change list stack:
|
|
||||||
//
|
|
||||||
// [... node]
|
|
||||||
//
|
|
||||||
// The change list stack is left unchanged.
|
|
||||||
fn diff_attr(
|
|
||||||
&mut self,
|
|
||||||
committed: &mut bool,
|
|
||||||
old: &'bump [Attribute<'bump>],
|
|
||||||
new: &'bump [Attribute<'bump>],
|
|
||||||
namespace: Option<&'static str>,
|
|
||||||
) {
|
|
||||||
for (old_attr, new_attr) in old.iter().zip(new.iter()) {
|
|
||||||
if old_attr.value != new_attr.value {
|
|
||||||
if !*committed {
|
|
||||||
*committed = true;
|
|
||||||
// self.edits.push_root();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if old_attr.name == new_attr.name {
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
// Do O(n^2) passes to add/update and remove attributes, since
|
|
||||||
// there are almost always very few attributes.
|
|
||||||
//
|
|
||||||
// The "fast" path is when the list of attributes name is identical and in the same order
|
|
||||||
// With the Rsx and Html macros, this will almost always be the case
|
|
||||||
// 'outer: for new_attr in new {
|
|
||||||
// if new_attr.is_volatile {
|
|
||||||
// // self.edits.commit_traversal();
|
|
||||||
// self.edits
|
|
||||||
// .set_attribute(new_attr.name, new_attr.value, namespace);
|
|
||||||
// } else {
|
|
||||||
// for old_attr in old {
|
|
||||||
// if old_attr.name == new_attr.name {
|
|
||||||
// if old_attr.value != new_attr.value {
|
|
||||||
// // self.edits.commit_traversal();
|
|
||||||
// self.edits
|
|
||||||
// .set_attribute(new_attr.name, new_attr.value, namespace);
|
|
||||||
// }
|
|
||||||
// continue 'outer;
|
|
||||||
// } else {
|
|
||||||
// // names are different, a varying order of attributes has arrived
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // self.edits.commit_traversal();
|
|
||||||
// self.edits
|
|
||||||
// .set_attribute(new_attr.name, new_attr.value, namespace);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// 'outer2: for old_attr in old {
|
|
||||||
// for new_attr in new {
|
|
||||||
// if old_attr.name == new_attr.name {
|
|
||||||
// continue 'outer2;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // self.edits.commit_traversal();
|
|
||||||
// self.edits.remove_attribute(old_attr.name);
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Diff the given set of old and new children.
|
// Diff the given set of old and new children.
|
||||||
//
|
//
|
||||||
// The parent must be on top of the change list stack when this function is
|
// The parent must be on top of the change list stack when this function is
|
||||||
|
@ -696,73 +586,99 @@ impl<'a, 'bump> DiffMachine<'a, 'bump> {
|
||||||
// [... parent]
|
// [... parent]
|
||||||
//
|
//
|
||||||
// the change list stack is in the same state when this function returns.
|
// the change list stack is in the same state when this function returns.
|
||||||
|
//
|
||||||
|
// If old no anchors are provided, then it's assumed that we can freely append to the parent.
|
||||||
|
//
|
||||||
|
// Remember, non-empty lists does not mean that there are real elements, just that there are virtual elements.
|
||||||
fn diff_children(
|
fn diff_children(
|
||||||
&mut self,
|
&mut self,
|
||||||
committed: &mut bool,
|
|
||||||
old: &'bump [VNode<'bump>],
|
old: &'bump [VNode<'bump>],
|
||||||
new: &'bump [VNode<'bump>],
|
new: &'bump [VNode<'bump>],
|
||||||
|
old_anchor: &mut Option<ElementId>,
|
||||||
|
new_anchor: &mut Option<ElementId>,
|
||||||
) {
|
) {
|
||||||
// if new.is_empty() {
|
const IS_EMPTY: bool = true;
|
||||||
// if !old.is_empty() {
|
const IS_NOT_EMPTY: bool = false;
|
||||||
// // self.edits.commit_traversal();
|
|
||||||
// self.remove_all_children(old);
|
|
||||||
// }
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if new.len() == 1 {
|
match (old_anchor, new.is_empty()) {
|
||||||
// match (&old.first(), &new[0]) {
|
// Both are empty, dealing only with potential anchors
|
||||||
// (Some(VNodeKind::Text(old_vtext)), VNodeKind::Text(new_vtext))
|
(Some(_), IS_EMPTY) => {
|
||||||
// if old_vtext.text == new_vtext.text =>
|
*new_anchor = *old_anchor;
|
||||||
// {
|
if old.len() > 0 {
|
||||||
// // Don't take this fast path...
|
// clean up these virtual nodes (components, fragments, etc)
|
||||||
// }
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// (_, VNodeKind::Text(text)) => {
|
// Completely adding new nodes, removing any placeholder if it exists
|
||||||
// // self.edits.commit_traversal();
|
(Some(anchor), IS_NOT_EMPTY) => match old_anchor {
|
||||||
// log::debug!("using optimized text set");
|
// If there's anchor to work from, then we replace it with the new children
|
||||||
// self.edits.set_text(text.text);
|
Some(anchor) => {
|
||||||
// return;
|
self.edits.push_root(*anchor);
|
||||||
// }
|
let meta = self.create_children(new);
|
||||||
|
if meta.added_to_stack > 0 {
|
||||||
|
self.edits.replace_with(meta.added_to_stack)
|
||||||
|
} else {
|
||||||
|
// no items added to the stack... hmmmm....
|
||||||
|
*new_anchor = *old_anchor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// todo: any more optimizations
|
// If there's no anchor to work with, we just straight up append them
|
||||||
// (_, _) => {}
|
None => {
|
||||||
// }
|
let meta = self.create_children(new);
|
||||||
// }
|
self.edits.append_children(meta.added_to_stack);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
// if old.is_empty() {
|
// Completely removing old nodes and putting an anchor in its place
|
||||||
// if !new.is_empty() {
|
// no anchor (old has nodes) and the new is empty
|
||||||
// // self.edits.commit_traversal();
|
// remove all the old nodes
|
||||||
// self.create_and_append_children(new);
|
(None, IS_EMPTY) => {
|
||||||
// }
|
// load the first real
|
||||||
// return;
|
if let Some(to_replace) = find_first_real_node(old, self.vdom) {
|
||||||
// }
|
//
|
||||||
|
self.edits.push_root(to_replace.dom_id.get().unwrap());
|
||||||
|
|
||||||
// let new_is_keyed = new[0].key.is_some();
|
// Create the anchor
|
||||||
// let old_is_keyed = old[0].key.is_some();
|
let anchor_id = self.vdom.reserve_node();
|
||||||
|
self.edits.create_placeholder(anchor_id);
|
||||||
|
*new_anchor = Some(anchor_id);
|
||||||
|
|
||||||
// debug_assert!(
|
// Replace that node
|
||||||
// new.iter().all(|n| n.key.is_some() == new_is_keyed),
|
self.edits.replace_with(1);
|
||||||
// "all siblings must be keyed or all siblings must be non-keyed"
|
} else {
|
||||||
// );
|
// no real nodes -
|
||||||
// debug_assert!(
|
*new_anchor = *old_anchor;
|
||||||
// old.iter().all(|o| o.key.is_some() == old_is_keyed),
|
}
|
||||||
// "all siblings must be keyed or all siblings must be non-keyed"
|
|
||||||
// );
|
|
||||||
|
|
||||||
// if new_is_keyed && old_is_keyed {
|
// remove the rest
|
||||||
// // log::warn!("using the wrong approach");
|
for child in &old[1..] {
|
||||||
// self.diff_non_keyed_children(old, new);
|
self.edits.push_root(child.element_id().unwrap());
|
||||||
// // todo!("Not yet implemented a migration away from temporaries");
|
self.edits.remove();
|
||||||
// // let t = self.edits.next_temporary();
|
}
|
||||||
// // self.diff_keyed_children(old, new);
|
}
|
||||||
// // self.edits.set_next_temporary(t);
|
|
||||||
// } else {
|
(None, IS_NOT_EMPTY) => {
|
||||||
// // log::debug!("diffing non keyed children");
|
let new_is_keyed = new[0].key.is_some();
|
||||||
// self.diff_non_keyed_children(old, new);
|
let old_is_keyed = old[0].key.is_some();
|
||||||
// }
|
|
||||||
|
debug_assert!(
|
||||||
|
new.iter().all(|n| n.key.is_some() == new_is_keyed),
|
||||||
|
"all siblings must be keyed or all siblings must be non-keyed"
|
||||||
|
);
|
||||||
|
debug_assert!(
|
||||||
|
old.iter().all(|o| o.key.is_some() == old_is_keyed),
|
||||||
|
"all siblings must be keyed or all siblings must be non-keyed"
|
||||||
|
);
|
||||||
|
|
||||||
|
if new_is_keyed && old_is_keyed {
|
||||||
|
self.diff_keyed_children(old, new);
|
||||||
|
} else {
|
||||||
self.diff_non_keyed_children(old, new);
|
self.diff_non_keyed_children(old, new);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Diffing "keyed" children.
|
// Diffing "keyed" children.
|
||||||
//
|
//
|
||||||
|
@ -1280,6 +1196,35 @@ impl<'a, 'bump> DiffMachine<'a, 'bump> {
|
||||||
todo!()
|
todo!()
|
||||||
// self.edits.remove_self_and_next_siblings();
|
// self.edits.remove_self_and_next_siblings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_scope_mut(&mut self, id: &ScopeId) -> Option<&'bump mut Scope> {
|
||||||
|
// ensure we haven't seen this scope before
|
||||||
|
// if we have, then we're trying to alias it, which is not allowed
|
||||||
|
debug_assert!(!self.seen_nodes.contains(id));
|
||||||
|
|
||||||
|
unsafe { self.vdom.get_scope_mut(*id) }
|
||||||
|
}
|
||||||
|
pub fn get_scope(&mut self, id: &ScopeId) -> Option<&'bump Scope> {
|
||||||
|
// ensure we haven't seen this scope before
|
||||||
|
// if we have, then we're trying to alias it, which is not allowed
|
||||||
|
unsafe { self.vdom.get_scope(*id) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When we create new nodes, we need to propagate some information back up the call chain.
|
||||||
|
// This gives the caller some information on how to handle things like insertins, appending, and subtree discarding.
|
||||||
|
pub struct CreateMeta {
|
||||||
|
pub is_static: bool,
|
||||||
|
pub added_to_stack: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CreateMeta {
|
||||||
|
fn new(is_static: bool, added_to_tack: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
is_static,
|
||||||
|
added_to_stack: added_to_tack,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum KeyedPrefixResult {
|
enum KeyedPrefixResult {
|
||||||
|
@ -1291,6 +1236,20 @@ enum KeyedPrefixResult {
|
||||||
MoreWorkToDo(usize),
|
MoreWorkToDo(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_first_real_node<'a>(
|
||||||
|
nodes: &'a [VNode<'a>],
|
||||||
|
scopes: &'a SharedResources,
|
||||||
|
) -> Option<&'a VNode<'a>> {
|
||||||
|
for node in nodes {
|
||||||
|
let iter = RealChildIterator::new(node, scopes);
|
||||||
|
if let Some(node) = iter.next() {
|
||||||
|
return Some(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// This iterator iterates through a list of virtual children and only returns real children (Elements or Text).
|
/// This iterator iterates through a list of virtual children and only returns real children (Elements or Text).
|
||||||
///
|
///
|
||||||
/// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like
|
/// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like
|
||||||
|
|
|
@ -5,7 +5,10 @@
|
||||||
//!
|
//!
|
||||||
//!
|
//!
|
||||||
|
|
||||||
use crate::{innerlude::ScopeId, ElementId};
|
use crate::{
|
||||||
|
innerlude::{Attribute, Listener, ScopeId},
|
||||||
|
ElementId,
|
||||||
|
};
|
||||||
|
|
||||||
/// The `DomEditor` provides an imperative interface for the Diffing algorithm to plan out its changes.
|
/// The `DomEditor` provides an imperative interface for the Diffing algorithm to plan out its changes.
|
||||||
///
|
///
|
||||||
|
@ -86,18 +89,20 @@ impl<'real, 'bump> DomEditor<'real, 'bump> {
|
||||||
}
|
}
|
||||||
|
|
||||||
// events
|
// events
|
||||||
pub(crate) fn new_event_listener(
|
pub(crate) fn new_event_listener(&mut self, listener: &Listener) {
|
||||||
&mut self,
|
let Listener {
|
||||||
event: &'static str,
|
event,
|
||||||
scope: ScopeId,
|
|
||||||
element_id: usize,
|
|
||||||
realnode: ElementId,
|
|
||||||
) {
|
|
||||||
self.edits.push(NewEventListener {
|
|
||||||
scope,
|
scope,
|
||||||
|
mounted_node,
|
||||||
|
..
|
||||||
|
} = listener;
|
||||||
|
|
||||||
|
let element_id = mounted_node.get().unwrap().as_u64();
|
||||||
|
|
||||||
|
self.edits.push(NewEventListener {
|
||||||
|
scope: scope.clone(),
|
||||||
event_name: event,
|
event_name: event,
|
||||||
element_id,
|
mounted_node_id: element_id,
|
||||||
mounted_node_id: realnode.as_u64(),
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,17 +118,27 @@ impl<'real, 'bump> DomEditor<'real, 'bump> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn set_attribute(
|
pub(crate) fn set_attribute(&mut self, attribute: &'bump Attribute) {
|
||||||
&mut self,
|
let Attribute {
|
||||||
field: &'static str,
|
name,
|
||||||
value: &'bump str,
|
value,
|
||||||
ns: Option<&'static str>,
|
is_static,
|
||||||
) {
|
is_volatile,
|
||||||
self.edits.push(SetAttribute { field, value, ns });
|
namespace,
|
||||||
|
} = attribute;
|
||||||
|
// field: &'static str,
|
||||||
|
// value: &'bump str,
|
||||||
|
// ns: Option<&'static str>,
|
||||||
|
self.edits.push(SetAttribute {
|
||||||
|
field: name,
|
||||||
|
value,
|
||||||
|
ns: *namespace,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn remove_attribute(&mut self, name: &'static str) {
|
pub(crate) fn remove_attribute(&mut self, attribute: &Attribute) {
|
||||||
|
let name = attribute.name;
|
||||||
self.edits.push(RemoveAttribute { name });
|
self.edits.push(RemoveAttribute { name });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -169,7 +184,6 @@ pub enum DomEdit<'bump> {
|
||||||
event_name: &'static str,
|
event_name: &'static str,
|
||||||
scope: ScopeId,
|
scope: ScopeId,
|
||||||
mounted_node_id: u64,
|
mounted_node_id: u64,
|
||||||
element_id: usize,
|
|
||||||
},
|
},
|
||||||
RemoveEventListener {
|
RemoveEventListener {
|
||||||
event: &'static str,
|
event: &'static str,
|
||||||
|
|
|
@ -117,6 +117,16 @@ pub struct Listener<'bump> {
|
||||||
pub(crate) callback: RefCell<Option<BumpBox<'bump, dyn FnMut(VirtualEvent) + 'bump>>>,
|
pub(crate) callback: RefCell<Option<BumpBox<'bump, dyn FnMut(VirtualEvent) + 'bump>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Listener<'_> {
|
||||||
|
// serialize the listener event stuff to a string
|
||||||
|
pub fn serialize(&self) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
pub fn deserialize() {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Virtual Components for custom user-defined components
|
/// Virtual Components for custom user-defined components
|
||||||
/// Only supports the functional syntax
|
/// Only supports the functional syntax
|
||||||
pub struct VComponent<'src> {
|
pub struct VComponent<'src> {
|
||||||
|
|
|
@ -87,15 +87,12 @@ impl Scope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn update_caller<'creator_node>(&mut self, caller: Rc<WrappedCaller>) {
|
pub(crate) fn update_scope_dependencies<'creator_node>(
|
||||||
self.caller = caller;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn update_children<'creator_node>(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
|
caller: Rc<WrappedCaller>,
|
||||||
child_nodes: ScopeChildren,
|
child_nodes: ScopeChildren,
|
||||||
// child_nodes: &'creator_node [VNode<'creator_node>],
|
|
||||||
) {
|
) {
|
||||||
|
self.caller = caller;
|
||||||
// let child_nodes = unsafe { std::mem::transmute(child_nodes) };
|
// let child_nodes = unsafe { std::mem::transmute(child_nodes) };
|
||||||
let child_nodes = unsafe { child_nodes.extend_lifetime() };
|
let child_nodes = unsafe { child_nodes.extend_lifetime() };
|
||||||
self.child_nodes = child_nodes;
|
self.child_nodes = child_nodes;
|
||||||
|
|
17
packages/core/tests/diffing.rs
Normal file
17
packages/core/tests/diffing.rs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_core as dioxus;
|
||||||
|
use dioxus_html as dioxus_elements;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn diffing_works() {}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn html_and_rsx_generate_the_same_output() {
|
||||||
|
let old = rsx! {
|
||||||
|
div { "Hello world!" }
|
||||||
|
};
|
||||||
|
|
||||||
|
// let new = html! {
|
||||||
|
// <div>"Hello world!"</div>
|
||||||
|
// };
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ use dioxus_core::prelude::*;
|
||||||
use dioxus_html as dioxus_elements;
|
use dioxus_html as dioxus_elements;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
dioxus_desktop::launch(App, |f| f.with_maximized(true)).expect("Failed");
|
dioxus_desktop::launch(App, |f| f.with_window(|w| w.with_maximized(true))).expect("Failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
static App: FC<()> = |cx| {
|
static App: FC<()> = |cx| {
|
||||||
|
|
45
packages/desktop/src/cfg.rs
Normal file
45
packages/desktop/src/cfg.rs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
|
use dioxus_core::DomEdit;
|
||||||
|
use wry::{
|
||||||
|
application::{
|
||||||
|
error::OsError,
|
||||||
|
event_loop::EventLoopWindowTarget,
|
||||||
|
menu::MenuBar,
|
||||||
|
window::{Fullscreen, Icon, Window, WindowBuilder},
|
||||||
|
},
|
||||||
|
webview::{RpcRequest, RpcResponse},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct DesktopConfig<'a> {
|
||||||
|
pub window: WindowBuilder,
|
||||||
|
pub(crate) manual_edits: Option<DomEdit<'a>>,
|
||||||
|
pub(crate) pre_rendered: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DesktopConfig<'_> {
|
||||||
|
/// Initializes a new `WindowBuilder` with default values.
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
window: Default::default(),
|
||||||
|
pre_rendered: None,
|
||||||
|
manual_edits: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_prerendered(&mut self, content: String) -> &mut Self {
|
||||||
|
self.pre_rendered = Some(content);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_window(&mut self, f: impl FnOnce(WindowBuilder) -> WindowBuilder) -> &mut Self {
|
||||||
|
// gots to do a swap because the window builder only takes itself as muy self
|
||||||
|
// I wish more people knew about returning &mut Self
|
||||||
|
let mut builder = WindowBuilder::default();
|
||||||
|
std::mem::swap(&mut self.window, &mut builder);
|
||||||
|
builder = f(builder);
|
||||||
|
std::mem::swap(&mut self.window, &mut builder);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,14 @@
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<script>
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="_dioxusroot">
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
<script>
|
||||||
class Interpreter {
|
class Interpreter {
|
||||||
constructor(root) {
|
constructor(root) {
|
||||||
this.root = root;
|
this.root = root;
|
||||||
|
@ -117,8 +124,10 @@
|
||||||
console.log(reply);
|
console.log(reply);
|
||||||
this.stack.push(this.root);
|
this.stack.push(this.root);
|
||||||
|
|
||||||
for (let x = 0; x < reply.length; x++) {
|
let edits = reply.edits;
|
||||||
let edit = reply[x];
|
|
||||||
|
for (let x = 0; x < edits.length; x++) {
|
||||||
|
let edit = edits[x];
|
||||||
console.log(edit);
|
console.log(edit);
|
||||||
|
|
||||||
let f = this[edit.type];
|
let f = this[edit.type];
|
||||||
|
@ -183,11 +192,19 @@
|
||||||
|
|
||||||
async function initialize() {
|
async function initialize() {
|
||||||
const reply = await rpc.call('initiate');
|
const reply = await rpc.call('initiate');
|
||||||
const interpreter = new Interpreter(window.document.getElementById("_dioxusroot"));
|
let root = window.document.getElementById("_dioxusroot");
|
||||||
|
const interpreter = new Interpreter(root);
|
||||||
console.log(reply);
|
console.log(reply);
|
||||||
|
|
||||||
for (let x = 0; x < reply.length; x++) {
|
let pre_rendered = reply.pre_rendered;
|
||||||
let edit = reply[x];
|
if (pre_rendered !== undefined) {
|
||||||
|
root.innerHTML = pre_rendered;
|
||||||
|
}
|
||||||
|
|
||||||
|
const edits = reply.edits;
|
||||||
|
|
||||||
|
for (let x = 0; x < edits.length; x++) {
|
||||||
|
let edit = edits[x];
|
||||||
console.log(edit);
|
console.log(edit);
|
||||||
|
|
||||||
let f = interpreter[edit.type];
|
let f = interpreter[edit.type];
|
||||||
|
@ -198,12 +215,6 @@
|
||||||
}
|
}
|
||||||
console.log("initializing...");
|
console.log("initializing...");
|
||||||
initialize();
|
initialize();
|
||||||
</script>
|
</script>
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<div id="_dioxusroot">
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -3,6 +3,7 @@ use std::ops::{Deref, DerefMut};
|
||||||
use std::sync::mpsc::channel;
|
use std::sync::mpsc::channel;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
|
use cfg::DesktopConfig;
|
||||||
use dioxus_core::*;
|
use dioxus_core::*;
|
||||||
pub use wry;
|
pub use wry;
|
||||||
|
|
||||||
|
@ -15,6 +16,7 @@ use wry::{
|
||||||
webview::{RpcRequest, RpcResponse},
|
webview::{RpcRequest, RpcResponse},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod cfg;
|
||||||
mod dom;
|
mod dom;
|
||||||
mod escape;
|
mod escape;
|
||||||
mod events;
|
mod events;
|
||||||
|
@ -24,14 +26,14 @@ static HTML_CONTENT: &'static str = include_str!("./index.html");
|
||||||
|
|
||||||
pub fn launch(
|
pub fn launch(
|
||||||
root: FC<()>,
|
root: FC<()>,
|
||||||
builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
|
builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
launch_with_props(root, (), builder)
|
launch_with_props(root, (), builder)
|
||||||
}
|
}
|
||||||
pub fn launch_with_props<P: Properties + 'static>(
|
pub fn launch_with_props<P: Properties + 'static>(
|
||||||
root: FC<P>,
|
root: FC<P>,
|
||||||
props: P,
|
props: P,
|
||||||
builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
|
builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
WebviewRenderer::run(root, props, builder)
|
WebviewRenderer::run(root, props, builder)
|
||||||
}
|
}
|
||||||
|
@ -46,11 +48,17 @@ enum RpcEvent<'a> {
|
||||||
Initialize { edits: Vec<DomEdit<'a>> },
|
Initialize { edits: Vec<DomEdit<'a>> },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct Response<'a> {
|
||||||
|
pre_rendered: Option<String>,
|
||||||
|
edits: Vec<DomEdit<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
impl<T: Properties + 'static> WebviewRenderer<T> {
|
impl<T: Properties + 'static> WebviewRenderer<T> {
|
||||||
pub fn run(
|
pub fn run(
|
||||||
root: FC<T>,
|
root: FC<T>,
|
||||||
props: T,
|
props: T,
|
||||||
user_builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
|
user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
Self::run_with_edits(root, props, user_builder, None)
|
Self::run_with_edits(root, props, user_builder, None)
|
||||||
}
|
}
|
||||||
|
@ -58,13 +66,22 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
|
||||||
pub fn run_with_edits(
|
pub fn run_with_edits(
|
||||||
root: FC<T>,
|
root: FC<T>,
|
||||||
props: T,
|
props: T,
|
||||||
user_builder: impl FnOnce(WindowBuilder) -> WindowBuilder,
|
user_builder: impl for<'a, 'b> FnOnce(&'a mut DesktopConfig<'b>) -> &'a mut DesktopConfig<'b>,
|
||||||
redits: Option<Vec<DomEdit<'static>>>,
|
redits: Option<Vec<DomEdit<'static>>>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
log::info!("hello edits");
|
log::info!("hello edits");
|
||||||
let event_loop = EventLoop::new();
|
let event_loop = EventLoop::new();
|
||||||
|
|
||||||
let window = user_builder(WindowBuilder::new()).build(&event_loop)?;
|
let mut cfg = DesktopConfig::new();
|
||||||
|
user_builder(&mut cfg);
|
||||||
|
|
||||||
|
let DesktopConfig {
|
||||||
|
window,
|
||||||
|
manual_edits,
|
||||||
|
pre_rendered,
|
||||||
|
} = cfg;
|
||||||
|
|
||||||
|
let window = window.build(&event_loop)?;
|
||||||
|
|
||||||
let vir = VirtualDom::new_with_props(root, props);
|
let vir = VirtualDom::new_with_props(root, props);
|
||||||
|
|
||||||
|
@ -73,8 +90,6 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
|
||||||
// let registry = Arc::new(RwLock::new(Some(WebviewRegistry::new())));
|
// let registry = Arc::new(RwLock::new(Some(WebviewRegistry::new())));
|
||||||
|
|
||||||
let webview = WebViewBuilder::new(window)?
|
let webview = WebViewBuilder::new(window)?
|
||||||
// .with_visible(false)
|
|
||||||
// .with_transparent(true)
|
|
||||||
.with_url(&format!("data:text/html,{}", HTML_CONTENT))?
|
.with_url(&format!("data:text/html,{}", HTML_CONTENT))?
|
||||||
.with_rpc_handler(move |_window: &Window, mut req: RpcRequest| {
|
.with_rpc_handler(move |_window: &Window, mut req: RpcRequest| {
|
||||||
match req.method.as_str() {
|
match req.method.as_str() {
|
||||||
|
@ -87,17 +102,32 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
|
||||||
|
|
||||||
// Create the thin wrapper around the registry to collect the edits into
|
// Create the thin wrapper around the registry to collect the edits into
|
||||||
let mut real = dom::WebviewDom::new();
|
let mut real = dom::WebviewDom::new();
|
||||||
|
let pre = pre_rendered.clone();
|
||||||
|
|
||||||
// Serialize the edit stream
|
let response = match pre {
|
||||||
|
Some(content) => {
|
||||||
|
lock.rebuild_in_place().unwrap();
|
||||||
|
|
||||||
|
Response {
|
||||||
|
edits: Vec::new(),
|
||||||
|
pre_rendered: Some(content),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
//
|
||||||
let edits = {
|
let edits = {
|
||||||
let mut edits = Vec::new();
|
let mut edits = Vec::new();
|
||||||
lock.rebuild(&mut real, &mut edits).unwrap();
|
lock.rebuild(&mut real, &mut edits).unwrap();
|
||||||
serde_json::to_value(edits).unwrap()
|
edits
|
||||||
|
};
|
||||||
|
Response {
|
||||||
|
edits,
|
||||||
|
pre_rendered: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Give back the registry into its slot
|
serde_json::to_value(&response).unwrap()
|
||||||
// *reg_lock = Some(real.consume());
|
|
||||||
edits
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Return the edits into the webview runtime
|
// Return the edits into the webview runtime
|
||||||
|
@ -128,13 +158,18 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
|
||||||
lock.progress_with_event(&mut real, &mut edits)
|
lock.progress_with_event(&mut real, &mut edits)
|
||||||
.await
|
.await
|
||||||
.expect("failed to progress");
|
.expect("failed to progress");
|
||||||
let edits = serde_json::to_value(edits).unwrap();
|
|
||||||
|
let response = Response {
|
||||||
|
edits,
|
||||||
|
pre_rendered: None,
|
||||||
|
};
|
||||||
|
let response = serde_json::to_value(&response).unwrap();
|
||||||
|
|
||||||
// Give back the registry into its slot
|
// Give back the registry into its slot
|
||||||
// *reg_lock = Some(real.consume());
|
// *reg_lock = Some(real.consume());
|
||||||
|
|
||||||
// Return the edits into the webview runtime
|
// Return the edits into the webview runtime
|
||||||
Some(RpcResponse::new_result(req.id.take(), Some(edits)))
|
Some(RpcResponse::new_result(req.id.take(), Some(response)))
|
||||||
});
|
});
|
||||||
|
|
||||||
response
|
response
|
||||||
|
|
|
@ -16,3 +16,9 @@ vdom.rebuild_in_place();
|
||||||
let text = dioxus_ssr::render_root(&vdom);
|
let text = dioxus_ssr::render_root(&vdom);
|
||||||
assert_eq!(text, "<div>hello world!</div>")
|
assert_eq!(text, "<div>hello world!</div>")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Pre-rendering
|
||||||
|
|
||||||
|
|
||||||
|
|
10
packages/ssr/examples/hydration.rs
Normal file
10
packages/ssr/examples/hydration.rs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
//! Example: realworld usage of hydration
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
use dioxus::virtual_dom::VirtualDom;
|
||||||
|
use dioxus_core as dioxus;
|
||||||
|
use dioxus_core::prelude::*;
|
||||||
|
use dioxus_hooks::use_state;
|
||||||
|
use dioxus_html as dioxus_elements;
|
||||||
|
|
||||||
|
fn main() {}
|
|
@ -22,7 +22,7 @@ async fn main() -> Result<(), std::io::Error> {
|
||||||
let dom = VirtualDom::launch_with_props_in_place(Example, ExampleProps { initial_name });
|
let dom = VirtualDom::launch_with_props_in_place(Example, ExampleProps { initial_name });
|
||||||
|
|
||||||
Ok(Response::builder(200)
|
Ok(Response::builder(200)
|
||||||
.body(format!("{}", dioxus_ssr::render_vdom(&dom)))
|
.body(format!("{}", dioxus_ssr::render_vdom(&dom, |c| c)))
|
||||||
.content_type(tide::http::mime::HTML)
|
.content_type(tide::http::mime::HTML)
|
||||||
.build())
|
.build())
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,7 +15,10 @@ fn main() {
|
||||||
let mut dom = VirtualDom::new(App);
|
let mut dom = VirtualDom::new(App);
|
||||||
dom.rebuild_in_place().expect("failed to run virtualdom");
|
dom.rebuild_in_place().expect("failed to run virtualdom");
|
||||||
|
|
||||||
file.write_fmt(format_args!("{}", TextRenderer::from_vdom(&dom)))
|
file.write_fmt(format_args!(
|
||||||
|
"{}",
|
||||||
|
TextRenderer::from_vdom(&dom, Default::default())
|
||||||
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//!
|
||||||
//! This crate demonstrates how to implement a custom renderer for Dioxus VNodes via the `TextRenderer` renderer.
|
//! This crate demonstrates how to implement a custom renderer for Dioxus VNodes via the `TextRenderer` renderer.
|
||||||
//! The `TextRenderer` consumes a Dioxus Virtual DOM, progresses its event queue, and renders the VNodes to a String.
|
//! The `TextRenderer` consumes a Dioxus Virtual DOM, progresses its event queue, and renders the VNodes to a String.
|
||||||
//!
|
//!
|
||||||
|
@ -12,8 +16,11 @@ use dioxus_core::*;
|
||||||
|
|
||||||
pub fn render_vnode(vnode: &VNode, string: &mut String) {}
|
pub fn render_vnode(vnode: &VNode, string: &mut String) {}
|
||||||
|
|
||||||
pub fn render_vdom(dom: &VirtualDom) -> String {
|
pub fn render_vdom(dom: &VirtualDom, cfg: impl FnOnce(SsrConfig) -> SsrConfig) -> String {
|
||||||
format!("{:}", TextRenderer::from_vdom(dom))
|
format!(
|
||||||
|
"{:}",
|
||||||
|
TextRenderer::from_vdom(dom, cfg(SsrConfig::default()))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
|
pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
|
||||||
|
@ -27,29 +34,6 @@ pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SsrConfig {
|
|
||||||
// currently not supported - control if we indent the HTML output
|
|
||||||
indent: bool,
|
|
||||||
|
|
||||||
// Control if elements are written onto a new line
|
|
||||||
newline: bool,
|
|
||||||
|
|
||||||
// Currently not implemented
|
|
||||||
// Don't proceed onto new components. Instead, put the name of the component.
|
|
||||||
// TODO: components don't have names :(
|
|
||||||
_skip_components: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for SsrConfig {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
indent: false,
|
|
||||||
|
|
||||||
newline: false,
|
|
||||||
_skip_components: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/// A configurable text renderer for the Dioxus VirtualDOM.
|
/// A configurable text renderer for the Dioxus VirtualDOM.
|
||||||
///
|
///
|
||||||
///
|
///
|
||||||
|
@ -60,7 +44,7 @@ impl Default for SsrConfig {
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// const App: FC<()> = |cx| cx.render(rsx!(div { "hello world" }));
|
/// static App: FC<()> = |cx| cx.render(rsx!(div { "hello world" }));
|
||||||
/// let mut vdom = VirtualDom::new(App);
|
/// let mut vdom = VirtualDom::new(App);
|
||||||
/// vdom.rebuild_in_place();
|
/// vdom.rebuild_in_place();
|
||||||
///
|
///
|
||||||
|
@ -81,11 +65,11 @@ impl Display for TextRenderer<'_> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TextRenderer<'a> {
|
impl<'a> TextRenderer<'a> {
|
||||||
pub fn from_vdom(vdom: &'a VirtualDom) -> Self {
|
pub fn from_vdom(vdom: &'a VirtualDom, cfg: SsrConfig) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
cfg,
|
||||||
root: vdom.base_scope().root(),
|
root: vdom.base_scope().root(),
|
||||||
vdom: Some(vdom),
|
vdom: Some(vdom),
|
||||||
cfg: SsrConfig::default(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,6 +116,21 @@ impl<'a> TextRenderer<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we write the element's id as a data attribute
|
||||||
|
//
|
||||||
|
// when the page is loaded, the `querySelectorAll` will be used to collect all the nodes, and then add
|
||||||
|
// them interpreter's stack
|
||||||
|
match (self.cfg.pre_render, node.element_id()) {
|
||||||
|
(true, Some(id)) => {
|
||||||
|
write!(f, " dio_el=\"{}\"", id)?;
|
||||||
|
//
|
||||||
|
for listener in el.listeners {
|
||||||
|
// write the listeners
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
match self.cfg.newline {
|
match self.cfg.newline {
|
||||||
true => write!(f, ">\n")?,
|
true => write!(f, ">\n")?,
|
||||||
false => write!(f, ">")?,
|
false => write!(f, ">")?,
|
||||||
|
@ -162,10 +161,15 @@ impl<'a> TextRenderer<'a> {
|
||||||
}
|
}
|
||||||
VNodeKind::Component(vcomp) => {
|
VNodeKind::Component(vcomp) => {
|
||||||
let idx = vcomp.ass_scope.get().unwrap();
|
let idx = vcomp.ass_scope.get().unwrap();
|
||||||
if let Some(vdom) = self.vdom {
|
match (self.vdom, self.cfg.skip_components) {
|
||||||
|
(Some(vdom), false) => {
|
||||||
let new_node = vdom.get_scope(idx).unwrap().root();
|
let new_node = vdom.get_scope(idx).unwrap().root();
|
||||||
self.html_render(new_node, f, il + 1)?;
|
self.html_render(new_node, f, il + 1)?;
|
||||||
}
|
}
|
||||||
|
_ => {
|
||||||
|
// render the component by name
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
VNodeKind::Suspended { .. } => {
|
VNodeKind::Suspended { .. } => {
|
||||||
// we can't do anything with suspended nodes
|
// we can't do anything with suspended nodes
|
||||||
|
@ -175,6 +179,52 @@ impl<'a> TextRenderer<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct SsrConfig {
|
||||||
|
// currently not supported - control if we indent the HTML output
|
||||||
|
indent: bool,
|
||||||
|
|
||||||
|
// Control if elements are written onto a new line
|
||||||
|
newline: bool,
|
||||||
|
|
||||||
|
// Choose to write ElementIDs into elements so the page can be re-hydrated later on
|
||||||
|
pre_render: bool,
|
||||||
|
|
||||||
|
// Currently not implemented
|
||||||
|
// Don't proceed onto new components. Instead, put the name of the component.
|
||||||
|
// TODO: components don't have names :(
|
||||||
|
skip_components: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for SsrConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
indent: false,
|
||||||
|
pre_render: false,
|
||||||
|
newline: false,
|
||||||
|
skip_components: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SsrConfig {
|
||||||
|
pub fn indent(mut self, a: bool) -> Self {
|
||||||
|
self.indent = a;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn newline(mut self, a: bool) -> Self {
|
||||||
|
self.newline = a;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn pre_render(mut self, a: bool) -> Self {
|
||||||
|
self.pre_render = a;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn skip_components(mut self, a: bool) -> Self {
|
||||||
|
self.skip_components = a;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -182,15 +232,14 @@ mod tests {
|
||||||
use dioxus_core as dioxus;
|
use dioxus_core as dioxus;
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
use dioxus_html as dioxus_elements;
|
use dioxus_html as dioxus_elements;
|
||||||
use dioxus_html::GlobalAttributes;
|
|
||||||
|
|
||||||
const SIMPLE_APP: FC<()> = |cx| {
|
static SIMPLE_APP: FC<()> = |cx| {
|
||||||
cx.render(rsx!(div {
|
cx.render(rsx!(div {
|
||||||
"hello world!"
|
"hello world!"
|
||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
|
|
||||||
const SLIGHTLY_MORE_COMPLEX: FC<()> = |cx| {
|
static SLIGHTLY_MORE_COMPLEX: FC<()> = |cx| {
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
title: "About W3Schools"
|
title: "About W3Schools"
|
||||||
|
@ -209,14 +258,14 @@ mod tests {
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
const NESTED_APP: FC<()> = |cx| {
|
static NESTED_APP: FC<()> = |cx| {
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
div {
|
div {
|
||||||
SIMPLE_APP {}
|
SIMPLE_APP {}
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
const FRAGMENT_APP: FC<()> = |cx| {
|
static FRAGMENT_APP: FC<()> = |cx| {
|
||||||
cx.render(rsx!(
|
cx.render(rsx!(
|
||||||
div { "f1" }
|
div { "f1" }
|
||||||
div { "f2" }
|
div { "f2" }
|
||||||
|
@ -229,21 +278,28 @@ mod tests {
|
||||||
fn to_string_works() {
|
fn to_string_works() {
|
||||||
let mut dom = VirtualDom::new(SIMPLE_APP);
|
let mut dom = VirtualDom::new(SIMPLE_APP);
|
||||||
dom.rebuild_in_place().expect("failed to run virtualdom");
|
dom.rebuild_in_place().expect("failed to run virtualdom");
|
||||||
dbg!(render_vdom(&dom));
|
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");
|
||||||
|
dbg!(render_vdom(&dom, |c| c.pre_render(true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn nested() {
|
fn nested() {
|
||||||
let mut dom = VirtualDom::new(NESTED_APP);
|
let mut dom = VirtualDom::new(NESTED_APP);
|
||||||
dom.rebuild_in_place().expect("failed to run virtualdom");
|
dom.rebuild_in_place().expect("failed to run virtualdom");
|
||||||
dbg!(render_vdom(&dom));
|
dbg!(render_vdom(&dom, |c| c));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn fragment_app() {
|
fn fragment_app() {
|
||||||
let mut dom = VirtualDom::new(FRAGMENT_APP);
|
let mut dom = VirtualDom::new(FRAGMENT_APP);
|
||||||
dom.rebuild_in_place().expect("failed to run virtualdom");
|
dom.rebuild_in_place().expect("failed to run virtualdom");
|
||||||
dbg!(render_vdom(&dom));
|
dbg!(render_vdom(&dom, |c| c));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -256,26 +312,23 @@ mod tests {
|
||||||
let mut dom = VirtualDom::new(SLIGHTLY_MORE_COMPLEX);
|
let mut dom = VirtualDom::new(SLIGHTLY_MORE_COMPLEX);
|
||||||
dom.rebuild_in_place().expect("failed to run virtualdom");
|
dom.rebuild_in_place().expect("failed to run virtualdom");
|
||||||
|
|
||||||
file.write_fmt(format_args!("{}", TextRenderer::from_vdom(&dom)))
|
file.write_fmt(format_args!(
|
||||||
|
"{}",
|
||||||
|
TextRenderer::from_vdom(&dom, SsrConfig::default())
|
||||||
|
))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
// #[test]
|
#[test]
|
||||||
// fn styles() {
|
fn styles() {
|
||||||
// const STLYE_APP: FC<()> = |cx| {
|
static STLYE_APP: FC<()> = |cx| {
|
||||||
// //
|
cx.render(rsx! {
|
||||||
// cx.render(rsx! {
|
div { style: { color: "blue", font_size: "46px" } }
|
||||||
// div {
|
})
|
||||||
// style: {
|
};
|
||||||
// color: "blue",
|
|
||||||
// font_size: "46px"
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// })
|
|
||||||
// };
|
|
||||||
|
|
||||||
// let mut dom = VirtualDom::new(STLYE_APP);
|
let mut dom = VirtualDom::new(STLYE_APP);
|
||||||
// dom.rebuild_in_place().expect("failed to run virtualdom");
|
dom.rebuild_in_place().expect("failed to run virtualdom");
|
||||||
// dbg!(render_vdom(&dom));
|
dbg!(render_vdom(&dom, |c| c));
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
21
packages/web/src/cfg.rs
Normal file
21
packages/web/src/cfg.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
pub struct WebConfig {
|
||||||
|
pub(crate) hydrate: bool,
|
||||||
|
}
|
||||||
|
impl Default for WebConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self { hydrate: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl WebConfig {
|
||||||
|
/// Enable SSR hydration
|
||||||
|
///
|
||||||
|
/// This enables Dioxus to pick up work from a pre-renderd HTML file. Hydration will completely skip over any async
|
||||||
|
/// work and suspended nodes.
|
||||||
|
///
|
||||||
|
/// Dioxus will load up all the elements with the `dio_el` data attribute into memory when the page is loaded.
|
||||||
|
///
|
||||||
|
pub fn hydrate(mut self, f: bool) -> Self {
|
||||||
|
self.hydrate = f;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,12 +8,19 @@ use fxhash::FxHashMap;
|
||||||
use wasm_bindgen::{closure::Closure, JsCast};
|
use wasm_bindgen::{closure::Closure, JsCast};
|
||||||
use web_sys::{
|
use web_sys::{
|
||||||
window, Document, Element, Event, HtmlElement, HtmlInputElement, HtmlOptionElement, Node,
|
window, Document, Element, Event, HtmlElement, HtmlInputElement, HtmlOptionElement, Node,
|
||||||
|
NodeList,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::{nodeslab::NodeSlab, WebConfig};
|
||||||
|
|
||||||
pub struct WebsysDom {
|
pub struct WebsysDom {
|
||||||
pub stack: Stack,
|
stack: Stack,
|
||||||
nodes: Vec<Node>,
|
|
||||||
|
/// A map from ElementID (index) to Node
|
||||||
|
nodes: NodeSlab,
|
||||||
|
|
||||||
document: Document,
|
document: Document,
|
||||||
|
|
||||||
root: Element,
|
root: Element,
|
||||||
|
|
||||||
event_receiver: async_channel::Receiver<EventTrigger>,
|
event_receiver: async_channel::Receiver<EventTrigger>,
|
||||||
|
@ -33,11 +40,8 @@ pub struct WebsysDom {
|
||||||
last_node_was_text: bool,
|
last_node_was_text: bool,
|
||||||
}
|
}
|
||||||
impl WebsysDom {
|
impl WebsysDom {
|
||||||
pub fn new(root: Element) -> Self {
|
pub fn new(root: Element, cfg: WebConfig) -> Self {
|
||||||
let document = window()
|
let document = load_document();
|
||||||
.expect("must have access to the window")
|
|
||||||
.document()
|
|
||||||
.expect("must have access to the Document");
|
|
||||||
|
|
||||||
let (sender, receiver) = async_channel::unbounded::<EventTrigger>();
|
let (sender, receiver) = async_channel::unbounded::<EventTrigger>();
|
||||||
|
|
||||||
|
@ -48,14 +52,36 @@ impl WebsysDom {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut nodes = Vec::with_capacity(1000);
|
let mut nodes = NodeSlab::new(2000);
|
||||||
|
let mut listeners = FxHashMap::default();
|
||||||
|
|
||||||
// let root_id = nodes.insert(root.clone().dyn_into::<Node>().unwrap());
|
// re-hydrate the page - only supports one virtualdom per page
|
||||||
|
if cfg.hydrate {
|
||||||
|
// Load all the elements into the arena
|
||||||
|
let node_list: NodeList = document.query_selector_all("dio_el").unwrap();
|
||||||
|
let len = node_list.length() as usize;
|
||||||
|
|
||||||
|
for x in 0..len {
|
||||||
|
let node: Node = node_list.get(x as u32).unwrap();
|
||||||
|
let el: &Element = node.dyn_ref::<Element>().unwrap();
|
||||||
|
let id: String = el.get_attribute("dio_el").unwrap();
|
||||||
|
let id = id.parse::<usize>().unwrap();
|
||||||
|
|
||||||
|
// this autoresizes the vector if needed
|
||||||
|
nodes[id] = Some(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load all the event listeners into our listener register
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut stack = Stack::with_capacity(10);
|
||||||
|
let root_node = root.clone().dyn_into::<Node>().unwrap();
|
||||||
|
stack.push(root_node);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
stack: Stack::with_capacity(10),
|
stack,
|
||||||
nodes,
|
nodes,
|
||||||
listeners: FxHashMap::default(),
|
listeners,
|
||||||
document,
|
document,
|
||||||
event_receiver: receiver,
|
event_receiver: receiver,
|
||||||
trigger: sender_callback,
|
trigger: sender_callback,
|
||||||
|
@ -97,19 +123,14 @@ impl WebsysDom {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn push(&mut self, root: u64) {
|
fn push(&mut self, root: u64) {
|
||||||
// let key = DefaultKey::from(KeyData::from_ffi(root));
|
|
||||||
let key = root as usize;
|
let key = root as usize;
|
||||||
let domnode = self.nodes.get_mut(key);
|
let domnode = &self.nodes[key];
|
||||||
|
|
||||||
let real_node: Node = match domnode {
|
let real_node: Node = match domnode {
|
||||||
Some(n) => n.clone(),
|
Some(n) => n.clone(),
|
||||||
None => todo!(),
|
None => todo!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// let domnode = domnode.unwrap().as_mut().unwrap();
|
|
||||||
// .expect(&format!("Failed to pop know root: {:#?}", key))
|
|
||||||
// .unwrap();
|
|
||||||
|
|
||||||
self.stack.push(real_node);
|
self.stack.push(real_node);
|
||||||
}
|
}
|
||||||
// drop the node off the stack
|
// drop the node off the stack
|
||||||
|
@ -205,7 +226,7 @@ impl WebsysDom {
|
||||||
|
|
||||||
let id = id as usize;
|
let id = id as usize;
|
||||||
self.stack.push(textnode.clone());
|
self.stack.push(textnode.clone());
|
||||||
*self.nodes.get_mut(id).unwrap() = textnode;
|
self.nodes[id] = Some(textnode);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_element(&mut self, tag: &str, ns: Option<&'static str>, id: u64) {
|
fn create_element(&mut self, tag: &str, ns: Option<&'static str>, id: u64) {
|
||||||
|
@ -227,7 +248,7 @@ impl WebsysDom {
|
||||||
let id = id as usize;
|
let id = id as usize;
|
||||||
|
|
||||||
self.stack.push(el.clone());
|
self.stack.push(el.clone());
|
||||||
*self.nodes.get_mut(id).unwrap() = el;
|
self.nodes[id] = Some(el);
|
||||||
// let nid = self.node_counter.?next();
|
// let nid = self.node_counter.?next();
|
||||||
// let nid = self.nodes.insert(el).data().as_ffi();
|
// let nid = self.nodes.insert(el).data().as_ffi();
|
||||||
// log::debug!("Called [`create_element`]: {}, {:?}", tag, nid);
|
// log::debug!("Called [`create_element`]: {}, {:?}", tag, nid);
|
||||||
|
@ -264,6 +285,9 @@ impl WebsysDom {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
el.set_attribute(&format!("dioxus-event"), &format!("{}", event))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
// Register the callback to decode
|
// Register the callback to decode
|
||||||
|
|
||||||
if let Some(entry) = self.listeners.get_mut(event) {
|
if let Some(entry) = self.listeners.get_mut(event) {
|
||||||
|
@ -512,26 +536,6 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
VirtualEvent::MouseEvent(MouseEvent(Rc::new(CustomMouseEvent(evt))))
|
VirtualEvent::MouseEvent(MouseEvent(Rc::new(CustomMouseEvent(evt))))
|
||||||
// MouseEvent(Box::new(RawMouseEvent {
|
|
||||||
// alt_key: evt.alt_key(),
|
|
||||||
// button: evt.button() as i32,
|
|
||||||
// buttons: evt.buttons() as i32,
|
|
||||||
// client_x: evt.client_x(),
|
|
||||||
// client_y: evt.client_y(),
|
|
||||||
// ctrl_key: evt.ctrl_key(),
|
|
||||||
// meta_key: evt.meta_key(),
|
|
||||||
// page_x: evt.page_x(),
|
|
||||||
// page_y: evt.page_y(),
|
|
||||||
// screen_x: evt.screen_x(),
|
|
||||||
// screen_y: evt.screen_y(),
|
|
||||||
// shift_key: evt.shift_key(),
|
|
||||||
// get_modifier_state: GetModifierKey(Box::new(|f| {
|
|
||||||
// // evt.get_modifier_state(f)
|
|
||||||
// todo!("This is not yet implemented properly, sorry :(");
|
|
||||||
// })),
|
|
||||||
// }))
|
|
||||||
// todo!()
|
|
||||||
// VirtualEvent::MouseEvent()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
|
"pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
|
||||||
|
@ -645,3 +649,14 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<EventTrigger> {
|
||||||
dioxus_core::events::EventPriority::High,
|
dioxus_core::events::EventPriority::High,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn prepare_websys_dom() -> Element {
|
||||||
|
load_document().get_element_by_id("dioxusroot").unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_document() -> Document {
|
||||||
|
web_sys::window()
|
||||||
|
.expect("should have access to the Window")
|
||||||
|
.document()
|
||||||
|
.expect("should have access to the Document")
|
||||||
|
}
|
|
@ -2,38 +2,66 @@
|
||||||
//! --------------
|
//! --------------
|
||||||
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys.
|
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys.
|
||||||
|
|
||||||
|
pub use crate::cfg::WebConfig;
|
||||||
|
use crate::dom::load_document;
|
||||||
use dioxus::prelude::{Context, Properties, VNode};
|
use dioxus::prelude::{Context, Properties, VNode};
|
||||||
use dioxus::virtual_dom::VirtualDom;
|
use dioxus::virtual_dom::VirtualDom;
|
||||||
pub use dioxus_core as dioxus;
|
pub use dioxus_core as dioxus;
|
||||||
|
use dioxus_core::error::Result;
|
||||||
use dioxus_core::{events::EventTrigger, prelude::FC};
|
use dioxus_core::{events::EventTrigger, prelude::FC};
|
||||||
use futures_util::{pin_mut, Stream, StreamExt};
|
use futures_util::{pin_mut, Stream, StreamExt};
|
||||||
use fxhash::FxHashMap;
|
use fxhash::FxHashMap;
|
||||||
use web_sys::{window, Document, Element, Event, Node};
|
use js_sys::Iterator;
|
||||||
|
use web_sys::{window, Document, Element, Event, Node, NodeList};
|
||||||
|
|
||||||
mod cache;
|
mod cache;
|
||||||
mod new;
|
mod cfg;
|
||||||
|
mod dom;
|
||||||
|
mod nodeslab;
|
||||||
|
|
||||||
/// Launches the VirtualDOM from the specified component function.
|
/// Launches the VirtualDOM from the specified component function.
|
||||||
///
|
///
|
||||||
/// This method will block the thread with `spawn_local`
|
/// This method will block the thread with `spawn_local`
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
///
|
||||||
|
///
|
||||||
pub fn launch<F>(root: FC<()>, config: F)
|
pub fn launch<F>(root: FC<()>, config: F)
|
||||||
where
|
where
|
||||||
F: FnOnce(()),
|
F: FnOnce(WebConfig) -> WebConfig,
|
||||||
{
|
{
|
||||||
wasm_bindgen_futures::spawn_local(run(root))
|
launch_with_props(root, (), config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Launches the VirtualDOM from the specified component function and props.
|
||||||
|
///
|
||||||
|
/// This method will block the thread with `spawn_local`
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
///
|
||||||
pub fn launch_with_props<T, F>(root: FC<T>, root_props: T, config: F)
|
pub fn launch_with_props<T, F>(root: FC<T>, root_props: T, config: F)
|
||||||
where
|
where
|
||||||
T: Properties + 'static,
|
T: Properties + 'static,
|
||||||
F: FnOnce(()),
|
F: FnOnce(WebConfig) -> WebConfig,
|
||||||
{
|
{
|
||||||
wasm_bindgen_futures::spawn_local(run_with_props(root, root_props))
|
let config = config(WebConfig::default());
|
||||||
|
let fut = run_with_props(root, root_props, config);
|
||||||
|
|
||||||
|
wasm_bindgen_futures::spawn_local(async {
|
||||||
|
match fut.await {
|
||||||
|
Ok(_) => log::error!("Your app completed running... somehow?"),
|
||||||
|
Err(e) => log::error!("Your app crashed! {}", e),
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This method is the primary entrypoint for Websys Dioxus apps. Will panic if an error occurs while rendering.
|
/// This method is the primary entrypoint for Websys Dioxus apps. Will panic if an error occurs while rendering.
|
||||||
/// See DioxusErrors for more information on how these errors could occour.
|
/// See DioxusErrors for more information on how these errors could occour.
|
||||||
///
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
/// ```ignore
|
/// ```ignore
|
||||||
/// fn main() {
|
/// fn main() {
|
||||||
/// wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
|
/// wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
|
||||||
|
@ -42,29 +70,19 @@ where
|
||||||
///
|
///
|
||||||
/// Run the app to completion, panicing if any error occurs while rendering.
|
/// Run the app to completion, panicing if any error occurs while rendering.
|
||||||
/// Pairs well with the wasm_bindgen async handler
|
/// Pairs well with the wasm_bindgen async handler
|
||||||
pub async fn run(root: FC<()>) {
|
pub async fn run_with_props<T: Properties + 'static>(
|
||||||
run_with_props(root, ()).await;
|
root: FC<T>,
|
||||||
}
|
root_props: T,
|
||||||
|
cfg: WebConfig,
|
||||||
pub async fn run_with_props<T: Properties + 'static>(root: FC<T>, root_props: T) {
|
) -> Result<()> {
|
||||||
let dom = VirtualDom::new_with_props(root, root_props);
|
let dom = VirtualDom::new_with_props(root, root_props);
|
||||||
event_loop(dom).await.expect("Event loop failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn event_loop(mut internal_dom: VirtualDom) -> dioxus_core::error::Result<()> {
|
let root_el = load_document().get_element_by_id("dioxus_root").unwrap();
|
||||||
use wasm_bindgen::JsCast;
|
let mut websys_dom = dom::WebsysDom::new(root_el, cfg);
|
||||||
|
|
||||||
let root = prepare_websys_dom();
|
// let mut edits = Vec::new();
|
||||||
let root_node = root.clone().dyn_into::<Node>().unwrap();
|
// internal_dom.rebuild(&mut websys_dom, &mut edits)?;
|
||||||
|
// websys_dom.process_edits(&mut edits);
|
||||||
let mut websys_dom = crate::new::WebsysDom::new(root.clone());
|
|
||||||
|
|
||||||
websys_dom.stack.push(root_node.clone());
|
|
||||||
websys_dom.stack.push(root_node);
|
|
||||||
|
|
||||||
let mut edits = Vec::new();
|
|
||||||
internal_dom.rebuild(&mut websys_dom, &mut edits)?;
|
|
||||||
websys_dom.process_edits(&mut edits);
|
|
||||||
|
|
||||||
log::info!("Going into event loop");
|
log::info!("Going into event loop");
|
||||||
loop {
|
loop {
|
||||||
|
@ -105,11 +123,14 @@ pub async fn event_loop(mut internal_dom: VirtualDom) -> dioxus_core::error::Res
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare_websys_dom() -> Element {
|
fn iter_node_list() {}
|
||||||
web_sys::window()
|
|
||||||
.expect("should have access to the Window")
|
// struct NodeListIter {
|
||||||
.document()
|
// node_list: NodeList,
|
||||||
.expect("should have access to the Document")
|
// }
|
||||||
.get_element_by_id("dioxusroot")
|
// impl Iterator for NodeListIter {}
|
||||||
.unwrap()
|
|
||||||
|
struct HydrationNode {
|
||||||
|
id: usize,
|
||||||
|
node: Node,
|
||||||
}
|
}
|
||||||
|
|
41
packages/web/src/nodeslab.rs
Normal file
41
packages/web/src/nodeslab.rs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
use std::ops::{Index, IndexMut};
|
||||||
|
|
||||||
|
use web_sys::Node;
|
||||||
|
|
||||||
|
pub struct NodeSlab {
|
||||||
|
nodes: Vec<Option<Node>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NodeSlab {
|
||||||
|
pub fn new(capacity: usize) -> NodeSlab {
|
||||||
|
NodeSlab {
|
||||||
|
nodes: Vec::with_capacity(capacity),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert_and_extend(&mut self, node: Node, id: usize) {
|
||||||
|
if id > self.nodes.len() * 3 {
|
||||||
|
panic!("Trying to insert an element way too far out of bounds");
|
||||||
|
}
|
||||||
|
|
||||||
|
if id < self.nodes.len() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Index<usize> for NodeSlab {
|
||||||
|
type Output = Option<Node>;
|
||||||
|
fn index(&self, index: usize) -> &Self::Output {
|
||||||
|
&self.nodes[index]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IndexMut<usize> for NodeSlab {
|
||||||
|
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||||
|
if index >= self.nodes.len() * 3 {
|
||||||
|
panic!("Trying to mutate an element way too far out of bounds");
|
||||||
|
}
|
||||||
|
if index > self.nodes.len() {
|
||||||
|
self.nodes.resize_with(index, || None);
|
||||||
|
}
|
||||||
|
&mut self.nodes[index]
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue