Introduction
Tauri est un framework open-source révolutionnaire pour développer des applications desktop cross-platform (Windows, macOS, Linux) en combinant des technologies web frontend (HTML, CSS, JS/TS) avec un backend Rust ultra-performant et sécurisé. Contrairement à Electron qui embarque Chromium (150+ Mo par app), Tauri utilise le WebView natif du système (10-20 Mo), réduisant drastiquement la taille et la consommation mémoire. En 2026, avec Tauri v2, les capabilities permettent un contrôle granulaire des permissions, évitant les failles de sécurité courantes. Ce tutoriel expert vous guide pas à pas pour créer une app complète : un gestionnaire de tâches avec persistance fichier, appels Rust asynchrones et build optimisé. Parfait pour les devs seniors cherchant scalabilité, sécurité zero-trust et performances natives. À la fin, votre app sera production-ready, signable et distributable via AppImage/MSI/DMG. (142 mots)
Prérequis
- Rust : Version stable (1.80+), installez via rustup.rs
- Node.js : 20+ avec npm/yarn/pnpm
- Système de build : Visual Studio (Windows), Xcode (macOS), ou clang/gcc (Linux)
- Outils optionnels : Git, un éditeur comme VS Code avec extensions Rust/Tauri
- Connaissances : Rust intermédiaire, TypeScript, Web APIs (Fetch, DOM)
Installer Tauri CLI
cargo install tauri-cli --version "^2.0"
npm install -g @tauri-apps/cli
tauri --version
tauri doctorCette commande installe la CLI Tauri v2 via Cargo pour les outils Rust, et npm pour les wrappers JS. tauri doctor vérifie l'environnement (WebView2 sur Windows, etc.). Évitez les versions git pour la stabilité en prod ; mettez à jour avec cargo update si besoin.
Vérification et première commande
Exécutez tauri doctor pour valider les dépendances système. Sur Windows, installez WebView2 Runtime si absent. Analogie : comme un check-up avant chirurgie, cela prévient 90% des builds foireux. Passez ensuite à la création du projet avec un template Vite + Vanilla pour simplicité experte.
Créer le projet Tauri
npm create tauri-app@latest mon-app-tauri
cd mon-app-tauri
npm install
npm run tauri devCrée un projet boilerplate avec Vite frontend et Tauri backend. npm run tauri dev lance le hot-reload : Rust compile en ~2s, WebView s'ouvre. Personnalisez le template (React/Svelte) via prompts. Piège : sur macOS, accordez permissions Developer Tools dans Sécurité.
Structure du projet
Le dossier src-tauri contient le backend Rust (Cargo.toml, main.rs, tauri.conf.json). src gère le frontend web. Les capabilities (v2) séparent permissions en fichiers JSON isolés, comme des rôles RBAC. Modifiez src-tauri/capabilities/default.json pour exposer des APIs sans allowlist obsolète.
Configurer tauri.conf.json
{
"productName": "MonAppTauri",
"version": "1.0.0",
"identifier": "com.exemple.monapp",
"build": {
"devPath": "../src",
"distDir": "../dist"
},
"app": {
"windows": [{
"title": "Gestionnaire Tâches",
"width": 800,
"height": 600,
"resizable": true
}],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
}Configure nom, fenêtres, bundle multiplateforme et icônes (pré-générez-les). identifier doit être unique (reverse-domain). En v2, CSP null autorise eval() pour DX ; en prod, raffermissez-le. Piège : oubliez distDir et Vite ne build pas.
Définir les capabilities
{
"$schema": "https://schema.tauri.app/config/2.0.0/capability",
"identifier": "main-capability",
"description": "Capabilities principales",
"local": true,
"windows": ["main"],
"permissions": [
"core:default",
{
"identifier": "fs:allow-read",
"allow": [
{ "path": "$APPDATA/../local/tasks.json" }
]
},
{
"identifier": "shell:allow-execute",
"allow": [{
"name": "ls",
"args": true,
"sidecar": false
}]
}
]
}Définit permissions granulaires : lecture JSON dans AppData, exécution shell. $APPDATA est résolu nativement. Associez à tauri.conf.json via capabilities: ["main-capability"]. Avantage v2 : isolation fenêtres, audits sécurisés.
Implémenter le backend Rust
Créez des commandes invocables depuis JS via invoke. Utilisez serde pour JSON, std::fs pour persistance. Analogie : Rust comme un garde du corps – vérifie tout avant d'accéder au DOM.
Commandes Rust (main.rs)
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use serde::{Deserialize, Serialize};
use std::fs;
use tauri::{command, Manager};
#[derive(Serialize, Deserialize)]
struct Task {
id: u32,
title: String,
done: bool,
}
type Tasks = Vec<Task>;
const TASKS_FILE: &str = "tasks.json";
#[command]
fn get_tasks() -> Result<Tasks, String> {
let path = dirs::data_local_dir()
.ok_or("Impossible de trouver data dir")?
.join(TASKS_FILE);
if !path.exists() {
return Ok(vec![]);
}
let data = fs::read_to_string(&path).map_err(|e| e.to_string())?;
let tasks: Tasks = serde_json::from_str(&data).unwrap_or(vec![]);
Ok(tasks)
}
#[command]
fn add_task(title: String) -> Result<Tasks, String> {
let mut tasks = get_tasks().unwrap_or(vec![]);
let new_id = tasks.len() as u32 + 1;
tasks.push(Task { id: new_id, title, done: false });
let path = dirs::data_local_dir().unwrap().join(TASKS_FILE);
fs::write(&path, serde_json::to_string_pretty(&tasks).unwrap()).map_err(|e| e.to_string())?;
Ok(tasks)
}
#[command]
fn toggle_task(id: u32) -> Result<Tasks, String> {
let mut tasks = get_tasks().map_err(|e| e)?;
if let Some(task) = tasks.iter_mut().find(|t| t.id == id) {
task.done = !task.done;
let path = dirs::data_local_dir().unwrap().join(TASKS_FILE);
fs::write(&path, serde_json::to_string_pretty(&tasks).unwrap()).map_err(|e| e.to_string())?;
}
Ok(tasks)
}
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_fs::init())
.invoke_handler(tauri::generate_handler![get_tasks, add_task, toggle_task])
.run(tauri::generate_context!())
.expect("Erreur lors du run Tauri");
}Implémente CRUD tâches avec persistance JSON locale. dirs résout chemins OS-safe. Ajoutez tauri-plugin-fs dans Cargo.toml. Erreur courante : oublier windows_subsystem (no console Windows). Sécurisé via capabilities.
Frontend TypeScript (main.tsx)
import { createRoot } from 'react-dom/client';
import React, { useState, useEffect } from 'react';
import { invoke } from '@tauri-apps/api/tauri';
import { listen } from '@tauri-apps/api/event';
type Task = {
id: number;
title: string;
done: boolean;
};
const App: React.FC = () => {
const [tasks, setTasks] = useState<Task[]>([]);
const [newTask, setNewTask] = useState('');
const loadTasks = async () => {
try {
const loaded: Task[] = await invoke('get_tasks');
setTasks(loaded);
} catch (e) {
console.error(e);
}
};
useEffect(() => {
loadTasks();
}, []);
const addTask = async () => {
if (!newTask.trim()) return;
try {
const updated: Task[] = await invoke('add_task', { title: newTask });
setTasks(updated);
setNewTask('');
} catch (e) {
console.error(e);
}
};
const toggleTask = async (id: number) => {
try {
const updated: Task[] = await invoke('toggle_task', { id });
setTasks(updated);
} catch (e) {
console.error(e);
}
};
return (
<div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
<h1>Gestionnaire de Tâches Tauri</h1>
<div>
<input
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
placeholder="Nouvelle tâche"
style={{ marginRight: '10px', padding: '8px' }}
/>
<button onClick={addTask}>Ajouter</button>
</div>
<ul style={{ listStyle: 'none', padding: 0 }}>
{tasks.map((task) => (
<li key={task.id} style={{ margin: '10px 0' }}>
<input
type="checkbox"
checked={task.done}
onChange={() => toggleTask(task.id)}
/>
<span style={{ textDecoration: task.done ? 'line-through' : 'none', marginLeft: '10px' }}>
{task.title}
</span>
</li>
))}
</ul>
</div>
);
};
const root = createRoot(document.getElementById('root') as HTMLElement);
root.render(<App />);Frontend React simple avec invoke pour appeler Rust. Utilisez @tauri-apps/api. Hot-reload via Vite. Ajoutez npm i react react-dom @types/react @types/react-dom @tauri-apps/api. Piège : async sans try/catch crash l'app.
Mettre à jour Cargo.toml
[package]
name = "mon-app-tauri"
version = "1.0.0"
edition = "2021"
[build-dependencies]
tauri-build = { version = "2.0", features = [] }
[dependencies]
tauri = { version = "2.0", features = [ "shell-open", "protocol-asset" ] }
tauri-plugin-fs = "2.0"
serde = { version = "1.0", features = [ "derive" ] }
serde_json = "1.0"
tokio = { version = "1", features = [ "full" ] }
dirs = "5.0"Dépendances pour Tauri v2, FS plugin, JSON, Tokio async. tauri-build pour context. edition 2021 pour features modernes. Exécutez cargo check après édition.
Build pour production
npm run tauri build
# Ou pour un target spécifique
npm run tauri build -- --target x86_64-apple-darwin
# Signer sur macOS (exemple)
security find-identity -v -p codesigningGénère bundles natifs (EXE/DMG/AppImage). --target pour cross-compile (installez toolchains via rustup). Sur macOS/Windows, signez pour Store. Taille finale ~15 Mo vs 200+ Electron.
Bonnes pratiques
- Capabilities first : Toujours isoler permissions par fenêtre/role ; auditez avec
tauri audit. - Rust pour core logic : Déplacez calculs lourds (crypto, DB) en natif ; JS pour UI seulement.
- Plugins officiels : Utilisez
sqlpour SQLite,storepour key-value au lieu de fs raw. - Tests end-to-end :
cargo test+ Playwright pour UI ; CI avec GitHub Actions. - Updater : Intégrez
tauri-plugin-updaterpour auto-updates via GitHub Releases.
Erreurs courantes à éviter
- Permissions manquantes :
invokeéchoue silencieusement ; vérifiez logs Rust avecRUST_LOG=debug npm run tauri dev. - Chemins absolus : Utilisez toujours
$APPDATA/$HOME;dirscrate pour portabilité. - Hot-reload lent : Augmentez
devServertimeout dans tauri.conf ; évitezwatchsur node_modules. - Build cross-plat : Installez targets Rust (
rustup target add) ; testez sur VM avant merge.
Pour aller plus loin
Plongez dans les docs Tauri v2, explorez plugins comme tauri-plugin-sql pour DB embarquée ou tauri-plugin-global-shortcut. Pour maîtriser Rust/Tauri en profondeur, découvrez nos formations Learni certifiantes. Contribuez sur GitHub pour plugins custom.