Feat: rename recoil to atoms

This commit is contained in:
Jonathan Kelley 2021-06-16 11:19:37 -04:00
parent 47e896038e
commit 36ea39ae30
29 changed files with 398 additions and 249 deletions

View file

@ -1,13 +1,37 @@
[package]
name = "dioxus"
version = "0.1.0"
authors = ["Jonathan Kelley <jkelleyrtp@gmail.com>"]
edition = "2018"
description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
"license" = "MIT/Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dioxus-core = { path="./packages/core", version="0.1.0" }
dioxus-core-macro = { path="./packages/core-macro", version="0.1.0" }
dioxus-hooks = { path="./packages/hooks", version="0.0.0" }
[features]
default = ["ssr", "hooks", "router", "web", "core", "atoms"]
atoms = []
core = []
ssr = []
hooks = []
router = []
web = []
desktop = []
[workspace]
# members = ["packages/core-macro"]
members = [
"packages/core-macro",
"packages/core",
"packages/web",
"packages/dioxus",
"packages/ssr",
"packages/docsite",
"packages/recoil",
"packages/atoms",
"packages/router",
]
# "packages/webview",

View file

@ -1,5 +1,5 @@
[package]
name = "recoil"
name = "atoms"
version = "0.0.0"
authors = ["Jonathan Kelley <jkelleyrtp@gmail.com>"]
edition = "2018"
@ -8,15 +8,15 @@ edition = "2018"
[dependencies]
anyhow = "1.0.40"
dioxus-core = { path = "../core" }
dioxus-core = { path="../core" }
thiserror = "1.0.24"
log = "0.4.14"
im-rc = "15.0.0"
futures = "0.3.15"
[dev-dependencies]
uuid = { version = "0.8.2", features = ["v4", "wasm-bindgen"] }
dioxus-web = { path = "../web" }
uuid = { version="0.8.2", features=["v4", "wasm-bindgen"] }
dioxus-web = { path="../web" }
wasm-bindgen-futures = "*"
wasm-logger = "0.2.0"

View file

@ -1,10 +1,10 @@
# Recoil.rs - Official global state management solution for Dioxus Apps
# Atom.rs - Official global state management solution for Dioxus Apps
Recoil.rs provides a global state management API for Dioxus apps built on the concept of "atomic state." Instead of grouping state together into a single bundle ALA Redux, Recoil provides individual building blocks of state called Atoms. These atoms can be set/get anywhere in the app and combined to craft complex state. Recoil should be easier to learn and more efficient than Redux. Recoil.rs is modeled after the Recoil.JS project.
Atom.rs provides a global state management API for Dioxus apps built on the concept of "atomic state." Instead of grouping state together into a single bundle ALA Redux, Atom provides individual building blocks of state called Atoms. These atoms can be set/get anywhere in the app and combined to craft complex state. Atom should be easier to learn and more efficient than Redux. Atom.rs is modeled after the Atom.JS project.
Recoil.rs is officially supported by the Dioxus team. By doing so, are are "planting our flag in the stand" for atomic state management instead of bundled (Redux-style) state management. Atomic state management fits well with the internals of Dioxus, meaning Recoil.rs state management will be faster, more efficient, and less sensitive to data races than Redux-style apps.
Atom.rs is officially supported by the Dioxus team. By doing so, we are "planting our flag in the sand" for atomic state management instead of bundled (Redux-style) state management. Atomic state management fits well with the internals of Dioxus and idiomatic Rust, meaning Atom.rs state management will be faster, more efficient, and less sensitive to data races than Redux-style apps.
Internally, Dioxus uses batching to speed up linear-style operations. Recoil.rs integrates with this batching optimization, making app-wide changes extremely fast. This way, Recoil.rs can be pushed significantly harder than Redux without the need to enable/disable debug flags to prevent performance slowdowns.
Internally, Dioxus uses batching to speed up linear-style operations. Atom.rs integrates with this batching optimization, making app-wide state changes extremely fast. This way, Atom.rs can be pushed significantly harder than Redux without the need to enable/disable debug flags to prevent performance slowdowns.
# Guide
@ -16,16 +16,16 @@ A simple atom of state is defined globally as a const:
const Light: Atom<&str> = |_| "Green";
```
This atom of state is initialized with a value of `"Green"`. The atom that is returned does not actually contain any values. Instead, the atom's key - which is automatically generated in this instance - is used in the context of a Recoil App.
This atom of state is initialized with a value of `"Green"`. The atom that is returned does not actually contain any values. Instead, the atom's key - which is automatically generated in this instance - is used in the context of a Atom App.
This is then later used in components like so:
```rust
fn App(ctx: Context<()>) -> VNode {
// The recoil root must be initialized at the top of the application before any use_recoil hooks
recoil::init_recoil_root(&ctx, |_| {});
// The Atom root must be initialized at the top of the application before any use_Atom hooks
Atoms::init_Atom_root(&ctx, |_| {});
let color = recoil::use_read(ctx, Light);
let color = Atoms::use_read(ctx, Light);
ctx.render(rsx!{
h1 {"Color of light: {color}"}
@ -37,8 +37,8 @@ Atoms are considered "Writable" objects since any consumer may also set the Atom
```rust
fn App(ctx: Context<()>) -> VNode {
let color = recoil::use_read(ctx, Light);
let set_color = recoil::use_write(ctx, Light);
let color = Atoms::use_read(ctx, Light);
let set_color = Atoms::use_write(ctx, Light);
rsx!{in ctx,
div {
h1{"Color: {color}"}
@ -54,7 +54,7 @@ fn App(ctx: Context<()>) -> VNode {
```rust
fn App(ctx: Context<()>) -> VNode {
let (color, set_color) = recoil::use_read_write(ctx, Light);
let (color, set_color) = Atoms::use_read_write(ctx, Light);
rsx!{in ctx,
div {
h1{"Color: {color}"}
@ -68,9 +68,9 @@ fn App(ctx: Context<()>) -> VNode {
Selectors are a concept popular in the JS world as a way of narrowing down a selection of state outside the VDOM lifecycle. Selectors have two functions: 1) summarize/narrow down some complex state and 2) memoize calculations.
Selectors are only `readable` - they cannot be set. This differs from RecoilJS where selectors _are_ `writable`. Selectors, as you might've guessed, "select" some data from atoms and other selectors.
Selectors are only `readable` - they cannot be set. This differs from AtomJS where selectors _are_ `writable`. Selectors, as you might've guessed, "select" some data from atoms and other selectors.
Selectors provide a `SelectorApi` which essentially exposes a read-only `RecoilApi`. They have the `get` method which allows any readable valued to be obtained for the purpose of generating a new value. A `Selector` may only return `'static` data, however `SelectorBorrowed` may return borrowed data.
Selectors provide a `SelectorApi` which essentially exposes a read-only `AtomApi`. They have the `get` method which allows any readable valued to be obtained for the purpose of generating a new value. A `Selector` may only return `'static` data, however `SelectorBorrowed` may return borrowed data.
returning static data:
@ -104,7 +104,7 @@ Returning borrowed data is generally frowned upon, but may be useful when used w
- If a selected value equals its previous selection (via PartialEq), the old value must be kept around to avoid evaluating subscribed components.
- It's unlikely that a change in a dependency's data will not change the selector's output.
In general, borrowed selectors introduce a slight memory overhead as they need to retain previous state to safely memoize downstream subscribers. The amount of state kept around scales with the amount of `gets` in a selector - though as the number of dependencies increase, the less likely the selector actually stays memoized. Recoil tries to optimize this behavior the best it can to balance component evaluations with memory overhead.
In general, borrowed selectors introduce a slight memory overhead as they need to retain previous state to safely memoize downstream subscribers. The amount of state kept around scales with the amount of `gets` in a selector - though as the number of dependencies increase, the less likely the selector actually stays memoized. Atom tries to optimize this behavior the best it can to balance component evaluations with memory overhead.
## Families
@ -126,7 +126,7 @@ Whenever you `select` on a `Family`, the ID of the entry is tracked. Other subsc
```rust
fn App(ctx: Context<()>) -> VNode {
let (rating, set_rating) = recoil::use_read_write(ctx, CloudRatings.select("AWS"));
let (rating, set_rating) = Atoms::use_read_write(ctx, CloudRatings.select("AWS"));
rsx!{in ctx,
div {
h1{ "AWS rating: {rating}" }

View file

@ -0,0 +1,28 @@
use dioxus::virtual_dom::VirtualDom;
use dioxus_core::prelude::*;
fn main() {
let mut dom = VirtualDom::new(App);
let edits = dom.rebuild().unwrap();
dbg!(edits);
}
static App: FC<()> = |ctx| {
//
ctx.render(rsx! {
div {
"abc"
"123"
}
})
};
static Fragment: FC<()> = |ctx| {
//
let children = ctx.children();
ctx.render(LazyNodes::new(move |c: &NodeCtx| {
//
let frag = c.bump().alloc(VFragment::new(None, children));
VNode::Fragment(frag)
}))
};

View file

@ -33,3 +33,31 @@ impl EmptyBuilder {
pub fn fc_to_builder<T: Properties>(_: FC<T>) -> T::Builder {
T::builder()
}
mod eliminate_bounds {
struct BNode<'a> {
_p: &'a (),
}
trait Broper {}
impl Broper for () {}
struct Bontext<'a, P: Broper + 'a> {
inner: &'a (),
props: P,
}
fn Bexample(c: Bontext<()>) -> BNode {
todo!()
}
struct MyBrops<'a> {
_i: &'a (),
}
impl<'a> Broper for MyBrops<'a> {}
fn Bexample2<'a>(c: Bontext<'a, MyBrops<'a>>) -> BNode {
todo!()
}
}

View file

@ -1,3 +1,14 @@
//! This module contains the stateful DiffMachine and all methods to diff VNodes, their properties, and their children.
//!
//! Notice:
//! ------
//!
//! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and modified to support Components,
//! Fragments, Suspense, and additional batching operations.
//!
//! Implementation Details:
//! -----------------------
//!
//! Diff the `old` node with the `new` node. Emits instructions to modify a
//! physical DOM node that reflects `old` into something that reflects `new`.
//!
@ -8,39 +19,18 @@
//!
//! The change list stack is in the same state when this function exits.
//!
//! ----
//! Further Reading and Thoughts
//! ----------------------------
//!
//! There are more ways of increasing diff performance here that are currently not implemented.
//! Additionally, the caching mechanism has also been tweaked.
//!
//! Instead of having "cached" nodes, each component is, by default, a cached node. This leads to increased
//! memory overhead for large numbers of small components, but we can optimize this by tracking alloc size over time
//! and shrinking bumps down if possible.
//!
//! Additionally, clean up of these components is not done at diff time (though it should), but rather, the diffing
//! proprogates removal lifecycle events for affected components into the event queue. It's not imperative that these
//! are ran immediately, but it should be noted that cleanup of components might be able to emit changes.
//!
//! This diffing only ever occurs on a component-by-component basis (not entire trees at once).
//!
//! Currently, the listener situation is a bit broken.
//! We aren't removing listeners (choosing to leak them instead) :(
//! Eventually, we'll set things up so add/remove listener is an instruction again
//!
//! A major assumption of this diff algorithm when combined with the ChangeList is that the Changelist will be
//! fresh and the event queue is clean. This lets us continue to batch edits together under the same ChangeList
//!
//! More info on how to improve this diffing algorithm:
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
use crate::{arena::ScopeArena, innerlude::*};
use bumpalo::Bump;
use fxhash::{FxHashMap, FxHashSet};
use generational_arena::Arena;
use std::{
cell::{RefCell, RefMut},
cmp::Ordering,
collections::VecDeque,
rc::{Rc, Weak},
sync::atomic::AtomicU32,
};
@ -207,7 +197,18 @@ impl<'a> DiffMachine<'a> {
todo!("Fragments not currently supported in diffing")
}
(_, VNode::Fragment(_)) => {
(VNode::Fragment(_), VNode::Fragment(_)) => {
todo!("Fragments not currently supported in diffing")
}
(old_n, VNode::Fragment(_)) => {
match old_n {
VNode::Element(_) => todo!(),
VNode::Text(_) => todo!(),
VNode::Fragment(_) => todo!(),
VNode::Suspended => todo!(),
VNode::Component(_) => todo!(),
}
todo!("Fragments not currently supported in diffing")
}
}

View file

@ -1,72 +1,11 @@
//! <div align="center">
//! <h1>🌗🚀 📦 Dioxus</h1>
//! <p>
//! <strong>A concurrent, functional, virtual DOM for Rust</strong>
//! </p>
//! </div>
//! Dioxus: a concurrent, functional, reactive virtual dom for any renderer in Rust.
//! Dioxus Core
//! ----------
//!
//! This crate aims to maintain a hook-based, renderer-agnostic framework for cross-platform UI development.
//!
//! ## Components
//! The base unit of Dioxus is the `component`. Components can be easily created from just a function - no traits required:
//! ```
//! use dioxus_core::prelude::*;
//!
//! #[derive(Properties)]
//! struct Props { name: String }
//!
//! fn Example(ctx: Context, props: &Props) -> VNode {
//! html! { <div> "Hello {ctx.name}!" </div> }
//! }
//! ```
//! Components need to take a "Context" parameter which is generic over some properties. This defines how the component can be used
//! and what properties can be used to specify it in the VNode output. Component state in Dioxus is managed by hooks - if you're new
//! to hooks, check out the hook guide in the official guide.
//!
//! Components can also be crafted as static closures, enabling type inference without all the type signature noise:
//! ```
//! use dioxus_core::prelude::*;
//!
//! #[derive(Properties)]
//! struct Props { name: String }
//!
//! static Example: FC<Props> = |ctx| {
//! html! { <div> "Hello {ctx.name}!" </div> }
//! }
//! ```
//!
//! If the properties struct is too noisy for you, we also provide a macro that converts variadic functions into components automatically.
//! Many people don't like the magic of proc macros, so this is entirely optional. Under-the-hood, we simply transplant the
//! function arguments into a struct, so there's very little actual magic happening.
//!
//! ```
//! use dioxus_core::prelude::*;
//!
//! #[derive_props]
//! static Example: FC = |ctx, name: &String| {
//! html! { <div> "Hello {name}!" </div> }
//! }
//! ```
//!
//! ## Hooks
//! Dioxus uses hooks for state management. Hooks are a form of state persisted between calls of the function component. Instead of
//! using a single struct to store data, hooks use the "use_hook" building block which allows the persistence of data between
//! function component renders. Each hook stores some data in a "memory cell" and needs to be called in a consistent order.
//! This means hooks "anything with `use_x`" may not be called conditionally.
//!
//! This allows functions to reuse stateful logic between components, simplify large complex components, and adopt more clear context
//! subscription patterns to make components easier to read.
//!
//! ## Supported Renderers
//! Instead of being tightly coupled to a platform, browser, or toolkit, Dioxus implements a VirtualDOM object which
//! can be consumed to draw the UI. The Dioxus VDOM is reactive and easily consumable by 3rd-party renderers via
//! the `Patch` object. See [Implementing a Renderer](docs/8-custom-renderer.md) and the `StringRenderer` classes for information
//! on how to implement your own custom renderer. We provide 1st-class support for these renderers:
//! - dioxus-desktop (via WebView)
//! - dioxus-web (via WebSys)
//! - dioxus-ssr (via StringRenderer)
//! - dioxus-liveview (SSR + StringRenderer)
//!
pub mod arena;
@ -104,7 +43,6 @@ pub(crate) mod innerlude {
pub type FC<P> = fn(Context<P>) -> VNode;
// Re-export the FC macro
pub use crate as dioxus;
pub use crate::nodebuilder as builder;
pub use dioxus_core_macro::{html, rsx};
}
@ -131,7 +69,6 @@ pub mod prelude {
pub use bumpalo::Bump;
// Re-export the FC macro
pub use crate as dioxus;
pub use crate::nodebuilder as builder;
// pub use dioxus_core_macro::fc;

View file

@ -251,87 +251,61 @@ impl<'a> EditMachine<'a> {
pub fn push_temporary(&mut self, temp: u32) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: push_temporary({})", temp);
self.emitter.push(Edit::PushTemporary { temp });
// self.emitter.push_temporary(temp);
}
pub fn remove_child(&mut self, child: usize) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: remove_child({})", child);
// self.emitter.remove_child(child as u32);
self.emitter.push(Edit::RemoveChild { n: child as u32 })
}
pub fn insert_before(&mut self) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: insert_before()");
// self.emitter.insert_before();
self.emitter.push(Edit::InsertBefore {})
}
pub fn set_text(&mut self, text: &'a str) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: set_text({:?})", text);
// self.emitter.set_text(text);
self.emitter.push(Edit::SetText { text });
// .set_text(text.as_ptr() as u32, text.len() as u32);
}
pub fn remove_self_and_next_siblings(&mut self) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: remove_self_and_next_siblings()");
self.emitter.push(Edit::RemoveSelfAndNextSiblings {});
// self.emitter.remove_self_and_next_siblings();
}
pub fn replace_with(&mut self) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: replace_with()");
self.emitter.push(Edit::ReplaceWith {});
// if let Some(id) = self.current_known {
// // update mapping
// self.emitter.push(Edit::MakeKnown{node: id});
// self.current_known = None;
// }
// self.emitter.replace_with();
self.emitter.push(Edit::ReplaceWith {});
}
pub fn set_attribute(&mut self, name: &'a str, value: &'a str, is_namespaced: bool) {
debug_assert!(self.traversal_is_committed());
// todo!()
if name == "class" && !is_namespaced {
// let class_id = self.ensure_string(value);
// let class_id = self.ensure_string(value);
// debug!("emit: set_class({:?})", value);
// self.emitter.set_class(class_id.into());
self.emitter.push(Edit::SetClass { class_name: value });
} else {
self.emitter.push(Edit::SetAttribute { name, value });
// let name_id = self.ensure_string(name);
// let value_id = self.ensure_string(value);
// debug!("emit: set_attribute({:?}, {:?})", name, value);
// self.state
// .emitter
// .set_attribute(name_id.into(), value_id.into());
}
}
pub fn remove_attribute(&mut self, name: &'a str) {
// todo!("figure out how to get this working with ensure string");
self.emitter.push(Edit::RemoveAttribute { name });
// self.emitter.remove_attribute(name);
// debug_assert!(self.traversal_is_committed());
// // debug!("emit: remove_attribute({:?})", name);
// let name_id = self.ensure_string(name);
// self.emitter.remove_attribute(name_id.into());
}
pub fn append_child(&mut self) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: append_child()");
self.emitter.push(Edit::AppendChild {});
// self.emitter.append_child();
}
pub fn create_text_node(&mut self, text: &'a str) {
@ -378,7 +352,6 @@ impl<'a> EditMachine<'a> {
pub fn remove_event_listener(&mut self, event: &'a str) {
debug_assert!(self.traversal_is_committed());
self.emitter.push(Edit::RemoveListener { event });
// debug!("emit: remove_event_listener({:?})", event);
}
pub fn save_known_root(&mut self, id: u32) {
@ -388,7 +361,6 @@ impl<'a> EditMachine<'a> {
pub fn load_known_root(&mut self, id: u32) {
log::debug!("emit: TraverseToKnown({:?})", id);
// self.current_known = Some(id);
self.emitter.push(Edit::TraverseToKnown { node: id })
}
}

View file

@ -1,13 +0,0 @@
[package]
name = "dioxus"
version = "0.1.0"
authors = ["Jonathan Kelley <jkelleyrtp@gmail.com>"]
edition = "2018"
description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
"license" = "MIT/Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dioxus-core = { path = "../core", version = "0.1.0" }
dioxus-core-macro = { path = "../core-macro", version = "0.1.0" }
dioxus-hooks = { path = "../hooks", version = "0.0.0" }

View file

@ -1,51 +0,0 @@
# Dioxus
This crate provides all the batteries required to build Dioxus apps.
Included in this crate is:
- Dioxus core
- Essential hooks (use_state, use_ref, use_reducer, etc)
- rsx! and html! macros
You'll still need to pull in a renderer to render the Dioxus VDOM. Any one of:
- dioxus-web (to run in WASM)
- dioxus-ssr (to run on the server or for static sites)
- dioxus-webview (to run on the desktop)
- dioxus-mobile (to run on iOS/Android)
Make sure dioxus and its renderer share the same major version; the renderers themselves rely on dioxus.
```toml
[dependencies]
dioxus = "0.2"
dioxus-web = "0.2"
```
```rust
use dioxus::*;
fn main() {
dioxus_web::start(|ctx| {
rsx!{in ctx, div { "Hello world" }}
})
}
```
Additionally, you'll want to look at other projects for more batteries
- essential-hooks (use_router, use_storage, use_cache, use_channel)
- Recoil.rs or Reducer.rs for state management
- 3D renderer (ThreeD), charts (Sciviz), game engine (Bevy)
Extra resources:
- The guide is available at:
- The crate docs are at:
- Video tutorials are at:
- Examples are at:
- Full clones are at:
- Community at: [www.reddit.com/r/dioxus]()
Happy building!

View file

@ -1,9 +0,0 @@
pub mod prelude {
pub use dioxus_core::prelude::*;
pub use dioxus_core_macro::fc;
}
// use dioxus_core::prelude::FC;
// Re-export core completely
pub use dioxus_core as core;

View file

@ -7,7 +7,7 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dioxus-ssr = { path = "../ssr" }
dioxus-ssr = { path="../ssr" }
pulldown-cmark = "0.8.0"
recoil = { path = "../recoil" }
codeblocks = { path = "../../../ecosystem-dioxus/syntec-dioxus/" }
atoms = { path="../atoms" }
codeblocks = { path="../../../ecosystem-dioxus/syntec-dioxus/" }

View file

@ -8,7 +8,7 @@ license = "MIT/Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dioxus-core = { path = "../core", version = "0.1.2" }
dioxus-core = { path="../core", version="0.1.2" }
js-sys = "0.3"
wasm-bindgen = "0.2.71"
lazy_static = "1.4.0"
@ -21,7 +21,7 @@ console_error_panic_hook = "0.1.6"
generational-arena = "0.2.8"
wasm-bindgen-test = "0.3.21"
once_cell = "1.7.2"
recoil = { path = "../recoil" }
atoms = { path="../atoms" }
# wasm-bindgen = "0.2.70"
# futures = "0.3.12"
@ -75,7 +75,7 @@ opt-level = 's'
crate-type = ["cdylib", "rlib"]
[dev-dependencies]
uuid = { version = "0.8.2", features = ["v4", "wasm-bindgen"] }
uuid = { version="0.8.2", features=["v4", "wasm-bindgen"] }
[[example]]
name = "todomvc"

View file

@ -6,7 +6,6 @@ use dioxus_core::{
prelude::ScopeIdx,
};
use fxhash::FxHashMap;
use log::debug;
use wasm_bindgen::{closure::Closure, JsCast};
use web_sys::{window, Document, Element, Event, HtmlInputElement, HtmlOptionElement, Node};
@ -38,6 +37,16 @@ pub(crate) struct PatchMachine {
pub(crate) events: EventDelegater,
pub(crate) current_known: Option<u32>,
// We need to make sure to add comments between text nodes
// We ensure that the text siblings are patched by preventing the browser from merging
// neighboring text nodes. Originally inspired by some of React's work from 2016.
// -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
// -> https://github.com/facebook/react/pull/5753
//
// `ptns` = Percy text node separator
// TODO
pub(crate) last_node_was_text: bool,
}
#[derive(Debug)]
@ -146,8 +155,6 @@ impl Stack {
pub fn pop(&mut self) -> Node {
let res = self.list.pop().unwrap();
// debug!("stack-pop: {:?}", res);
res
}
@ -156,10 +163,6 @@ impl Stack {
}
pub fn top(&self) -> &Node {
// log::info!(
// "Called top of stack with {} items remaining",
// self.list.len()
// );
match self.list.last() {
Some(a) => a,
None => panic!("Called 'top' of an empty stack, make sure to push the root first"),
@ -185,13 +188,13 @@ impl PatchMachine {
stack: Stack::with_capacity(20),
temporaries: Default::default(),
document,
last_node_was_text: false,
}
}
pub fn unmount(&mut self) {
self.stack.clear();
self.temporaries.clear();
// self.templates.clear();
}
pub fn start(&mut self) {
@ -205,11 +208,6 @@ impl PatchMachine {
self.temporaries.clear();
}
pub fn get_template(&self, id: CacheId) -> Option<&Node> {
todo!()
// self.templates.get(&id)
}
pub fn handle_edit(&mut self, edit: &Edit) {
match *edit {
// 0
@ -276,11 +274,7 @@ impl PatchMachine {
Edit::SetAttribute { name, value } => {
let node = self.stack.top();
log::debug!("setting attribute for element");
if let Some(node) = node.dyn_ref::<web_sys::Element>() {
// node.set_attribute(name, value).unwrap();
log::info!("setting attr {} {}", name, value);
node.set_attribute(name, value).unwrap();
}
if let Some(node) = node.dyn_ref::<HtmlInputElement>() {
@ -296,13 +290,10 @@ impl PatchMachine {
}
if let Some(node) = node.dyn_ref::<HtmlOptionElement>() {
log::debug!("el is html option element");
if name == "selected" {
node.set_selected(true);
}
}
log::debug!("el is none");
// if let Some(node) = node.dyn_ref::<web_sys::Namesp>() {}
}
// 4
@ -357,15 +348,35 @@ impl PatchMachine {
}
// 9
Edit::CreateTextNode { text } => self.stack.push(
self.document
.create_text_node(text)
.dyn_into::<Node>()
.unwrap(),
),
Edit::CreateTextNode { text } => {
//
// We ensure that the text siblings are patched by preventing the browser from merging
// neighboring text nodes. Originally inspired by some of React's work from 2016.
// -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
// -> https://github.com/facebook/react/pull/5753
//
// `ptns` = Percy text node separator
// TODO
if self.last_node_was_text {
let n = self
.document
.create_comment("ptns")
.dyn_into::<Node>()
.unwrap();
}
self.stack.push(
self.document
.create_text_node(text)
.dyn_into::<Node>()
.unwrap(),
);
self.last_node_was_text = true;
}
// 10
Edit::CreateElement { tag_name } => {
self.last_node_was_text = false;
let el = self
.document
.create_element(tag_name)
@ -438,6 +449,7 @@ impl PatchMachine {
// 14
Edit::CreateElementNs { tag_name, ns } => {
self.last_node_was_text = false;
let el = self
.document
.create_element_ns(Some(ns), tag_name)

220
src/lib.rs Normal file
View file

@ -0,0 +1,220 @@
//! <div align="center">
//! <h1>🌗🚀 📦 Dioxus</h1>
//! <p>
//! <strong>A concurrent, functional, virtual DOM for Rust</strong>
//! </p>
//! </div>
//! Dioxus: a concurrent, functional, reactive virtual dom for any renderer in Rust.
//!
//! This crate aims to maintain a hook-based, renderer-agnostic framework for cross-platform UI development.
//!
//! ## Overview and Goals
//! Dioxus' ultimate goal is to save you from writing new code when bringing your application to new platforms. We forsee
//! a future where WebApps, Mobile Apps, Desktop Apps, and even AR apps can be written in the same language, ecosystem,
//! and leverage the same platform-agnostic libraries.
//!
//! In this aim we chose to use a variety of techniques:
//! - We use a VirtualDOM to abstract the true renderer from application logic.
//! - We use functions as components to limit the API churn for greater stability.
//! - We use hooks as state to allow reusable logic across the whole ecosystem.
//! - We support an extensible and compile-time safe DSL for building interfaces.
//!
//! Our guiding stars (in order of priority):
//! - Ergonomics
//! - Reusability
//! - Speed and memory efficiency
//! - Safety
//!
//! ## Components
//! The base unit of Dioxus is the `component`. Components can be easily created from just a function - no traits or
//! proc macros required:
//!
//! ```
//! use dioxus::prelude::*;
//!
//! fn Example(ctx: Context<()>) -> VNode {
//! html! { <div> "Hello, world!" </div> }
//! }
//! ```
//! Components need to take a "Context" parameter which is generic over some properties. This defines how the component can be used
//! and what properties can be used to specify it in the VNode output. Components without properties may be generic over
//! `()`, and components with properties must declare their properties as a struct:
//!
//! ```
//! #[derive(Props)]
//! struct Props { name: String }
//!
//! fn Example(ctx: Context<Props>) -> VNode {
//! html! { <div> "Hello {ctx.props.name}!" </div> }
//! }
//! ```
//!
//! Props that are valid for the `'static` lifetime automatically get memoized by Diouxs. This means the component won't
//! re-render if its Props didn't change. However, Props that borrow data from their parent cannot be safely memoized, and
//! will always re-render if their parent changes. To borrow data from a parent, your component needs to add explicit lifetimes,
//! otherwise Rust will get confused about whether data is borrowed from either Props or Context. Since Dioxus manages
//! these lifetimes internally, Context and your Props must share the same lifetime:
//!
//! ```
//! #[derive(Props)]
//! struct Props<'a> { name: &'a str }
//!
//! fn Example<'a>(ctx: Context<'a, Props<'a>>) -> VNode {
//! html! { <div> "Hello {ctx.props.name}!" </div> }
//! }
//! ```
//!
//!
//!
//! The lifetimes might look a little messy, but are crucially important for Dioxus's efficiency and overall ergonimics.
//! Components can also be crafted as static closures, enabling type inference without all the type signature noise. However,
//! closure-style components cannot work with borrowed data, due to limitations in Rust's lifetime system.
//!
//! ```
//! #[derive(Props)]
//! struct Props { name: String }
//!
//! static Example: FC<Props> = |ctx| {
//! html! { <div> "Hello {ctx.props.name}!" </div> }
//! }
//! ```
//!
//! To use custom properties for components, you'll need to derive the `Props` trait for your properties. This trait
//! exposes a compile-time correct builder pattern (similar to typed-builder) that can be used in the `rsx!` and `html!`
//! macros to build components. Component props may have default fields notated by the `Default` attribute:
//!
//! ```
//! #[derive(Props)]
//! struct Props {
//! name: String
//!
//! #[props(default = false)]
//! checked: bool,
//!
//! #[props(default, setter(strip_option, into))]
//! title: Option<String>
//! }
//! ```
//!
//! These flags roughly follow that of typed-builder, though tweaked to support the `Props` usecase.
//!
//! ## Hooks and State
//! Dioxus uses hooks for state management. Hooks are a form of state persisted between calls of the function component.
//!
//! ```
//! static Example: FC<()> = |ctx| {
//! let (val, set_val) = use_state(&ctx, || 0);
//! ctx.render(rsx!(
//! button { onclick: move |_| set_val(val + 1) }
//! ))
//! }
//! ````
//!
//! Instead of using a single struct to represent a component and its state, hooks use the "use_hook" building block
//! which allows the persistence of data between function component renders. This primitive is exposed directly through
//! the `Context` item:
//! ```
//! fn my_hook<'a>(ctx: &impl Scoped<'a>) -> &'a String {
//! ctx.use_hook(
//! // Initializer stores a value
//! || String::new("stored_data"),
//!
//! // Runner returns the hook value every time the component is rendered
//! |hook| &*hook,
//!
//! // Cleanup runs after the component is unmounted
//! |hook| log::debug!("cleaning up hook with value {:#?}", hook)
//! )
//! }
//! ```
//! Under the hood, hooks store their data in a series of "memory cells". The first render defines the layout of these
//! memory cells, and on each subsequent render, each `use_hook` call accesses its corresponding memory cell. If a hook
//! accesses the wrong memory cell, `use_hook` will panic, and your app will crash. You can always use `try_use_hook` but
//! these types of errors can be easily mitigated by following the rules of hooks:
//!
//! - Dont call Hooks inside loops, conditions, or nested functions
//! - Don't call hooks in changing order between renders
//!
//! Hooks provide a very powerful way to reuse stateful logic between components, simplify large complex components,
//! and adopt more clear context subscription patterns to make components easier to read. The mechanics of hooks in Dioxus
//! shares a great amount of similarity with React's hooks and there are many guides to hooks in React online.
//!
//! ## Supported Renderers
//! Instead of being tightly coupled to a platform, browser, or toolkit, Dioxus implements a VirtualDOM object which
//! can be consumed to draw the UI. The Dioxus VDOM is reactive and easily consumable by 3rd-party renderers via
//! the `Patch` object. See [Implementing a Renderer](docs/8-custom-renderer.md) and the `StringRenderer` classes for information
//! on how to implement your own custom renderer. We provide 1st-class support for these renderers:
//!
//! - dioxus-desktop (via WebView)
//! - dioxus-web (via WebSys)
//! - dioxus-ssr (via StringRenderer)
//! - dioxus-liveview (SSR + StringRenderer)
//!
//! In the main `Dioxus` crate, these are all accessible through configuration flags.
//!
//! ## Rendering to the Web
//!
//! Most dioxus apps will be initialized in roughly the same way. The `launch` method in `web` will immediately start a
//! VirtualDOM and await it using `wasm_bindgen_futures`.
//!
//! VirtualDOM from
//! a component root and immediately awaits it
//!
//!
//! ```
//! use dioxus::prelude::*;
//! fn main() {
//! wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
//! console_error_panic_hook::set_once();
//!
//! diouxs::web::launch(Example);
//! }
//!
//! static Example: FC<()> = |ctx| {
//! ctx.render(rsx! {
//! div { "Hello World!" }
//! })
//! };
//! ```
// Just a heads-up, the core functionality of dioxus rests in Dioxus-Core. This crate just wraps a bunch of utilities
// together and exports their namespaces to something predicatble.
#[cfg(feature = "core")]
pub mod core {
//! Core functionality that includes the VirtualDOM, diffing, and Context APIs
// Re-export core completely
pub use dioxus_core::*;
}
pub mod prelude {
//! A glob import that includes helper types like FC, rsx!, html!, and required traits
pub use dioxus_core::prelude::*;
pub use dioxus_core_macro::fc;
}
#[cfg(feature = "web")]
pub mod web {
//! A web-sys based renderer for building fast and interactive web applications
}
#[cfg(feature = "ssr")]
pub mod ssr {
//! A dedicated renderer for writing a Dioxus VirtualDOM to a string
}
#[cfg(feature = "ssr")]
pub mod hooks {
//! Useful hooks like use_state, use_ref
}
#[cfg(feature = "ssr")]
pub mod router {
//! A cross-platform router implementation
}
#[cfg(feature = "ssr")]
pub mod testing {
//! Tools to make it easier to write tests for Dioxus components
}
#[cfg(feature = "atoms")]
pub mod atoms {}
#[cfg(feature = "desktop")]
pub mod desktop {
//! A webview based renderer for building desktop applications with Dioxus
}