En pocas palabras
Framework para aplicaciones multiplataforma con interfaz web y capacidades nativas mediante Rust.
Qué problema resuelve
Necesidad de empaquetar una interfaz web como aplicación con integración del sistema.
Cuándo utilizarlo
Utilidades de escritorio y aplicaciones compatibles móviles. Ejemplo: una herramienta local de análisis de archivos.
De la idea a la práctica
Funcionamiento, ejemplos y decisiones para pasar al siguiente nivel.
En el Radar compartido: Trial (valoración de la edición aportada).
Cómo funciona
Usa el webview del sistema para presentar la UI y un backend Rust para comandos nativos. Las capacidades determinan qué operaciones puede invocar cada contexto.
Ejemplo de uso
Una interfaz selecciona un archivo y solicita al backend un análisis limitado a esa ruta. La aplicación expone únicamente comandos necesarios y valida argumentos antes de acceder al disco.
Arquitectura
┌──────────────── Ventana ─────────────────┐
│ Frontend (HTML/CSS/JS: React, Svelte, Vue…) │
│ invoke('comando', args) │
└───────────────────┬──────────────────────┘
│ IPC validado por capacidades
┌───────────────────▼──────────────────────┐
│ Núcleo Rust: comandos, plugins, sistema │
└──────────────────────────────────────────┘
El webview es el del sistema: WebView2 en Windows, WKWebView en macOS e iOS, WebKitGTK en Linux, y el WebView de Android. Por eso los binarios ocupan pocos megabytes, frente a los más de cien habituales en Electron, que empaqueta Chromium.
Comando en Rust e invocación desde el frontend
#[derive(serde::Serialize)]
struct Resumen { lineas: usize, errores: usize }
#[tauri::command]
fn analizar_log(ruta: String) -> Result<Resumen, String> {
let contenido = std::fs::read_to_string(&ruta).map_err(|e| e.to_string())?;
let errores = contenido.lines().filter(|l| l.contains("ERROR")).count();
Ok(Resumen { lineas: contenido.lines().count(), errores })
}
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_dialog::init())
.invoke_handler(tauri::generate_handler![analizar_log])
.run(tauri::generate_context!())
.expect("error al iniciar la aplicación");
}
import { invoke } from '@tauri-apps/api/core';
import { open } from '@tauri-apps/plugin-dialog';
const ruta = await open({ filters: [{ name: 'Logs', extensions: ['log'] }] });
if (ruta) {
const resumen = await invoke<{ lineas: number; errores: number }>(
'analizar_log',
{ ruta },
);
}
Capacidades
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "principal",
"windows": ["main"],
"permissions": [
"core:default",
"dialog:allow-open",
{
"identifier": "fs:allow-read-text-file",
"allow": [{ "path": "$DOCUMENT/**" }]
}
]
}
Cada ventana recibe solo los permisos que necesita, con alcances de rutas. Si el frontend se ve comprometido (por ejemplo, por XSS), el daño se limita a lo permitido.
Plugins oficiales
fs, dialog, http, shell, store, sql, notification, updater, deep-link, global-shortcut, biometric (móvil), entre otros. El updater verifica firmas de las actualizaciones con una clave propia antes de instalarlas.
Tauri frente a Electron
| Aspecto | Electron | Tauri |
|---|---|---|
| Motor web | Chromium empaquetado (consistente) | Webview del sistema (varía por SO) |
| Backend | Node.js | Rust (y plugins) |
| Tamaño y memoria | Mayor | Mucho menor |
| Modelo de seguridad | Configurable, abierto por defecto | Capacidades explícitas |
| Móvil | No | iOS y Android desde Tauri 2 |
Elige Tauri cuando el tamaño, la memoria o la seguridad importen y puedas probar en los webviews de cada sistema; Electron cuando necesites un motor idéntico en todas las plataformas o librerías de Node.
Criterios de implementación
Probar diferencias entre webviews y sistemas. Gestionar firma, actualizaciones y distribución, y restringir contenido remoto y acceso a archivos mediante capacidades explícitas.
Continúa en las fuentes
Documentación oficial para verificar detalles y explorar más.