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/.vscode/spellright.dict b/.vscode/spellright.dict
index 5c8da440e..ef16ea2de 100644
--- a/.vscode/spellright.dict
+++ b/.vscode/spellright.dict
@@ -11,3 +11,8 @@ transpiling
fullstack
Serverless
vnode
+fn
+Dodrio
+fc
+Jemalloc
+Cloudfare
diff --git a/README.md b/README.md
index 159574755..9e6c7df58 100644
--- a/README.md
+++ b/README.md
@@ -54,30 +54,34 @@ Here, the `Context` object is used to access hook state, create subscriptions, a
```rust
// A very terse component!
#[fc]
-async fn Example(ctx: &Context, name: String) -> VNode {
+fn Example(ctx: &Context, name: String) -> VNode {
html! {
"Hello {name}!"
}
}
// or
#[functional_component]
-static Example: FC = |ctx, name: String| async html! {
"Hello {name}!"
};
+static Example: FC = |ctx, name: String| html! {
"Hello {name}!"
};
```
The final output of components must be a tree of VNodes. We provide an html macro for using JSX-style syntax to write these, though, you could use any macro, DSL, templating engine, or the constructors directly.
## Concurrency
-In Dioxus, components are asynchronous and can their rendering can be paused at any time by awaiting a future. Hooks can combine this functionality with the Context and Subscription APIs to craft dynamic and efficient user experiences.
+In Dioxus, VNodes are asynchronous and can their rendering can be paused at any time by awaiting a future. Hooks can combine this functionality with the Context and Subscription APIs to craft dynamic and efficient user experiences.
```rust
-async fn user_data(ctx: &Context<()>) -> VNode {
- let Profile { name, birthday, .. } = fetch_data().await;
- html! {
-
- }
+fn user_data(ctx: &Context<()>) -> VNode {
+ // Register this future as a task
+ use_suspense(ctx, async {
+ // Continue on with the component as usual, waiting for data to arrive
+ let Profile { name, birthday, .. } = fetch_data().await;
+ html! {
+
+ }
+ })
}
```
Asynchronous components are powerful but can also be easy to misuse as they pause rendering for the component and its children. Refer to the concurrent guide for information on how to best use async components.
@@ -112,6 +116,7 @@ Dioxus LiveHost is a paid service dedicated to hosting your Dioxus Apps - whethe
- Analytics
- Lighthouse optimization
- On-premise support (see license terms)
+- Cloudfare/DDoS protection integrations
For small teams, LiveHost is free. Check out the pricing page to see if Dioxus LiveHost is good your team.
@@ -121,9 +126,9 @@ We use the dedicated `dioxus-cli` to build and test dioxus web-apps. This can ru
Alternatively, `trunk` works but can't run examples.
- tide_ssr: Handle an HTTP request and return an html body using the html! macro. `cargo run --example tide_ssr`
-- doc_generator: Use dioxus ssr to generate the website and docs. `cargo run --example doc_generator`
+- doc_generator: Use dioxus SSR to generate the website and docs. `cargo run --example doc_generator`
- fc_macro: Use the functional component macro to build terse components. `cargo run --example fc_macro`
-- hello_web: Start a simple wasm app. Requires a webpacker like dioxus-cli or trunk `cargo run --example hello`
+- hello_web: Start a simple wasm app. Requires a web packer like dioxus-cli or trunk `cargo run --example hello`
- router: `cargo run --example router`
- tide_ssr: `cargo run --example tide_ssr`
- webview: Use liveview to bridge into a webview context for a simple desktop application. `cargo run --example webview`
diff --git a/SOLVEDPROBLEMS.md b/SOLVEDPROBLEMS.md
new file mode 100644
index 000000000..bf9591eca
--- /dev/null
+++ b/SOLVEDPROBLEMS.md
@@ -0,0 +1,159 @@
+# Solved problems while building Dioxus
+
+## FC Macro for more elegant components
+Originally the syntax of the FC macro was meant to look like:
+
+```rust
+#[fc]
+fn example(ctx: &Context<{ name: String }>) -> VNode {
+ html! {
"Hello, {name}!"
}
+}
+```
+
+`Context` was originally meant to be more obviously parameterized around a struct definition. However, while this works with rustc, this does not work well with Rust Analyzer. Instead, the new form was chosen which works with Rust Analyzer and happens to be more ergonomic.
+
+```rust
+#[fc]
+fn example(ctx: &Context, name: String) -> VNode {
+ html! {
"Hello, {name}!"
}
+}
+```
+
+## Anonymous Components
+
+In Yew, the function_component macro turns a struct into a Trait `impl` with associated type `props`. Like so:
+
+```rust
+#[derive(Properties)]
+struct Props {
+ // some props
+}
+
+struct SomeComponent;
+impl FunctionProvider for SomeComponent {
+ type TProps = Props;
+
+ fn run(&mut self, props: &Props) -> Html {
+ // user's functional component goes here
+ }
+}
+
+pub type SomeComponent = FunctionComponent;
+```
+By default, the underlying component is defined as a "functional" implementation of the `Component` trait with all the lifecycle methods. In Dioxus, we don't allow components as structs, and instead take a "hooks-only" approach. However, we still need props. To get these without dealing with traits, we just assume functional components are modules. This lets the macros assume an FC is a module, and `FC::Props` is its props and `FC::component` is the component. Yew's method does a similar thing, but with associated types on traits.
+
+Perhaps one day we might use traits instead.
+
+The FC macro needs to work like this to generate a final module signature:
+
+```rust
+// "Example" can be used directly
+// The "associated types" are just children of the module
+// That way, files can just be components (yay, no naming craziness)
+mod Example {
+ // Associated metadata important for liveview
+ static NAME: &'static str = "Example";
+
+ struct Props {
+ name: String
+ }
+
+ fn component(ctx: &Context) -> VNode {
+ html! {
}
+}
+```
+
+These definitions might be ugly, but the fc macro cleans it all up. The fc macro also allows some configuration
+
+```rust
+#[fc]
+fn example(ctx: &Context, name: String) -> VNode {
+ html! {
}
+ }
+}
+```
+
+
+
+## Live Components
+Live components are a very important part of the Dioxus ecosystem. However, the goal with live components was to constrain their implementation purely to APIs available through Context (concurrency, context, subscription).
+
+From a certain perspective, live components are simply server-side-rendered components that update when their props change. Here's more-or-less how live components work:
+
+```rust
+#[fc]
+static LiveFc: FC = |ctx, refresh_handler: impl FnOnce| {
+ // Grab the "live context"
+ let live_context = ctx.use_context::();
+
+ // Ensure this component is registered as "live"
+ live_context.register_scope();
+
+ // send our props to the live context and get back a future
+ let vnodes = live_context.request_update(ctx);
+
+ // Suspend the rendering of this component until the vnodes are finished arriving
+ // Render them once available
+ ctx.suspend(async move {
+ let output = vnodes.await;
+
+ // inject any listener handles (ie button clicks, views, etc) to the parsed nodes
+ output[1].add_listener("onclick", refresh_handler);
+
+ // Return these nodes
+ // Nodes skip diffing and go straight to rendering
+ output
+ })
+}
+```
+
+Notice that LiveComponent receivers (the client-side interpretation of a LiveComponent) are simply suspended components waiting for updates from the LiveContext (the context that wraps the app to make it "live").
+
+## Allocation Strategy (ie incorporating Dodrio research)
+----
+The `VNodeTree` type is a very special type that allows VNodes to be created using a pluggable allocator. The html! macro creates something that looks like:
+
+```rust
+static Example: FC<()> = |ctx| {
+ html! {
"blah"
}
+};
+
+// expands to...
+
+static Example: FC<()> = |ctx| {
+ // This function converts a Fn(allocator) -> VNode closure to a DomTree struct that will later be evaluated.
+ html_macro_to_vnodetree(move |allocator| {
+ let mut node0 = allocator.alloc(VElement::div);
+ let node1 = allocator.alloc_text("blah");
+ node0.children = [node1];
+ node0
+ })
+};
+```
+At runtime, the new closure is created that captures references to `ctx`. Therefore, this closure can only be evaluated while `ctx` is borrowed and in scope. However, this closure can only be evaluated with an `allocator`. Currently, the global and Bumpalo allocators are available, though in the future we will add support for creating a VDom with any allocator or arena system (IE Jemalloc, wee-alloc, etc). The intention here is to allow arena allocation of VNodes (no need to box nested VNodes). Between diffing phases, the arena will be overwritten as old nodes are replaced with new nodes. This saves allocation time and enables bump allocators.
+
diff --git a/examples/fc_macro.rs b/examples/fc_macro.rs
index cff3de0e9..682f6444c 100644
--- a/examples/fc_macro.rs
+++ b/examples/fc_macro.rs
@@ -20,3 +20,76 @@ fn main() {
fn example(ctx: &Context, name: String) -> VNode {
html! {
"Hello, {name}!"
}
}
+
+/*
+TODO
+
+/// The macro can also be applied to statics in order to make components less verbose
+/// The FC type automatically adds the inference, and property fields are automatically added as function arguments
+#[fc]
+static Example: FC = |ctx, name: String| {
+ html! {
"Hello, {name}!"
}
+};
+*/
+
+// This trait is not exposed to users directly, though they could manually implement this for struct-style components
+trait Comp {
+ type Props: Properties;
+ fn render(&self, ctx: &mut Context) -> VNode;
+ fn builder(&self) -> Self::Props;
+}
+trait Properties {
+ fn new() -> Self;
+}
+
+impl Comp for FC {
+ type Props = T;
+
+ fn render(&self, ctx: &mut Context) -> VNode {
+ let g = self(ctx);
+ g
+ }
+
+ fn builder(&self) -> T {
+ T::new()
+ }
+}
+
+// impl) -> VNode> Comp for F {
+// type Props = T;
+
+// fn render(&self, ctx: &mut Context) -> VNode {
+// let g = self(ctx);
+// g
+// }
+
+// fn builder(&self) -> T {
+// T::new()
+// }
+// }
+
+impl Properties for () {
+ fn new() -> Self {
+ ()
+ }
+}
+#[allow(unused, non_upper_case_globals)]
+static MyComp: FC<()> = |ctx| {
+ html! {
+
+
+ }
+};
+
+fn my_comp(ctx: &Context<()>) -> VNode {
+ todo!()
+}
+
+fn test() {
+ let mut ctx = Context { props: &() };
+ let f = MyComp.render(&mut ctx);
+ let props = MyComp.builder();
+
+ // let f = my_comp.render(&mut ctx);
+ // let props = my_comp.builder();
+}
diff --git a/examples/tide_ssr.rs b/examples/tide_ssr.rs
index 68cf9444f..409b86b37 100644
--- a/examples/tide_ssr.rs
+++ b/examples/tide_ssr.rs
@@ -8,6 +8,7 @@
//! thousands of simultaneous clients on minimal hardware.
use dioxus::prelude::*;
+use dioxus_ssr::TextRenderer;
use rand::Rng;
use tide::{Request, Response};
@@ -50,20 +51,17 @@ async fn fibsum(req: Request<()>) -> tide::Result {
// Generate another random number to try
let other_fib_to_try = rand::thread_rng().gen_range(1..42);
- let g = html! {
-
-
- };
-
- let nodes = html! {
+ let text = TextRenderer::<()>::to_text(html! {
+ // Header
+ // Body
@@ -97,11 +95,10 @@ async fn fibsum(req: Request<()>) -> tide::Result {
- };
+ });
Ok(Response::builder(203)
- .body(nodes.to_string())
- .header("custom-header", "value")
+ .body(text)
.content_type(tide::http::mime::HTML)
.build())
}
diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs
index 1327329b0..dabd6357a 100644
--- a/packages/core-macro/src/lib.rs
+++ b/packages/core-macro/src/lib.rs
@@ -186,6 +186,48 @@ impl Parse for FunctionComponent {
fn ensure_fn_block(item: Item) -> syn::Result {
match item {
Item::Fn(it) => Ok(it),
+ Item::Static(it) => {
+ let syn::ItemStatic {
+ attrs,
+ vis,
+ static_token,
+ mutability,
+ ident,
+ colon_token,
+ ty,
+ eq_token,
+ expr,
+ semi_token,
+ } = ⁢
+ match ty.as_ref() {
+ Type::BareFn(bare) => {}
+ // Type::Array(_)
+ // | Type::Group(_)
+ // | Type::ImplTrait(_)
+ // | Type::Infer(_)
+ // | Type::Macro(_)
+ // | Type::Never(_)
+ // | Type::Paren(_)
+ // | Type::Path(_)
+ // | Type::Ptr(_)
+ // | Type::Reference(_)
+ // | Type::Slice(_)
+ // | Type::TraitObject(_)
+ // | Type::Tuple(_)
+ // | Type::Verbatim(_)
+ _ => {}
+ };
+
+ // TODO: Add support for static block
+ // Ensure that the contents of the static block can be extracted to a function
+ // TODO: @Jon
+ // Decide if statics should be converted to functions (under the hood) or stay as statics
+ // They _do_ get promoted, but also have a &'static ref
+ Err(syn::Error::new_spanned(
+ it,
+ "`function_component` attribute not ready for statics",
+ ))
+ }
other => Err(syn::Error::new_spanned(
other,
"`function_component` attribute can only be applied to functions",
diff --git a/packages/core/.vscode/settings.json b/packages/core/.vscode/settings.json
new file mode 100644
index 000000000..2f418701b
--- /dev/null
+++ b/packages/core/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "rust-analyzer.inlayHints.enable": true
+}
\ No newline at end of file
diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml
index 7e9685a5f..410bb970c 100644
--- a/packages/core/Cargo.toml
+++ b/packages/core/Cargo.toml
@@ -10,13 +10,13 @@ description = "Core functionality for Dioxus - a concurrent renderer-agnostic Vi
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-generational-arena = "0.2.8"
-once_cell = "1.5.2"
dioxus-html-macro = { path = "../html-macro", version = "0.1.0" }
+# Backs some static data
+once_cell = "1.5.2"
-# web-sys = "0.3.46"
-# js-sys = "0.3.46"
-# typed-arena = "2.0.1"
-# virtual-dom-rs = { path = "../virtual-dom-rs" }
-# virtual-node = { path = "../virtual-node" }
+# Backs the scope creation and reutilization
+generational-arena = "0.2.8"
+
+# Bumpalo backs the VNode creation
+bumpalo = { version = "3.6.0", features = ["collections"] }
diff --git a/packages/core/README.md b/packages/core/README.md
index aa34e371e..53a934881 100644
--- a/packages/core/README.md
+++ b/packages/core/README.md
@@ -1,22 +1,39 @@
# Dioxus-core
-In the world of Rust UI + wasm UI toolkits, Dioxus is taking a fairly opinionated stance by being functional-only with hooks/context as the primary method of interacting with state.
+This is the core crate for the Dioxus Virtual DOM. This README will focus on the technical design and layout of this Virtual DOM implementation. If you want to read more about using Dioxus, then check out the Dioxus crate, documentation, and website.
-To focus on building an effective user experience,
+## Internals
+Dioxus-core builds off the many frameworks that came before it. Notably, Dioxus borrows these concepts:
+- React: hooks, concurrency, suspense
+- Dodrio: bump allocation / custom faster allocators, stack machine for fast changes
+- Percy: html! macro implementation and agnostic edit list
+- Yew: passion and inspiration ❤️
+## Goals
+We have big goals for Dioxus. The final implementation must:
+- Be **fast**. Allocators are typically slow in WASM/Rust, so we should have a smart way of allocating.
+- Be concurrent. Components should be able to pause rendering using a threading mechanism.
+- Support "broadcasting". Edit lists should be separate from the Renderer implementation.
+- Support SSR. VNodes should render to a string that can be served via a web server.
+- Be "live". Components should be able to be both server rendered and client rendered without needing frontend APIs.
+- Be modular. Components and hooks should be work anywhere without worrying about target platform.
+## Optimizations
-Sources
----
-percy: https://github.com/chinedufn/percy/blob/master/crates/virtual-dom-rs/src/patch/mod.rs
-yew:
-dodrio:
-rsx:
+- Support a pluggable allocation strategy that makes VNode creation **very** fast
+- Support lazy DomTrees (ie DomTrees that are not actually created when the view fn is ran)
+- Support advanced diffing strategies
-react:
-fre:
+## Design Quirks
+- Use of "Context" as a way of mitigating threading issues and the borrow checker. (JS relies on globals)
+- html! is lazy - needs to be used with a partner function to actually allocate the html.
+
+```rust
+let text = TextRenderer::render(html! {
"hello world"
});
+//
hello world
+```
diff --git a/packages/core/examples/dummy.rs b/packages/core/examples/dummy.rs
new file mode 100644
index 000000000..1cc6a8706
--- /dev/null
+++ b/packages/core/examples/dummy.rs
@@ -0,0 +1,49 @@
+// #![allow(unused, non_upper_case_globals)]
+// use bumpalo::Bump;
+// use dioxus_core::nodebuilder::*;
+// use dioxus_core::{nodes::DomTree, prelude::*};
+// use std::{collections::HashMap, future::Future, marker::PhantomData};
+
+fn main() {}
+
+// struct DummyRenderer {
+// alloc: Bump,
+// }
+
+// impl DummyRenderer {
+// // "Renders" a domtree by logging its children and outputs
+// fn render() {}
+
+// // Takes a domtree, an initial value, a new value, and produces the diff list
+// fn produce_diffs() {}
+// }
+
+// struct Props<'a> {
+// name: &'a str,
+// }
+
+// /// This component does "xyz things"
+// /// This is sample documentation
+// static Component: FC = |ctx| {
+// // This block mimics that output of the html! macro
+
+// DomTree::new(move |bump| {
+// // parse into RSX structures
+// // regurgetate as rust types
+
+// //
"Child 1" "Child 2"
+// div(bump)
+// .attr("class", "edit")
+// .child(text("Child 1"))
+// .child(text("Child 2"))
+// .finish()
+// })
+// };
+
+// /*
+// source
+// |> c1 -> VNode
+// |> c2 -> VNode
+// |> c3 -> VNode
+// |> c4 -> VNode
+// */
diff --git a/packages/core/examples/macrosrc.rs b/packages/core/examples/macrosrc.rs
new file mode 100644
index 000000000..893744763
--- /dev/null
+++ b/packages/core/examples/macrosrc.rs
@@ -0,0 +1,57 @@
+#![allow(unused, non_upper_case_globals)]
+use bumpalo::Bump;
+use dioxus_core::prelude::*;
+use dioxus_core::{nodebuilder::*, virtual_dom::DomTree};
+use std::{collections::HashMap, future::Future, marker::PhantomData};
+
+fn main() {}
+struct Props<'a> {
+ use_macro: bool,
+
+ // todo uh not static
+ // incorporate lifetimes into the thing somehow
+ text: &'a str,
+}
+
+fn Component(ctx: Context) -> VNode {
+ // Write asynchronous rendering code that immediately returns a "suspended" VNode
+ // The concurrent API will then progress this component when the future finishes
+ // You can suspend the entire component, or just parts of it
+ let product_list = ctx.suspend(async {
+ // Suspend the rendering that completes when the future is done
+ match fetch_data().await {
+ Ok(data) => html! {
},
+ Err(_) => html! {
},
+ }
+ });
+
+ // VNodes can be constructed via a builder or the html! macro
+ // However, both of these are "lazy" - they need to be evaluated (aka, "viewed")
+ // We can "view" them with Context for ultimate speed while inside components
+ if ctx.props.use_macro {
+ ctx.view(|bump| {
+ div(bump)
+ .attr("class", "edit")
+ .child(text("Hello"))
+ .child(text(ctx.props.text))
+ .finish()
+ })
+ } else {
+ // "View" indicates exactly *when* allocations take place, everything is lazy up to this point
+ ctx.view(html! {
+
+ // TODO!
+ // Get all this working again
+ //
"Products"
+ // // Subnodes can even be suspended
+ // // When completely rendered, they won't cause the component itself to re-render, just their slot
+ //
{product_list}
+
+ })
+ }
+}
+
+/// An example of a datafetching service
+async fn fetch_data() -> Result {
+ todo!()
+}
diff --git a/packages/core/examples/simple.rs b/packages/core/examples/simple.rs
deleted file mode 100644
index b44d95261..000000000
--- a/packages/core/examples/simple.rs
+++ /dev/null
@@ -1,103 +0,0 @@
-use std::future::Future;
-
-use dioxus_core::{prelude::*, virtual_dom::Properties};
-// use virtual_dom_rs::Closure;
-
-// Stop-gap while developing
-// Need to update the macro
-type VirtualNode = VNode;
-
-pub fn main() {
- let dom = VirtualDom::new_with_props(root);
- // let mut renderer = TextRenderer::new(dom);
- // let output = renderer.render();
-}
-
-#[derive(PartialEq)]
-struct Props {
- name: String,
-}
-impl Properties for Props {}
-
-fn root(ctx: &mut Context) -> VNode {
- // the regular html syntax
-
- // html! {
- //
- //
- //
- //
- //
- // }
-
- // or a manually crated vnode
- {
- let mut node_0 = VNode::element("div");
- {
- if let Some(ref mut element_node) = node_0.as_velement_mut() {
- // element_node.attrs.insert("blah", "blah");
- // element_node.children.extend(node_0.into_iter());
- }
- }
-
- let mut node_1: IterableNodes = ("Hello world!").into();
-
- node_1.first().insert_space_before_text();
- let mut node_2 = VNode::element("button");
-
- let node_3 = VNode::Component(VComponent::from_fn(
- Head,
- Props {
- name: "".to_string(),
- },
- ));
-
- {
- // let closure = Closure::wrap(Box::new(|_| {}) as Box);
- // let closure_rc = std::rc::Rc::new(closure);
- // node_2
- // .as_velement_mut()
- // .expect("Not an element")
- // .events
- // .0
- // .insert("onclick".to_string(), closure_rc);
- }
-
- if let Some(ref mut element_node) = node_0.as_velement_mut() {
- element_node.children.extend(node_1.into_iter());
- }
- if let Some(ref mut element_node) = node_0.as_velement_mut() {
- element_node.children.extend(node_2.into_iter());
- }
-
- node_0
- }
-}
-
-fn Head(ctx: &mut Context) -> VNode {
- html! {
- "Head Section"
- }
-}
-
-fn Body(ctx: &mut Context) -> VNode {
- html! {
- {"Footer Section"}
- }
-}
-
-fn Footer(ctx: &mut Context) -> VNode {
- let mut v = 10_i32;
- format!("Is this the real life, or is this fantasy, caught in a landslide");
-
- html! {
-
- }
-}
diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs
index 668cd2556..3f2c35978 100644
--- a/packages/core/src/lib.rs
+++ b/packages/core/src/lib.rs
@@ -65,6 +65,11 @@
//! - dioxus-liveview (SSR + StringRenderer)
//!
+pub mod nodebuilder;
+pub mod nodes;
+pub mod validation;
+pub mod virtual_dom;
+
/// Re-export common types for ease of development use.
/// Essential when working with the html! macro
///
@@ -72,1173 +77,20 @@
///
pub mod prelude {
use crate::nodes;
- pub use crate::virtual_dom::{Context, VirtualDom, FC};
- pub use nodes::iterables::IterableNodes;
+ pub use crate::virtual_dom::{Context, VirtualDom};
pub use nodes::*;
+ // pub use nodes::iterables::IterableNodes;
+
+ // FC is a shorthand for statics to easily declare a function that takes a context and produces a dom tree
+ // The DomTree is lazy and needs to be evaluated with an arena for any allocation to take place
+ // Hmmm, maybe get statics to work again?
+ // pub type FC<'a, T> = fn(&'a Context) -> VNode<'a>;
+ pub use crate::virtual_dom::{__domtree_helper, FC};
// TODO @Jon, fix this
// hack the VNode type until VirtualNode is fixed in the macro crate
- pub type VirtualNode = VNode;
+ pub type VirtualNode<'a> = VNode<'a>;
// Re-export from the macro crate
pub use dioxus_html_macro::html;
}
-
-/// The Dioxus Virtual Dom integrates an event system and virtual nodes to create reactive user interfaces.
-///
-/// This module includes all life-cycle related mechanics, including the virtual dom, scopes, properties, and lifecycles.
-pub mod virtual_dom {
- use super::*;
- use crate::nodes::VNode;
- use generational_arena::Arena;
-
- /// An integrated virtual node system that progresses events and diffs UI trees.
- /// Differences are converted into patches which a renderer can use to draw the UI.
- pub struct VirtualDom {
- /// All mounted components are arena allocated to make additions, removals, and references easy to work with
- /// A generational arean is used to re-use slots of deleted scopes without having to resize the underlying arena.
- components: Arena,
-
- /// Components generate lifecycle events
- event_queue: Vec,
- }
-
- impl VirtualDom {
- /// 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.
- /// The root component can access things like routing in its context.
- pub fn new(root: FC<()>) -> Self {
- Self::new_with_props(root)
- }
-
- /// Start a new VirtualDom instance with a dependent props.
- /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
- ///
- /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
- /// to toss out the entire tree.
- pub fn new_with_props(root: FC) -> Self {
- // Set a first lifecycle event to add the component
- let first_event = LifecycleEvent::Add;
-
- Self {
- components: Arena::new(),
- event_queue: vec![first_event],
- }
- }
- }
-
- enum LifecycleEvent {
- Add,
- }
-
- /// Functional Components leverage the type FC to
- pub type FC = fn(&mut Context) -> VNode;
-
- /// The Scope that wraps a functional component
- /// Scope's hold subscription, context, and hook information, however, it is allocated on the heap.
- pub struct Scope {
- hook_idx: i32,
- hooks: Vec<()>,
- }
-
- impl Scope {
- fn new() -> Self {
- Self {
- hook_idx: 0,
- hooks: vec![],
- }
- }
- }
-
- pub struct HookState {}
-
- /// 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.
- ///
- /// ```ignore
- /// #[derive(Properties)]
- /// struct Props {
- /// name: String
- /// }
- ///
- /// fn example(ctx: &Context) -> VNode {
- /// html! {
- ///
"Hello, {ctx.props.name}"
- /// }
- /// }
- /// ```
- pub struct Context<'source, T> {
- /// Direct access to the properties used to create this component.
- pub props: &'source T,
- }
-
- impl<'a, T> Context<'a, T> {
- /// Access the children elements passed into the component
- pub fn children(&self) -> Vec {
- todo!("Children API not yet implemented for component Context")
- }
-
- /// Access a parent context
- pub fn parent_context(&self) -> C {
- todo!("Context API is not ready yet")
- }
-
- /// Create a subscription that schedules a future render for the reference component
- pub fn subscribe(&self) -> impl FnOnce() -> () {
- todo!("Subscription API is not ready yet");
- || {}
- }
-
- /// The `Enter` API is an internal way of tracking the location in the Dioxus Virtual DOM
- /// The Context object consumes itself, producing a new lifetime without reallocating
- fn enter(self) -> Self {
- todo!("Entrance API for reusing Context object not ready yet")
- }
- }
-
- pub trait Properties: PartialEq + 'static {
- /*
- Props cannot contain references (as of today) :(
- Use a smart pointer
- */
- }
-
- // Auto derive for pure components
- impl Properties for () {}
-
- // Set up a derive macro
- // #[derive(Macro)]
-}
-
-/// Virtual Node Support
-/// VNodes represent lazily-constructed VDom trees that support diffing and event handlers.
-///
-/// These VNodes should be *very* cheap and *very* fast to construct - building a full tree should be insanely quick.
-///
-pub mod nodes {
- pub use vcomponent::VComponent;
- pub use velement::VElement;
- pub use vnode::VNode;
- pub use vtext::VText;
-
- /// Tools for the base unit of the virtual dom - the VNode
- /// VNodes are intended to be quickly-allocated, lightweight enum values.
- ///
- /// Components will be generating a lot of these very quickly, so we want to
- /// limit the amount of heap allocations / overly large enum sizes.
- mod vnode {
- use super::*;
-
- #[derive(PartialEq)]
- pub enum VNode {
- /// An element node (node type `ELEMENT_NODE`).
- Element(VElement),
- /// A text node (node type `TEXT_NODE`).
- ///
- /// Note: This wraps a `VText` instead of a plain `String` in
- /// order to enable custom methods like `create_text_node()` on the
- /// wrapped type.
- Text(VText),
-
- /// A User-defined componen node (node type COMPONENT_NODE)
- Component(VComponent),
- }
-
- impl VNode {
- /// Create a new virtual element node with a given tag.
- ///
- /// These get patched into the DOM using `document.createElement`
- ///
- /// ```ignore
- /// let div = VNode::element("div");
- /// ```
- pub fn element(tag: S) -> Self
- where
- S: Into,
- {
- VNode::Element(VElement::new(tag))
- }
-
- /// Create a new virtual text node with the given text.
- ///
- /// These get patched into the DOM using `document.createTextNode`
- ///
- /// ```ignore
- /// let div = VNode::text("div");
- /// ```
- pub fn text(text: S) -> Self
- where
- S: Into,
- {
- VNode::Text(VText::new(text.into()))
- }
-
- /// Return a [`VElement`] reference, if this is an [`Element`] variant.
- ///
- /// [`VElement`]: struct.VElement.html
- /// [`Element`]: enum.VNode.html#variant.Element
- pub fn as_velement_ref(&self) -> Option<&VElement> {
- match self {
- VNode::Element(ref element_node) => Some(element_node),
- _ => None,
- }
- }
-
- /// Return a mutable [`VElement`] reference, if this is an [`Element`] variant.
- ///
- /// [`VElement`]: struct.VElement.html
- /// [`Element`]: enum.VNode.html#variant.Element
- pub fn as_velement_mut(&mut self) -> Option<&mut VElement> {
- match self {
- VNode::Element(ref mut element_node) => Some(element_node),
- _ => None,
- }
- }
-
- /// Return a [`VText`] reference, if this is an [`Text`] variant.
- ///
- /// [`VText`]: struct.VText.html
- /// [`Text`]: enum.VNode.html#variant.Text
- pub fn as_vtext_ref(&self) -> Option<&VText> {
- match self {
- VNode::Text(ref text_node) => Some(text_node),
- _ => None,
- }
- }
-
- /// Return a mutable [`VText`] reference, if this is an [`Text`] variant.
- ///
- /// [`VText`]: struct.VText.html
- /// [`Text`]: enum.VNode.html#variant.Text
- pub fn as_vtext_mut(&mut self) -> Option<&mut VText> {
- match self {
- VNode::Text(ref mut text_node) => Some(text_node),
- _ => None,
- }
- }
-
- /// Used by html-macro to insert space before text that is inside of a block that came after
- /// an open tag.
- ///
- /// html! {
{world}
}
- ///
- /// So that we end up with
world
when we're finished parsing.
- pub fn insert_space_before_text(&mut self) {
- match self {
- VNode::Text(text_node) => {
- text_node.text = " ".to_string() + &text_node.text;
- }
- _ => {}
- }
- }
-
- /// Used by html-macro to insert space after braced text if we know that the next block is
- /// another block or a closing tag.
- ///
- /// html! {
{Hello} {world}
} ->
Hello world
- /// html! {
{Hello}
} ->
Hello
- ///
- /// So that we end up with
Hello world
when we're finished parsing.
- pub fn insert_space_after_text(&mut self) {
- match self {
- VNode::Text(text_node) => {
- text_node.text += " ";
- }
- _ => {}
- }
- }
- }
-
- // -----------------------------------------------
- // Convert from DOM elements to the primary enum
- // -----------------------------------------------
- impl From for VNode {
- fn from(other: VText) -> Self {
- VNode::Text(other)
- }
- }
-
- impl From for VNode {
- fn from(other: VElement) -> Self {
- VNode::Element(other)
- }
- }
-
- impl From<&str> for VNode {
- fn from(other: &str) -> Self {
- VNode::text(other)
- }
- }
-
- impl From for VNode {
- fn from(other: String) -> Self {
- VNode::text(other.as_str())
- }
- }
-
- // -----------------------------------------------
- // Allow VNodes to be iterated for map-based UI
- // -----------------------------------------------
- impl IntoIterator for VNode {
- type Item = VNode;
- // TODO: Is this possible with an array [VNode] instead of a vec?
- type IntoIter = ::std::vec::IntoIter;
-
- fn into_iter(self) -> Self::IntoIter {
- vec![self].into_iter()
- }
- }
-
- impl Into<::std::vec::IntoIter> for VNode {
- fn into(self) -> ::std::vec::IntoIter {
- self.into_iter()
- }
- }
-
- // -----------------------------------------------
- // Allow debug/display adherent to the HTML spec
- // -----------------------------------------------
- use std::fmt;
- impl fmt::Debug for VNode {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- VNode::Element(e) => write!(f, "Node::{:?}", e),
- VNode::Text(t) => write!(f, "Node::{:?}", t),
- VNode::Component(c) => write!(f, "Node::{:?}", c),
- }
- }
- }
-
- // Turn a VNode into an HTML string (delegate impl to variants)
- impl fmt::Display for VNode {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- VNode::Element(element) => write!(f, "{}", element),
- VNode::Text(text) => write!(f, "{}", text),
- VNode::Component(c) => write!(f, "{}", c),
- }
- }
- }
- }
-
- mod velement {
- use super::*;
- use std::collections::HashMap;
-
- #[derive(PartialEq)]
- pub struct VElement {
- /// The HTML tag, such as "div"
- pub tag: String,
-
- /// HTML attributes such as id, class, style, etc
- pub attrs: HashMap,
- // TODO: @JON Get this to not heap allocate, but rather borrow
- // pub attrs: HashMap<&'static str, &'static str>,
-
- // TODO @Jon, re-enable "events"
- //
- // /// Events that will get added to your real DOM element via `.addEventListener`
- // pub events: Events,
- pub events: HashMap,
-
- /// The children of this `VNode`. So a
structure would
- /// have a parent div and one child, em.
- pub children: Vec,
- }
-
- impl VElement {
- pub fn new(tag: S) -> Self
- where
- S: Into,
- {
- VElement {
- tag: tag.into(),
- attrs: HashMap::new(),
- events: HashMap::new(),
- // events: Events(HashMap::new()),
- children: vec![],
- }
- }
- }
-
- // -----------------------------------------------
- // Allow debug/display adherent to the HTML spec
- // -----------------------------------------------
- use std::fmt;
- impl fmt::Debug for VElement {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(
- f,
- "Element(<{}>, attrs: {:?}, children: {:?})",
- self.tag, self.attrs, self.children,
- )
- }
- }
-
- impl fmt::Display for VElement {
- // Turn a VElement and all of it's children (recursively) into an HTML string
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "<{}", self.tag).unwrap();
-
- for (attr, value) in self.attrs.iter() {
- write!(f, r#" {}="{}""#, attr, value)?;
- }
-
- write!(f, ">")?;
-
- for child in self.children.iter() {
- write!(f, "{}", child.to_string())?;
- }
-
- if !crate::validation::is_self_closing(&self.tag) {
- write!(f, "{}>", self.tag)?;
- }
-
- Ok(())
- }
- }
- }
-
- mod vtext {
- #[derive(PartialEq)]
- pub struct VText {
- pub text: String,
- }
-
- impl VText {
- /// Create an new `VText` instance with the specified text.
- pub fn new(text: S) -> Self
- where
- S: Into,
- {
- VText { text: text.into() }
- }
- }
-
- // -----------------------------------------------
- // Convert from primitives directly into VText
- // -----------------------------------------------
- impl From<&str> for VText {
- fn from(text: &str) -> Self {
- VText {
- text: text.to_string(),
- }
- }
- }
-
- impl From for VText {
- fn from(text: String) -> Self {
- VText { text }
- }
- }
-
- // -----------------------------------------------
- // Allow debug/display adherent to the HTML spec
- // -----------------------------------------------
- use std::fmt;
- impl fmt::Debug for VText {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "Text({})", self.text)
- }
- }
-
- // Turn a VText into an HTML string
- impl fmt::Display for VText {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "{}", self.text)
- }
- }
- }
-
- /// Virtual Components for custom user-defined components
- /// Only supports the functional syntax
- mod vcomponent {
- use crate::{
- prelude::{Context, FC},
- virtual_dom::Properties,
- };
- use std::{any::TypeId, fmt, future::Future};
-
- use super::VNode;
- #[derive(PartialEq)]
- pub struct VComponent {
- props_id: TypeId,
- // callerIDs are unsafely coerced to function pointers
- // This is okay because #1, we store the props_id and verify and 2# the html! macro rejects components not made this way
- //
- // Manually constructing the VComponent is not possible from 3rd party crates
- }
-
- impl VComponent {
- /// Construct a VComponent directly from a function component
- /// This should be *very* fast - we store the function pointer and props type ID. It should also be small on the stack
- pub fn from_fn(f: FC
, props: P) -> Self {
- // Props needs to be static
- let props_id = std::any::TypeId::of::
();
-
- // Cast the caller down
-
- Self { props_id }
- }
-
- /// Construct a VComponent directly from a concurrent component
- pub fn from_asyncfn(f: fn(&mut Context