diff --git a/.vscode/settings.json b/.vscode/settings.json index 2f418701b..80f51cff8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "rust-analyzer.inlayHints.enable": true + "rust-analyzer.inlayHints.enable": false } \ No newline at end of file diff --git a/notes/CHANGELOG.md b/notes/CHANGELOG.md index 9be0d2427..1f232c486 100644 --- a/notes/CHANGELOG.md +++ b/notes/CHANGELOG.md @@ -51,6 +51,8 @@ - [x] (Core) Arena allocate VNodes - [x] (Core) Allow VNodes to borrow arena contents - [x] (Core) Introduce the VDOM and patch API for 3rd party renderers -- [ ] (Core) Implement lifecycle -- [ ] (Core) Implement an event system +- [x] (Core) Implement lifecycle +- [x] (Core) Implement an event system +- [ ] (Core) Implement child nodes, scope creation +- [ ] (Core) Implement dirty tagging and compression diff --git a/packages/web/examples/basic.rs b/packages/web/examples/basic.rs index 3f3c5cb68..b1a52ab62 100644 --- a/packages/web/examples/basic.rs +++ b/packages/web/examples/basic.rs @@ -1,17 +1,14 @@ -//! basic example that renders a simple domtree to the page :) +//! Basic example that renders a simple domtree to the browser. use dioxus_core::prelude::*; use dioxus_web::*; fn main() { - // Enable logging - wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); + wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App)); +} - // Route panic as console_log - console_error_panic_hook::set_once(); - - // Render the app - WebsysRenderer::simple_render(html! { +static App: FC<()> = |ctx, _| { + ctx.view(html! {
"Count is {}"
@@ -19,5 +16,5 @@ fn main() {
- }); -} + }) +}; diff --git a/packages/web/examples/weather.rs b/packages/web/examples/weather.rs index d785fc5cc..c54fbb86c 100644 --- a/packages/web/examples/weather.rs +++ b/packages/web/examples/weather.rs @@ -8,45 +8,40 @@ use dioxus_web::*; fn main() { wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); console_error_panic_hook::set_once(); - // todo: wire up the component macro macro - // div! { class = "asd" onclick = move |_| {} - // div! { class = "asd" onclick = move |_| {} } - // h1! { "123456" "123456" "123456" } - // h2! { "123456" "123456" "123456" } - // h3! { "123456" "123456" "123456" } - // }; - WebsysRenderer::simple_render(html! { -
-
-
-
- // Title -
- "Jon's awesome site!!11" -
- - // Subtext / description -
- "He worked so hard on it :)" -
- -
- // Main number -
- "1337" + WebsysRenderer::new(|ctx, _| { + ctx.view(html! { +
+
+
+
+ // Title +
+ "Jon's awesome site!!11"
-
- // Try another -
- // - "Legit made my own React" - // + // Subtext / description +
+ "He worked so hard on it :)" +
+ +
+ // Main number +
+ "1337" +
+
+ + // Try another +
-
+ }) }); } diff --git a/packages/web/src/interpreter.rs b/packages/web/src/interpreter.rs index c603fa2af..747c26f5b 100644 --- a/packages/web/src/interpreter.rs +++ b/packages/web/src/interpreter.rs @@ -52,9 +52,8 @@ impl Stack { ); match self.list.last() { Some(a) => a, - None => panic!("should not happen"), + None => panic!("Called 'top' of an empty stack, make sure to push the root first"), } - // &self.list[self.list.len() - 1] } } diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 0aa52dbc6..2782ac691 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -1,26 +1,12 @@ //! Dioxus WebSys //! -------------- -//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser. -//! -//! While it is possible to render a single component directly, it is not possible to render component trees. For these, -//! an external renderer is needed to progress the component lifecycles. The `WebsysRenderer` shows how to use the Virtual DOM -//! API to progress these lifecycle events to generate a fully-mounted Virtual DOM instance which can be renderer in the -//! `render` method. -//! -//! ```ignore -//! fn main() { -//! let renderer = WebsysRenderer::<()>::new(|_| html! {
"Hello world"
}); -//! let output = renderer.render(); -//! assert_eq!(output, "
Hello World
"); -//! } -//! ``` -//! -//! The `WebsysRenderer` is particularly useful when needing to cache a Virtual DOM in between requests +//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys. + +use fxhash::FxHashMap; use web_sys::{window, Document, Element, Event, Node}; use dioxus::prelude::VElement; -// use dioxus::{patch::Patch, prelude::VText}; -// use dioxus::{patch::Patch, prelude::VText}; + pub use dioxus_core as dioxus; use dioxus_core::{ events::EventTrigger, @@ -33,15 +19,24 @@ use futures::{ use mpsc::UnboundedSender; pub mod interpreter; use interpreter::PatchMachine; + /// The `WebsysRenderer` provides a way of rendering a Dioxus Virtual DOM to the browser's DOM. /// Under the hood, we leverage WebSys and interact directly with the DOM - /// pub struct WebsysRenderer { internal_dom: VirtualDom, + + // this should be a component index + event_map: FxHashMap<(u32, u32), u32>, } impl WebsysRenderer { + /// Run the app to completion, panicing if any error occurs while rendering. + /// Pairs well with the wasm_bindgen async handler + pub async fn start(root: FC<()>) { + Self::new(root).run().await.expect("Virtual DOM failed"); + } + /// Create a new instance of the Dioxus Virtual Dom with no properties for the root component. /// /// This means that the root component must either consumes its own context, or statics are used to generate the page. @@ -60,109 +55,40 @@ impl WebsysRenderer { /// Create a new text renderer from an existing Virtual DOM. /// This will progress the existing VDom's events to completion. pub fn from_vdom(dom: VirtualDom) -> Self { - Self { internal_dom: dom } + Self { + internal_dom: dom, + event_map: FxHashMap::default(), + } } - /// Run the renderer, progressing any events that crop up - /// Yield on event handlers pub async fn run(&mut self) -> dioxus_core::error::Result<()> { - // Connect the listeners to the event loop let (mut sender, mut receiver) = mpsc::unbounded::(); - // Send the start event sender .send(EventTrigger::start_event()) .await .expect("Should not fail"); - prepare_websys_dom(); + let body = prepare_websys_dom(); + let mut patch_machine = PatchMachine::new(body.clone()); + let root_node = body.first_child().unwrap(); + patch_machine.stack.push(root_node); // Event loop waits for the receiver to finish up // TODO! Connect the sender to the virtual dom's suspense system // Suspense is basically an external event that can force renders to specific nodes while let Some(event) = receiver.next().await { - // event is triggered - // relevant listeners are ran - // internal state is modified, components are tagged for changes - - match self.internal_dom.progress_with_event(event) { - Err(_) => {} - Ok(_) => {} // Ok(_) => render_diffs(), + let diffs = self.internal_dom.progress_with_event(event)?; + for edit in &diffs { + patch_machine.handle_edit(edit); } - // waiting for next event to arrive from the external triggers } - Ok(()) - } - - pub fn simple_render(tree: impl for<'a> Fn(&'a Bump) -> VNode<'a>) { - let bump = Bump::new(); - - // Choose the body to render the app into - let window = web_sys::window().expect("should have access to the Window"); - let document = window - .document() - .expect("should have access to the Document"); - let body = document.body().unwrap(); - - // Build a dummy div - let container: &Element = body.as_ref(); - container.set_inner_html(""); - container - .append_child( - document - .create_element("div") - .expect("should create element OK") - .as_ref(), - ) - .expect("should append child OK"); - - // Create the old dom and the new dom - // The old is just an empty div, like the one we made above - let old = html! {
}(&bump); - let new = tree(&bump); - - // Build a machine that diffs doms - let mut diff_machine = DiffMachine::new(&bump); - diff_machine.diff_node(&old, &new); - - // Build a machine that patches doms - // In practice, the diff machine might be on a different computer, sending us patches - let mut patch_machine = PatchMachine::new(body.clone().into()); - - // need to make sure we push the root node onto the stack before trying to run anything - // this provides an entrance for the diffing machine to do its work - // Here, we grab the div out of the container (the body) to connect with the dummy div we made above - // This is because we don't support fragments (yet) - let root_node = container.first_child().unwrap(); - patch_machine.stack.push(root_node); - - // Consume the diff machine, generating the patch list - for patch in diff_machine.consume() { - patch_machine.handle_edit(&patch); - log::info!("Patch is {:?}", patch); - } - } - - pub fn complex_render( - tree1: impl for<'a> Fn(&'a Bump) -> VNode<'a>, - tree2: impl for<'a> Fn(&'a Bump) -> VNode<'a>, - ) { - let bump = Bump::new(); - - let old = tree1(&bump); - let new = tree2(&bump); - - let mut machine = DiffMachine::new(&bump); - machine.diff_node(&old, &new); - - for patch in machine.consume() { - println!("Patch is {:?}", patch); - } + Ok(()) // should actually never return from this, should be an error } } -fn prepare_websys_dom() { +fn prepare_websys_dom() -> Element { // Initialize the container on the dom // Hook up the body as the root component to render tinto let window = web_sys::window().expect("should have access to the Window"); @@ -182,6 +108,8 @@ fn prepare_websys_dom() { .as_ref(), ) .expect("should append child OK"); + + container.clone() } // Progress the mount of the root component @@ -213,39 +141,15 @@ mod tests { env::set_var("RUST_LOG", "trace"); pretty_env_logger::init(); log::info!("Hello!"); - let renderer = WebsysRenderer::simple_render(html! { -
- "Hello world" - - -
- }); - } - #[test] - fn complex_patch() { - env::set_var("RUST_LOG", "trace"); - pretty_env_logger::init(); - log::info!("Hello!"); - let renderer = WebsysRenderer::complex_render( - html! { + wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|ctx, _| { + ctx.view(html! {
"Hello world" -
-

"Heading"

-
+ +
- }, - html! { -
- "Hello world" - "Hello world" - "Hello world" -
-

"Heading"

-
-
- }, - ); + }) + })) } }