Feat: update websys with lifecycle

This commit is contained in:
Jonathan Kelley 2021-02-24 02:22:05 -05:00
parent 83451372aa
commit 4d01455729
6 changed files with 76 additions and 179 deletions

View file

@ -1,3 +1,3 @@
{ {
"rust-analyzer.inlayHints.enable": true "rust-analyzer.inlayHints.enable": false
} }

View file

@ -51,6 +51,8 @@
- [x] (Core) Arena allocate VNodes - [x] (Core) Arena allocate VNodes
- [x] (Core) Allow VNodes to borrow arena contents - [x] (Core) Allow VNodes to borrow arena contents
- [x] (Core) Introduce the VDOM and patch API for 3rd party renderers - [x] (Core) Introduce the VDOM and patch API for 3rd party renderers
- [ ] (Core) Implement lifecycle - [x] (Core) Implement lifecycle
- [ ] (Core) Implement an event system - [x] (Core) Implement an event system
- [ ] (Core) Implement child nodes, scope creation
- [ ] (Core) Implement dirty tagging and compression

View file

@ -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_core::prelude::*;
use dioxus_web::*; use dioxus_web::*;
fn main() { fn main() {
// Enable logging wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); }
// Route panic as console_log static App: FC<()> = |ctx, _| {
console_error_panic_hook::set_once(); ctx.view(html! {
// Render the app
WebsysRenderer::simple_render(html! {
<div> <div>
<div class="flex items-center justify-center flex-col"> <div class="flex items-center justify-center flex-col">
<div class="font-bold text-xl"> "Count is {}" </div> <div class="font-bold text-xl"> "Count is {}" </div>
@ -19,5 +16,5 @@ fn main() {
<button onclick={move |_| log::info!("button2 clicked!")}> "decrement" </button> <button onclick={move |_| log::info!("button2 clicked!")}> "decrement" </button>
</div> </div>
</div> </div>
}); })
} };

View file

@ -8,45 +8,40 @@ use dioxus_web::*;
fn main() { fn main() {
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once(); 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! { WebsysRenderer::new(|ctx, _| {
<div> ctx.view(html! {
<div class="flex items-center justify-center flex-col"> <div>
<div class="flex items-center justify-center"> <div class="flex items-center justify-center flex-col">
<div class="flex flex-col bg-white rounded p-4 w-full max-w-xs"> <div class="flex items-center justify-center">
// Title <div class="flex flex-col bg-white rounded p-4 w-full max-w-xs">
<div class="font-bold text-xl"> // Title
"Jon's awesome site!!11" <div class="font-bold text-xl">
</div> "Jon's awesome site!!11"
// Subtext / description
<div class="text-sm text-gray-500">
"He worked so hard on it :)"
</div>
<div class="flex flex-row items-center justify-center mt-6">
// Main number
<div class="font-medium text-6xl">
"1337"
</div> </div>
</div>
// Try another // Subtext / description
<div class="flex flex-row justify-between mt-6"> <div class="text-sm text-gray-500">
// <a href=format!("http://localhost:8080/fib/{}", other_fib_to_try) class="underline"> "He worked so hard on it :)"
"Legit made my own React" </div>
// </a>
<div class="flex flex-row items-center justify-center mt-6">
// Main number
<div class="font-medium text-6xl">
"1337"
</div>
</div>
// Try another
<div class="flex flex-row justify-between mt-6">
// <a href=format!("http://localhost:8080/fib/{}", other_fib_to_try) class="underline">
"Legit made my own React"
// </a>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> })
}); });
} }

View file

@ -52,9 +52,8 @@ impl Stack {
); );
match self.list.last() { match self.list.last() {
Some(a) => a, 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]
} }
} }

View file

@ -1,26 +1,12 @@
//! Dioxus WebSys //! Dioxus WebSys
//! -------------- //! --------------
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser. //! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys.
//!
//! While it is possible to render a single component directly, it is not possible to render component trees. For these, use fxhash::FxHashMap;
//! 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! {<div> "Hello world" </div>});
//! let output = renderer.render();
//! assert_eq!(output, "<div>Hello World</div>");
//! }
//! ```
//!
//! The `WebsysRenderer` is particularly useful when needing to cache a Virtual DOM in between requests
use web_sys::{window, Document, Element, Event, Node}; use web_sys::{window, Document, Element, Event, Node};
use dioxus::prelude::VElement; use dioxus::prelude::VElement;
// use dioxus::{patch::Patch, prelude::VText};
// use dioxus::{patch::Patch, prelude::VText};
pub use dioxus_core as dioxus; pub use dioxus_core as dioxus;
use dioxus_core::{ use dioxus_core::{
events::EventTrigger, events::EventTrigger,
@ -33,15 +19,24 @@ use futures::{
use mpsc::UnboundedSender; use mpsc::UnboundedSender;
pub mod interpreter; pub mod interpreter;
use interpreter::PatchMachine; use interpreter::PatchMachine;
/// The `WebsysRenderer` provides a way of rendering a Dioxus Virtual DOM to the browser's DOM. /// 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 /// Under the hood, we leverage WebSys and interact directly with the DOM
/// ///
pub struct WebsysRenderer { pub struct WebsysRenderer {
internal_dom: VirtualDom, internal_dom: VirtualDom,
// this should be a component index
event_map: FxHashMap<(u32, u32), u32>,
} }
impl WebsysRenderer { 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. /// 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. /// 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. /// Create a new text renderer from an existing Virtual DOM.
/// This will progress the existing VDom's events to completion. /// This will progress the existing VDom's events to completion.
pub fn from_vdom(dom: VirtualDom) -> Self { 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<()> { pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
// Connect the listeners to the event loop
let (mut sender, mut receiver) = mpsc::unbounded::<EventTrigger>(); let (mut sender, mut receiver) = mpsc::unbounded::<EventTrigger>();
// Send the start event
sender sender
.send(EventTrigger::start_event()) .send(EventTrigger::start_event())
.await .await
.expect("Should not fail"); .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 // Event loop waits for the receiver to finish up
// TODO! Connect the sender to the virtual dom's suspense system // TODO! Connect the sender to the virtual dom's suspense system
// Suspense is basically an external event that can force renders to specific nodes // Suspense is basically an external event that can force renders to specific nodes
while let Some(event) = receiver.next().await { while let Some(event) = receiver.next().await {
// event is triggered let diffs = self.internal_dom.progress_with_event(event)?;
// relevant listeners are ran for edit in &diffs {
// internal state is modified, components are tagged for changes patch_machine.handle_edit(edit);
match self.internal_dom.progress_with_event(event) {
Err(_) => {}
Ok(_) => {} // Ok(_) => render_diffs(),
} }
// waiting for next event to arrive from the external triggers
} }
Ok(()) Ok(()) // should actually never return from this, should be an error
}
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! { <div> </div> }(&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);
}
} }
} }
fn prepare_websys_dom() { fn prepare_websys_dom() -> Element {
// Initialize the container on the dom // Initialize the container on the dom
// Hook up the body as the root component to render tinto // Hook up the body as the root component to render tinto
let window = web_sys::window().expect("should have access to the Window"); let window = web_sys::window().expect("should have access to the Window");
@ -182,6 +108,8 @@ fn prepare_websys_dom() {
.as_ref(), .as_ref(),
) )
.expect("should append child OK"); .expect("should append child OK");
container.clone()
} }
// Progress the mount of the root component // Progress the mount of the root component
@ -213,39 +141,15 @@ mod tests {
env::set_var("RUST_LOG", "trace"); env::set_var("RUST_LOG", "trace");
pretty_env_logger::init(); pretty_env_logger::init();
log::info!("Hello!"); log::info!("Hello!");
let renderer = WebsysRenderer::simple_render(html! {
<div>
"Hello world"
<button onclick={move |_| log::info!("button1 clicked!")}> "click me" </button>
<button onclick={move |_| log::info!("button2 clicked!")}> "click me" </button>
</div>
});
}
#[test] wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|ctx, _| {
fn complex_patch() { ctx.view(html! {
env::set_var("RUST_LOG", "trace");
pretty_env_logger::init();
log::info!("Hello!");
let renderer = WebsysRenderer::complex_render(
html! {
<div> <div>
"Hello world" "Hello world"
<div> <button onclick={move |_| log::info!("button1 clicked!")}> "click me" </button>
<h1> "Heading" </h1> <button onclick={move |_| log::info!("button2 clicked!")}> "click me" </button>
</div>
</div> </div>
}, })
html! { }))
<div>
"Hello world"
"Hello world"
"Hello world"
<div>
<h1> "Heading" </h1>
</div>
</div>
},
);
} }
} }