Chore: some cleanup and documentation

This commit is contained in:
Jonathan Kelley committed 2021-06-10 01:01:53 -04:00
1 parent fe6938ceb3
commit 517d7f1495
39 files changed
+507 -2142

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
# Getting Started
This page is an overview of the Dioxus documentation.
+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)
+142
View File
@@ -0,0 +1,142 @@
# 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.
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 web
```
It's very important to add `dioxus` with the `web` 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 `web` feature ensures the we only depend on the smallest set of required crates to compile.
If you plan to develop extensions for the `Dioxus` ecosystem, please use `dioxus-core` crate. The `dioxus` crate re-exports `dioxus-core` alongside other utilties. As a result, `dioxus-core` is much more stable than the batteries-included `dioxus` crate.
Now, let's edit our `main.rs` file:
```rust
use diouxs::prelude::*;
fn main() {
dioxus::web::start(App)
}
fn App(ctx: Context<()>) -> VNode {
ctx.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 bit of code starts the WASM renderer as a future (JS Promise) and then awaits it. This is very similar to the `ReactDOM.render()` method you use for a React app. We pass in the `App` function as a our app.
```rust
fn main() {
dioxus::web::start(App)
}
```
Finally, our app. Every component in Dioxus is a function that takes in a `Context` object and returns a `VNode`.
```rust
fn App(ctx: Context<()>) -> VNode {
ctx.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 `ctx.render`.
Now, let's launch our app in a development server:
```shell
$ dioxus develop
```
Huzzah! We have a simple app.
![Hello world](../assets/01-setup-helloworld.png)
If we wanted to golf a bit, we can shrink our hello-world even smaller:
```rust
fn main() {
dioxus::web::start(|ctx| ctx.render(diouxs::rsx!( div { "Hello, World!" } ))
}
```
Anyways, let's move on to something a bit more complex.
+156
View File
@@ -0,0 +1,156 @@
# VNode Macros
Dioxus comes preloaded with two macros for creating VNodes.
## html! macro
The html! macro supports a limited subset of the html standard. This macro will happily accept a copy-paste from something like tailwind builder. However, writing HTML by hand is a bit tedious - IDE tools for Rust don't support linting/autocomplete/syntax highlighting. RSX is much more natural for Rust programs and _does_ integrate well with Rust IDE tools.
There is also limited support for dynamic handlers, but it will function similarly to JSX.
You'll want to write RSX where you can, and in a future release we'll have a tool that automatically converts HTML to RSX.
```rust
#[fc]
fn Example(ctx: Context, name: &str, pending: bool, count: i32 ) -> VNode {
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 programs. 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 will eventually provide a macro 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 ) -> VNode {
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
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.
```rust
static Example: FC<()> = |ctx| {
let text = "example";
let g = async {
wait(10).await;
"hello"
};
let user_name = use_read_async(ctx, USERNAME);
let title = ctx.suspend(user_name, |user_name| rsx!{ h1 { "Welcome back, {user_name}" } });
ctx.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}",
// bool-based classnames
classes: [("big", true), ("small", false)]
// Bool-based props
// *must* be in the tuple form, cannot enter as a variable
tag: ("type", false)
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!(in ctx, h1 { "big" })
2 => rsx!(in ctx, h2 { "medium" })
_ => rsx!(in ctx, h3 { "small" })
}}
// Optionals
{true.and_then(|f| rsx!{ h1 {"Conditional Rendering"} })}
// Bool options
{(rsx!{ h1 {"Conditional Rendering"}, true)}
// Child nodes
// Returns &[VNode]
{ctx.children()}
// Duplicating nodes
// Clones the nodes by reference, so they are literally identical
{{
let node = rsx!(in ctx, h1{ "TopNode" });
(0..10).map(|_| node.clone())
}}
// Any expression that is `IntoVNode`
{expr}
}
}
})
}
```
+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