Manual Completo de Tauri

Introducción a Tauri

Tauri es un framework para construir aplicaciones de escritorio pequeñas, rápidas y seguras usando tecnologías web modernas. Combina Rust en el backend con cualquier framework frontend como React, Vue o Svelte.

¿Por qué Tauri?

Comparación con Electron

Tauri

  • Binarios ~3MB
  • Memoria: ~30MB
  • Backend en Rust
  • Usa el navegador del sistema
  • Enfoque en seguridad

Electron

  • Binarios ~120MB
  • Memoria: ~100MB
  • Backend en Node.js
  • Incluye Chromium
  • Ecosistema maduro

Configuración Inicial

Requisitos previos

Antes de comenzar, necesitas instalar:

Crear un nuevo proyecto

Puedes crear un proyecto Tauri usando create-tauri-app:

npm create tauri-app@latest

O configurarlo manualmente:

# Para proyectos con Vite + React
npm create vite@latest my-tauri-app --template react
cd my-tauri-app
npm install @tauri-apps/api
npm install -D @tauri-apps/cli
npm run tauri init

Estructura del proyecto

Un proyecto Tauri típico tiene esta estructura:

. ├── src/ # Frontend (React/Vue/etc) │ ├── main.jsx │ └── App.jsx ├── src-tauri/ # Backend Rust │ ├── Cargo.toml # Config de Rust │ ├── tauri.conf.json # Config de Tauri │ └── src/ │ └── main.rs # Punto de entrada Rust ├── public/ # Assets estáticos └── package.json # Dependencias frontend

Desarrollo Frontend

Tauri es agnóstico al framework frontend. Puedes usar React, Vue, Svelte o incluso HTML plano.

Comunicación Frontend-Backend

Tauri proporciona varias formas de comunicación:

Ejemplo: Llamar a un comando Rust desde JS

src/App.jsx
import { invoke } from '@tauri-apps/api/tauri';

function App() {
  const [greeting, setGreeting] = React.useState('');

  async function greet(name) {
    // Llama al comando "greet" definido en Rust
    const result = await invoke('greet', { name });
    setGreeting(result);
  }

  return (
    <div>
      <button onClick={() => greet('World')}>
        Saludar
      </button>
      <p>{greeting}</p>
    </div>
  );
}
src-tauri/src/main.rs
#[tauri::command]
fn greet(name: &str) -> String {
    format!("Hola, {}!", name)
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![greet])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

API de Tauri

Tauri proporciona APIs listas para usar:

Ejemplo: Sistema de archivos
import { readDir, BaseDirectory } from '@tauri-apps/api/fs';

async function listFiles() {
  const entries = await readDir('Documents', { 
    dir: BaseDirectory.Home 
  });
  
  entries.forEach(entry => {
    console.log(entry.name);
  });
}

Backend con Rust

Rust proporciona el poder del backend en Tauri. Veamos algunas características avanzadas.

Comandos con validación

src-tauri/src/main.rs
use serde::Deserialize;
use tauri::command;

#[derive(Debug, Deserialize)]
struct User {
    name: String,
    age: u8,
}

#[command]
fn create_user(user: User) -> Result<String, String> {
    if user.age < 18 {
        return Err("El usuario debe ser mayor de edad".into());
    }
    
    Ok(format!("Usuario {} creado con edad {}", user.name, user.age))
}

fn main() {
    tauri::Builder::default()
        .invoke_handler(tauri::generate_handler![create_user])
        .run(tauri::generate_context!())
        .expect("error running tauri application");
}

Acceso al sistema de archivos

src-tauri/src/main.rs
use std::fs;
use tauri::command;

#[command]
fn read_file(path: String) -> Result<String, String> {
    fs::read_to_string(&path)
        .map_err(|e| format!("Error leyendo archivo: {}", e))
}

#[command]
fn write_file(path: String, contents: String) -> Result<(), String> {
    fs::write(&path, contents)
        .map_err(|e| format!("Error escribiendo archivo: {}", e))
}

Eventos personalizados

src-tauri/src/main.rs
use tauri::{Manager, RunEvent};

fn main() {
    tauri::Builder::default()
        .setup(|app| {
            let main_window = app.get_window("main").unwrap();
            
            // Escuchar evento desde el frontend
            main_window.listen("frontend-event", |event| {
                println!("Evento recibido: {:?}", event.payload());
            });
            
            Ok(())
        })
        .build(tauri::generate_context!())
        .expect("error building tauri application")
        .run(|app_handle, e| {
            if let RunEvent::ExitRequested { .. } = e {
                // Enviar evento al frontend antes de salir
                app_handle.emit_all("backend-event", "Aplicación cerrando").unwrap();
            }
        });
}

Interacción con el Sistema

Menú nativo

src-tauri/src/main.rs
use tauri::{Menu, MenuItem, Submenu, CustomMenuItem};

fn main() {
    let menu = Menu::new()
        .add_submenu(Submenu::new(
            "Archivo",
            Menu::new()
                .add_item(CustomMenuItem::new("new", "Nuevo"))
                .add_item(CustomMenuItem::new("open", "Abrir"))
                .add_native_item(MenuItem::Separator)
                .add_item(CustomMenuItem::new("quit", "Salir")),
        ))
        .add_submenu(Submenu::new(
            "Editar",
            Menu::new()
                .add_native_item(MenuItem::Undo)
                .add_native_item(MenuItem::Redo)
                .add_native_item(MenuItem::Separator)
                .add_native_item(MenuItem::Cut)
                .add_native_item(MenuItem::Copy)
                .add_native_item(MenuItem::Paste),
        ));

    tauri::Builder::default()
        .menu(menu)
        .on_menu_event(|event| {
            match event.menu_item_id() {
                "quit" => std::process::exit(0),
                _ => println!("Clicked on {}", event.menu_item_id()),
            }
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Tray (bandeja del sistema)

src-tauri/src/main.rs
use tauri::{SystemTray, SystemTrayMenu, CustomMenuItem};

fn main() {
    let tray_menu = SystemTrayMenu::new()
        .add_item(CustomMenuItem::new("show", "Mostrar"))
        .add_item(CustomMenuItem::new("hide", "Ocultar"))
        .add_native_item(SystemTrayMenuItem::Separator)
        .add_item(CustomMenuItem::new("quit", "Salir"));

    let system_tray = SystemTray::new().with_menu(tray_menu);

    tauri::Builder::default()
        .system_tray(system_tray)
        .on_system_tray_event(|app, event| {
            match event {
                SystemTrayEvent::MenuItemClick { id, .. } => {
                    match id.as_str() {
                        "quit" => std::process::exit(0),
                        "hide" => app.get_window("main").unwrap().hide().unwrap(),
                        "show" => app.get_window("main").unwrap().show().unwrap(),
                        _ => {}
                    }
                }
                _ => {}
            }
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Notificaciones del sistema

src/App.jsx
import { sendNotification } from '@tauri-apps/api/notification';

function App() {
  const showNotification = () => {
    sendNotification({
      title: 'Tauri App',
      body: 'Esta es una notificación del sistema!',
      icon: 'path/to/icon.png'
    });
  };

  return (
    <button onClick={showNotification}>
      Mostrar notificación
    </button>
  );
}

Build y Despliegue

Construir la aplicación

Para crear un ejecutable:

npm run tauri build

Esto generará instaladores para tu plataforma:

Configuración del build

Puedes personalizar el build en src-tauri/tauri.conf.json:

src-tauri/tauri.conf.json
{
  "build": {
    "distDir": "../dist",
    "devPath": "http://localhost:3000",
    "beforeBuildCommand": "npm run build"
  },
  "package": {
    "productName": "MiApp",
    "version": "1.0.0"
  },
  "tauri": {
    "bundle": {
      "identifier": "com.example.miapp",
      "icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png"],
      "resources": [],
      "targets": "all",
      "windows": {
        "certificateThumbprint": null,
        "digestAlgorithm": "sha256",
        "timestampUrl": ""
      }
    },
    "allowlist": {
      "all": false,
      "fs": {
        "scope": ["$HOME/Documents"]
      }
    }
  }
}

Actualizaciones automáticas

Tauri soporta actualizaciones automáticas:

src-tauri/src/main.rs
use tauri::updater::Builder;

fn main() {
    tauri::Builder::default()
        .setup(|app| {
            let handle = app.handle();
            
            tauri::async_runtime::spawn(async move {
                let builder = Builder::new(handle.config());
                match builder.build().await {
                    Ok(updater) => {
                        if let Ok(Some(update)) = updater.check().await {
                            if update.download_and_install().await.is_ok() {
                                std::process::exit(0);
                            }
                        }
                    }
                    Err(e) => println!("Error en actualizador: {}", e),
                }
            });
            
            Ok(())
        })
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

Conclusión

Tauri es una excelente alternativa moderna para desarrollar aplicaciones de escritorio con tecnologías web. Combina lo mejor de Rust para el backend con la flexibilidad de cualquier framework frontend.

Recursos adicionales: