mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
Feat: WIP
This commit is contained in:
parent
cd20d3c168
commit
ce34d0dfcd
13 changed files with 275 additions and 18 deletions
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"rust-analyzer.inlayHints.enable": true
|
||||
}
|
1
.vscode/spellright.dict
vendored
Normal file
1
.vscode/spellright.dict
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
bearly
|
6
CHANGELOG.md
Normal file
6
CHANGELOG.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
# Initial Release
|
||||
- [ ] (Macro) Tweak event syntax to not be dependent on wasm32 target (just return regular closures)
|
||||
- [ ] (Macro) Tweak component syntax to accept a new custom element
|
||||
- [ ] (Core) Introduce the VDOM and patch API for 3rd party renderers
|
||||
- [ ] (Web) Web-sys renderer and web tests
|
||||
- [ ] (Examples) Tide example with templating
|
|
@ -1,11 +1,15 @@
|
|||
[workspace]
|
||||
members = [
|
||||
# Built-n
|
||||
# Built-in
|
||||
"packages/core",
|
||||
"packages/macro",
|
||||
"packages/hooks",
|
||||
"packages/recoil",
|
||||
"packages/redux",
|
||||
"packages/web",
|
||||
#
|
||||
#
|
||||
#
|
||||
# Pulled from percy
|
||||
"packages/html-macro",
|
||||
"packages/html-macro-test",
|
||||
|
|
79
README.md
79
README.md
|
@ -1,9 +1,9 @@
|
|||
# Dioxus: A concurrent, functional, arena-allocated VDOM implementation for creating UIs in Rust
|
||||
# Dioxus: A concurrent, functional, virtual DOM for Rust
|
||||
|
||||
Dioxus is a new approach for creating performant cross platform user experiences in Rust. In Dioxus, the UI is represented by a tree of Virtual Nodes not bound to any renderer framework. Instead, external renderers can leverage Dioxus' virtual DOM and event system as a source of truth for rendering to a medium of their choice. Developers used to crafting react-based experiences should feel comfortable with Dioxus.
|
||||
|
||||
## Hello World
|
||||
Dioxus should look and feel just like writing functional React components. In Dioxus, there are no class components with lifecycles. All state management is done via hooks. This encourages logic resusability and lessens the burden on Dioxus to maintain a non-breaking lifecycle API.
|
||||
Dioxus should look and feel just like writing functional React components. In Dioxus, there are no class components with lifecycles. All state management is done via hooks. This encourages logic reusability and lessens the burden on Dioxus to maintain a non-breaking lifecycle API.
|
||||
|
||||
```rust
|
||||
#[derive(Properties, PartialEq)]
|
||||
|
@ -11,32 +11,79 @@ struct MyProps {
|
|||
name: String
|
||||
}
|
||||
|
||||
fn Example(ctx: Context<MyProps>) -> VNode {
|
||||
html! { <div> "Hello {ctx.props().name}!" </div> }
|
||||
fn Example(ctx: &Context<MyProps>) -> VNode {
|
||||
html! { <div> "Hello {:?ctx.props().name}!" </div> }
|
||||
}
|
||||
```
|
||||
|
||||
Here, the `Context` object is used to access hook state, create subscriptions, and interact with the built-in context API. Props, children, and component APIs are accessible via the `Context` object. If using the functional component macro, it's possible to inline props into the function definition itself.
|
||||
|
||||
```rust
|
||||
#[functional_component]
|
||||
fn Example(ctx: &Context<{ name: String }>) -> VNode {
|
||||
html! { <div> "Hello {:?name}!" </div> }
|
||||
}
|
||||
|
||||
// or
|
||||
|
||||
#[functional_component]
|
||||
static Example: FC<{ name: String }> = |ctx| html! { <div> "Hello {:?name}!" </div> };
|
||||
```
|
||||
|
||||
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, or templating engine. Work is being done on a terra template processor for existing templates.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
To build user interfaces, you must provide a way of creating VNodes. We provide a macro `dioxus-rsx` which makes it easy to drop in html templates and event listeners to make interactive user experiences.
|
||||
|
||||
Inspired by React's Concurrent Mode, components in Dioxus are asynchronous by default. When components need to load asynchronous data, their rendering will be halted until ready, leading to fewer DOM updates and greater performance. External crates can tap into this system using futures to craft useful transition-based hooks.
|
||||
|
||||
Rules of Dioxus:
|
||||
- Every component is asynchronous
|
||||
- Components will queued when completed
|
||||
-
|
||||
## Features
|
||||
|
||||
Dioxus supports:
|
||||
- Hooks
|
||||
- Concurrent rendering
|
||||
- Context subscriptions
|
||||
- Isomorphism
|
||||
|
||||
## Concurrency
|
||||
|
||||
Dioxus, using React as a reference, provides the ability to have asynchronous components. With Dioxus, this is a valid component:
|
||||
|
||||
```rust
|
||||
async fn user_data(ctx: &Context<()>) -> VNode {
|
||||
let Profile { name, birthday, .. } = use_context::<UserContext>(ctx).fetch_data().await;
|
||||
html! {
|
||||
<div>
|
||||
{"Hello, {:?name}!"}
|
||||
{if birthday === std::Instant::now() {html! {"Happy birthday!"}}}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
## Example
|
||||
- Isomorphic: serve a server-rendered webapp using Tide
|
||||
|
||||
## Documentation
|
||||
| Chapter | Description |
|
||||
| -------------- | ------------------------------------------ |
|
||||
| 1-hello-world | Intro to Dioxus |
|
||||
| 2-utilities | Tools to make writing apps easier |
|
||||
| 3-hello-world | Html + functional_component macro |
|
||||
| 4-hello-world | Renderer + event integration using web_sys |
|
||||
| 5-hello-world | Virtual DOM, patching/diffing |
|
||||
| 6-hello-world | Standard bundle of useful hooks |
|
||||
| 7-hello-world | Html + functional_component macro |
|
||||
| 8-hello-world | Renderer + event integration using web_sys |
|
||||
| 9-hello-world | Renderer + event integration using web_sys |
|
||||
| 10-hello-world | Renderer + event integration using web_sys |
|
||||
|
||||
## Packages
|
||||
| Package | Use |
|
||||
| --------- | ------------------------------------------ |
|
||||
| core | Virtual DOM, patching/diffing |
|
||||
| hooks | Standard bundle of useful hooks |
|
||||
| macro | Html + functional_component macro |
|
||||
| web | Renderer + event integration using web_sys |
|
||||
| live-view | Dedicated isomorphic framework |
|
||||
| recoil | Data-flow-graph state management |
|
||||
| redux | Reducer-style state management |
|
||||
| bearly | Simple and idiomatic state management |
|
||||
|
||||
|
|
|
@ -9,3 +9,8 @@ edition = "2018"
|
|||
[dependencies]
|
||||
generational-arena = "0.2.8"
|
||||
typed-arena = "2.0.1"
|
||||
virtual-dom-rs = { path = "../virtual-dom-rs" }
|
||||
virtual-node = { path = "../virtual-node" }
|
||||
html-macro = { path = "../html-macro" }
|
||||
# web-sys = "0.3.46"
|
||||
# js-sys = "0.3.46"
|
||||
|
|
75
packages/core/examples/simple.rs
Normal file
75
packages/core/examples/simple.rs
Normal file
|
@ -0,0 +1,75 @@
|
|||
use std::future::Future;
|
||||
|
||||
use dioxus_core::{component::AnyContext, prelude::*};
|
||||
use virtual_dom_rs::Closure;
|
||||
|
||||
pub fn main() {
|
||||
let dom = VirtualDom::new(root);
|
||||
let mut renderer = TextRenderer::new(dom);
|
||||
let output = renderer.render();
|
||||
}
|
||||
|
||||
fn root(ctx: &mut AnyContext) -> VirtualNode {
|
||||
// the regular html syntax
|
||||
|
||||
// html! {
|
||||
// <html>
|
||||
// <Head />
|
||||
// <Body />
|
||||
// <Footer />
|
||||
// </html>
|
||||
// }
|
||||
|
||||
// or a manually crated vnode
|
||||
{
|
||||
let mut node_0 = VirtualNode::element("div");
|
||||
let mut node_1: IterableNodes = ("Hello world!").into();
|
||||
node_1.first().insert_space_before_text();
|
||||
let mut node_2 = VirtualNode::element("button");
|
||||
{
|
||||
// let closure = Closure::wrap(Box::new(|_| {}) as Box<FnMut(_)>);
|
||||
// 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());
|
||||
}
|
||||
|
||||
VirtualNode
|
||||
|
||||
node_0
|
||||
}
|
||||
}
|
||||
|
||||
fn Head(ctx: &mut AnyContext) -> VirtualNode {
|
||||
html! {
|
||||
<head>
|
||||
{"Head Section"}
|
||||
</head>
|
||||
}
|
||||
}
|
||||
|
||||
fn Body(ctx: &mut AnyContext) -> VirtualNode {
|
||||
html! {
|
||||
<body>
|
||||
{"Footer Section"}
|
||||
</body>
|
||||
}
|
||||
}
|
||||
|
||||
fn Footer(ctx: &mut AnyContext) -> VirtualNode {
|
||||
html! {
|
||||
<div>
|
||||
{"Footer Section"}
|
||||
</div>
|
||||
}
|
||||
}
|
|
@ -1 +1,92 @@
|
|||
use html_macro::html;
|
||||
use virtual_node::{IterableNodes, VElement, VirtualNode};
|
||||
|
||||
/// A re-export of everything to get macros working smoothly
|
||||
pub mod prelude {
|
||||
pub use crate::component::Context;
|
||||
pub use crate::renderer::TextRenderer;
|
||||
pub use crate::types::FC;
|
||||
pub use crate::virtual_dom::VirtualDom;
|
||||
pub use html_macro::html;
|
||||
pub use virtual_node::{IterableNodes, VirtualNode};
|
||||
}
|
||||
|
||||
pub mod virtual_dom {
|
||||
use super::*;
|
||||
|
||||
pub struct VirtualDom {}
|
||||
|
||||
impl VirtualDom {
|
||||
pub fn new(root: types::FC) -> Self {
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Virtual Node Support
|
||||
pub mod nodes {
|
||||
pub type VNode = virtual_node::VirtualNode;
|
||||
// pub enum VNode {
|
||||
// VText,
|
||||
// VElement,
|
||||
// VComponent,
|
||||
// }
|
||||
}
|
||||
|
||||
/// Example on how to craft a renderer that interacts with the VirtualDom
|
||||
pub mod renderer {
|
||||
use crate::virtual_dom::VirtualDom;
|
||||
|
||||
use super::*;
|
||||
|
||||
/// Renders a full Dioxus app to a String
|
||||
///
|
||||
pub struct TextRenderer {}
|
||||
|
||||
impl TextRenderer {
|
||||
/// Create a new Text Renderer which renders the VirtualDom to a string
|
||||
pub fn new(dom: VirtualDom) -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
pub fn render(&mut self) -> String {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub mod component {
|
||||
|
||||
/// A wrapper around component contexts that hides component property types
|
||||
pub struct AnyContext {}
|
||||
pub struct Context<T> {
|
||||
_props: std::marker::PhantomData<T>,
|
||||
}
|
||||
|
||||
pub trait Properties {}
|
||||
impl Properties for () {}
|
||||
|
||||
fn test() {}
|
||||
}
|
||||
|
||||
/// Utility types that wrap internals
|
||||
pub mod types {
|
||||
use super::*;
|
||||
use component::{AnyContext, Context};
|
||||
use nodes::VNode;
|
||||
|
||||
pub type FC = fn(&mut AnyContext) -> VNode;
|
||||
}
|
||||
|
||||
// #[cg(test)]
|
||||
mod integration_tests {
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Test a basic usage of a virtual dom + text renderer combo
|
||||
#[test]
|
||||
fn simple_integration() {
|
||||
let dom = VirtualDom::new(|_| html! { <div>Hello World!</div> });
|
||||
let mut renderer = TextRenderer::new(dom);
|
||||
let output = renderer.render();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -111,6 +111,7 @@ custom components: https://chinedufn.github.io/percy/html-macro/custom-component
|
|||
})
|
||||
.collect();
|
||||
|
||||
// TODO @Jon - this is where we need to start injecting new logic about props/children etc
|
||||
let node = quote! {
|
||||
let mut #var_name_component = #component_ident { #(#component_props),* };
|
||||
let mut #var_name_node = #var_name_component.render();
|
||||
|
|
|
@ -9,4 +9,4 @@ repository = "https://github.com/chinedufn/percy"
|
|||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
lazy_static = "1.0"
|
||||
lazy_static = "1.4.0"
|
||||
|
|
|
@ -57,16 +57,22 @@ pub enum VirtualNode {
|
|||
/// 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),
|
||||
}
|
||||
|
||||
#[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<String, String>,
|
||||
|
||||
/// Events that will get added to your real DOM element via `.addEventListener`
|
||||
pub events: Events,
|
||||
|
||||
/// The children of this `VirtualNode`. So a <div> <em></em> </div> structure would
|
||||
/// have a parent div and one child, em.
|
||||
pub children: Vec<VirtualNode>,
|
||||
|
@ -77,6 +83,9 @@ pub struct VText {
|
|||
pub text: String,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct VComponent {}
|
||||
|
||||
impl VirtualNode {
|
||||
/// Create a new virtual element node with a given tag.
|
||||
///
|
||||
|
@ -162,6 +171,7 @@ impl VirtualNode {
|
|||
CreatedNode::without_closures(text_node.create_text_node())
|
||||
}
|
||||
VirtualNode::Element(element_node) => element_node.create_element_node().into(),
|
||||
VirtualNode::Component(_) => todo!("WIP on Component Syntax"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -302,6 +312,9 @@ impl VElement {
|
|||
|
||||
element.append_child(&child_elem).unwrap();
|
||||
}
|
||||
VirtualNode::Component(_) => {
|
||||
todo!("WIP on Component Syntax")
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -529,6 +542,7 @@ impl fmt::Debug for VirtualNode {
|
|||
match self {
|
||||
VirtualNode::Element(e) => write!(f, "Node::{:?}", e),
|
||||
VirtualNode::Text(t) => write!(f, "Node::{:?}", t),
|
||||
VirtualNode::Component(c) => write!(f, "Node::{:?}", c),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
9
packages/web/Cargo.toml
Normal file
9
packages/web/Cargo.toml
Normal file
|
@ -0,0 +1,9 @@
|
|||
[package]
|
||||
name = "dioxus-web"
|
||||
version = "0.0.0"
|
||||
authors = ["Jonathan Kelley <jkelleyrtp@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
1
packages/web/src/lib.rs
Normal file
1
packages/web/src/lib.rs
Normal file
|
@ -0,0 +1 @@
|
|||
|
Loading…
Reference in a new issue