wip: more progress on parity docs.

Placeholders in the rsx macro for future features.
This commit is contained in:
Jonathan Kelley 2021-05-28 12:56:21 -04:00
parent 4d5c528b07
commit c5089ba3c5
12 changed files with 147 additions and 241 deletions

View File

@ -30,3 +30,8 @@ partialeq
rsx
Ctx
fmt
Reqwest
Gloo
mobx
img
svg

View File

@ -2,33 +2,65 @@
Sorted by priority
| Feature | Dioxus | React |
| ---------------------- | ------ | ----- |
| Conditional Rendering | ✅ | ✅ |
| Map, Iterator | ✅ | ✅ |
| Keyed Components | ✅ | ✅ |
| Web | ✅ | ✅ |
| Desktop (webview) | ✅ | ✅ |
| Context | ✅ | ✅ |
| Hook | ✅ | ✅ |
| SSR | ✅ | ✅ |
| Runs natively | ✅ | 👀 |
| Null components | 👀 | ✅ |
| Fragments | 👀 | ✅ |
| Component Children | 👀 | ✅ |
| NodeRef | 👀 | ✅ |
| Controlled Inputs | 👀 | ✅ |
| No-div components | 👀 | ✅ |
| CSS/Inline Styles | 👀 | ✅ |
| 1st class global state | 👀 | ✅ |
| -------------------- | ----- | ----- |
| 1st class router | 👀 | ✅ |
| Suspense | 👀 | 👀 |
| Animation | 👀 | ✅ |
| Mobile | 👀 | ✅ |
| Desktop (native) | 👀 | ✅ |
| 3D Renderer | 👀 | ✅ |
| -------------------- | ----- | ----- |
| Portal | 👀 | ✅ |
| Error boundary | 👀 | ✅ |
| Code-splitting | 👀 | ✅ |
| Feature | Dioxus | React | Notes |
| ---------------------- | ------ | ----- | ------------------------------------ |
| ----- Phase 1 ----- | ----- | ----- | ----- |
| Conditional Rendering | ✅ | ✅ | if/then to hide/show component |
| Map, Iterator | ✅ | ✅ | map/filter/reduce rsx! |
| Keyed Components | ✅ | ✅ | advanced diffing with keys |
| Web | ✅ | ✅ | renderer for web browser |
| Desktop (webview) | ✅ | ✅ | renderer for desktop |
| Context | ✅ | ✅ | share state through the tree |
| Hook | ✅ | ✅ | memory cells in components |
| SSR | ✅ | ✅ | render directly to string |
| Runs natively | ✅ | 👀 | runs as a sharable binary |
| Null components | 👀 | ✅ | allow returning None |
| Fragments | 👀 | ✅ | no requirement on main root |
| Component Children | 👀 | ✅ | ctx.children() as a list of nodes |
| NodeRef | 👀 | ✅ | gain direct access to nodes |
| Controlled Inputs | 👀 | ✅ | stateful wrappers around inputs |
| No-div components | 👀 | ✅ | components that render components |
| CSS/Inline Styles | 🛠 | ✅ | syntax for inline/conditional styles |
| 1st class global state | 🛠 | ✅ | redux/recoil/mobx on top of context |
| ----- Phase 2 ----- | ----- | ----- | ----- |
| 1st class router | 👀 | ✅ | Hook built on top of history |
| Assets | 👀 | ✅ | include css/svg/img url statically |
| Integrated classnames | 🛠 | 👀 | built-in `classnames` |
| Suspense | 👀 | 👀 | schedule future render from future |
| Transition | 👀 | 👀 | High-level control over suspense |
| Animation | 👀 | ✅ | Spring-style animations |
| Mobile | 👀 | ✅ | Render with cacao |
| Desktop (native) | 👀 | ✅ | Render with native desktop |
| 3D Renderer | 👀 | ✅ | react-three-fiber |
| ----- Phase 3 ----- | ----- | ----- | ----- |
| Portal | 👀 | ✅ | cast elements through tree |
| Error/Panic boundary | 👀 | ✅ | catch panics and display BSOD |
| Code-splitting | 👀 | ✅ | Make bundle smaller/lazy |
| LiveView | 👀 | 👀 | Example for SSR + WASM apps |
## Required services:
---
Gloo is covering a lot of these. We want to build hooks around these, and provide examples on how to use them.
https://github.com/rustwasm/gloo
If the gloo service doesn't exist, then we need to contribute to the project
| Service | Hook examples | Current Projects |
| ---------------------------- | ------------- | ---------------- |
| Fetch | 👀 | Reqwest/surf |
| Local storage (cache) | 👀 | Gloo |
| Persistent storage (IndexDB) | 👀 | 👀 |
| WebSocket | 👀 | Gloo |
| 3D Renderer / WebGL | 👀 | Gloo |
| Web Worker | 👀 | 👀 |
| Router | 👀 | 👀 |
| Notifications | 👀 | 👀 |
| WebRTC Client | 👀 | 👀 |
| Service Workers | 👀 | 👀 |
| Resize Observer | 👀 | 👀 |
| Canvas | 👀 | 👀 |
| Clipboard | 👀 | 👀 |
| Fullscreen | 👀 | 👀 |
| History API | 👀 | 👀 |

View File

@ -213,6 +213,14 @@ impl Parse for Component {
break 'parsing;
}
if content.peek(token::Brace) {
let inner: ParseBuffer;
syn::braced!(inner in content);
if inner.peek(Token![...]) {
todo!("Inline props not yet supported");
}
}
body.push(content.parse::<ComponentField>()?);
// consume comma if it exists
@ -329,7 +337,7 @@ impl Parse for Element {
let forked = content.fork();
if forked.call(Ident::parse_any).is_ok()
&& forked.parse::<Token![:]>().is_ok()
&& forked.parse::<Expr>().is_ok()
&& forked.parse::<Token![:]>().is_err()
{
attrs.push(content.parse::<ElementAttr>()?);
} else {
@ -386,7 +394,7 @@ struct ElementAttr {
}
enum AttrType {
Value(LitStr),
BumpText(LitStr),
FieldTokens(Expr),
EventTokens(Expr),
Event(ExprClosure),
@ -420,14 +428,34 @@ impl Parse for ElementAttr {
AttrType::Event(s.parse()?)
}
} else {
let fork = s.fork();
if let Ok(rawtext) = fork.parse::<LitStr>() {
s.advance_to(&fork);
AttrType::Value(rawtext)
} else {
let toks = s.parse::<Expr>()?;
AttrType::FieldTokens(toks)
match name_str.as_str() {
"style" => {
//
todo!("inline style not yet supported")
}
"classes" => {
//
todo!("custom class lsit not supported")
}
"namespace" => {
//
todo!("custom namespace not supported")
}
"ref" => {
//
todo!("custom ref not supported")
}
_ => {
if s.peek(LitStr) {
let rawtext = s.parse::<LitStr>().unwrap();
AttrType::BumpText(rawtext)
} else {
let toks = s.parse::<Expr>()?;
AttrType::FieldTokens(toks)
}
}
}
// let lit_str = if name_str == "style" && s.peek(token::Brace) {
// // special-case to deal with literal styles.
// let outer;
@ -465,7 +493,7 @@ impl ToTokens for &ElementAttr {
let nameident = &self.name;
let _attr_stream = TokenStream2::new();
match &self.ty {
AttrType::Value(value) => {
AttrType::BumpText(value) => {
tokens.append_all(quote! {
.attr(#name, {
use bumpalo::core_alloc::fmt::Write;

View File

@ -97,7 +97,7 @@ static VALID_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
"source",
"span",
"strong",
"style",
// "style",
"sub",
"summary",
"sup",

View File

@ -5,10 +5,11 @@ This is the core crate for the Dioxus Virtual DOM. This README will focus on the
We reserve the "dioxus" name and aggregate all the various renderers under it. If you want just a single dioxus renderer, then chose from "dioxus-web", "dioxus-desktop", etc.
## Internals
Dioxus-core builds off the many frameworks that came before it. Notably, Dioxus borrows these concepts:
- React: hooks, concurrency, suspense
- Dodrio: bump allocation, double buffering, and source code for nodes + NodeBuilder
- Dodrio: bump allocation, double buffering, and source code for NodeBuilder
- Percy: html! macro architecture, platform-agnostic edits
- Yew: passion and inspiration ❤️
@ -28,7 +29,7 @@ We have big goals for Dioxus. The final implementation must:
- Support a pluggable allocation strategy that makes VNode creation **very** fast
- Support lazy DomTrees (ie DomTrees that are not actually created when the html! macro is used)
- Support advanced diffing strategies (patience, Myers, etc)
- Support advanced diffing strategies (patience, Meyers, etc)
## Design Quirks

View File

@ -12,7 +12,10 @@ fn main() {
}
baller::Baller {}
baller::Baller {}
Baller {}
Baller {
// todo: manual props
// {...BallerProps {}}
}
div {
a: "asd",
a: "asd",
@ -23,11 +26,12 @@ fn main() {
"asdas",
"asdas",
"asdas",
div {},
div {
},
div {
// classes: {[ ("baller", true), ("maller", false) ]}
// class: "asdasd"
// class: "{customname}",
// class: {[("baller", true), ("hover", false)]}
},
}
}
@ -50,5 +54,6 @@ pub struct TallerProps {
}
pub fn Taller(ctx: Context, props: &TallerProps) -> DomTree {
let b = true;
todo!()
}

View File

@ -1,123 +0,0 @@
use crate::{innerlude::*, nodebuilder::IntoDomTree};
use crate::{nodebuilder::LazyNodes, nodes::VNode};
use bumpalo::Bump;
use std::{cell::RefCell, future::Future, ops::Deref, pin::Pin, rc::Rc, sync::atomic::AtomicUsize};
/// Context API
///
/// The context API provides a mechanism for components to borrow state from other components higher in the tree.
/// By combining the Context API and the Subscription API, we can craft ergonomic global state management systems.
///
/// This API is inherently dangerous because we could easily cause UB by allowing &T and &mut T to exist at the same time.
/// To prevent this, we expose the RemoteState<T> and RemoteLock<T> types which act as a form of reverse borrowing.
/// This is very similar to RwLock, except that RemoteState is copy-able. Unlike RwLock, derefing RemoteState can
/// cause panics if the pointer is null. In essence, we sacrifice the panic protection for ergonomics, but arrive at
/// a similar end result.
///
/// Instead of placing the onus on the receiver of the data to use it properly, we wrap the source object in a
/// "shield" where gaining &mut access can only be done if no active StateGuards are open. This would fail and indicate
/// a failure of implementation.
pub struct RemoteState<T> {
inner: *const T,
}
impl<T> Copy for RemoteState<T> {}
impl<T> Clone for RemoteState<T> {
fn clone(&self) -> Self {
Self { inner: self.inner }
}
}
static DEREF_ERR_MSG: &'static str = r#"""
[ERROR]
This state management implementation is faulty. Report an issue on whatever implementation is using this.
Context should *never* be dangling!. If a Context is torn down, so should anything that references it.
"""#;
impl<T> Deref for RemoteState<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
// todo!
// Try to borrow the underlying context manager, register this borrow with the manager as a "weak" subscriber.
// This will prevent the panic and ensure the pointer still exists.
// For now, just get an immutable reference to the underlying context data.
//
// It's important to note that ContextGuard is not a public API, and can only be made from UseContext.
// This guard should only be used in components, and never stored in hooks
unsafe {
match self.inner.as_ref() {
Some(ptr) => ptr,
None => panic!(DEREF_ERR_MSG),
}
}
}
}
// impl<'a> crate::virtual_dom::Context<'a> {
// // impl<'a, P> super::Context<'a, P> {
// pub fn use_context<I, O>(&'a self, _narrow: impl Fn(&'_ I) -> &'_ O) -> RemoteState<O> {
// todo!()
// }
// pub fn create_context<T: 'static>(&self, creator: impl FnOnce() -> T) {}
// }
/// # SAFETY ALERT
///
/// The underlying context mechanism relies on mutating &mut T while &T is held by components in the tree.
/// By definition, this is UB. Therefore, implementing use_context should be done with upmost care to invalidate and
/// prevent any code where &T is still being held after &mut T has been taken and T has been mutated.
///
/// While mutating &mut T while &T is captured by listeners, we can do any of:
/// 1) Prevent those listeners from being called and avoid "producing" UB values
/// 2) Delete instances of closures where &T is captured before &mut T is taken
/// 3) Make clones of T to preserve the original &T.
/// 4) Disable any &T remotely (like RwLock, RefCell, etc)
///
/// To guarantee safe usage of state management solutions, we provide Dioxus-Reducer and Dioxus-Dataflow built on the
/// SafeContext API. This should provide as an example of how to implement context safely for 3rd party state management.
///
/// It's important to recognize that while safety is a top concern for Dioxus, ergonomics do take prescendence.
/// Contrasting with the JS ecosystem, Rust is faster, but actually "less safe". JS is, by default, a "safe" language.
/// However, it does not protect you against data races: the primary concern for 3rd party implementers of Context.
///
/// We guarantee that any &T will remain consistent throughout the life of the Virtual Dom and that
/// &T is owned by components owned by the VirtualDom. Therefore, it is impossible for &T to:
/// - be dangling or unaligned
/// - produce an invalid value
/// - produce uninitialized memory
///
/// The only UB that is left to the implementer to prevent are Data Races.
///
/// Here's a strategy that is UB:
/// 1. &T is handed out via use_context
/// 2. an event is reduced against the state
/// 3. An &mut T is taken
/// 4. &mut T is mutated.
///
/// Now, any closures that caputed &T are subject to a data race where they might have skipped checks and UB
/// *will* affect the program.
///
/// Here's a strategy that's not UB (implemented by SafeContext):
/// 1. ContextGuard<T> is handed out via use_context.
/// 2. An event is reduced against the state.
/// 3. The state is cloned.
/// 4. All subfield selectors are evaluated and then diffed with the original.
/// 5. Fields that have changed have their ContextGuard poisoned, revoking their ability to take &T.a.
/// 6. The affected fields of Context are mutated.
/// 7. Scopes with poisoned guards are regenerated so they can take &T.a again, calling their lifecycle.
///
/// In essence, we've built a "partial borrowing" mechanism for Context objects.
///
/// =================
/// nb
/// =================
/// If you want to build a state management API directly and deal with all the unsafe and UB, we provide
/// `use_context_unchecked` with all the stability with *no* guarantess of Data Race protection. You're on
/// your own to not affect user applications.
///
/// - Dioxus reducer is built on the safe API and provides a useful but slightly limited API.
/// - Dioxus Dataflow is built on the unsafe API and provides an even snazzier API than Dioxus Reducer.
fn _blah() {}

View File

@ -41,11 +41,9 @@ use std::{
cell::{RefCell, RefMut},
cmp::Ordering,
collections::VecDeque,
rc::{Rc, Weak},
sync::atomic::AtomicU32,
// rc::{Rc, Weak},
sync::{Arc, Weak},
};
type Rc<T> = Arc<T>;
/// The DiffState is a cursor internal to the VirtualDOM's diffing algorithm that allows persistence of state while
/// diffing trees of components. This means we can "re-enter" a subtree of a component by queuing a "NeedToDiff" event.

View File

@ -6,7 +6,7 @@
//! </div>
//! Dioxus: a concurrent, functional, reactive virtual dom for any renderer in Rust.
//!
//! This crate aims to maintain a uniform hook-based, renderer-agnostic UI framework for cross-platform development.
//! 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:
@ -16,8 +16,8 @@
//! #[derive(Properties)]
//! struct Props { name: String }
//!
//! fn Example(ctx: &mut Context<Props>) -> VNode {
//! html! { <div> "Hello {ctx.props.name}!" </div> }
//! fn Example(ctx: Context, props: &Props) -> DomTree {
//! html! { <div> "Hello {props.name}!" </div> }
//! }
//! ```
//! Components need to take a "Context" parameter which is generic over some properties. This defines how the component can be used
@ -37,11 +37,14 @@
//! ```
//!
//! 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::*;
//!
//! #[fc]
//! static Example: FC = |ctx, name: String| {
//! #[derive_props]
//! static Example: FC = |ctx, name: &String| {
//! html! { <div> "Hello {name}!" </div> }
//! }
//! ```
@ -49,7 +52,8 @@
//! ## 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.
//! 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.
@ -67,7 +71,7 @@
pub mod arena;
pub mod component; // Logic for extending FC
pub mod context; // Logic for providing hook + context functionality to user components
pub mod debug_renderer;
pub mod diff;
pub mod patch; // An "edit phase" described by transitions and edit operations // Test harness for validating that lifecycles and diffs work appropriately
@ -86,7 +90,7 @@ pub mod builder {
// types used internally that are important
pub(crate) mod innerlude {
pub use crate::component::*;
pub use crate::context::*;
pub use crate::debug_renderer::*;
pub use crate::diff::*;
pub use crate::error::*;
@ -99,10 +103,6 @@ pub(crate) mod innerlude {
pub type FC<P> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
// TODO @Jon, fix this
// hack the VNode type until VirtualNode is fixed in the macro crate
pub type VirtualNode<'a> = VNode<'a>;
// Re-export the FC macro
pub use crate as dioxus;
pub use crate::nodebuilder as builder;

View File

@ -5,14 +5,10 @@
use crate::{
events::VirtualEvent,
innerlude::{Context, NodeCtx, Properties, ScopeIdx, FC},
innerlude::{Context, Properties, ScopeIdx, FC},
};
use bumpalo::Bump;
use std::fmt::Debug;
use std::sync::Arc;
use std::{any::Any, cell::RefCell, marker::PhantomData};
type Rc<T> = Arc<T>;
use std::{cell::RefCell, fmt::Debug, marker::PhantomData, rc::Rc};
/// A domtree represents the result of "Viewing" the context
/// It's a placeholder over vnodes, to make working with lifetimes easier
@ -21,25 +17,16 @@ pub struct DomTree {
pub(crate) root: VNode<'static>,
}
// ==============================
// VNODES
// ==============================
/// 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.
#[derive(Debug)]
pub enum VNode<'src> {
/// An element node (node type `ELEMENT_NODE`).
Element(&'src VElement<'src>),
/// 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<'src>),
/// A "suspended component"
@ -101,7 +88,6 @@ impl<'a> VNode<'a> {
// ========================================================
// VElement (div, h1, etc), attrs, keys, listener handle
// ========================================================
#[derive(Debug)]
pub struct VElement<'a> {
/// Elements have a tag name, zero or more attributes, and zero or more
pub key: NodeKey<'a>,
@ -112,20 +98,6 @@ pub struct VElement<'a> {
pub namespace: Option<&'a str>,
}
impl<'a> VElement<'a> {
// The tag of a component MUST be known at compile time
pub fn new(_tag: &'a str) -> Self {
todo!()
// VElement {
// tag,
// attrs: HashMap::new(),
// events: HashMap::new(),
// // events: Events(HashMap::new()),
// children: vec![],
// }
}
}
/// An attribute on a DOM node, such as `id="my-thing"` or
/// `href="https://example.com"`.
#[derive(Clone, Debug)]
@ -174,18 +146,9 @@ pub struct Listener<'bump> {
pub scope: ScopeIdx,
pub id: usize,
// pub(crate) _i: &'bump str,
// #[serde(skip_serializing, skip_deserializing, default="")]
// /// The callback to invoke when the event happens.
/// The callback to invoke when the event happens.
pub(crate) callback: &'bump (dyn Fn(VirtualEvent)),
}
impl Debug for Listener<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Listener")
.field("event", &self.event)
.finish()
}
}
/// The key for keyed children.
///
@ -263,12 +226,6 @@ pub struct VComponent<'src> {
_p: PhantomData<&'src ()>,
}
impl std::fmt::Debug for VComponent<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
impl<'a> VComponent<'a> {
// use the type parameter on props creation and move it into a portable context
// this lets us keep scope generic *and* downcast its props when we need to:

View File

@ -29,12 +29,9 @@ use std::{
fmt::Debug,
future::Future,
pin::Pin,
// rc::{Rc, Weak},
sync::{Arc, Weak},
rc::{Rc, Weak},
};
type Rc<T> = Arc<T>;
/// 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 {
@ -1100,15 +1097,16 @@ mod tests {
// ensure the virtualdom is send + sync
// needed for use in async/await contexts
#[test]
fn is_send_sync() {
fn check_send<T: Send>(a: T) -> T {
fn check_send<T: Send>(_a: T) -> T {
todo!()
}
fn check_sync<T: Sync>(a: T) -> T {
fn check_sync<T: Sync>(_a: T) -> T {
todo!()
}
let _ = check_send(VirtualDom::new(|ctx, props| ctx.render(rsx! { div {}})));
let _ = check_sync(VirtualDom::new(|ctx, props| ctx.render(rsx! { div {}})));
let _ = check_send(VirtualDom::new(|ctx, _props| ctx.render(rsx! { div {}})));
let _ = check_sync(VirtualDom::new(|ctx, _props| ctx.render(rsx! { div {}})));
}
}

View File

@ -29,7 +29,12 @@ static Example: FC<ExampleProps> = |ctx, props| {
ctx.render(rsx! {
div {
class: "py-12 px-4 text-center w-full max-w-2xl mx-auto"
class: "py-12 px-4 text-center w-full max-w-2xl mx-auto",
// classes: [Some("asd")]
// style: {
// a: "asd"
// b: "ad"
// }
span {
class: "text-sm font-semibold"
"Dioxus Example: Jack and Jill"