mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-26 06:00:21 +00:00
Feat: update websys with lifecycle
This commit is contained in:
parent
83451372aa
commit
4d01455729
6 changed files with 76 additions and 179 deletions
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"rust-analyzer.inlayHints.enable": true
|
"rust-analyzer.inlayHints.enable": false
|
||||||
}
|
}
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
});
|
})
|
||||||
}
|
};
|
||||||
|
|
|
@ -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>
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -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]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue