wip: book documentation

This commit is contained in:
Jonathan Kelley committed 2021-10-16 17:04:28 -04:00
1 parent 7dedad4c26
commit 16dbf4a6f8
87 files changed
+997 -319

No files matched your search

+101
View File
@@ -0,0 +1,101 @@
# Introduction
![dioxuslogo](./images/dioxuslogo_full.png)
**Dioxus** is a framework and ecosystem for building fast, scalable, and robust user interfaces with the Rust programming language. This guide will help you get up-and-running with Dioxus running on the Web, Desktop, Mobile, and more.
```rust, ignore
// An example Dioxus app - closely resembles React
static App: FC<()> = |cx, props| {
let mut count = use_state(cx, || 0);
cx.render(rsx!(
h1 { "High-Five counter: {count}" }
button { onclick: move |_| count += 1, "Up high!" }
button { onclick: move |_| count -= 1, "Down low!" }
))
};
```
The Dioxus API and patterns closely resemble React - if this guide is lacking in any general concept or an error message is confusing, we recommend substituting "React" for "Dioxus" in your web search terms. A major goal of Dioxus is to provide a familiar toolkit for UI in Rust, so we've chosen to follow in the footsteps of popular UI frameworks (React, Redux, etc) - if you know React, then you already know Dioxus. If you don't know either, this guide will still help you!
### Web Support
---
The Web is the most-supported target platform for Dioxus. To run on the Web, your app must be compiled to WebAssembly and depend on the `dioxus` crate with the `web` feature enabled. Because of the WASM limitation, not every crate will work with your web-apps, so you'll need to make sure that your crates work without native system calls (timers, IO, etc).
Because the web is a fairly mature platform, we expect there to be very little API churn for web-based features.
[Jump to the getting started guide for the web.]()
Examples:
- [TodoMVC](https://github.com/dioxusLabs/todomvc/)
- [ECommerce]()
- [Photo Editor]()
[![todomvc](https://github.com/DioxusLabs/todomvc/raw/master/example.png)](https://github.com/dioxusLabs/todomvc/)
### SSR Support
---
Dioxus supports server-side rendering! In a pinch, you can literally "debug" the VirtualDom:
```rust
let dom = VirtualDom::new(App);
println!("{:?}, dom");
```
For rendering statically to an `.html` file or from a WebServer, then you'll want to make sure the `ssr` feature is enabled in the `dioxus` crate and use the `dioxus::ssr` API. We don't expect the SSR API to change drastically in the future.
```rust
let contents = dioxus::ssr::render_vdom(&dom, |c| c);
```
[Jump to the getting started guide for SSR.]()
Examples:
- [Example DocSite]()
- [Tide WebServer]()
- [Markdown to fancy HTML generator]()
### Desktop Support
---
The desktop is a powerful target for Dioxus, but is currently limited in capability when compared to the Web platform. Currently, desktop apps are rendered with the platform's WebView library, but your Rust code is running natively on a native thread. This means that browser APIs are *not* available, so rendering WebGL, Canvas, etc is not as easy as the Web. However, native system APIs *are* accessible, so streaming, WebSockets, filesystem, etc are all viable APIs. In the future, we plan to move to a custom webrenderer-based DOM renderer with WGPU integrations.
Desktop APIs will likely be in flux as we figure out better patterns than our ElectronJS counterpart.
[Jump to the getting started guide for Desktop.]()
Examples:
- [File explorer]()
- [Bluetooth scanner]()
- [Device Viewer]()
[![FileExplorerExample](https://github.com/DioxusLabs/file-explorer-example/raw/master/image.png)](https://github.com/dioxusLabs/file-explorer/)
### Mobile Support
---
Mobile is currently the least-supported renderer target for Dioxus. Mobile apps are rendered with the platform's WebView, meaning that animations, transparency, and native widgets are not currently achievable. In addition, iOS is the only supported Mobile Platform. It is possible to get Dioxus running on Android and rendered with WebView, but the Rust windowing library that Dioxus uses - tao - does not currently supported Android.
[Jump to the getting started guide for Mobile.]()
Examples:
- [Todo App]()
- [Chat App]()
### LiveView Support
---
The internal architecture of Dioxus was designed from day one to support the `LiveView` use-case, where a web server hosts a running app for each connected user. As of today, there is no out-of-the-box LiveView support - you'll need to wire this up yourself. While not currently fully implemented, the expectation is that LiveView apps can be a hybrid between WASM and server-rendered where only portions of a page are "live" and the rest of the page is either server-rendered, statically generated, or handled by the host SPA.
### Multithreaded Support
---
The Dioxus VirtualDom, sadly, is not `Send.` This means you can't easily use Dioxus with most web frameworks like Tide, Rocket, Axum, etc. Currently, your two options include:
- Actix: Actix supports `!Send` handlers
- VirtualDomPool: A thread-per-core VirtualDom pool that uses message passing and serialization
When working with web frameworks that require `Send`, it is possible to render a VirtualDom immediately to a String - but you cannot hold the VirtualDom across an await point. For retained-state SSR (essentially LiveView), you'll need to create a VirtualDomPool which solves the `Send` problem.
+62
View File
@@ -0,0 +1,62 @@
# Summary
- [Introduction](README.md)
- [Getting Setup](setup.md)
- [Hello, World!](hello_world.md)
- [Core Topics](concepts/00-index.md)
- [Intro to VNodes](concepts/vnodes.md)
- [VNodes with RSX, HTML, and NodeFactory](concepts/rsx.md)
- [RSX in Depth](concepts/rsx_in_depth.md)
- [Components and Props](concepts/components.md)
- [Memoization](concepts/memoization.md)
- [Hooks and Internal State](concepts/hooks.md)
- [Event handlers](concepts/event_handlers.md)
- [Global State](concepts/sharedstate.md)
- [User Input and Controlled Components](concepts/errorhandling.md)
- [Error handling](concepts/errorhandling.md)
- [Reference Guide]()
- [Advanced Guides]()
- [Custom Renderer]()
- [LiveView Support]()
- [Bundling and Distributing]()
- [Web]()
- [Getting Started]()
- [Down-casting Nodes]()
- [Wrapping Web APIs]()
- [SSR]()
- [Wrapping Web APIs]()
- [Desktop]()
- [Wrapping Web APIs]()
- [Mobile]()
- [Wrapping Web APIs]()
<!-- - [Command Line Tool](cli/README.md)
- [init](cli/init.md)
- [build](cli/build.md)
- [watch](cli/watch.md)
- [serve](cli/serve.md)
- [test](cli/test.md)
- [clean](cli/clean.md)
- [Format](format/README.md)
- [SUMMARY.md](format/summary.md)
- [Draft chapter]()
- [Configuration](format/configuration/README.md)
- [General](format/configuration/general.md)
- [Preprocessors](format/configuration/preprocessors.md)
- [Renderers](format/configuration/renderers.md)
- [Environment Variables](format/configuration/environment-variables.md)
- [Theme](format/theme/README.md)
- [index.hbs](format/theme/index-hbs.md)
- [Syntax highlighting](format/theme/syntax-highlighting.md)
- [Editor](format/theme/editor.md)
- [MathJax Support](format/mathjax.md)
- [mdBook-specific features](format/mdbook.md)
- [Continuous Integration](continuous-integration.md)
- [For Developers](for_developers/README.md)
- [Preprocessors](for_developers/preprocessors.md)
- [Alternative Backends](for_developers/backends.md) -->
-----------
[Contributors](misc/contributors.md)
+273
View File
@@ -0,0 +1,273 @@
# Custom Renderer
Dioxus is an incredibly portable framework for UI development. The lessons, knowledge, hooks, and components you acquire over time can always be used for future projects. However, sometimes those projects cannot leverage a supported renderer or you need to implement your own better renderer.
Great news: the design of the renderer is entirely up to you! We provide suggestions and inspiration with the 1st party renderers, but only really require implementation of the `RealDom` trait for things to function properly.
## The specifics:
Implementing the renderer is fairly straightforward. The renderer needs to:
1. Handle the stream of edits generated by updates to the virtual DOM
2. Register listeners and pass events into the virtual DOM's event system
3. Progress the virtual DOM with an async executor (or disable the suspense API and use `progress_sync`)
Essentially, your renderer needs to implement the `RealDom` trait and generate `EventTrigger` objects to update the VirtualDOM. From there, you'll have everything needed to render the VirtualDOM to the screen.
Internally, Dioxus handles the tree relationship, diffing, memory management, and the event system, leaving as little as possible required for renderers to implement themselves.
For reference, check out the WebSys renderer as a starting point for your custom renderer.
## Trait implementation and DomEdits
The current `RealDom` trait lives in `dioxus_core/diff`. A version of it is provided here (but might not be up-to-date):
```rust
pub trait RealDom<'a> {
fn handle_edit(&mut self, edit: DomEdit);
fn request_available_node(&mut self) -> ElementId;
fn raw_node_as_any(&self) -> &mut dyn Any;
}
```
For reference, the "DomEdit" type is a serialized enum that represents an atomic operation occurring on the RealDom. The variants roughly follow this set:
```rust
enum DomEdit {
PushRoot,
AppendChildren,
ReplaceWith,
CreateTextNode,
CreateElement,
CreateElementNs,
CreatePlaceholder,
NewEventListener,
RemoveEventListener,
SetText,
SetAttribute,
RemoveAttribute,
}
```
The Dioxus diffing mechanism operates as a [stack machine](https://en.wikipedia.org/wiki/Stack_machine) where the "push_root" method pushes a new "real" DOM node onto the stack and "append_child" and "replace_with" both remove nodes from the stack.
### An example
For the sake of understanding, lets consider this example - a very simple UI declaration:
```rust
rsx!( h1 {"hello world"} )
```
To get things started, Dioxus must first navigate to the container of this h1 tag. To "navigate" here, the internal diffing algorithm generates the DomEdit `PushRoot` where the ID of the root is the container.
When the renderer receives this instruction, it pushes the actual Node onto its own stack. The real renderer's stack will look like this:
```rust
instructions: [
PushRoot(Container)
]
stack: [
ContainerNode,
]
```
Next, Dioxus will encounter the h1 node. The diff algorithm decides that this node needs to be created, so Dioxus will generate the DomEdit `CreateElement`. When the renderer receives this instruction, it will create an unmounted node and push into its own stack:
```rust
instructions: [
PushRoot(Container),
CreateElement(h1),
]
stack: [
ContainerNode,
h1,
]
```
Next, Dioxus sees the text node, and generates the `CreateTextNode` DomEdit:
```rust
instructions: [
PushRoot(Container),
CreateElement(h1),
CreateTextNode("hello world")
]
stack: [
ContainerNode,
h1,
"hello world"
]
```
Remember, the text node is not attached to anything (it is unmounted) so Dioxus needs to generate an Edit that connects the text node to the h1 element. It depends on the situation, but in this case we use `AppendChildren`. This pops the text node off the stack, leaving the h1 element as the next element in line.
```rust
instructions: [
PushRoot(Container),
CreateElement(h1),
CreateTextNode("hello world"),
AppendChildren(1)
]
stack: [
ContainerNode,
h1
]
```
We call `AppendChildren` again, popping off the h1 node and attaching it to the parent:
```rust
instructions: [
PushRoot(Container),
CreateElement(h1),
CreateTextNode("hello world"),
AppendChildren(1),
AppendChildren(1)
]
stack: [
ContainerNode,
]
```
Finally, the container is popped since we don't need it anymore.
```rust
instructions: [
PushRoot(Container),
CreateElement(h1),
CreateTextNode("hello world"),
AppendChildren(1),
AppendChildren(1),
Pop(1)
]
stack: []
```
Over time, our stack looked like this:
```rust
[]
[Container]
[Container, h1]
[Container, h1, "hello world"]
[Container, h1]
[Container]
[]
```
Notice how our stack is empty once UI has been mounted. Conveniently, this approach completely separates the VirtualDOM and the Real DOM. Additionally, these edits are serializable, meaning we can even manage UIs across a network connection. This little stack machine and serialized edits makes Dioxus independent of platform specifics.
Dioxus is also really fast. Because Dioxus splits the diff and patch phase, it's able to make all the edits to the RealDOM in a very short amount of time (less than a single frame) making rendering very snappy. It also allows Dioxus to cancel large diffing operations if higher priority work comes in while it's diffing.
It's important to note that there _is_ one layer of connectedness between Dioxus and the renderer. Dioxus saves and loads elements (the PushRoot edit) with an ID. Inside the VirtualDOM, this is just tracked as a u64.
Whenever a `CreateElement` edit is generated during diffing, Dioxus increments its node counter and assigns that new element its current NodeCount. The RealDom is responsible for remembering this ID and pushing the correct node when PushRoot(ID) is generated. Dioxus doesn't reclaim IDs of elements its removed, but your renderer probably will want to. To do this, we suggest using a `SecondarySlotMap` if implementing the renderer in Rust, or just deferring to a HashMap-type approach.
This little demo serves to show exactly how a Renderer would need to process an edit stream to build UIs. A set of serialized EditStreams for various demos is available for you to test your custom renderer against.
## Event loop
Like most GUIs, Dioxus relies on an event loop to progress the VirtualDOM. The VirtualDOM itself can produce events as well, so it's important that your custom renderer can handle those too.
The code for the WebSys implementation is straightforward, so we'll add it here to demonstrate how simple an event loop is:
```rust
pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
// Push the body element onto the WebsysDom's stack machine
let mut websys_dom = crate::new::WebsysDom::new(prepare_websys_dom());
websys_dom.stack.push(root_node);
// Rebuild or hydrate the virtualdom
self.internal_dom.rebuild(&mut websys_dom)?;
// Wait for updates from the real dom and progress the virtual dom
loop {
let user_input_future = websys_dom.wait_for_event();
let internal_event_future = self.internal_dom.wait_for_event();
match select(user_input_future, internal_event_future).await {
Either::Left((trigger, _)) => trigger,
Either::Right((trigger, _)) => trigger,
}
}
while let Some(trigger) = {
websys_dom.stack.push(body_element.first_child().unwrap());
self.internal_dom
.progress_with_event(&mut websys_dom, trigger)?;
}
}
```
It's important that you decode the real events from your event system into Dioxus' synthetic event system (synthetic meaning abstracted). This simply means matching your event type and creating a Dioxus `VirtualEvent` type. Your custom event must implement the corresponding event trait. Right now, the VirtualEvent system is modeled almost entirely around the HTML spec, but we are interested in slimming it down.
```rust
fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
match event.type_().as_str() {
"keydown" | "keypress" | "keyup" => {
struct CustomKeyboardEvent(web_sys::KeyboardEvent);
impl dioxus::events::KeyboardEvent for CustomKeyboardEvent {
fn char_code(&self) -> usize { self.0.char_code() }
fn ctrl_key(&self) -> bool { self.0.ctrl_key() }
fn key(&self) -> String { self.0.key() }
fn key_code(&self) -> usize { self.0.key_code() }
fn locale(&self) -> String { self.0.locale() }
fn location(&self) -> usize { self.0.location() }
fn meta_key(&self) -> bool { self.0.meta_key() }
fn repeat(&self) -> bool { self.0.repeat() }
fn shift_key(&self) -> bool { self.0.shift_key() }
fn which(&self) -> usize { self.0.which() }
fn get_modifier_state(&self, key_code: usize) -> bool { self.0.get_modifier_state() }
}
VirtualEvent::KeyboardEvent(Rc::new(event.clone().dyn_into().unwrap()))
}
_ => todo!()
```
## Custom raw elements
If you need to go as far as relying on custom elements for your renderer - you totally can. This still enables you to use Dioxus' reactive nature, component system, shared state, and other features, but will ultimately generate different nodes. All attributes and listeners for the HTML and SVG namespace are shuttled through helper structs that essentially compile away (pose no runtime overhead). You can drop in your own elements any time you want, with little hassle. However, you must be absolutely sure your renderer can handle the new type, or it will crash and burn.
These custom elements are defined as unit structs with trait implementations.
For example, the `div` element is (approximately!) defined as such:
```rust
struct div;
impl div {
/// Some glorious documentaiton about the class proeprty.
#[inline]
fn class<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
cx.attr("class", val, None, false)
}
// more attributes
}
```
You've probably noticed that many elements in the `rsx!` and `html!` macros support on-hover documentation. The approach we take to custom elements means that the unit struct is created immediately where the element is used in the macro. When the macro is expanded, the doc comments still apply to the unit struct, giving tons of in-editor feedback, even inside a proc macro.
## Compatibility
Forewarning: not every hook and service will work on your platform. Dioxus wraps things that need to be cross-platform in "synthetic" types. However, downcasting to a native type might fail if the types don't match.
There are three opportunities for platform incompatibilities to break your program:
1. When downcasting elements via `Ref.downcast_ref<T>()`
2. When downcasting events via `Event.downcast_ref<T>()`
3. Calling platform-specific APIs that don't exist
The best hooks will properly detect the target platform and still provide functionality, failing gracefully when a platform is not supported. We encourage - and provide - an indication to the user on what platforms a hook supports. For issues 1 and 2, these return a result as to not cause panics on unsupported platforms. When designing your hooks, we recommend propagating this error upwards into user facing code, making it obvious that this particular service is not supported.
This particular code _will panic_ due to the unwrap on downcast_ref. Try to avoid these types of patterns.
```rust
let div_ref = use_node_ref(cx);
cx.render(rsx!{
div { ref: div_ref, class: "custom class",
button { "click me to see my parent's class"
onclick: move |_| if let Some(div_ref) = div_ref {
log::info!("Div class is {}", div_ref.downcast_ref::<web_sys::Element>().unwrap().class())
}
}
}
})
```
## Conclusion
That should be it! You should have nearly all the knowledge required on how to implement your own renderer. We're super interested in seeing Dioxus apps brought to custom desktop renderers, mobile renderer, video game UI, and even augmented reality! If you're interesting in contributing to any of the these projects, don't be afraid to reach out or join the community.
+32
View File
@@ -0,0 +1,32 @@
# Dioxus Liveview
Liveview is a configuration where a server and a client work together to render a Dioxus app. Liveview monomorphizes a web application, eliminating the need for frontend-specific APIs.
This is a developer-friendly alternative to the JAM-stack (Javascript + API + Markdown), combining the WASM-compatibility and async performance of Rust.
## Why liveview?
### No APIs necessary!
Because Liveview combines the server and the client, you'll find dedicated APIs unnecessary. You'll still want to implement a datafetching service for Live-apps, but this can be implemented as a crate and shared between apps. This approach was designed to let you model out your data requirements without needing to maintain a public versioned API.
You can find more information to data modeling and fetching for LiveApps in the "Book of Dioxus Patterns".
### Ease of deployment
There is no hassle for deploying Dioxus liveview apps - simply upload the binary to your hosting provider of choice. There simply is no need to deal with intermediate APIs. An app's frontend and backend are tightly coupled to each other, meaning that the backed and frontend are always up to date.
This is especially powerful for small teams, where fast iteration, versioned unique prototypes, and simple deployments are important. As your app grows, Dioxus will happily grow with you, serving up to 100K RPS (thanks to async Rust performance).
### Power of server rendering combined with the reactive nature of JavaScript
Liveview apps are backed by a server and enhanced with web-assembly. This completely eliminates the need to write Javascript to add dynamic content to webpages. Apps are written in only **one** language and require essentially 0 knowledge of build systems, transpiling or ECMAScript versions. Minimum browser support is guaranteed to cover 95% of users, and the Dioxus-CLI can be configured to generate polyfills for even more support.
### Support with LiveHost by Dioxus-Labs
The Dioxus-CLI comes ready for use with premium Dioxus-Labs services, like LiveHost. Simply link up your git repo to the Dioxus LiveHost and start deploying your fullstack Dioxus app today. LiveHost supports:
- Versioned fronted/backend with unique links
- Builtin CI/CD
- Managed and pluggable storage database backends
- Serverless support for minimal latency
- Site Analytics
- Lighthouse optimization
- On-premise support (see license terms)
Dioxus LiveHost is a Dioxus LiveApp management service with all supported features in a single binary. For OSS, we permit free usage and deployment of LiveHost to your favorite hosting provider.
+1
View File
@@ -0,0 +1 @@
# Command Line Tool
+1
View File
@@ -0,0 +1 @@
# build
+1
View File
@@ -0,0 +1 @@
# clean
+1
View File
@@ -0,0 +1 @@
# init
+1
View File
@@ -0,0 +1 @@
# serve
+1
View File
@@ -0,0 +1 @@
# test
+1
View File
@@ -0,0 +1 @@
# watch
+73
View File
@@ -0,0 +1,73 @@
# Core Topics
In this chapter of the book, we'll cover some core topics on how Dioxus works and how to best leverage the features to build a beautiful, reactive app.
At a very high level, Dioxus is simply a Rust framework for _declaring_ user interfaces and _reacting_ to changes.
1) We declare what we want our user interface to look like given a state using Rust-based logic and control flow.
2) We declare how we want our state to change when the user triggers an event.
## Declarative UI
Dioxus is a *declarative* framework. This means that instead of manually writing calls to "create element" and "set element background to red," we simply *declare* what we want the element to look like and let Dioxus handle the differences.
Let's pretend that we have a stoplight we need to control - it has a color state with red, yellow, and green as options.
Using an imperative approach, we would have to manually declare each element and then handlers for advancing the stoplight.
```rust
let container = Container::new();
let green_light = Light::new().color("green").enabled(true);
let yellow_light = Light::new().color("yellow").enabled(false);
let red_light = Light::new().color("red").enabled(false);
container.push(green_light);
container.push(yellow_light);
container.push(red_light);
container.onclick(move |_| {
if red_light.enabled() {
red_light.set_enabled(false);
green_light.set_enabled(true);
} else if yellow_light.enabled() {
yellow_light.set_enabled(false);
red_light.set_enabled(true);
} else if green_light.enabled() {
green_light.set_enabled(false);
yellow_light.set_enabled(true);
}
});
```
As the UI grows in scale, our logic to keep each element in the proper state would grow exponentially. This can become very unwieldy and lead to out-of-sync UIs that harm user experience.
Instead, with Dioxus, we *declare* what we want our UI to look like:
```rust
let state = "red";
rsx!(
Container {
Light { color: "red", enabled: {state == "red"} }
Light { color: "yellow", enabled: {state == "yellow"} }
Light { color: "green", enabled: {state == "green"} }
onclick: |_| {
state = match state {
"green" => "yellow",
"yellow" => "red",
"red" => "green",
}
}
}
)
```
Remember: this concept is not new! Many frameworks are declarative - with React being the most popular. Declarative frameworks tend to be much more enjoyable to work with than imperative frameworks.
Here's some reading about declaring UI in React:
- [https://stackoverflow.com/questions/33655534/difference-between-declarative-and-imperative-in-react-js](https://stackoverflow.com/questions/33655534/difference-between-declarative-and-imperative-in-react-js)
- [https://medium.com/@myung.kim287/declarative-vs-imperative-251ce99c6c44](https://medium.com/@myung.kim287/declarative-vs-imperative-251ce99c6c44)
+16
View File
@@ -0,0 +1,16 @@
```rust
fn Example<'a>(cx: Context<'a>, props: &()) -> DomTree<'a> {
let service = use_combubulator(cx);
let Status { name, pending, count } = service.info();
html! {
<div>
<p> "Hello, {name}!" </p>
<p> "Status: {pending}!" </p>
<p> "Count {count}!" </p>
<button onclick={|_| service.update()}>
"Refresh services"
</button>
</div>
}
}
```
+45
View File
@@ -0,0 +1,45 @@
# Context API
```rust
// Create contexts available to children
// Only one context can be associated with any given component
// This is known as "exposed state". Children can access this context,
// but will not be automatically subscribed.
fn ContextCreate(cx: &mut Context<()>) -> DomTree {
let context = cx.set_context(|| CustomContext::new());
html! { <> {cx.children()} </> }
}
fn ContextRead(cx: &mut Context<()>) -> DomTree {
// Panics if context is not available
let some_cx = cx.get_context::<CustomContext>();
let text = some_cx.select("some_selector");
html! { <div> "{text}" </div> }
}
fn Subscription(cx: &mut Context<()>) -> DomTree {
// Open a "port" on the component for actions to trigger a re-evaluation
let subscription = cx.new_subscription();
// A looping timer - the effect is re-called on every re-evaluation
use_async_effect(cx, move || async {
timer::new(2000).await;
subscription.call();
}, None);
// A one-shot timer, the deps don't change so the effect only happens once
use_async_effect_deps(cx, move || async {
timer::new(2000).await;
subscription.call();
}, ());
}
// Mix subscriptions and context to make a simple Redux
fn use_global_state<T: UserContextTrait>(cx: &mut Context<()>) -> T {
let some_cx = cx.get_context::<T>();
let component_subscription = cx.new_subscription();
some_cx.subscribe_component(component_subscription);
some_cx
}
```
+33
View File
@@ -0,0 +1,33 @@
# Subscriptions
Yew subscriptions are used to schedule update for components into the future. The `Context` object can create subscriptions:
```rust
fn Component(cx: Component<()>) -> DomTree {
let update = cx.schedule();
// Now, when the subscription is called, the component will be re-evaluted
update.consume();
}
```
Whenever a component's subscription is called, the component will then re-evaluated. You can consider the input properties of
a component to be just another form of subscription. By default, the Dioxus component system automatically diffs a component's props
when the parent function is called, and if the props are different, the child component's subscription is called.
The subscription API exposes this functionality allowing hooks and state management solutions the ability to update components whenever
some state or event occurs outside of the component. For instance, the `use_context` hook uses this to subscribe components that use a
particular context.
```rust
fn use_context<I>(cx: Context<T>) -> I {
}
```
Whitespace-only changes.
Whitespace-only changes.
+82
View File
@@ -0,0 +1,82 @@
# Concurrent mode
Concurrent mode provides a mechanism for building efficient asynchronous components. With this feature, components don't need to render immediately, and instead can schedule a future render by returning a future.
To make a component asynchronous, simply change its function signature to async.
```rust
fn Example(cx: Context<()>) -> Vnode {
rsx!{ <div> "Hello world!" </div> }
}
```
becomes
```rust
async fn Example(cx: Context<()>) -> Vnode {
rsx!{ <div> "Hello world!" </div> }
}
```
Now, logic in components can be awaited to delay updates of the component and its children. Like so:
```rust
async fn Example(cx: Context<()>) -> Vnode {
let name = fetch_name().await;
rsx!{ <div> "Hello {name}" </div> }
}
async fetch_name() -> String {
// ...
}
```
This component will only schedule its render once the fetch is complete. However, we _don't_ recommend using async/await directly in your components.
Async is a notoriously challenging yet rewarding tool for efficient tools. If not careful, locking and unlocking shared aspects of the component's context can lead to data races and panics. If a shared resource is locked while the component is awaiting, then other components can be locked or panic when trying to access the same resource. These rules are especially important when references to shared global state are accessed using the context object's lifetime. If mutable references to data captured immutably by the context are taken, then the component will panic, and cause confusion.
Instead, we suggest using hooks and future combinators that can safely utilize the safe guards of the component's Context when interacting with async tasks.
As part of our Dioxus hooks crate, we provide a data loader hook which pauses a component until its async dependencies are ready. This caches requests, reruns the fetch if dependencies have changed, and provides the option to render something else while the component is loading.
```rust
async fn ExampleLoader(cx: Context<()>) -> Vnode {
/*
Fetch, pause the component from rendering at all.
The component is locked while waiting for the request to complete
While waiting, an alternate component is scheduled in its place.
This API stores the result on the Context object, so the loaded data is taken as reference.
*/
let name: &Result<SomeStructure> = use_fetch_data("http://example.com/json", ())
.place_holder(|cx, props|rsx!{<div> "loading..." </div>})
.delayed_place_holder(1000, |cx, props|rsx!{ <div> "still loading..." </div>})
.await;
match name {
Ok(name) => rsx! { <div> "Hello {something}" </div> },
Err(e) => rsx! { <div> "An error occured :(" </div>}
}
}
```
```rust
async fn Example(cx: Context<()>) -> DomTree {
// Diff this set between the last set
// Check if we have any outstanding tasks?
//
// Eventually, render the component into the VDOM when the future completes
<div>
<Example />
</div>
// Render a div, queue a component
// Render the placeholder first, then when the component is ready, then render the component
<div>
<Suspense placeholder={html!{<div>"Loading"</div>}}>
<Example />
</Suspense>
</div>
}
```
+56
View File
@@ -0,0 +1,56 @@
# Memoization and the arena allocator
Dioxus differs slightly from other UI virtual doms in some subtle ways due to its memory allocator.
One important aspect to understand is how props are passed down from parent components to children. All "components" (custom user-made UI elements) are tightly allocated together in an arena. However, because props and hooks are generically typed, they are casted to any and allocated on the heap - not in the arena with the components.
With this system, we try to be more efficient when leaving the component arena and entering the heap. By default, props are memoized between renders using COW and context. This makes props comparisons fast - done via ptr comparisons on the cow pointer. Because memoization is done by default, parent re-renders will _not_ cascade to children if the child's props did not change.
https://dmitripavlutin.com/use-react-memo-wisely/
This behavior is defined as an implicit attribute to user components. When in React land you might wrap a component is `react.memo`, Dioxus components are automatically memoized via an implicit attribute. You can manually configure this behavior on any component with "nomemo" to disable memoization.
```rust
fn test() -> DomTree {
html! {
<>
<SomeComponent nomemo />
// same as
<SomeComponent nomemo=true />
</>
}
}
static TestComponent: FC<()> = |cx, props|html!{<div>"Hello world"</div>};
static TestComponent: FC<()> = |cx, props|{
let g = "BLAH";
html! {
<div> "Hello world" </div>
}
};
#[functional_component]
static TestComponent: FC<{ name: String }> = |cx, props|html! { <div> "Hello {name}" </div> };
```
## Why this behavior?
"This is different than React, why differ?".
Take a component likes this:
```rust
fn test(cx: Context<()>) -> DomTree {
let Bundle { alpha, beta, gamma } = use_context::<SomeContext>(cx);
html! {
<div>
<Component name=alpha />
<Component name=beta />
<Component name=gamma />
</div>
}
}
```
While the contents of the destructued bundle might change, not every child component will need to be re-rendered.
+155
View File
@@ -0,0 +1,155 @@
# Signals: Skipping the Diff
In most cases, the traditional VirtualDOM diffing pattern is plenty fast. Dioxus will compare trees of VNodes, find the differences, and then update the Renderer's DOM with the updates. However, this can generate a lot of overhead for certain types of components. In apps where reducing visual latency is a top priority, you can opt into the `Signals` api to entirely disable diffing of hot-path components. Dioxus will then automatically construct a state machine for your component, making updates nearly instant.
Signals build on the same infrastructure that powers asynchronous rendering where in-tree values can be updated outside of the render phase. In async rendering, a future is used as the signal source. With the raw signal API, any value can be used as a signal source.
By default, Dioxus will only try to diff subtrees of components with dynamic content, automatically skipping diffing static content.
## What does this look like?
Your component today might look something like this:
```rust
fn Comp(cx: Context<()>) -> DomTree {
let (title, set_title) = use_state(cx, || "Title".to_string());
cx.render(rsx!{
input {
value: title,
onchange: move |new| set_title(new.value())
}
})
}
```
This component is fairly straightforward - the input updates its own value on every change. However, every call to set_title will re-render the component. If we add a large list, then every time we update the title input, Dioxus will need to diff the entire list, over, and over, and over. This is **a lot** of wasted clock-cycles!
```rust
fn Comp(cx: Context<()>) -> DomTree {
let (title, set_title) = use_state(cx, || "Title".to_string());
cx.render(rsx!{
div {
input {
value: title,
onchange: move |new| set_title(new.value())
}
ul {
{0..10000.map(|f| rsx!{
li { "{f}" }
})}
}
}
})
}
```
Many experienced React developers will just say "this is bad design" - but we consider it to be a pit of failure, rather than a pit of success! That's why signals exist - to push you in a more performant (and ergonomic) direction. Signals let us directly bind values to their final place in the VirtualDOM. Whenever the signal value is updated, Dioxus will only the DOM nodes where that signal is used. Signals are built into Dioxus, so we can directly bind attributes of elements to their updates.
We can use signals to generate a two-way binding between data and the input box. Our text input is now just a two-line component!
```rust
fn Comp(cx: Context<()>) -> DomTree {
let mut title = use_signal(&cx, || String::from("Title"));
cx.render(rsx!(input { value: title }))
}
```
For a slightly more interesting example, this component calculates the sum between two numbers, but totally skips the diffing process.
```rust
fn Calculator(cx: Context<()>) -> DomTree {
let mut a = use_signal(&cx, || 0);
let mut b = use_signal(&cx, || 0);
let mut c = a + b;
rsx! {
input { value: a }
input { value: b }
p { "a + b = {c}" }
}
}
```
Do you notice how we can use built-in operations on signals? Under the hood, we actually create a new derived signal that depends on `a` and `b`. Whenever `a` or `b` update, then `c` will update. If we need to create a new derived signal that's more complex that a basic operation (`std::ops`) we can either chain signals together or combine them:
```rust
let mut a = use_signal(&cx, || 0);
let mut b = use_signal(&cx, || 0);
// Chain signals together using the `with` method
let c = a.with(b).map(|(a, b)| *a + *b);
```
## Deref and DerefMut
If we ever need to get the value out of a signal, we can simply `deref` it.
```rust
let mut a = use_signal(&cx, || 0);
let c = *a + *b;
```
Calling `deref` or `derefmut` is actually more complex than it seems. When a value is derefed, you're essentially telling Dioxus that _this_ element _needs_ to be subscribed to the signal. If a signal is derefed outside of an element, the entire component will be subscribed and the advantage of skipping diffing will be lost. Dioxus will throw an error in the console when this happens to tell you that you're using signals wrong, but your component will continue to work.
## Global Signals
Sometimes you want a signal to propagate across your app, either through far-away siblings or through deeply-nested components. In these cases, we use Dirac: Dioxus's first-class state management toolkit. Dirac atoms automatically implement the Signal API. This component will bind the input element to the `TITLE` atom.
```rust
const TITLE: Atom<String> = || "".to_string();
const Provider: FC<()> = |cx, props|{
let title = use_signal(&cx, &TITLE);
rsx!(cx, input { value: title })
};
```
If we use the `TITLE` atom in another component, we can cause updates to flow between components without calling render or diffing either component trees:
```rust
const Receiver: FC<()> = |cx, props|{
let title = use_signal(&cx, &TITLE);
log::info!("This will only be called once!");
rsx!(cx,
div {
h1 { "{title}" }
div {}
footer {}
}
)
};
```
Dioxus knows that the receiver's `title` signal is used only in the text node, and skips diffing Receiver entirely, knowing to update _just_ the text node.
If you build a complex app on top of Dirac, you'll likely notice that many of your components simply won't be diffed at all. For instance, our Receiver component will never be diffed once it has been mounted!
## Signals and Iterators
Sometimes you want to use a collection of items. With Signals, you can bypass diffing for collections - a very powerful technique to avoid re-rendering on large collections.
By default, Dioxus is limited when you use iter/map. With the `For` component, you can provide an iterator and a function for the iterator to map to.
Dioxus automatically understands how to use your signals when mixed with iterators through Deref/DerefMut. This lets you efficiently map collections while avoiding the re-rendering of lists. In essence, signals act as a hint to Dioxus on how to avoid un-necessary checks and renders, making your app faster.
```rust
const DICT: AtomFamily<String, String> = |_| {};
const List: FC<()> = |cx, props|{
let dict = use_signal(&cx, &DICT);
cx.render(rsx!(
ul {
For { each: dict, map: |k, v| rsx!( li { "{v}" }) }
}
))
};
```
## Remote Signals
Apps that use signals will enjoy a pleasant hybrid of server-side and client-side rendering.
```rust
```
## How does it work?
Signals internally use Dioxus' asynchronous rendering infrastructure to perform updates out of the tree.
+54
View File
@@ -0,0 +1,54 @@
# Subtrees
One way of extending the Dioxus VirtualDom is through the use of "Subtrees." Subtrees are chunks of the VirtualDom tree distinct from the rest of the tree. They still participate in event bubbling, diffing, etc, but will have a separate set of edits generated during the diff phase.
For a VirtualDom that has a root tree with two subtrees, the edits follow a pattern of:
Root
-> Tree 1
-> Tree 2
-> Original root tree
- Root edits
- Tree 1 Edits
- Tree 2 Edits
- Root Edits
The goal of this functionality is to enable things like Portals, Windows, and inline alternative renderers without needing to spin up a new VirtualDom.
With the right renderer plugins, a subtree could be rendered as anything - a 3D scene, SVG, or even as the contents of a new window or modal. This functionality is similar to "Portals" in React, but much more "renderer agnostic." Portals, by nature, are not necessarily cross-platform and rely on renderer functionality, so it makes sense to abstract their purpose into the subtree concept.
The desktop renderer comes pre-loaded with the window and notification subtree plugins, making it possible to render subtrees into entirely different windows.
Subtrees also solve the "bridging" issues in React where two different renderers need two different VirtualDoms to work properly. In Dioxus, you only ever need one VirtualDom and the right renderer plugins.
## API
Due to their importance in the hierarchy, Components - not nodes - are treated as subtree roots.
```rust
fn Subtree<P>(cx: Context, props: P) -> DomTree {
}
fn Window() -> DomTree {
Subtree {
onassign: move |e| {
// create window
}
children()
}
}
fn 3dRenderer -> DomTree {
Subtree {
onassign: move |e| {
// initialize bevy
}
}
}
```
+18
View File
@@ -0,0 +1,18 @@
# 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 reusability and lessens the burden on Dioxus to maintain a non-breaking lifecycle API.
```rust
#[derive(Properties, PartialEq)]
struct MyProps {
name: String
}
fn Example(cx: Context<MyProps>) -> DomTree {
cx.render(html! {
<div> "Hello {cx.name}!" </div>
})
}
```
For functions to be valid components, they must take the `Context` object which is generic over some properties. The properties parameter must implement the `Properties` trait, which can be automatically derived. Whenever the input properties of a component changes, the function component will be re-ran and a new set of VNodes will be generated.
+18
View File
@@ -0,0 +1,18 @@
# Components and Props
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)]
struct MyProps {
name: String
}
fn Example(cx: Context<MyProps>) -> DomTree {
html! { <div> "Hello {cx.cx.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. The functional component macro makes life more productive by inlining props directly as function arguments, similar to how Rocket parses URIs.
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, templating engine, or the constructors directly.
+1
View File
@@ -0,0 +1 @@
# Error handling
+1
View File
@@ -0,0 +1 @@
# Event handlers
+1
View File
@@ -0,0 +1 @@
# Hooks and Internal State
+1
View File
@@ -0,0 +1 @@
# Conditional Lists and Keys
+1
View File
@@ -0,0 +1 @@
# Memoization
+63
View File
@@ -0,0 +1,63 @@
# VNodes with RSX, HTML, and NodeFactory
Many modern frameworks provide a domain-specific-language for declaring user-interfaces. In the case of React, this language extension is called JSX and must be handled through additional dependencies and pre/post processors to transform your source code. With Rust, we can simply provide a procedural macro in the Dioxus dependency itself that mimics the JSX language.
With Dioxus, we actually ship two different macros - a macro that mimics JSX (the `html!` macro) and a macro that mimics Rust's native nested-struct syntax (the `rsx!` macro). These macros simply transform their inputs into NodeFactory calls.
For instance, this html! call:
```rust
html!(<div> "hello world" </div>)
```
becomes this NodeFactory call:
```rust
|f| f.element(
dioxus_elements::div, // tag
[], // listeners
[], // attributes
[f.static_text("hello world")], // children
None // key
)
```
The NodeFactory API is fairly ergonomic, making it a viable option to use directly. The NodeFactory API is also compile-time correct and has incredible syntax highlighting support. We use what Rust calls a "unit type" - the `dioxus_elements::div` and associated methods to ensure that a `div` can only have attributes associated with `div`s. This lets us tack on relevant documentation, autocomplete support, and jump-to-definition for methods and attributes.
![Compile time correct syntax](../images/compiletimecorrect.png)
## html! macro
The html! macro supports a limited subset of the html standard. Rust's macro parsing tools are somewhat limited, so all text between tags _must be quoted_.
However, writing HTML by hand is a bit tedious - IDE tools for Rust don't support linting/autocomplete/syntax highlighting. We suggest using RSX - it's more natural for Rust programs and _does_ integrate well with Rust IDE tools.
```rust
let name = "jane";
let pending = false;
let count = 10;
dioxus::ssr::render_lazy(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 programs. Writing these should feel very natural, much like assembling a struct. VSCode also supports these with code folding, bracket-tabbing, bracket highlighting, section selecting, inline documentation, GOTO definition, and refactoring support.
When helpful, the Dioxus VSCode extension provides a way of converting a selection of HTML directly to RSX, so you can import templates from the web directly into your existing app.
It's also a bit easier on the eyes than HTML.
```rust
dioxus::ssr::render_lazy(rsx! {
div {
p {"Hello, {name}!"}
p {"Status: {pending}!"}
p {"Count {count}!"}
}
});
```
In the next section, we'll cover the `rsx!` macro in more depth.
+229
View File
@@ -0,0 +1,229 @@
# RSX in Depth
The RSX macro makes it very easy to assemble complex UIs with a very natural Rust syntax:
```rust
rsx!(div {
button {
"Add todo",
onclick: move |e| todos.write().new_todo()
}
ul {
class: "todo-list"
(todos.iter().map(|(key, todo)| rsx!(
li {
class: "beautiful-todo"
key: "f"
h3 { "{todo.title}" }
p { "{todo.contents}"}
}
)))
}
})
```
In this section, we'll cover the `rsx!` macro in depth. If you prefer to learn through examples, the `reference` guide has plenty of examples on how to use `rsx!` effectively.
### Element structure
```rust
div {
hidden: false,
"some text"
child {}
Component {}
{/* literal tokens that resolve to vnodes */}
}
```
Each element takes a comma-separated list of expressions to build the node. Roughly, here's how they work:
- `name: value` sets a 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 `IntoIterator<T> where T: IntoVnode` to work properly
Commas are entirely optional, but might be useful to delineate between elements and attributes.
The `render` function provides an **extremely efficient** allocator for VNodes and text, so try not to use the `format!` macro in your components. Rust's default `ToString` methods pass through the global allocator, but all text in components is allocated inside a manually-managed Bump arena. To push you in the right direction, all text-based attributes take `std::fmt::Arguments` directly, so you'll want to reach for `format_args!` when the built-in `f-string` interpolation just doesn't cut it.
### Ignoring `cx.render` with `rsx!(cx, ...)`
Sometimes, writing `cx.render` is a hassle. The `rsx! macro will accept any token followed by a comma as the target to call "render" on:
```rust
cx.render(rsx!( div {} ))
// becomes
rsx!(cx, div {})
```
### Conditional Rendering
Sometimes, you might not want to render an element given a condition. The rsx! macro will accept any tokens directly contained with curly braces, provided they resolve to a type that implements `IntoIterator<VNode>`. This lets us write any Rust expression that resolves to a VNode:
```rust
rsx!({
if enabled {
rsx!(cx, div {"enabled"})
} else {
rsx!(cx, li {"disabled"})
}
})
```
A convenient way of hiding/showing an element is returning an `Option<VNode>`. When combined with `and_then`, we can succinctly control the display state given some boolean:
```rust
rsx!({
a.and_then(rsx!(div {"enabled"}))
})
```
It's important to note that the expression `rsx!()` is typically lazy - this expression must be _rendered_ to produce a VNode. When using match statements, we must render every arm as to avoid the `no two closures are identical` rule that Rust imposes:
```rust
// this will not compile!
match case {
true => rsx!(div {}),
false => rsx!(div {})
}
// the nodes must be rendered first
match case {
true => rsx!(cx, div {}),
false => rsx!(cx, div {})
}
```
### Lists
Again, because anything that implements `IntoIterator<VNode>` is valid, we can use lists directly in our `rsx!`:
```rust
let items = vec!["a", "b", "c"];
cx.render(rsx!{
ul {
{items.iter().map(|f| rsx!(li { "a" }))}
}
})
```
Sometimes, it makes sense to render VNodes into a list:
```rust
let mut items = vec![];
for _ in 0..5 {
items.push(rsx!(cx, li {} ))
}
rsx!(cx, {items} )
```
#### Lists and Keys
When rendering the VirtualDom to the screen, Dioxus needs to know which elements have been added and which have been removed. These changes are determined through a process called "diffing" - an old set of elements is compared to a new set of elements. If an element is removed, then it won't show up in the new elements, and Dioxus knows to remove it.
However, with lists, Dioxus does not exactly know how to determine which elements have been added or removed if the order changes or if an element is added or removed from the middle of the list.
In these cases, it is vitally important to specify a "key" alongside the element. Keys should be persistent between renders.
```rust
fn render_list(cx: Context, items: HashMap<String, Todo>) -> DomTree {
rsx!(cx, ul {
{items.iter().map(|key, item| {
li {
key: key,
h2 { "{todo.title}" }
p { "{todo.contents}" }
}
})}
})
}
```
There have been many guides made for keys in React, so we recommend reading up to understand their importance:
- [React guide on keys](https://reactjs.org/docs/lists-and-keys.html)
- [Importance of keys (Medium)](https://kentcdodds.com/blog/understanding-reacts-key-prop)
### Complete Reference
```rust
let text = "example";
cx.render(rsx!{
div {
h1 { "Example" },
{title}
// fstring interpolation
"{text}"
p {
// Attributes
tag: "type",
// Anything that implements display can be an attribute
abc: 123,
enabled: true,
// attributes also supports interpolation
// `class` is not a restricted keyword unlike JS and ClassName
class: "big small wide short {text}",
class: format_args!("attributes take fmt::Arguments. {}", 99),
tag: {"these tokens are placed directly"}
// Children
a { "abcder" },
// Children with attributes
h2 { "hello", class: "abc-123" },
// Child components
CustomComponent { a: 123, b: 456, key: "1" },
// Child components with paths
crate::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> } },
// Matching
// Requires rendering the nodes first.
// rsx! is lazy, and the underlying closures cannot have the same type
// Rendering produces the VNode type
{match rand::gen_range::<i32>(1..3) {
1 => rsx!(cx, h1 { "big" })
2 => rsx!(cx, h2 { "medium" })
_ => rsx!(cx, h3 { "small" })
}}
// Optionals
{true.and_then(|f| rsx!( h1 {"Conditional Rendering"} ))}
// Child nodes
{cx.children()}
// Any expression that is `IntoVNode`
{expr}
}
}
})
```
+1
View File
@@ -0,0 +1 @@
# Global State
+29
View File
@@ -0,0 +1,29 @@
## Testing
To test your Rust code, you can annotate any function with the `#[test]` block. In VSCode with RA, this will provide a lens to click and run the test.
```rust
#[test]
fn component_runs() {
assert!(true)
}
```
This will test your Rust code _without_ going through the browser. This is ideal for squashing logic bugs and ensuring components render appropriately when the browsers's DOM is not needed. If you need to run tests in the browser, you can annotate your blocks with the `#[dioxus::test]` block.
```rust
#[dioxus::test]
fn runs_in_browser() {
// ...
}
```
Then, when you run
```
$ dioxus test --chrome
```
Dioxus will build and test your code using the Chrome browser as a harness.
There's a lot more to testing if you dive in, so check out the [Testing]() guide for more information
+64
View File
@@ -0,0 +1,64 @@
# VNodes and Elements
At the heart of Dioxus is the concept of an "element" - a container that can have children, properties, event handlers, and other important attributes. Dioxus only knows how to render the `VNode` datastructure - an Enum variant of an Element, Text, Components, Fragments, and Anchors.
Because Dioxus is meant for the Web and uses WebView as a desktop and mobile renderer, almost all elements in Dioxus share properties with their HTML counterpart. When we declare our elements, we'll do so using HTML semantics:
```rust
rsx!(
div {
"hello world"
}
)
```
As you would expect, this snippet would generate a simple hello-world div. In fact, we can render these nodes directly with the SSR crate:
```rust
dioxus::ssr::render_lazy(rsx!(
div {
"hello world"
}
))
```
And produce the corresponding html structure:
```html
<div>hello world</div>
```
Our structure declared above is made of two variants of the `VNode` datastructure:
- A VElement with a tagname of `div`
- A VText with contents of `"hello world"`
## All the VNode types
VNodes can be any of:
- **Element**: a container with a tag name, namespace, attributes, children, and event listeners
- **Text**: bump allocated text derived from string formatting
- **Fragments**: a container of elements with no parent
- **Suspended**: a container for nodes that aren't yet ready to be rendered
- **Anchor**: a special type of node that is only available when fragments have no children
In practice, only elements and text can be initialized directly while other node types can only be created through hooks or NodeFactory methods.
## Bump Arena Allocation
To speed up the process of building our elements and text, Dioxus uses a special type of memory allocator tuned for large batches of small allocations called a Bump Arena. We use the `bumpalo` allocator which was initially developed for Dioxus' spiritual predecessor: `Dodrio.`
- Bumpalo: [https://github.com/fitzgen/bumpalo](https://github.com/fitzgen/bumpalo)
- Dodrio: [https://github.com/fitzgen/dodrio](https://github.com/fitzgen/dodrio)
In other frontend frameworks for Rust, nearly every string is allocated using the global allocator. This means that strings in Rust do not benefit from the immutable string interning optimizations that JavaScript engines employ. By using a smaller, faster, more limited allocator, we can increase framework performance, bypassing even the naive WasmBindgen benchmarks for very quick renders.
It's important to note that VNodes are not `'static` - the VNode definition has a lifetime attached to it:
```rust, ignore
enum VNode<'bump> {
VElement { tag: &'static str, children: &'bump [VNode<'bump>] },
VText { content: &'bump str },
// other VNodes ....
}
```
Because VNodes use a bump allocator as their memory backing, they can only be created through the `NodeFactory` API - which we'll cover in the next chapter. This particular detail is important to understand because "rendering" VNodes produces a lifetime attached to the bump arena - which must be explicitly declared when dealing with components that borrow data from their parents.
+1
View File
@@ -0,0 +1 @@
# For Developers
+1
View File
@@ -0,0 +1 @@
# Alternative Backends
+1
View File
@@ -0,0 +1 @@
# Preprocessors
+1
View File
@@ -0,0 +1 @@
# Format
+1
View File
@@ -0,0 +1 @@
# Configuration
@@ -0,0 +1 @@
# Environment Variables
+1
View File
@@ -0,0 +1 @@
# General
@@ -0,0 +1 @@
# Preprocessors
@@ -0,0 +1 @@
# Renderers
+1
View File
@@ -0,0 +1 @@
# MathJax Support
+1
View File
@@ -0,0 +1 @@
# mdBook-specific features
+1
View File
@@ -0,0 +1 @@
# SUMMARY.md
+1
View File
@@ -0,0 +1 @@
# Theme
+1
View File
@@ -0,0 +1 @@
# Editor
+1
View File
@@ -0,0 +1 @@
# index.hbs
@@ -0,0 +1 @@
# Syntax highlighting
+37
View File
@@ -0,0 +1,37 @@
### Immutability by default?
---
Rust, like JS and TS, supports both mutable and immutable data. With JS, `const` would be used to signify immutable data, while in rust, the absence of `mut` signifies immutable data.
Mutability:
```rust
let mut val = 10; // rust
let val = 10; // js
```
Immutability
```rust
let val = 10; // rust
const val = 10; // js
```
However, `const` in JS does not prohibit you from modify the value itself only disallowing assignment. In Rust, immutable **is immutable**. You _never_ have to work about accidentally mutating data; mutating immutable data in Rust requires deliberate advanced datastructures that you won't find in your typical frontend code.
## How do strings work?
---
In rust, we have `&str`, `&'static str` `String`, and `Rc<str>`. It's a lot, yes, and it might be confusing at first. But it's actually not too bad.
In Rust, UTF-8 is supported natively, allowing for emoji and extended character sets (like Chinese and Arabic!) instead of the typical ASCII. The primitive `str` can be seen as a couple of UTF-8 code points squished together with a dynamic size. Because this size is variable (not known at compile time for any single character), we reference an array of UTF-8 code points as `&str`. Essentially, we're referencing (the & symbol) some dynamic `str` (a collection of UTF-8 points).
For text encoded directly in your code, this collection of UTF-8 code points is given the `'static` reference lifetime - essentially meaning the text can be safely referenced for the entire runtime of your program. Contrast this with JS, where a string will only exist for as long as code references it before it gets cleaned up by the garbage collector.
For text that needs to have characters added, removed, sorted, uppercased, formatted, accessed for mutation, etc, Rust has the `String` type, which is essentially just a dynamically sized `str`. In JS, if you add a character to your string, you actually create an entirely new string (completely cloning the old one first). In Rust, you can safely added characters to strings _without_ having to clone first, making string manipulation in Rust very efficient.
Finally, we have `Rc<str>`. This is essentially Rust's version of JavaScript's `string`. In JS, whenever you pass a `string` around (and don't mutate it), you don't actually clone it, but rather just increment a counter that says "this code is using this string." This counter prevents the garbage collector from deleting the string before your code is done using it. Only when all parts of your code are done with the string, will the string be deleted. `Rc<str>` works exactly the same way in Rust, but requires a deliberate `.clone()` to get the same behavior. In most instances, Dioxus will automatically do this for you, saving the trouble of having to `clone` when you pass an `Rc<str>` into child components. `Rc<str>` is typically better than `String` for Rust - it allows cheap sharing of strings, and through `make_mut` you can always produce your own mutable copy for modifying. You might not see `Rc<str>` in other Rust libraries as much, but you will see it in Dioxus due to Dioxus' aggressive memoization and focus on efficiency and performance.
If you run into issues with `&str`, `String`, `Rc<str>`, just try cloning and `to_string` first. For the vast majority of apps, the slight performance hit will be unnoticeable. Once you get better with Strings, it's very easy to go back and remove all the clones for more efficient alternatives, but you will likely never need to.
+26
View File
@@ -0,0 +1,26 @@
# Choose your architecture type:
- Static Pages
- Server-side-rendering
- Pure SPA
- Hybrid SPA (liveview)
## Static Pages
You'll want the TextRenderer crate.
Use it imperatively by passing props and rendering to string.
## Server-side-rendering
You'll want the TextRenderer crate.
Serve it with a dedicated middleware crate.
## Pure SPA
You'll want the WebSys Crate.
## Hybrid SPA (hydration)
You'll want the WebSys Crate
You'll want the TextRenderer crate
## Hybrid SPA (liveview)
You'll want the WebSys crate.
You'll want the Liveview crate.
+155
View File
@@ -0,0 +1,155 @@
# Hello World
Let's put together a simple "hello world" to get acquainted with Dioxus. The Dioxus-CLI has an equivalent to "create-react-app" built-in, but setting up Dioxus apps is simple enough to not need additional tooling.
This demo will build a simple desktop app. Check out the platform-specific setup guides on how to port your app to different targets.
First, let's start a new project. Rust has the concept of executables and libraries. Executables have a `main.rs` and libraries have `lib.rs`. A project may have both. Our `hello world` will be an executable - we expect our app to launch when we run it! Cargo provides this for us:
```shell
$ cargo new --bin hello-dioxus
```
Now, we can `cd` into our project and poke around:
```shell
$ cd hello-dioxus
$ tree
.
├── Cargo.toml
├── .git
├── .gitignore
└── src
└── main.rs
```
We are greeted with a pre-initialized git repository, our code folder (`src`) and our project file (`Cargo.toml`).
Our `src` folder holds our code. Our `main.rs` file holds our `fn main` which will be executed when our app is ran.
```shell
$ more src/main.rs
```
```rust
fn main() {
println!("Hello, world!");
}
```
Right now, whenever our app is launched, "Hello world" will be echoed to the terminal.
```shell
$ cargo run
Compiling hello-dioxus v0.1.0
Finished dev [unoptimized + debuginfo] target(s) in 0.41s
Running `target/debug/hello-dioxus`
Hello, world!
```
Our `Cargo.toml` file holds our dependencies and project flags.
```shell
$ cat Cargo.toml
```
```toml
[package]
name = "hello-dioxus"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
```
To use the Dioxus library, we'll want to add the most recent version of `Dioxus` to our crate. If you have `cargo edit` installed, simply call:
```shell
$ cargo add dioxus --features desktop
```
It's very important to add `dioxus` with the `desktop` feature for this example. The `dioxus` crate is a batteries-include crate that combines a bunch of utility crates together, ensuring compatibility of the most important parts of the ecosystem. Under the hood, the `dioxus` crate configures various renderers, hooks, debug tooling, and more. The `desktop` feature ensures the we only depend on the smallest set of required crates to compile and render.
If you plan to develop extensions for the `Dioxus` ecosystem, please use the `dioxus` crate with the `core` feature to limit the amount of dependencies your project brings in.
Now, let's edit our `main.rs` file:
```rust
use diouxs::prelude::*;
fn main() {
dioxus::desktop::start(App, |c| c);
}
static App: FC<()> = |cx, props| {
cx.render(rsx! (
div { "Hello, world!" }
))
};
```
Let's dissect our example a bit.
This bit of code imports everything from the the `prelude` module. This brings into scope the right traits, types, and macros needed for working with Dioxus.
```rust
use diouxs::prelude::*;
```
This initialization code launches a Tokio runtime on a helper thread - where your code will run, and then the WebView on the main-thread. Due to platform requirements, the main thread is blocked by this call.
```rust
fn main() {
dioxus::desktop::start(App, |c| c);
}
```
Finally, our app. Every component in Dioxus is a function that takes in `Context` and `Props` and returns an `Option<VNode>`.
```rust
static App: FC<()> = |cx, props| {
cx.render(rsx! {
div { "Hello, world!" }
})
};
```
The closure `FC<()>` syntax is identical to the function syntax, but with lifetimes managed for you. In cases where props need to borrow from their parent, you will need to specify lifetimes using the function syntax:
```rust
fn App<'a>(cx: Context<'a>, props: &'a ()) -> DomTree<'a> {
cx.render(rsx! {
div { "Hello, world!" }
})
}
```
In React, you'll save data between renders with hooks. However, hooks rely on global variables which make them difficult to integrate in multi-tenant systems like server-rendering. In Dioxus, you are given an explicit `Context` object to control how the component renders and stores data.
Next, we're greeted with the `rsx!` macro. This lets us add a custom DSL for declaratively building the structure of our app. The semantics of this macro are similar to that of JSX and HTML, though with a familiar Rust-y interface. The `html!` macro is also available for writing components with a JSX/HTML syntax.
The `rsx!` macro is lazy: it does not immediately produce elements or allocates, but rather builds a closure which can be rendered with `cx.render`.
Now, let's launch our app:
```shell
$ cargo run
```
Huzzah! We have a simple app.
![Hello world](images/01-setup-helloworld.png)
If we wanted to golf a bit, we can shrink our hello-world even smaller:
```rust
fn main() {
static App: FC<()> = |cx, props| rsx!(cx, div {"hello world!"});
dioxus::web::start(App, |c| c);
}
```
Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

+1
View File
@@ -0,0 +1 @@
# Contributors
+16
View File
@@ -0,0 +1,16 @@
# Welcome to Dioxus!
## Running Examples
We use the dedicated `dioxus-cli` to build and test dioxus web-apps. This can run examples, tests, build web workers, launch development servers, bundle, and more. It's general purpose, but currently very tailored to Dioxus for liveview and bundling. If you've not used it before, `cargo install --path pacakages/dioxus-cli` will get it installed. This CLI tool should feel like using `cargo` but with 1st party support for assets, bundling, and other important dioxus-specific features.
Alternatively, `trunk` works but can't run examples.
- tide_ssr: Handle an HTTP request and return an html body using the html! macro. `cargo run --example tide_ssr`
- doc_generator: Use dioxus SSR to generate the website and docs. `cargo run --example doc_generator`
- fc_macro: Use the functional component macro to build terse components. `cargo run --example fc_macro`
- hello_web: Start a simple wasm app. Requires a web packer like dioxus-cli or trunk `cargo run --example hello`
- router: `cargo run --example router`
- tide_ssr: `cargo run --example tide_ssr`
- webview: Use liveview to bridge into a webview context for a simple desktop application. `cargo run --example webview`
- twitter-clone: A full-featured Twitter clone showcasing dioxus-liveview, state management patterns, and hooks. `cargo run --example twitter`
+3
View File
@@ -0,0 +1,3 @@
# The Server-Side-Rendering Guide
This guide will help you build your first app that leverages server-side-rendering to display the user interface.
+3
View File
@@ -0,0 +1,3 @@
# The WASM Guide
This guide will help you build your first app that leverages WASM in the browser to display the user interface.
+3
View File
@@ -0,0 +1,3 @@
# The Desktop Guide
This guide will help you build your first app that leverages webview for desktop to display the user interface.
+21
View File
@@ -0,0 +1,21 @@
## Concurrency
In Dioxus, VNodes are asynchronous and can their rendering can be paused at any time by awaiting a future. Hooks can combine this functionality with the Context and Subscription APIs to craft dynamic and efficient user experiences.
```rust
fn user_data(cx: Context<()>) -> DomTree {
// Register this future as a task
use_suspense(cx, async {
// Continue on with the component as usual, waiting for data to arrive
let Profile { name, birthday, .. } = 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.
+20
View File
@@ -0,0 +1,20 @@
## Liveview
With the Context, Subscription, and Asynchronous APIs, we've built Dioxus Liveview: a coupling of frontend and backend to deliver user experiences that do not require dedicated API development. Instead of building and maintaining frontend-specific API endpoints, components can directly access databases, server caches, and other services directly from the component.
These set of features are still experimental. Currently, we're still working on making these components more ergonomic
```rust
fn live_component(cx: &Context<()>) -> DomTree {
use_live_component(
cx,
// Rendered via the client
#[cfg(target_arch = "wasm32")]
|| html! { <div> {"Loading data from server..."} </div> },
// Renderered on the server
#[cfg(not(target_arch = "wasm32"))]
|| html! { <div> {"Server Data Loaded!"} </div> },
)
}
```
+31
View File
@@ -0,0 +1,31 @@
## Components
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)]
struct MyProps {
name: String
}
fn Example(cx: Context<MyProps>) -> DomTree {
html! { <div> "Hello {cx.cx.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. The functional component macro makes life more productive by inlining props directly as function arguments, similar to how Rocket parses URIs.
```rust
// A very terse component!
#[fc]
fn Example(cx: Context, name: String) -> DomTree {
html! { <div> "Hello {name}!" </div> }
}
// or
#[functional_component]
pub static Example: FC = |cx, name: String| 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, templating engine, or the constructors directly.
+57
View File
@@ -0,0 +1,57 @@
# Overview
Dioxus aims to provide a fast, friendly, and portable toolkit for building user interfaces with Rust.
This guide assumes you'll be building a SPA (single page application) for the web. The process for building desktop apps, server-rendered apps, static sites, and mobile apps, is more-or-less the same. You can check out the [Platform Specific Guides](../platforms/00-index.md) for more information on setting up Dioxus for any of the various targets you are building for.
# Setting up Dioxus
Dioxus requires a few main things to get up and running:
- The [Rust compiler](https://www.rust-lang.org) and associated build tooling
- An editor of your choice, with the [Rust-Analyzer LSP plugin](https://rust-analyzer.github.io)
Dioxus integrates very well with the Rust-Analyzer IDE plugin which will provide appropriate syntax highlighting, code navigation, folding, and more.
We also recommend installing the Dioxus CLI. The Dioxus CLI automates building and packaging for various targets and integrates with simulators, development servers, and app deployment. It'll be our one-stop-shop for anything related to building and sharing our Dioxus Apps. To install the CLI, you'll need cargo (should be automatically installed with Rust):
```
$ cargo install dioxus-cli
```
You can update the dioxus-cli at any time with:
```
$ cargo install --force dioxus-cli
```
If your version of the CLI is out of date, it'll remind you to update whenever a new version is uploaded to Rust's package manager [crates.io](https://crates.io). We use a dedicated 1st-party CLI to save you from having to run potentially untrusted code every time you add a crate to your project - as is standard in the NPM ecosystem. You can vet the source of the Dioxus-CLI yourself at its [GitHub repo](https://github.com/jkelleyrtp/dioxus/tree/master/packages/cli).
If you want to keep your traditional `npm install XXX` workflow for adding packages, you might want to install `cargo-edit` and a few other fun `cargo` extensions:
- [cargo edit](https://github.com/killercup/cargo-edit) for adding dependencies from the CLI
- [cargo-expand](https://github.com/dtolnay/cargo-expand) for expanding macro calls
- `cargo tree` - an integrated cargo command that lets you inspect your dependency tree
That's it! We won't need to touch NPM/WebPack/Babel/Parcel, etc.
# Important tools
With Rust, things like benchmarking, testing, and documentation are included in the language. We strongly recommend going through the official Rust book _completely_. However, our hope is that a Dioxus app can serve as a great first project. With Dioxus you'll learn about:
- Error handling
- Structs, Functions, Enums
- Closures
- Macros
We've put a lot of care into making Dioxus syntax familiar and easy to understand, so you won't need knowledge on async, lifetimes, and smart pointers until you really start building complex Dioxus apps.
We strongly encourage exploring the guides for more information on how to work with the integrated tooling:
- [Testing](Testing.md)
- [Documentation](Documentation.md)
- [Benchmarking](Benchmarking.md)
- [Building](Building.md)
- [Modules](Modules.md)
- [Crates](Crates.md)