Post

Desenvolvendo Aplicações Web com Rust e WebAssembly: Criando uma SPA do Zero

Introdução

O desenvolvimento de aplicações web modernas tem evoluído rapidamente, e a combinação de Rust com WebAssembly (Wasm) oferece uma nova abordagem para construir Single Page Applications (SPA) altamente performáticas e seguras. Neste artigo, exploraremos como desenvolver uma SPA utilizando Rust e compilá-la para WebAssembly, aproveitando o máximo desempenho no navegador.

Vantagens de usar Rust e Wasm para SPAs

Rust é uma linguagem conhecida por seu desempenho e segurança de memória, enquanto o WebAssembly é um formato binário que permite executar código de alto desempenho em navegadores web. Juntos, eles possibilitam escrever aplicações web rápidas, seguras e eficientes.

  • Desempenho: Código em Rust compilado para Wasm é executado em velocidade próxima ao código nativo.
  • Segurança: Rust previne erros comuns de programação, como ponteiros nulos ou vazamentos de memória.
  • Reutilização de Código: Possibilidade de compartilhar lógica entre frontend e backend se ambos forem escritos em Rust.

Configurando o Ambiente

Para começar, precisamos configurar nosso ambiente de desenvolvimento.

Pré-requisitos

  1. Rust instalado. Se ainda não tem, instale via rustup.
  2. Trunk: Uma ferramenta para build e bundling de aplicações Rust para Wasm.
  3. wasm-bindgen: Ferramenta para gerar bindings entre Rust e JavaScript.

Instalando o Trunk e o wasm-bindgen-cli

1
2
cargo install trunk
cargo install wasm-bindgen-cli

Criando o Projeto

Usaremos a crate yew, um framework Rust para desenvolvimento web em Wasm, semelhante ao React.

Passo 1: Criar um novo projeto

1
2
cargo new rust-wasm-spa
cd rust-wasm-spa

Passo 2: Configurar o Cargo.toml

Adicione as dependências no Cargo.toml:

1
2
3
[dependencies]
yew = { version = "0.20", features = ["csr"] }
wasm-bindgen = "0.2.84"

Passo 3: Escrever o Código

No arquivo src/main.rs, vamos escrever um componente básico.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
use yew::prelude::*;

struct App;

impl Component for App {
    type Message = ();
    type Properties = ();

    fn create(_ctx: &Context<Self>) -> Self {
        App
    }

    fn view(&self, _ctx: &Context<Self>) -> Html {
        html! {
            <div>
                <h1>{ "Olá, mundo!" }</h1>
                <p>{ "Bem-vindo à sua primeira SPA em Rust e WebAssembly." }</p>
            </div>
        }
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}

Passo 4: Configurar o Trunk

Crie um arquivo index.html na raiz do projeto:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8" />
    <title>Rust Wasm SPA</title>
</head>

<body>
    <div id="root"></div>
    <!-- O Trunk irá inserir os scripts necessários aqui -->
</body>

</html>

Passo 5: Executar a Aplicação

Utilize o Trunk para compilar e servir a aplicação:

1
trunk serve

Agora, acesse http://127.0.0.1:8080 no seu navegador, e você verá a mensagem “Olá, mundo!”.

Adicionando Interatividade

Vamos adicionar um contador simples para demonstrar interatividade.

Atualize o src/main.rs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
use yew::prelude::*;

struct App {
    counter: i32,
}

enum Msg {
    Increment,
    Decrement,
}

impl Component for App {
    type Message = Msg;
    type Properties = ();

    fn create(_ctx: &Context<Self>) -> Self {
        App { counter: 0 }
    }

    fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
        match msg {
            Msg::Increment => {
                self.counter += 1;
                true
            }
            Msg::Decrement => {
                self.counter -= 1;
                true
            }
        }
    }

    fn view(&self, ctx: &Context<Self>) -> Html {
        let link = ctx.link();
        html! {
            <div style="text-align: center; margin-top: 50px;">
                <h1>{ "Contador" }</h1>
                <p>{ self.counter }</p>
                <button onclick={link.callback(|_| Msg::Increment)}>{ "Incrementar" }</button>
                <button onclick={link.callback(|_| Msg::Decrement)}>{ "Decrementar" }</button>
            </div>
        }
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}

Agora você tem um contador funcional!

Consumo de APIs

Para tornar nossa SPA mais dinâmica, podemos consumir uma API. Vamos utilizar a crate reqwasm para fazer requisições HTTP.

Passo 1: Adicionar Dependência

Atualize o Cargo.toml:

1
2
3
4
5
6
7
8
[dependencies]
yew = { version = "0.20", features = ["csr"] }
wasm-bindgen = "0.2.84"
reqwasm = "0.2.0"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
wasm-bindgen-futures = "0.4"
web-sys = { version = "0.3", features = ["Window"] }

Passo 2: Consumir uma API

Vamos consumir a API pública do JSONPlaceholder para obter uma lista de posts.

Atualize o src/main.rs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
use yew::prelude::*;
use reqwasm::http::Request;
use serde::Deserialize;

struct App {
    posts: Vec<Post>,
    loading: bool,
}

enum Msg {
    FetchPosts,
    ReceiveResponse(Result<Vec<Post>, reqwasm::Error>),
}

#[derive(Deserialize, Debug, Clone, PartialEq)]
struct Post {
    id: u32,
    title: String,
    body: String,
}

impl Component for App {
    type Message = Msg;
    type Properties = ();

    fn create(ctx: &Context<Self>) -> Self {
        ctx.link().send_message(Msg::FetchPosts);
        App {
            posts: vec![],
            loading: true,
        }
    }

    fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
        match msg {
            Msg::FetchPosts => {
                let link = ctx.link().clone();
                wasm_bindgen_futures::spawn_local(async move {
                    let response = Request::get("https://jsonplaceholder.typicode.com/posts")
                        .send()
                        .await
                        .unwrap()
                        .json::<Vec<Post>>()
                        .await;
                    link.send_message(Msg::ReceiveResponse(response));
                });
                false
            }
            Msg::ReceiveResponse(response) => {
                match response {
                    Ok(posts) => {
                        self.posts = posts;
                    }
                    Err(_) => {
                        web_sys::window()
                            .unwrap()
                            .alert_with_message("Erro ao carregar posts")
                            .unwrap();
                    }
                }
                self.loading = false;
                true
            }
        }
    }

    fn view(&self, _ctx: &Context<Self>) -> Html {
        if self.loading {
            html! { <p>{ "Carregando..." }</p> }
        } else {
            html! {
                <div>
                    <h1>{ "Lista de Posts" }</h1>
                    <ul>
                        { for self.posts.iter().map(|post| self.view_post(post)) }
                    </ul>
                </div>
            }
        }
    }
}

impl App {
    fn view_post(&self, post: &Post) -> Html {
        html! {
            <li key={post.id}>
                <h3>{ &post.title }</h3>
                <p>{ &post.body }</p>
            </li>
        }
    }
}

fn main() {
    yew::Renderer::<App>::new().render();
}

Agora, quando você executar a aplicação, ela irá exibir uma lista de posts obtidos da API.

Conclusão

Desenvolver aplicações web utilizando Rust e WebAssembly é uma excelente maneira de construir SPAs rápidas e seguras. Com ferramentas como Yew e Trunk, o processo se torna semelhante ao desenvolvimento com frameworks JavaScript tradicionais, mas com as vantagens oferecidas pelo Rust.

Próximos Passos

  • Estilização: Integrar CSS ou utilizar crates para estilos, como yew-style.
  • Gerenciamento de Estado: Utilizar crates como yewdux para gerenciamento global de estado.
  • Roteamento: Implementar navegação entre páginas com yew_router.
  • Formulários e Validações: Criar formulários interativos e validar entradas do usuário.

Referências


Você pode encontrar o código completo no nosso repositório do GitHub. Pull requests são bem-vindos!

This post is licensed under CC BY 4.0 by the author.