Feat: update readme and examples

This commit is contained in:
Jonathan Kelley 2021-03-01 00:16:48 -05:00
parent c8bb392cad
commit ffaf687896
12 changed files with 608 additions and 63 deletions

View File

@ -27,3 +27,5 @@ RefMut
diffed
datafetching
partialeq
rsx
Ctx

View File

@ -10,37 +10,33 @@
Dioxus is a portable, performant, and ergonomic framework for building cross-platform user experiences in Rust.
```rust
static Example: FC<()> = |ctx, props| {
let (selection, set_selection) = use_state(&ctx, || "...?");
static Example: FC<()> = |ctx| {
let (value, set_value) = use_state(&ctx, || "...?");
// custom (more powerful) syntax
ctx.render(html! {
ctx.render(rsx! {
div {
h1 { "Hello, {value}" }
button { "?", onclick: move |_| set_value("world!") }
button { "?", onclick: move |_| set_value("Dioxus 🎉") }
h1 { "Hello, {selection}" }
button { "?", onclick: move |_| set_selection("world!") }
button { "?", onclick: move |_| set_selection("Dioxus 🎉") }
}
})
// Classic syntax
// no more updates, frozen
ctx.render(html!{
<div>
<h1> "Hello, {value}" </h1>
<button onclick={move |_| set_value("world!")}> "?" </button>
<button onclick={move |_| set_value("Dioxus 🎉")}> "?" </button>
</div>
})
};
```
Dioxus can be used to deliver webapps, desktop apps, static pages, liveview apps, Android apps, iOS Apps, and more. At its core, Dioxus is entirely renderer agnostic and has great documentation for creating new renderers for any platform.
Dioxus is supported by Dioxus Labs, a company providing end-to-end services for building, testing, deploying, and managing Dioxus apps on all supported platforms, designed especially for your next startup.
### Get Started with...
### **Things you'll love ❤️:**
- Minimal boilerplate
- Ergonomic lifetime design for props and state management
- Simple build, test, and deploy
- "Dioxus Designer" for instant component reloading
- SSR, WASM, desktop, and mobile support
---
## Get Started with...
<table style="width:100%" align="center">
<tr >
<th><a href="http://github.com/jkelleyrtp/dioxus">WebApps</a></th>
@ -54,32 +50,21 @@ Dioxus is supported by Dioxus Labs, a company providing end-to-end services for
## Features
Dioxus' goal is to be the most advanced UI system for Rust, targeting isomorphism and hybrid approaches. Our goal is to eliminate context-switching for cross-platform development - both in UI patterns and programming language. Hooks and components should work *everywhere* without compromise.
Dioxus Core supports:
- [x] Hooks for component state
- [ ] Concurrent rendering
- [ ] Context subscriptions
- [ ] State management integrations
Separately, we maintain a collection of high quality, cross-platform hooks and services in the dioxus-hooks repo:
- [ ] `dioxus-router`: A hook-based router implementation for Dioxus web apps
We also maintain two state management options that integrate cleanly with Dioxus apps:
- [ ] `dioxus-reducer`: ReduxJs-style global state management
- [ ] `dioxus-dataflow`: RecoilJs-style global state management
## Explore
- [**HTML Templates**: Drop in existing HTML5 templates with html! macro](docs/guides/00-index.md)
- [**RSX Templates**: Clean component design with rsx! macro](docs/guides/00-index.md)
- [**Running the examples**: Explore the vast collection of samples, tutorials, and demos](docs/guides/00-index.md)
- [**Building applications**: Use the Dioxus CLI to build and bundle apps for various platforms](docs/guides/01-ssr.md)
- [**Liveview**: Build custom liveview components that simplify datafetching on all platforms](docs/guides/01-ssr.md)
- [**State management**: Easily add powerful state management that comes integrated with Dioxus Core](docs/guides/01-ssr.md)
- [**Concurrency**: Drop in async where it fits and suspend components until new data is ready](docs/guides/01-ssr.md)
- [**1st party hooks**: router](docs/guides/01-ssr.md)
- [**1st party hooks**: Cross-platform router hook](docs/guides/01-ssr.md)
- [**Community hooks**: 3D renderers](docs/guides/01-ssr.md)
---
## Dioxus LiveHost
Dioxus LiveHost is a paid service dedicated to hosting your Dioxus Apps - whether they be server-rendered, wasm-only, or a liveview. LiveHost enables a wide set of features:
@ -95,12 +80,3 @@ Dioxus LiveHost is a paid service dedicated to hosting your Dioxus Apps - whethe
- Team + company management
For small teams, LiveHost is free 🎉. Check out the pricing page to see if Dioxus LiveHost is good fit for your team.

View File

@ -11,11 +11,7 @@ This macro allows allows a classic struct definition to be embedded directly int
```rust
// Inlines and destructure props *automatically*
#[functional_component]
fn Example(ctx: &mut Context<{
name: String
pending: bool
count: i32
}>) -> VNode {
fn Example(ctx: Context, name: &str, pending: bool, count: i32 ) -> DomTree {
html! {
<div>
<p> "Hello, {name}!" </p>

View File

@ -1 +1,91 @@
#
# VNode Macros
Dioxus comes preloaded with two macros for creating VNodes.
## html! macro
The html! macro supports the html standard. This macro will happily accept a copy-paste from something like tailwind builder. Writing this one by hand is a bit tedious and doesn't come with much help from Rust IDE tools.
There is also limited support for dynamic handlers, but it will function similarly to JSX.
```rust
#[fc]
fn Example(ctx: Context, name: &str, pending: bool, count: i32 ) -> DomTree {
ctx.render(html! {
<div>
<p> "Hello, {name}!" </p>
<p> "Status: {pending}!" </p>
<p> "Count {count}!" </p>
</div>
})
}
```
## rsx! macro
The rsx! macro is a VNode builder macro designed especially for Rust. Writing these should feel very natural, much like assembling a struct. VSCode also supports these with code folding, bracket-tabbing, bracket highlighting, and section selecting.
The Dioxus VSCode extension provides a function to convert a selection of html! template and turn it into rsx!, so you'll never need to transcribe templates by hand.
It's also a bit easier on the eyes 🙂.
```rust
#[fc]
fn Example(ctx: Context, name: &str, pending: bool, count: i32 ) -> DomTree {
ctx.render(rsx! {
div {
p {"Hello, {name}!"}
p {"Status: {pending}!"}
p {"Count {count}!"}
}
})
}
```
Each element takes a comma-separated list of expressions to build the node. Roughly, here's how they work:
- `name: value` sets the property on this element.
- `"text"` adds a new text element
- `tag {}` adds a new child element
- `CustomTag {}` adds a new child component
- `{expr}` pastes the `expr` tokens literally. They must be IntoCtx<Vnode> to work properly
Lists must include commas, much like how struct definitions work.
```rust
static Example: FC<()> = |ctx, props| {
ctx.render(rsx!{
div {
h1 { "Example" },
p {
// Props
tag: "type",
abc: 123,
enabled: true,
class: "big small wide short",
// Children
a { "abcder" },
// Children with props
h2 { "whatsup", class: "abc-123" },
// Child components
CustomComponent { a: 123, b: 456, key: "1" },
// Iterators
{ 0..3.map(|i| rsx!{ h1 {"{:i}"} }) },
// More rsx!, or even html!
{ rsx! { div { } } },
{ html! { <div> </div> } },
// Any expression that is Into<VNode>
{expr}
}
}
})
}
```

View File

@ -392,8 +392,6 @@ impl ToTokens for ToToksCtx<&LitStr> {
}
}
mod styles {}
#[cfg(test)]
mod test {
fn parse(input: &str) -> super::Result<super::HtmlRender> {

View File

@ -12,6 +12,7 @@ use syn::{
mod fc;
mod htm;
mod ifmt;
mod rsxt;
// mod styles;
/// The html! macro makes it easy for developers to write jsx-style markup in their components.
@ -25,6 +26,18 @@ pub fn html(s: TokenStream) -> TokenStream {
html.to_token_stream().into()
}
/// The html! macro makes it easy for developers to write jsx-style markup in their components.
/// We aim to keep functional parity with html templates.
#[proc_macro]
pub fn rsx(s: TokenStream) -> TokenStream {
let template: rsxt::RsxRender = match syn::parse(s) {
Ok(s) => s,
Err(e) => return e.to_compile_error().into(),
};
template.to_token_stream().into()
}
/// Label a function or static closure as a functional component.
/// This macro reduces the need to create a separate properties struct.
#[proc_macro_attribute]

View File

@ -0,0 +1,382 @@
/*
An example usage of rsx! would look like this:
```ignore
ctx.render(rsx!{
div {
h1 { "Example" },
p {
tag: "type",
abc: 123,
enabled: true,
class: "big small wide short",
a { "abcder" },
h2 { "whatsup", class: "abc-123" },
CustomComponent { a: 123, b: 456, key: "1" },
{ 0..3.map(|i| rsx!{ h1 {"{:i}"} }) },
{expr}
// expr can take:
// - iterator
// - |bump| { }
// - value (gets formatted as &str)
// - ... more as we upgrade it
}
}
})
```
each element is given by tag { [Attr] }
*/
use syn::parse::ParseBuffer;
use {
proc_macro::TokenStream,
proc_macro2::{Span, TokenStream as TokenStream2},
quote::{quote, ToTokens, TokenStreamExt},
syn::{
ext::IdentExt,
parse::{Parse, ParseStream},
token, Error, Expr, ExprClosure, Ident, LitBool, LitStr, Path, Result, Token,
},
};
// ==============================================
// Parse any stream coming from the rsx! macro
// ==============================================
pub struct RsxRender {}
impl Parse for RsxRender {
fn parse(input: ParseStream) -> Result<Self> {
let g: Element = input.parse()?;
Ok(Self {})
}
}
impl ToTokens for RsxRender {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let new_toks = quote! {
move |_| {
todo!()
}
};
new_toks.to_tokens(tokens)
}
}
// ==============================================
// Parse any div {} as a VElement
// ==============================================
enum Node {
Element(Element),
Text(TextNode),
}
impl ToTokens for ToToksCtx<&Node> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match &self.inner {
Node::Element(el) => self.recurse(el).to_tokens(tokens),
Node::Text(txt) => self.recurse(txt).to_tokens(tokens),
}
}
}
impl Node {
fn peek(s: ParseStream) -> bool {
(s.peek(Token![<]) && !s.peek2(Token![/])) || s.peek(token::Brace) || s.peek(LitStr)
}
}
impl Parse for Node {
fn parse(s: ParseStream) -> Result<Self> {
Ok(if s.peek(Token![<]) {
Node::Element(s.parse()?)
} else {
Node::Text(s.parse()?)
})
}
}
/// =======================================
/// Parse the VNode::Element type
/// =======================================
/// - [ ] Allow VComponent
///
///
struct Element {
name: Ident,
attrs: Vec<Attr>,
children: MaybeExpr<Vec<Node>>,
}
impl ToTokens for ToToksCtx<&Element> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
// let ctx = self.ctx;
let name = &self.inner.name;
tokens.append_all(quote! {
dioxus::builder::#name(bump)
});
for attr in self.inner.attrs.iter() {
self.recurse(attr).to_tokens(tokens);
}
match &self.inner.children {
MaybeExpr::Expr(expr) => tokens.append_all(quote! {
.children(#expr)
}),
MaybeExpr::Literal(nodes) => {
let mut children = nodes.iter();
if let Some(child) = children.next() {
let mut inner_toks = TokenStream2::new();
self.recurse(child).to_tokens(&mut inner_toks);
while let Some(child) = children.next() {
quote!(,).to_tokens(&mut inner_toks);
self.recurse(child).to_tokens(&mut inner_toks);
}
tokens.append_all(quote! {
.children([#inner_toks])
});
}
}
}
tokens.append_all(quote! {
.finish()
});
}
}
impl Parse for Element {
fn parse(s: ParseStream) -> Result<Self> {
// steps:
// grab ident as name
// peak to the next character
// ensure it's a {
// s.parse::<Token![<]>()?;
let name = Ident::parse_any(s)?;
let content: ParseBuffer;
// parse the guts of the div {} tag into the content buffer
syn::braced!(content in s);
// s.
// s.parse::<Token!["{"]>()?;
// s.parse()
// s.parse_terminated(parser)
// s.parse::<token::Brace>()?;
let mut attrs = vec![];
// let mut children = vec![];
let mut children: Vec<Node> = vec![];
// // keep looking for attributes
// while !s.peek(Token![>]) {
// // self-closing
// if s.peek(Token![/]) {
// s.parse::<Token![/]>()?;
// s.parse::<Token![>]>()?;
// return Ok(Self {
// name,
// attrs,
// children: MaybeExpr::Literal(vec![]),
// });
// }
// attrs.push(s.parse()?);
// }
// s.parse::<Token![>]>()?;
// // Contents of an element can either be a brace (in which case we just copy verbatim), or a
// // sequence of nodes.
// let children = if s.peek(token::Brace) {
// // expr
// let content;
// syn::braced!(content in s);
// MaybeExpr::Expr(content.parse()?)
// } else {
// // nodes
// let mut children = vec![];
// while !(s.peek(Token![<]) && s.peek2(Token![/])) {
// children.push(s.parse()?);
// }
// MaybeExpr::Literal(children)
// };
// // closing element
// s.parse::<Token![<]>()?;
// s.parse::<Token![/]>()?;
// let close = Ident::parse_any(s)?;
// if close.to_string() != name.to_string() {
// return Err(Error::new_spanned(
// close,
// "closing element does not match opening",
// ));
// }
// s.parse::<Token![>]>()?;
Ok(Self {
name,
attrs,
children,
})
}
}
/// =======================================
/// Parse a VElement's Attributes
/// =======================================
/// - [ ] Allow expressions as attribute
///
///
struct Attr {
name: Ident,
ty: AttrType,
}
impl Parse for Attr {
fn parse(s: ParseStream) -> Result<Self> {
let mut name = Ident::parse_any(s)?;
let name_str = name.to_string();
s.parse::<Token![=]>()?;
// Check if this is an event handler
// If so, parse into literal tokens
let ty = if name_str.starts_with("on") {
// remove the "on" bit
name = Ident::new(&name_str.trim_start_matches("on"), name.span());
let content;
syn::braced!(content in s);
// AttrType::Value(content.parse()?)
AttrType::Event(content.parse()?)
// AttrType::Event(content.parse()?)
} else {
let lit_str = if name_str == "style" && s.peek(token::Brace) {
// special-case to deal with literal styles.
let outer;
syn::braced!(outer in s);
// double brace for inline style.
// todo!("Style support not ready yet");
// if outer.peek(token::Brace) {
// let inner;
// syn::braced!(inner in outer);
// let styles: Styles = inner.parse()?;
// MaybeExpr::Literal(LitStr::new(&styles.to_string(), Span::call_site()))
// } else {
// just parse as an expression
MaybeExpr::Expr(outer.parse()?)
// }
} else {
s.parse()?
};
AttrType::Value(lit_str)
};
Ok(Attr { name, ty })
}
}
impl ToTokens for ToToksCtx<&Attr> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = self.inner.name.to_string();
let mut attr_stream = TokenStream2::new();
match &self.inner.ty {
AttrType::Value(value) => {
let value = self.recurse(value);
tokens.append_all(quote! {
.attr(#name, #value)
});
}
AttrType::Event(event) => {
tokens.append_all(quote! {
.on(#name, #event)
});
}
}
}
}
enum AttrType {
Value(MaybeExpr<LitStr>),
Event(ExprClosure),
// todo Bool(MaybeExpr<LitBool>)
}
/// =======================================
/// Parse just plain text
/// =======================================
/// - [ ] Perform formatting automatically
///
///
struct TextNode(MaybeExpr<LitStr>);
impl Parse for TextNode {
fn parse(s: ParseStream) -> Result<Self> {
Ok(Self(s.parse()?))
}
}
impl ToTokens for ToToksCtx<&TextNode> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let mut token_stream = TokenStream2::new();
self.recurse(&self.inner.0).to_tokens(&mut token_stream);
tokens.append_all(quote! {
{
use bumpalo::core_alloc::fmt::Write;
let mut s = bumpalo::collections::String::new_in(bump);
s.write_fmt(format_args_f!(#token_stream)).unwrap();
dioxus::builder::text2(s)
}
});
}
}
enum MaybeExpr<T> {
Literal(T),
Expr(Expr),
}
impl<T: Parse> Parse for MaybeExpr<T> {
fn parse(s: ParseStream) -> Result<Self> {
if s.peek(token::Brace) {
let content;
syn::braced!(content in s);
Ok(MaybeExpr::Expr(content.parse()?))
} else {
Ok(MaybeExpr::Literal(s.parse()?))
}
}
}
impl<'a, T> ToTokens for ToToksCtx<&'a MaybeExpr<T>>
where
T: 'a,
ToToksCtx<&'a T>: ToTokens,
{
fn to_tokens(&self, tokens: &mut TokenStream2) {
match &self.inner {
MaybeExpr::Literal(v) => self.recurse(v).to_tokens(tokens),
MaybeExpr::Expr(expr) => expr.to_tokens(tokens),
}
}
}
/// ToTokens context
struct ToToksCtx<T> {
inner: T,
}
impl<'a, T> ToToksCtx<T> {
fn new(inner: T) -> Self {
ToToksCtx { inner }
}
fn recurse<U>(&self, inner: U) -> ToToksCtx<U> {
ToToksCtx { inner }
}
}
impl ToTokens for ToToksCtx<&LitStr> {
fn to_tokens(&self, tokens: &mut TokenStream2) {
self.inner.to_tokens(tokens)
}
}

View File

@ -11,7 +11,7 @@ struct Context2<'a, P> {
rops: &'a P, // _p: PhantomData<&'a ()>,
}
impl<'a, P> Context2<'a, P> {
fn view(self, _f: impl FnOnce(&'a Bump) -> VNode<'a>) -> DTree {
fn render(self, _f: impl FnOnce(&'a Bump) -> VNode<'a>) -> DTree {
DTree {}
}

View File

@ -55,7 +55,7 @@ impl<'a> Context<'a> {
todo!()
}
fn view(self, _f: impl FnOnce(&'a String) + 'a) {}
fn render(self, _f: impl FnOnce(&'a String) + 'a) {}
// fn view(self, f: impl for<'b> FnOnce(&'a String) + 'a) {}
// fn view(self, f: impl for<'b> FnOnce(&'b String) + 'a) {}
}

View File

@ -0,0 +1,89 @@
use dioxus_core::prelude::*;
fn main() {}
fn Example(ctx: Context, props: ()) -> DomTree {
ctx.render(rsx! {
div {
<h1 tag="type" abc=123 class="big..."> "title1" "title2" </h1>
<h1 alsd> alkjsd </h1>
h1 { tag: "type", abc: 123, class: "big small wide short",
"title1"
"title1"
"title1"
"title"
}
h1 ("title") {
tag: "type",
abc: 123,
class: "big small wide short",
}
// <button
// class="inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
// onclick={move |_| set_name("jill")}
// onclick={move |_| set_name("jill")}
// >
// "Jill!"
// </button>
button { "Jill!",
class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
onclick: move |_| set_name("jill"),
onclick: move |_| set_name("jill"),
}
button {
class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
onclick: move |_| set_name("jill"),
onclick: move |_| set_name("jill"),
// this is valid
"Jill!",
// this is also valid
{"Jill!"}
}
h1 { "Text", class: "inline-block py-4 px-8 mr-6 leading-none" }
// <h1 class="inline-block py-4 px-8 mr-6 leading-none">
// "Text"
// </h1>
h1 {
div {
h1 {}
h2 {}
Brick {}
p {}
p {
tag: "type",
abc: 123,
enabled: true,
class: "big small wide short",
a { "abcder" },
h2 { "whatsup", class: "abc-123" },
CustomComponent { a: 123, b: 456, key: "1" },
}
div { class: "big small wide short",
div {},
div {},
div {},
div {},
}
}
}
h2 {}
h3 {}
"abcd123"
}
})
}

View File

@ -116,7 +116,7 @@ pub(crate) mod innerlude {
// Re-export the FC macro
pub use crate as dioxus;
pub use crate::nodebuilder as builder;
pub use dioxus_core_macro::{fc, html};
pub use dioxus_core_macro::{fc, html, rsx};
}
/// Re-export common types for ease of development use.
@ -144,7 +144,7 @@ pub mod prelude {
pub use crate::nodebuilder as builder;
// pub use dioxus_core_macro::fc;
pub use dioxus_core_macro::format_args_f;
pub use dioxus_core_macro::{fc, html};
pub use dioxus_core_macro::{fc, html, rsx};
// pub use crate::diff::DiffMachine;
pub use crate::dodriodiff::DiffMachine;

View File

@ -46,12 +46,11 @@ static Example: FC<()> = |ctx, props| {
</button>
<button
class="inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
// onclick={move |_| log::debug!("set jill")}>
onclick={move |_| set_name("jill")}
onclick={move |_| set_name("jill")}>
"Jill!"
</button>
class="inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
onclick={move |_| set_name("jill")}
onclick={move |_| set_name("jill")}>
"Jill!"
</button>
</div>
</div>
</section>