Tauri 2: apps desktop com Rust e frontend web sem o peso do Electron

1. O que é Tauri e por que ele desafia o Electron?

Tauri é um framework para construção de aplicações desktop multiplataforma que combina um backend em Rust com um frontend web tradicional (HTML, CSS, JavaScript/TypeScript). Diferentemente do Electron, que empacota um navegador Chromium completo, Tauri utiliza o renderizador nativo do sistema operacional (WebView) — no Windows é o WebView2 baseado no Edge, no macOS o WKWebView, e no Linux o WebKitGTK.

O resultado é impressionante: enquanto um app "Hello World" em Electron consome cerca de 100-150 MB de RAM e gera binários de 150-200 MB, a mesma aplicação em Tauri roda com 10-20 MB de memória e produz executáveis de apenas 3-8 MB. O tempo de inicialização também é drasticamente menor — tipicamente abaixo de 1 segundo contra 3-5 segundos do Electron.

A motivação original do projeto (lançado em 2020) era justamente oferecer uma alternativa mais leve, segura e performática para desenvolvedores web que desejam criar aplicativos desktop sem abrir mão da produtividade. Com a versão 2 (estável desde outubro de 2023), o framework amadureceu significativamente, adicionando suporte a plugins oficiais, sistema de permissões granular e melhorias no IPC.

2. Primeiros passos com Tauri 2: configuração e scaffolding

Para começar, você precisa do Rust (via rustup) e Node.js (v16+). Instale a CLI do Tauri:

npm install -g @tauri-apps/cli@latest

Crie um novo projeto:

npm create tauri-app@latest

Durante a configuração interativa, escolha um nome, o gerenciador de pacotes (npm, yarn, pnpm) e o template de frontend (React, Vue, Svelte ou Vanilla). A estrutura gerada será:

meu-app/
├── src/              # Frontend (React, Vue, etc.)
├── src-tauri/        # Backend Rust
│   ├── src/
│   │   └── main.rs   # Código principal do Rust
│   ├── Cargo.toml    # Dependências Rust
│   └── tauri.conf.json  # Configurações do Tauri
├── package.json
└── index.html

O arquivo tauri.conf.json centraliza as configurações:

{
  "build": {
    "frontendDist": "../dist",
    "devUrl": "http://localhost:5173",
    "beforeDevCommand": "npm run dev",
    "beforeBuildCommand": "npm run build"
  },
  "app": {
    "windows": [
      {
        "title": "Meu App Tauri",
        "width": 1024,
        "height": 768,
        "resizable": true
      }
    ]
  }
}

Para desenvolvimento, execute npm run tauri dev. O comando inicia o servidor Vite (ou outro bundler) e abre a janela nativa com a WebView.

3. Comunicação entre Rust e frontend: IPC e comandos

O coração da comunicação é o sistema de comandos (commands). No lado Rust, você expõe funções com o atributo #[tauri::command]:

// src-tauri/src/main.rs
use tauri::Manager;

#[tauri::command]
fn greet(name: &str) -> String {
    format!("Olá, {}! Bem-vindo ao Tauri 2.", name)
}

#[tauri::command]
fn calculate_fibonacci(n: u32) -> Result<Vec<u64>, String> {
    if n > 90 {
        return Err("Número muito grande".to_string());
    }
    let mut sequence = vec![0, 1];
    for i in 2..=n as usize {
        sequence.push(sequence[i-1] + sequence[i-2]);
    }
    Ok(sequence[..=n as usize].to_vec())
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![greet, calculate_fibonacci])
        .run(tauri::generate_context!())
        .expect("Erro ao executar aplicação");
}

No frontend, a invocação usa invoke:

import { invoke } from '@tauri-apps/api/core';

// Comando simples
async function saudacao() {
  const mensagem = await invoke('greet', { name: 'Maria' });
  console.log(mensagem); // "Olá, Maria! Bem-vindo ao Tauri 2."
}

// Comando com retorno complexo e tratamento de erro
async function fibonacci() {
  try {
    const sequencia = await invoke('calculate_fibonacci', { n: 10 });
    console.log(sequencia); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
  } catch (erro) {
    console.error('Erro:', erro);
  }
}

Para eventos bidirecionais (úteis para notificações em tempo real):

// Rust: emitindo evento
app_handle.emit("progress", 75).unwrap();

// Frontend: escutando evento
import { listen } from '@tauri-apps/api/event';
const unlisten = await listen('progress', (event) => {
  console.log(`Progresso: ${event.payload}%`);
});

4. Sistema de plugins e extensibilidade

O Tauri 2 possui um ecossistema robusto de plugins oficiais. Para usar um, adicione-o ao Cargo.toml e configure no tauri.conf.json:

// Cargo.toml
[dependencies]
tauri-plugin-dialog = "2"
tauri-plugin-fs = "2"
tauri-plugin-shell = "2"
// tauri.conf.json
{
  "plugins": {
    "dialog": {},
    "fs": {
      "scope": ["$APPDATA/*", "$HOME/Documents/*"]
    },
    "shell": {
      "open": true
    }
  }
}

No frontend, a API do plugin fica disponível:

import { open } from '@tauri-apps/plugin-dialog';
import { readTextFile } from '@tauri-apps/plugin-fs';

async function abrirArquivo() {
  const arquivo = await open({
    multiple: false,
    filters: [{ name: 'Textos', extensions: ['txt', 'md'] }]
  });
  if (arquivo) {
    const conteudo = await readTextFile(arquivo);
    console.log(conteudo);
  }
}

Para criar um plugin customizado, você encapsula comandos Rust em um crate separado:

// src-tauri/plugins/meu-plugin/src/lib.rs
use tauri::{plugin::Plugin, Runtime};

pub struct MeuPlugin;

impl<R: Runtime> Plugin<R> for MeuPlugin {
    fn name(&self) -> &'static str {
        "meu-plugin"
    }

    fn initialize(&mut self, app: &tauri::AppHandle<R>, _config: serde_json::Value) -> Result<(), Box<dyn std::error::Error>> {
        // Configuração inicial
        Ok(())
    }
}

5. Segurança e permissões no Tauri 2

O modelo de segurança do Tauri 2 é baseado em capabilities — você declara explicitamente quais comandos e plugins podem ser acessados pelo frontend. As permissões são configuradas no tauri.conf.json:

{
  "app": {
    "security": {
      "csp": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'"
    }
  },
  "capabilities": [
    {
      "identifier": "default",
      "description": "Permissões padrão do app",
      "windows": ["main"],
      "permissions": [
        "core:default",
        "dialog:default",
        "fs:read",
        "fs:write",
        {
          "identifier": "shell:allow-open",
          "allow": [{ "url": "https://*" }]
        }
      ]
    }
  ]
}

Boas práticas incluem:
- Validar todas as entradas no Rust (nunca confiar no frontend)
- Usar escopo mínimo de permissões (ex: fs:read ao invés de fs:all)
- Habilitar CSP restritivo
- Para operações sensíveis, exigir confirmação do usuário via diálogos nativos

6. Build, distribuição e atualizações automáticas

Para build de produção:

npm run tauri build

Isso gera:
- Windows: src-tauri/target/release/bundle/msi/ ou nsis/
- macOS: src-tauri/target/release/bundle/dmg/
- Linux: src-tauri/target/release/bundle/appimage/ ou deb/

Para atualizações automáticas, configure o plugin updater:

// Cargo.toml
tauri-plugin-updater = "2"
// tauri.conf.json
{
  "plugins": {
    "updater": {
      "endpoints": ["https://meuservidor.com/updates/{{target}}/{{current_version}}"],
      "pubkey": "seu-public-key-aqui"
    }
  }
}

No servidor, hospede um manifesto JSON:

{
  "version": "1.2.0",
  "notes": "Correção de bugs e melhorias de performance",
  "pub_date": "2024-06-15",
  "platforms": {
    "windows-x86_64": { "url": "https://meuservidor.com/releases/app_1.2.0_x64.msi", "signature": "" },
    "darwin-x86_64": { "url": "https://meuservidor.com/releases/app_1.2.0_x64.dmg", "signature": "" },
    "linux-x86_64": { "url": "https://meuservidor.com/releases/app_1.2.0_amd64.deb", "signature": "" }
  }
}

7. Casos de uso reais e comparação com Electron

Empresas como a Linear (ferramenta de gerenciamento de projetos) migraram partes de sua stack de Electron para Tauri, reportando redução de 70% no uso de memória. O Discord também testou Tauri para seu aplicativo de desktop, com resultados promissores em performance.

Limitações atuais do Tauri 2:
- Suporte limitado a WebGL (funciona, mas sem aceleração total em alguns cenários)
- APIs nativas avançadas (bluetooth, USB) ainda dependem de plugins comunitários
- Ecossistema de plugins menor que o do Electron

Quando escolher Tauri:
- Apps que priorizam leveza e performance
- Projetos onde o tamanho do binário importa (ex: ferramentas de linha de comando com GUI)
- Equipes já familiarizadas com Rust ou que desejam segurança de memória no backend

Quando preferir Electron:
- Apps que dependem fortemente de Chromium (ex: ferramentas de desenvolvimento web)
- Necessidade de suporte a APIs experimentais do navegador
- Ecossistema maduro de plugins (Electron tem 10+ anos de comunidade)

Alternativas como Neutralinojs (backend em C++) e NW.js (também baseado em Chromium) ocupam nichos específicos, mas Tauri se destaca pelo equilíbrio entre produtividade web e performance nativa.

Referências