Trunk em Rust: Build e Deploy WebAssembly | Rust Brasil

Use Trunk em projetos Rust WebAssembly: configure index.html e Trunk.toml, rode o servidor local, otimize o build e publique os arquivos estáticos.

Trunk é uma ferramenta de build e servidor de desenvolvimento para aplicações frontend feitas em Rust e compiladas para WebAssembly. Você aponta o Trunk para um index.html, marca o binário ou biblioteca Rust com data-trunk, executa trunk serve durante o desenvolvimento e usa trunk build --release para gerar arquivos estáticos na pasta dist/. Esse fluxo é especialmente comum com Yew, mas também funciona em projetos client-side que usam wasm-bindgen sem depender de um framework específico.

A configuração mínima cabe em poucos comandos:

rustup target add wasm32-unknown-unknown
cargo install trunk --locked
trunk serve --open

Em produção, a saída é HTML, JavaScript, WebAssembly e assets estáticos. Ela pode ser publicada em Cloudflare Pages, Netlify, GitHub Pages, Gitea Pages, S3 ou qualquer servidor capaz de entregar arquivos com os tipos MIME corretos. Antes de escolher Trunk, porém, separe duas necessidades: ele é excelente para SPA client-side e sites estáticos com Wasm; aplicações com SSR, server functions ou um backend Rust integrado podem se beneficiar do CLI recomendado pelo próprio framework.

Resposta rápida: quando usar Trunk

CenárioTrunk é uma boa escolha?Motivo
SPA com YewSimfluxo consolidado com index.html, assets e reload
Aplicação Rust/Wasm sem frameworkSimreduz o trabalho manual com wasm-bindgen e empacotamento
Protótipo frontend em RustSimservidor local e build com poucos comandos
Leptos com SSR e server functionsDependecargo-leptos pode integrar melhor cliente e servidor
Dioxus fullstackDependeo CLI dx cobre o pipeline específico do framework
Biblioteca Wasm publicada no npmGeralmente não é o focowasm-pack costuma ser mais direto para empacotar uma biblioteca
Backend Axum tradicionalNãouse Cargo e a estratégia normal de deploy do servidor

Se a sua entrega final é uma pasta de arquivos estáticos e o navegador executa a aplicação, Trunk merece entrar na avaliação. Para comparar frameworks antes de montar o pipeline, veja Leptos vs Dioxus vs Yew e o guia de Rust para WebAssembly.

Criando o projeto mínimo

Comece com um projeto binário comum:

cargo new app-wasm
cd app-wasm
rustup target add wasm32-unknown-unknown
cargo install trunk --locked

Para um exemplo com Yew, adicione a dependência usando o Cargo:

cargo add yew --features csr

O src/main.rs pode conter um componente pequeno:

use yew::prelude::*;

#[function_component(App)]
fn app() -> Html {
    let clicks = use_state(|| 0_u32);

    let onclick = {
        let clicks = clicks.clone();
        Callback::from(move |_| clicks.set(*clicks + 1))
    };

    html! {
        <main>
            <h1>{ "Rust + WebAssembly com Trunk" }</h1>
            <button {onclick}>
                { format!("Cliques: {}", *clicks) }
            </button>
        </main>
    }
}

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

Na raiz do projeto, crie index.html:

<!doctype html>
<html lang="pt-BR">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="Aplicação Rust WebAssembly com Trunk">
    <title>App Rust Wasm</title>
    <link data-trunk rel="rust">
  </head>
  <body>
  </body>
</html>

O atributo data-trunk informa que aquele elemento pertence ao pipeline. A linha rel="rust" conecta o HTML ao crate atual. Ao rodar trunk serve, a ferramenta compila para wasm32-unknown-unknown, passa pelos bindings necessários e serve a saída por HTTP.

O papel de index.html no Trunk

Diferentemente de ferramentas que começam por um arquivo de configuração JavaScript, Trunk trata o HTML como ponto de entrada. Isso deixa visível, em um único lugar, quais folhas de estilo, imagens, ícones e artefatos Rust fazem parte da página.

Alguns padrões frequentes são:

<link data-trunk rel="rust" data-wasm-opt="z">
<link data-trunk rel="css" href="styles/app.css">
<link data-trunk rel="copy-dir" href="public">
<link data-trunk rel="icon" href="public/favicon.svg">

Nem todo projeto precisa dessas quatro linhas. Adicione apenas o que realmente existe e valide o comportamento com a versão instalada usando:

trunk --version
trunk build --help
trunk serve --help

Essa verificação evita copiar flags antigas de um tutorial. O ecossistema Wasm evolui, e a ajuda do binário homologado pelo time é a referência operacional para a CI.

Também mantenha o index.html semanticamente completo. O Wasm não substitui metadados, acessibilidade, idioma, viewport ou uma mensagem útil quando JavaScript está desativado. Uma aplicação rápida ainda pode oferecer uma experiência ruim se o HTML inicial não explicar o que está carregando.

Configurando Trunk.toml

Projetos pequenos podem funcionar sem configuração adicional. Quando o time precisa padronizar porta, diretório de saída e comportamento de desenvolvimento, crie Trunk.toml na raiz:

[build]
target = "index.html"
dist = "dist"
public_url = "/"

[serve]
address = "127.0.0.1"
port = 8080
open = false

Trate esse arquivo como parte do contrato de build. Em vez de cada pessoa decorar uma sequência diferente, o repositório passa a registrar onde está o HTML de entrada, onde a saída será escrita e como o servidor local deve subir.

O campo public_url merece atenção no deploy. Para um domínio na raiz, / normalmente é a escolha esperada. Para uma aplicação publicada em um subdiretório, como https://exemplo.com/ferramenta/, a base precisa refletir esse caminho; caso contrário, o navegador pode procurar o .wasm, o JavaScript e os assets na raiz errada.

Faça um teste local que reproduza o caminho final. Um build que funciona em localhost:8080/ pode quebrar quando entra em /app/ por causa de URLs absolutas, roteamento do frontend ou regras do provedor.

Desenvolvimento com trunk serve

O comando principal durante o desenvolvimento é:

trunk serve

Para abrir o navegador automaticamente:

trunk serve --open

O servidor observa alterações no projeto e executa o pipeline novamente. Isso encurta o ciclo entre editar, compilar e testar, mas não elimina a necessidade de entender erros do compilador. Se o build falhar, leia a primeira causa relevante em vez de apenas reiniciar o servidor.

Uma rotina produtiva combina ferramentas:

cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features
trunk build

cargo test normalmente exercita a parte nativa que pode ser testada sem navegador. Para comportamento DOM, eventos e integrações web, adicione testes adequados ao ambiente Wasm ou testes ponta a ponta. A página de estratégias de testes em Rust ajuda a separar teste unitário, integração e fluxo de usuário.

Proxy para uma API local

Durante o desenvolvimento, a SPA frequentemente precisa chamar uma API em outra porta. Trunk oferece mecanismos de proxy que podem evitar configurações improvisadas de CORS no ambiente local. Como a sintaxe aceita pode variar com a versão, confira trunk serve --help e registre a opção escolhida no script de desenvolvimento ou no Trunk.toml.

Mesmo com proxy, mantenha a fronteira explícita:

  • frontend: estado de interface, validação imediata e chamadas HTTP;
  • backend: autenticação, autorização, persistência e regras que não podem confiar no cliente;
  • contrato: tipos de request/response, erros e versionamento.

Se a API for escrita em Rust, Serde cuida da serialização, Axum pode atender HTTP e utoipa com OpenAPI ajuda a documentar o contrato.

Assets, CSS e arquivos públicos

Há duas estratégias simples para arquivos estáticos:

  1. declarar cada asset relevante no index.html com diretivas data-trunk;
  2. copiar um diretório inteiro quando os arquivos já têm a estrutura final esperada.

A primeira opção torna dependências mais explícitas. A segunda é prática para fontes, manifestos, imagens e arquivos que não exigem transformação. Em ambos os casos, evite copiar lixo de desenvolvimento para produção. Revise dist/ antes do deploy:

rm -rf dist
trunk build --release
find dist -maxdepth 3 -type f -printf '%p %k KB\n' | sort

Pergunte para cada arquivo grande: ele precisa estar no carregamento inicial? Pode ser comprimido? Pode ser carregado sob demanda? Existe duplicação? Um .wasm pequeno não compensa imagens enormes, fontes redundantes ou JavaScript de terceiros bloqueando a página.

Para CSS simples, o próprio pipeline pode copiar ou processar a folha declarada. Se o projeto usa Tailwind, Sass ou outra ferramenta externa, documente onde essa etapa roda e qual comando a CI executa. Não esconda uma dependência global que só existe na máquina de uma pessoa.

Build de produção e otimização

Gere a saída otimizada com:

trunk build --release

O --release ativa o perfil de release do Cargo. Você também pode ajustar o perfil no Cargo.toml:

[profile.release]
opt-level = "s"
lto = true
codegen-units = 1
panic = "abort"
strip = true

Essas opções são um ponto de experimento, não uma receita universal. opt-level = "s" busca tamanho, enquanto outras aplicações podem se beneficiar de otimização diferente. LTO e menos unidades de geração de código podem reduzir ou melhorar o artefato, mas também aumentam o tempo de build. Meça tamanho compactado, tempo de carregamento, tempo de compilação e desempenho real.

A ferramenta wasm-opt, do projeto Binaryen, pode aplicar otimizações adicionais. Dependendo da instalação e configuração, Trunk consegue integrá-la ao pipeline. Antes de torná-la obrigatória na CI, confirme:

  • a versão está fixada ou reproduzível;
  • o ganho existe depois de gzip ou Brotli;
  • o tempo extra de build é aceitável;
  • o artefato continua funcionando nos navegadores suportados;
  • a equipe sabe diagnosticar uma regressão.

O guia de otimização de WebAssembly em Rust apresenta wasm-opt no contexto mais amplo. Para entender quais crates e funções aumentam o binário, combine a análise com cargo-bloat e cargo-llvm-lines.

Deploy dos arquivos estáticos

Depois do build, publique o conteúdo de dist/, não o código-fonte inteiro. Um pipeline mínimo pode seguir esta ordem:

rustup target add wasm32-unknown-unknown
cargo install trunk --locked
trunk build --release
test -f dist/index.html
test -n "$(find dist -name '*.wasm' -print -quit)"

No provedor, verifique quatro pontos.

1. Tipos MIME

O servidor deve entregar .wasm com application/wasm. Provedores modernos de arquivos estáticos normalmente fazem isso automaticamente, mas um Nginx ou servidor próprio mal configurado pode responder com um tipo genérico e impedir o carregamento correto.

2. Compressão

Brotli e gzip reduzem o volume transferido. Avalie o tamanho efetivamente baixado pelo navegador, não apenas o arquivo bruto em dist/. Um artefato de 1 MB no disco pode ter comportamento bem diferente após compressão, dependendo do código e dos dados embutidos.

3. Cache

Arquivos com hash no nome podem receber cache longo e imutável. O index.html deve ter uma política mais curta, porque referencia a versão atual dos assets. Se o HTML antigo ficar preso no cache enquanto o .wasm correspondente já foi removido, o usuário recebe uma aplicação quebrada.

4. Fallback de SPA

Se o roteador usa URLs como /painel ou /configuracoes, uma atualização direta nessas rotas precisa voltar para index.html. Configure o fallback no provedor, mas não transforme todos os erros em HTTP 200 sem critério. Assets inexistentes devem continuar sendo erros reais; fallback é para rotas do frontend.

Teste o deploy como usuário:

  • abra a raiz em janela anônima;
  • navegue para uma rota interna;
  • atualize a rota;
  • simule rede lenta;
  • confira o console e a aba Network;
  • valide uma versão anterior ou procedimento de rollback.

CI reproduzível com Trunk

Evite instalar “a versão mais recente” de todas as ferramentas em cada build sem controle. Uma atualização pode mudar a saída ou quebrar a pipeline sem nenhuma alteração no repositório. Registre a versão aprovada no workflow, em um arquivo de ferramentas ou em um script versionado.

Exemplo conceitual para uma CI:

set -euo pipefail

rustup target add wasm32-unknown-unknown
cargo install trunk --version "$TRUNK_VERSION" --locked
cargo fmt --all -- --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features
trunk build --release

test -s dist/index.html
find dist -name '*.wasm' -size +0 -print -quit | grep -q .

Cacheie o diretório de dependências apenas depois de entender as chaves de invalidação. Misturar toolchain, sistema operacional, Cargo.lock e targets incompatíveis gera caches difíceis de diagnosticar. O artigo sobre sccache para Rust na CI cobre o cache de compilação; para imagens Docker, veja cargo-chef.

Também imponha um orçamento de tamanho com tolerância razoável. Falhar a CI por qualquer byte novo causa ruído; ignorar crescimento contínuo permite que o carregamento piore sem revisão. Uma política útil registra o tamanho atual, alerta para variações pequenas e exige justificativa quando o aumento passa de um limite definido pelo time.

Trunk, wasm-pack, cargo-leptos e dx

Essas ferramentas não são substitutas perfeitas.

FerramentaMelhor encaixeSaída ou responsabilidade principal
TrunkSPA e frontend Rust/Wasm baseado em HTMLsite estático em dist/ + servidor local
wasm-packbiblioteca Rust/Wasm consumida por JavaScript/npmpacote com bindings e metadados
cargo-leptosaplicação Leptos com integração cliente/servidorcoordenação específica do framework
dxaplicações Dioxusdesenvolvimento e build do ecossistema Dioxus
Cargoqualquer crate Rustcompilação, dependências, testes e perfis

Escolha pelo produto entregue. Se você desenvolve um componente Wasm para ser importado por um aplicativo TypeScript, wasm-pack pode ser mais natural. Se desenvolve uma SPA Yew cujo resultado é um site estático, Trunk simplifica o caminho. Se o framework combina SSR e funções no servidor, o CLI especializado entende mais do projeto do que um empacotador genérico.

Erros comuns e como diagnosticar

Target wasm32 não instalado

Sintoma: o compilador não encontra o target esperado.

rustup target list --installed
rustup target add wasm32-unknown-unknown

Build funciona, mas a página fica vazia

Abra o console do navegador. Procure falha ao baixar .wasm, exceção durante inicialização, seletor DOM ausente ou panic. Ative logs úteis no desenvolvimento e reduza o caso até um componente mínimo.

Arquivo Wasm retorna 404

Confira public_url, caminho do deploy e URLs geradas em dist/index.html. O problema frequentemente aparece quando a aplicação sai da raiz do domínio e passa para um subdiretório.

Rota interna retorna 404 ao atualizar

Configure o fallback de SPA no provedor. Diferencie rota de aplicação de arquivo estático real para não mascarar assets ausentes.

Build de release fica muito lento

LTO, codegen-units = 1, wasm-opt e uma árvore grande de features podem somar minutos. Use cargo –timings e técnicas para reduzir o tempo de compilação antes de remover otimizações sem medir.

Binário cresce após adicionar uma crate

Investigue features padrão, duplicação de dependências, genéricos monomorfizados e código incluído apenas por conveniência. Use cargo tree, cargo-bloat e cargo-llvm-lines. O guia de cargo tree ajuda a localizar duplicações e features ativadas.

Projeto de portfólio com Trunk

Um bom projeto não deve mostrar apenas um contador. Crie um painel local-first para acompanhar tarefas técnicas, com estes requisitos:

  • interface Yew compilada para Wasm;
  • armazenamento local e importação/exportação JSON;
  • filtro, ordenação e atalhos de teclado;
  • tratamento de erros visível;
  • testes da lógica independente do DOM;
  • build de release com orçamento de tamanho;
  • CI com fmt, Clippy, testes e Trunk;
  • deploy estático com rota interna funcionando após refresh;
  • README explicando arquitetura e trade-offs.

Depois, adicione uma API opcional em Axum para sincronização. Essa separação mostra que você entende a fronteira de confiança: o navegador pode validar a experiência, mas o backend continua responsável por autorização e persistência compartilhada.

Em vagas Rust e entrevistas, o valor não está em dizer “usei WebAssembly”. Explique por que escolheu Wasm, qual era a alternativa em JavaScript, como mediu o tamanho do bundle, como tratou cache, como reproduziu o build e quando não usaria Rust no frontend. Veja também os projetos práticos para portfólio Rust e a trilha de carreira em Rust.

Checklist antes de publicar

  • rustup target add wasm32-unknown-unknown documentado;
  • versão do Trunk controlada na CI;
  • trunk build --release executado do zero;
  • dist/index.html e pelo menos um .wasm não vazio;
  • public_url compatível com raiz ou subdiretório;
  • assets grandes revisados;
  • gzip ou Brotli verificado no deploy;
  • cache longo apenas para assets versionados;
  • fallback de SPA testado em rota interna;
  • rollback documentado;
  • console do navegador sem erros;
  • acessibilidade básica e estado de carregamento revisados.

Perguntas frequentes sobre Trunk

O que é Trunk no ecossistema Rust?

Trunk é um build tool e servidor de desenvolvimento para aplicações web em Rust/WebAssembly. Ele usa o index.html como entrada, coordena a compilação do crate, processa assets declarados e produz uma pasta estática pronta para hospedagem. Ele complementa o wasm-bindgen, que cuida da interoperabilidade entre Rust/Wasm e JavaScript.

Como instalar e executar Trunk?

Instale o target com rustup target add wasm32-unknown-unknown, instale a ferramenta com cargo install trunk --locked e rode trunk serve na raiz que contém index.html. Em equipe, fixe uma versão na CI e confira a ajuda do binário instalado para evitar opções obsoletas.

Trunk serve apenas para projetos Yew?

Não. Yew é o encaixe mais conhecido, mas Trunk pode orquestrar projetos client-side Rust/Wasm sem Yew. Para Leptos ou Dioxus com recursos fullstack, compare o pipeline com cargo-leptos ou dx, pois essas ferramentas conhecem as necessidades específicas dos frameworks.

Como gerar um build de produção com Trunk?

Execute trunk build --release, publique o conteúdo de dist/ e valide MIME, compressão, cache, URL-base e fallback de rotas. Não abra apenas dist/index.html com file://: sirva os arquivos por HTTP para reproduzir o comportamento do navegador em produção.

Trunk substitui wasm-bindgen e wasm-opt?

Não. Trunk coordena etapas. wasm-bindgen continua sendo a ponte de interoperabilidade usada no ecossistema Rust/Wasm, enquanto wasm-opt é uma otimização adicional que pode reduzir o artefato em certos projetos. Mantenha qualquer etapa extra somente se o ganho for medido e o build continuar reproduzível.

Conclusão

Trunk reduz a distância entre um crate Rust e um frontend WebAssembly publicável: um index.html explícito, trunk serve para o ciclo local e trunk build --release para gerar a saída estática. A ferramenta funciona melhor quando o produto é realmente client-side e o time quer um pipeline simples, versionado e fácil de reproduzir.

O erro é tratar a escolha como moda. Compare Trunk com o CLI específico do framework, meça o artefato comprimido, teste rotas e cache no ambiente real e mantenha a fronteira com o backend clara. Assim, Rust no navegador deixa de ser uma demonstração técnica e vira uma aplicação que pode ser construída, operada e explicada profissionalmente.