- })
};
```
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...
@@ -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.
-
-
-
-
-
-
-
-
-
diff --git a/docs/posts/02-utilites.md b/docs/posts/02-utilites.md
index 39baf3d4c..43164926b 100644
--- a/docs/posts/02-utilites.md
+++ b/docs/posts/02-utilites.md
@@ -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! {
"Hello, {name}!"
diff --git a/docs/posts/03-vnode-macros.md b/docs/posts/03-vnode-macros.md
index 1e0ca6a5f..a05cd3a88 100644
--- a/docs/posts/03-vnode-macros.md
+++ b/docs/posts/03-vnode-macros.md
@@ -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! {
+
+
"Hello, {name}!"
+
"Status: {pending}!"
+
"Count {count}!"
+
+ })
+}
+```
+
+## 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 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! {
} },
+
+ // Any expression that is Into
+ {expr}
+ }
+ }
+ })
+}
+```
diff --git a/packages/core-macro/src/htm.rs b/packages/core-macro/src/htm.rs
index 4c4aeb464..cf83eba32 100644
--- a/packages/core-macro/src/htm.rs
+++ b/packages/core-macro/src/htm.rs
@@ -392,8 +392,6 @@ impl ToTokens for ToToksCtx<&LitStr> {
}
}
-mod styles {}
-
#[cfg(test)]
mod test {
fn parse(input: &str) -> super::Result {
diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs
index 54bb68197..6bf00e5db 100644
--- a/packages/core-macro/src/lib.rs
+++ b/packages/core-macro/src/lib.rs
@@ -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]
diff --git a/packages/core-macro/src/rsxt.rs b/packages/core-macro/src/rsxt.rs
new file mode 100644
index 000000000..209b2a7cc
--- /dev/null
+++ b/packages/core-macro/src/rsxt.rs
@@ -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 {
+ 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 {
+ 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,
+ children: MaybeExpr>,
+}
+
+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 {
+ // steps:
+ // grab ident as name
+ // peak to the next character
+ // ensure it's a {
+
+ // s.parse::()?;
+ 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::()?;
+ // s.parse()
+ // s.parse_terminated(parser)
+ // s.parse::()?;
+
+ let mut attrs = vec![];
+ // let mut children = vec![];
+ let mut children: Vec = vec![];
+
+ // // keep looking for attributes
+ // while !s.peek(Token![>]) {
+ // // self-closing
+ // if s.peek(Token![/]) {
+ // s.parse::()?;
+ // s.parse::]>()?;
+ // return Ok(Self {
+ // name,
+ // attrs,
+ // children: MaybeExpr::Literal(vec![]),
+ // });
+ // }
+ // attrs.push(s.parse()?);
+ // }
+ // s.parse::]>()?;
+
+ // // 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::()?;
+ // s.parse::()?;
+ // 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::]>()?;
+ 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 {
+ let mut name = Ident::parse_any(s)?;
+ let name_str = name.to_string();
+ s.parse::()?;
+
+ // 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),
+ Event(ExprClosure),
+ // todo Bool(MaybeExpr)
+}
+
+/// =======================================
+/// Parse just plain text
+/// =======================================
+/// - [ ] Perform formatting automatically
+///
+///
+struct TextNode(MaybeExpr);
+
+impl Parse for TextNode {
+ fn parse(s: ParseStream) -> Result {
+ 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 {
+ Literal(T),
+ Expr(Expr),
+}
+
+impl Parse for MaybeExpr {
+ fn parse(s: ParseStream) -> Result {
+ 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>
+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 {
+ inner: T,
+}
+
+impl<'a, T> ToToksCtx {
+ fn new(inner: T) -> Self {
+ ToToksCtx { inner }
+ }
+
+ fn recurse(&self, inner: U) -> ToToksCtx {
+ ToToksCtx { inner }
+ }
+}
+
+impl ToTokens for ToToksCtx<&LitStr> {
+ fn to_tokens(&self, tokens: &mut TokenStream2) {
+ self.inner.to_tokens(tokens)
+ }
+}
diff --git a/packages/core/examples/alternative.rs b/packages/core/examples/alternative.rs
index 71ade9536..6cf18fb6f 100644
--- a/packages/core/examples/alternative.rs
+++ b/packages/core/examples/alternative.rs
@@ -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 {}
}
diff --git a/packages/core/examples/dummy.rs b/packages/core/examples/dummy.rs
index c243ad523..8f6c30a86 100644
--- a/packages/core/examples/dummy.rs
+++ b/packages/core/examples/dummy.rs
@@ -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) {}
}
diff --git a/packages/core/examples/rsx_usage.rs b/packages/core/examples/rsx_usage.rs
new file mode 100644
index 000000000..5a8aa971e
--- /dev/null
+++ b/packages/core/examples/rsx_usage.rs
@@ -0,0 +1,89 @@
+use dioxus_core::prelude::*;
+
+fn main() {}
+
+fn Example(ctx: Context, props: ()) -> DomTree {
+ ctx.render(rsx! {
+ div {
+