create-tauri-app y el esqueleto
Crear el proyecto, entender qué te pregunta el asistente y por qué, y leer el esqueleto generado línea por línea antes de tocar nada.
Crear el proyecto
Hay tres formas de invocar al asistente, según lo que tengas instalado:
bun create tauri-app # con bunnpm create tauri-app@latest # con npmcargo install create-tauri-app && cargo create-tauri-app # sin NodeLa tercera es interesante por lo que revela: se puede tener un proyecto de Tauri sin Node instalado, si el frontend no lo necesita. El asistente ofrece plantillas de HTML puro y de frameworks de Rust que compilan a WebAssembly. No es lo habitual, pero conviene saber que el framework no exige un ecosistema de JavaScript.
Lo que te pregunta, y qué contestar
El esqueleto, leído
Tres ficheros llevan todo lo interesante. Ábrelos en este orden.
src-tauri/src/lib.rs — la aplicación:
// Un comando: una función normal con un atributo. Lo único que la distingue// de cualquier otra función de Rust es que el frontend puede pedirla.#[tauri::command]fn greet(name: &str) -> String { format!("Hola, {name}! Has sido saludado desde Rust!")}#[cfg_attr(mobile, tauri::mobile_entry_point)]pub fn run() { tauri::Builder::default() .plugin(tauri_plugin_opener::init()) // Registrar. Un comando sin registrar no existe para el frontend. .invoke_handler(tauri::generate_handler![greet]) .run(tauri::generate_context!()) .expect("error al arrancar la aplicación");}Cinco cosas que ya sabes ver, después del nivel 4:
greetes una función corriente. El atributo la marca como invocable, nada más.generate_handler!es el registro. Es el paso que más se olvida, y el nivel 19 explica cómo dejar de poder olvidarlo.- El
plugin(...)de la plantilla registra el plugin que abre enlaces en el navegador del sistema. Es el ejemplo de plugin más pequeño que existe. generate_context!incrusta la configuración, los iconos y el manifiesto de permisos en el binario. Nivel 4.2, paso 3.- No hay
setuptodavía. Lo habrá en cuanto haya algo que inicializar.
src-tauri/tauri.conf.json — la configuración:
{ "productName": "mi-app", "version": "0.1.0", "identifier": "dev.tudominio.mi-app", "build": { "beforeDevCommand": "bun run dev", "devUrl": "http://localhost:1420", "beforeBuildCommand": "bun run build", "frontendDist": "../dist" }, "app": { "windows": [{ "title": "mi-app", "width": 800, "height": 600 }], "security": { "csp": null } }, "bundle": { "active": true, "targets": "all", "icon": ["icons/icon.png"] }}La sección build es el pegamento entre los dos proyectos y explica cómo funcionan los dos modos: en desarrollo, Tauri ejecuta beforeDevCommand y apunta la webview a devUrl; al compilar, ejecuta beforeBuildCommand y empaqueta lo que haya en frontendDist. Toda la diferencia entre dev y build del nivel 10 está en esas cuatro claves.
Fíjate también en security.csp: null. La plantilla viene con la política de contenido desactivada para que nada moleste mientras aprendes. Eso hay que cambiarlo antes de publicar nada, y es el nivel 31.
src/main.js — el frontend:
import { invoke } from "@tauri-apps/api/core";// Toda la superficie del puente, en una línea: un nombre y unos argumentos.const saludo = await invoke("greet", { name: "mundo" });Nada más. El nombre del comando es una cadena, los argumentos un objeto, y el resultado una promesa. La fase III desmonta lo que ocurre entre esa línea y la función de Rust.
Los nombres, que no coinciden
Un detalle que sorprende a todo el mundo la primera vez: la función de Rust se llama greet y recibe name, y el frontend la invoca con { name: ... }. Coincide porque el ejemplo es de una sola palabra. En cuanto el argumento se llame file_path en Rust, el frontend tendrá que enviarlo como filePath: el puente convierte entre la convención de Rust y la de JavaScript. El nivel 14 lo trata, y es una de las causas más frecuentes de «mi comando no recibe los argumentos».