reorganize the fullstack docs

This commit is contained in:
Evan Almloff 2023-05-02 17:43:07 -05:00
parent a9375af2b4
commit 515aee6c3c
9 changed files with 391 additions and 132 deletions

View file

@ -1,12 +1,20 @@
#![allow(non_snake_case, unused)]
use dioxus::prelude::*;
use dioxus_fullstack::prelude::*;
fn main() {
#[cfg(feature = "web")]
dioxus_web::launch_cfg(app, dioxus_web::Config::new().hydrate(true));
dioxus_web::launch_with_props(
app,
// Get the root props from the document
get_root_props_from_document().unwrap_or_default(),
dioxus_web::Config::new().hydrate(true),
);
#[cfg(feature = "ssr")]
{
use dioxus_fullstack::prelude::*;
use axum::extract::Path;
use axum::extract::State;
use axum::routing::get;
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async move {
@ -14,8 +22,39 @@ fn main() {
axum::Server::bind(&addr)
.serve(
axum::Router::new()
.connect_hot_reloading()
.serve_dioxus_application("", ServeConfigBuilder::new(app, ()))
// Serve the dist folder with the static javascript and WASM files created by the dixous CLI
.serve_static_assets("./dist")
// Register server functions
.register_server_fns("")
// Connect to the hot reload server in debug mode
.connect_hot_reload()
// Render the application. This will serialize the root props (the intial count) into the HTML
.route(
"/",
get(move |Path(intial_count): Path<usize>, State(ssr_state): State<SSRState>| async move { axum::body::Full::from(
ssr_state.render(
&ServeConfigBuilder::new(
app,
intial_count,
)
.build(),
)
)}),
)
// Render the application with a different intial count
.route(
"/:initial_count",
get(move |Path(intial_count): Path<usize>, State(ssr_state): State<SSRState>| async move { axum::body::Full::from(
ssr_state.render(
&ServeConfigBuilder::new(
app,
intial_count,
)
.build(),
)
)}),
)
.with_state(SSRState::default())
.into_make_service(),
)
.await
@ -24,8 +63,8 @@ fn main() {
}
}
fn app(cx: Scope) -> Element {
let mut count = use_state(cx, || 0);
fn app(cx: Scope<usize>) -> Element {
let mut count = use_state(cx, || *cx.props);
cx.render(rsx! {
h1 { "High-Five counter: {count}" }

View file

@ -0,0 +1,99 @@
#![allow(non_snake_case, unused)]
use dioxus::prelude::*;
use dioxus_fullstack::prelude::*;
fn main() {
#[cfg(feature = "web")]
dioxus_web::launch_with_props(
app,
// Get the root props from the document
get_root_props_from_document().unwrap_or_default(),
dioxus_web::Config::new().hydrate(true),
);
#[cfg(feature = "ssr")]
{
use axum::extract::Path;
use axum::extract::State;
use axum::routing::get;
// Register the server function before starting the server
DoubleServer::register().unwrap();
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async move {
let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 8080));
axum::Server::bind(&addr)
.serve(
axum::Router::new()
// Serve the dist folder with the static javascript and WASM files created by the dixous CLI
.serve_static_assets("./dist")
// Register server functions
.register_server_fns("")
// Connect to the hot reload server in debug mode
.connect_hot_reload()
// Render the application. This will serialize the root props (the intial count) into the HTML
.route(
"/",
get(move |Path(intial_count): Path<usize>, State(ssr_state): State<SSRState>| async move { axum::body::Full::from(
ssr_state.render(
&ServeConfigBuilder::new(
app,
intial_count,
)
.build(),
)
)}),
)
// Render the application with a different intial count
.route(
"/:initial_count",
get(move |Path(intial_count): Path<usize>, State(ssr_state): State<SSRState>| async move { axum::body::Full::from(
ssr_state.render(
&ServeConfigBuilder::new(
app,
intial_count,
)
.build(),
)
)}),
)
.with_state(SSRState::default())
.into_make_service(),
)
.await
.unwrap();
});
}
}
fn app(cx: Scope<usize>) -> Element {
let mut count = use_state(cx, || *cx.props);
cx.render(rsx! {
h1 { "High-Five counter: {count}" }
button { onclick: move |_| count += 1, "Up high!" }
button { onclick: move |_| count -= 1, "Down low!" }
button {
onclick: move |_| {
to_owned![count];
async move {
// Call the server function just like a local async function
if let Ok(new_count) = double_server(*count.current()).await {
count.set(new_count);
}
}
},
"Double"
}
})
}
#[server(DoubleServer)]
async fn double_server(number: usize) -> Result<usize, ServerFnError> {
// Perform some expensive computation or access a database on the server
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
let result = number * 2;
println!("server calculated {result}");
Ok(result)
}

View file

@ -4,11 +4,21 @@ use dioxus_fullstack::prelude::*;
fn main() {
#[cfg(feature = "web")]
dioxus_web::launch_cfg(app, dioxus_web::Config::new().hydrate(true));
dioxus_web::launch_with_props(
app,
// Get the root props from the document
get_root_props_from_document().unwrap_or_default(),
dioxus_web::Config::new().hydrate(true),
);
#[cfg(feature = "ssr")]
{
use axum::extract::Path;
use axum::extract::State;
use axum::routing::get;
// Register the server function before starting the server
DoubleServer::register().unwrap();
tokio::runtime::Runtime::new()
.unwrap()
.block_on(async move {
@ -16,8 +26,39 @@ fn main() {
axum::Server::bind(&addr)
.serve(
axum::Router::new()
// Serve Dioxus application automatically recognizes server functions and adds them to the API
.serve_dioxus_application("", ServeConfigBuilder::new(app, ()))
// Serve the dist folder with the static javascript and WASM files created by the dixous CLI
.serve_static_assets("./dist")
// Register server functions
.register_server_fns("")
// Connect to the hot reload server in debug mode
.connect_hot_reload()
// Render the application. This will serialize the root props (the intial count) into the HTML
.route(
"/",
get(move |Path(intial_count): Path<usize>, State(ssr_state): State<SSRState>| async move { axum::body::Full::from(
ssr_state.render(
&ServeConfigBuilder::new(
app,
intial_count,
)
.build(),
)
)}),
)
// Render the application with a different intial count
.route(
"/:initial_count",
get(move |Path(intial_count): Path<usize>, State(ssr_state): State<SSRState>| async move { axum::body::Full::from(
ssr_state.render(
&ServeConfigBuilder::new(
app,
intial_count,
)
.build(),
)
)}),
)
.with_state(SSRState::default())
.into_make_service(),
)
.await
@ -26,8 +67,8 @@ fn main() {
}
}
fn app(cx: Scope) -> Element {
let mut count = use_state(cx, || 0);
fn app(cx: Scope<usize>) -> Element {
let mut count = use_state(cx, || *cx.props);
cx.render(rsx! {
h1 { "High-Five counter: {count}" }
@ -49,7 +90,7 @@ fn app(cx: Scope) -> Element {
}
#[server(DoubleServer)]
async fn double_server(number: u32) -> Result<u32, ServerFnError> {
async fn double_server(number: usize) -> Result<usize, ServerFnError> {
// Perform some expensive computation or access a database on the server
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
let result = number * 2;

View file

@ -37,6 +37,12 @@
---
- [Fullstack](fullstack/index.md)
- [Getting Started](fullstack/getting_started.md)
- [Communicating with the Server](fullstack/server_functions.md)
---
- [Custom Renderer](custom_renderer/index.md)
---

View file

@ -0,0 +1,102 @@
> This guide assumes you read the [Web](web.md) guide and installed the [Dioxus-cli](https://github.com/DioxusLabs/cli)
# Getting Started
## Setup
For this guide, we're going to show how to use Dioxus with [Axum](https://docs.rs/axum/latest/axum/), but `dioxus-fullstack` also integrates with the [Warp](https://docs.rs/warp/latest/warp/) and [Salvo](https://docs.rs/salvo/latest/salvo/) web frameworks.
Make sure you have Rust and Cargo installed, and then create a new project:
```shell
cargo new --bin demo
cd demo
```
Add `dioxus` and `dioxus-fullstack` as dependencies:
```shell
cargo add dioxus
cargo add dioxus-fullstack --features axum, ssr
```
Next, add all the Axum dependencies. This will be different if you're using a different Web Framework
```shell
cargo add tokio --features full
cargo add axum
```
Your dependencies should look roughly like this:
```toml
[dependencies]
axum = "*"
dioxus = { version = "*" }
dioxus-fullstack = { version = "*", features = ["axum", "ssr"] }
tokio = { version = "*", features = ["full"] }
```
Now, set up your Axum app to serve the Dioxus app.
```rust
{{#include ../../../examples/server_basic.rs}}
```
Now, run your app with `cargo run` and open `http://localhost:8080` in your browser. You should see a server-side rendered page with a counter.
## Hydration
Right now, the page is static. We can't interact with the buttons. To fix this, we can hydrate the page with `dioxus-web`.
First, modify your `Cargo.toml` to include two features, one for the server called `ssr`, and one for the client called `web`.
```toml
[dependencies]
# Common dependancies
dioxus = { version = "*" }
dioxus-fullstack = { version = "*" }
# Web dependancies
dioxus-web = { version = "*", features=["hydrate"], optional = true }
# Server dependancies
axum = { version = "0.6.12", optional = true }
tokio = { version = "1.27.0", features = ["full"], optional = true }
[features]
default = []
ssr = ["axum", "tokio", "dioxus-fullstack/axum"]
web = ["dioxus-web"]
```
Next, we need to modify our `main.rs` to use either hydrate on the client or render on the server depending on the active features.
```rust
{{#include ../../../examples/hydration.rs}}
```
Now, build your client-side bundle with `dioxus build --features web` and run your server with `cargo run --features ssr`. You should see the same page as before, but now you can interact with the buttons!
## Sycronizing props between the server and client
Let's make the initial count of the counter dynamic based on the current page.
### Modifying the server
To do this, we must remove the serve_dioxus_application and replace it with a custom implementation of its four key functions:
- Serve static WASM and JS files with serve_static_assets
- Register server functions with register_server_fns (more information on server functions later)
- Connect to the hot reload server with connect_hot_reload
- A custom route that uses SSRState to server-side render the application
### Modifying the client
The only thing we need to change on the client is the props. `dioxus-fullstack` will automatically serialize the props it uses to server render the app and send them to the client. In the client section of `main.rs`, we need to add `get_root_props_from_document` to deserialize the props before we hydrate the app.
```rust
{{#include ../../../examples/hydration_props.rs}}
```
Now, build your client-side bundle with `dioxus build --features web` and run your server with `cargo run --features ssr`. Navigate to `http://localhost:8080/1` and you should see the counter start at 1. Navigate to `http://localhost:8080/2` and you should see the counter start at 2.

View file

@ -0,0 +1,59 @@
# Fullstack development
So far you have learned about three different approaches to target the web with Dioxus:
- [Client-side rendering with dioxus-web](../getting_started/web.md)
- [Server-side rendering with dioxus-liveview](../getting_started/liveview.md)
- [Server-side static HTML generation with dioxus-ssr](../getting_started/ssr.md)
## Summary of Existing Approaches
Each approach has its tradeoffs:
### Client-side rendering
- With Client side rendering, you send the entire content of your application to the client, and then the client generates all of the HTML of the page dynamically.
- This means that the page will be blank until the JavaScript bundle has loaded and the application has initialized. This can result in **slower first render times and makes the page less SEO-friendly**.
> SEO stands for Search Engine Optimization. It refers to the practice of making your website more likely to appear in search engine results. Search engines like Google and Bing use web crawlers to index the content of websites. Most of these crawlers are not able to run JavaScript, so they will not be able to index the content of your page if it is rendered client-side.
- Client-side rendered applications need to use **weakly typed requests to communicate with the server**
> Client-side rendering is a good starting point for most applications. It is well supported and makes it easy to communicate with the client/browser APIs
### Liveview
- Liveview rendering communicates with the server over a WebSocket connection. It essentially moves all of the work that Client-side rendering does to the server.
- This makes it **easy to communicate with the server, but more difficult to communicate with the client/browser APIS**.
- Each interaction also requires a message to be sent to the server and back which can cause **issues with latency**.
- Because Liveview uses a websocket to render, the page will be blank until the WebSocket connection has been established and the first renderer has been sent form the websocket. Just like with client side rendering, this can make the page **less SEO-friendly**.
- Because the page is rendered on the server and the page is sent to the client piece by piece, you never need to send the entire application to the client. The initial load time can be faster than client-side rendering with large applications because Liveview only needs to send a constant small websocket script regardless of the size of the application.
> Liveview is a good fit for applications that already need to communicate with the server frequently (like real time collaborative apps), but don't need to communicate with as many client/browser APIs
### Server-side rendering
- Server-side rendering generates all of the HTML of the page on the server before the page is sent to the client. This means that the page will be fully rendered when it is sent to the client. This results in a faster first render time and makes the page more SEO-friendly. However, it **only works for static pages**.
> Server-side rendering is not a good fit for purely static sites like a blog
## A New Approach
Each of these approaches has its tradeoffs. What if we could combine the best parts of each approach?
- **Fast initial render** time like SSR
- **Works well with SEO** like SSR
- **Type safe easy communication with the server** like Liveview
- **Access to the client/browser APIs** like Client-side rendering
- **Fast interactivity** like Client-side rendering
We can achieve this by rendering the initial page on the server (SSR) and then taking over rendering on the client (Client-side rendering). Taking over rendering on the client is called **hydration**.
Finally, we can use [server functions](server_functions.md) to communicate with the server in a type-safe way.
This approach uses both the dioxus-web and dioxus-ssr crates. To integrate those two packages and `axum`, `warp`, or `salvo`, Dioxus provides the `dioxus-fullstack` crate.

View file

@ -0,0 +1,31 @@
# Communicating with the server
`dixous-server` provides server functions that allow you to call an automatically generated API on the server from the client as if it were a local function.
To make a server function, simply add the `#[server(YourUniqueType)]` attribute to a function. The function must:
- Be an async function
- Have arguments and a return type that both implement serialize and deserialize (with [serde](https://serde.rs/)).
- Return a `Result` with an error type of ServerFnError
You must call `register` on the type you passed into the server macro in your main function before starting your server to tell Dioxus about the server function.
Let's continue building on the app we made in the [getting started](./getting_started.md) guide. We will add a server function to our app that allows us to double the count on the server.
First, add serde as a dependency:
```shell
cargo add serde
```
Next, add the server function to your `main.rs`:
```rust
{{#include ../../../examples/server_function.rs}}
```
Now, build your client-side bundle with `dioxus build --features web` and run your server with `cargo run --features ssr`. You should see a new button that multiplies the count by 2.
## Conclusion
That's it! You've created a full-stack Dioxus app. You can find more examples of full-stack apps and information about how to integrate with other frameworks and desktop renderers in the [dioxus-fullstack examples directory](https://github.com/DioxusLabs/dioxus/tree/master/packages/server/examples).

View file

@ -1,119 +1 @@
> This guide assumes you read the [Web](web.md) guide and installed the [Dioxus-cli](https://github.com/DioxusLabs/cli)
# Fullstack development
We can combine the `dioxus-web` renderer with the `dioxus-ssr` renderer to create a full-stack Dioxus application. By combining server-side rendering with client-side hydration we can create an application that is initially rendered on the server and then hydrates the application on the client. Server-side rendering results in a fast first paint and make our page SEO-friendly. Client-side hydration makes our page responsive once the application has fully loaded.
To help make full-stack development easier, Dioxus provides a `dioxus-fullstack` crate that integrates with popular web frameworks with utilities for full-stack development.
## Setup
For this guide, we're going to show how to use Dioxus with [Axum](https://docs.rs/axum/latest/axum/), but `dioxus-fullstack` also integrates with the [Warp](https://docs.rs/warp/latest/warp/) and [Salvo](https://docs.rs/salvo/latest/salvo/) web frameworks.
Make sure you have Rust and Cargo installed, and then create a new project:
```shell
cargo new --bin demo
cd demo
```
Add `dioxus` and `dioxus-fullstack` as dependencies:
```shell
cargo add dioxus
cargo add dioxus-fullstack --features axum, ssr
```
Next, add all the Axum dependencies. This will be different if you're using a different Web Framework
```shell
cargo add tokio --features full
cargo add axum
```
Your dependencies should look roughly like this:
```toml
[dependencies]
axum = "*"
dioxus = { version = "*" }
dioxus-fullstack = { version = "*", features = ["axum", "ssr"] }
tokio = { version = "*", features = ["full"] }
```
Now, set up your Axum app to serve the Dioxus app.
```rust
{{#include ../../../examples/server_basic.rs}}
```
Now, run your app with `cargo run` and open `http://localhost:8080` in your browser. You should see a server-side rendered page with a counter.
## Hydration
Right now, the page is static. We can't interact with the buttons. To fix this, we can hydrate the page with `dioxus-web`.
First, modify your `Cargo.toml` to include two features, one for the server called `ssr`, and one for the client called `web`.
```toml
[dependencies]
# Common dependancies
dioxus = { version = "*" }
dioxus-fullstack = { version = "*" }
# Web dependancies
dioxus-web = { version = "*", features=["hydrate"], optional = true }
# Server dependancies
axum = { version = "0.6.12", optional = true }
tokio = { version = "1.27.0", features = ["full"], optional = true }
[features]
default = []
ssr = ["axum", "tokio", "dioxus-fullstack/axum"]
web = ["dioxus-web"]
```
Next, we need to modify our `main.rs` to use either hydrate on the client or render on the server depending on the active features.
```rust
{{#include ../../../examples/hydration.rs}}
```
Now, build your client-side bundle with `dioxus build --features web` and run your server with `cargo run --features ssr`. You should see the same page as before, but now you can interact with the buttons!
## Sycronizing props between the server and client
Lets make the initial count of the counter dynamic based on the current page.
## Communicating with the server
`dixous-server` provides server functions that allow you to call an automatically generated API on the server from the client as if it were a local function.
To make a server function, simply add the `#[server(YourUniqueType)]` attribute to a function. The function must:
- Be an async function
- Have arguments and a return type that both implement serialize and deserialize (with [serde](https://serde.rs/)).
- Return a `Result` with an error type of ServerFnError
You must call `register` on the type you passed into the server macro in your main function before starting your server to tell Dioxus about the server function.
Let's add a server function to our app that allows us to multiply the count by a number on the server.
First, add serde as a dependency:
```shell
cargo add serde
```
Next, add the server function to your `main.rs`:
```rust
{{#include ../../../examples/server_function.rs}}
```
Now, build your client-side bundle with `dioxus build --features web` and run your server with `cargo run --features ssr`. You should see a new button that multiplies the count by 2.
## Conclusion
That's it! You've created a full-stack Dioxus app. You can find more examples of full-stack apps and information about how to integrate with other frameworks and desktop renderers in the [dioxus-fullstack examples directory](https://github.com/DioxusLabs/dioxus/tree/master/packages/server/examples).
# Fullstack

View file

@ -305,7 +305,7 @@ where
}
fn serve_dioxus_application<P: Clone + serde::Serialize + Send + Sync + 'static>(
mut self,
self,
server_fn_route: &'static str,
cfg: impl Into<ServeConfig<P>>,
) -> Self {