Pourquoi Tauri ?
Au milieu des nombreux choix pour développer un application Web en tant qu'application native, vous pouvez être confronté à une multitude de options : React Native, Flutter, Electron... Cependant, si vous cherchez une solution qui offre une expérience utilisateur fluide tout en offrant le contrôle total sur votre code source, Tauri est peut-être la solution idéale pour vous.
Un cas d'usage concret serait de développer une application de gestion des tâches. Avec Tauri, vous pouvez créer une interface Web moderne avec React ou Vue.js, tout en utilisant les capacités natives de votre système d'exploitation (comme le stockage local, les notifications push, etc.). Cela offre un bon équilibre entre l'interface utilisateur moderne et la fonctionnalité native.
Prerequis
Pour commencer avec Tauri, vous aurez besoin des éléments suivants :
- Node.js: Minimum version 14.0.0
- npm ou yarn: Pour gérer les dépendances du projet
- Rust: Minimum version 1.56.0 (pour la compilation de l'application Tauri)
- Un éditeur de code préféré : Par exemple, Visual Studio Code
Vous pouvez installer ces outils en suivant les instructions sur le site officiel :
Concepts fondamentaux
1. Projet Tauri
Un projet Tauri est composé de deux parties principales : une application Web et une application Native (le "shell").
Structure du projet :
my-tauri-app/
├── tauri/
│ ├── src-tauri/
│ │ ├── main.rs
│ │ └── Cargo.toml
│ └── tauri.conf.json
├── frontend/
│ ├── public/
│ ├── src/
│ ├── package.json
│ └── webpack.config.js
└── index.html
2. Configuration Tauri
La configuration de Tauri est définie dans tauri.conf.json. C'est le fichier qui contrôle comment votre application sera compilée et déployée.
{
"build": {
"distDir": "../frontend/dist",
"devPath": "http://localhost:3000"
},
"allowlist": {
"all": true
}
}
3. Communication Frontend/Native
Tauri utilise une API de communication bidirectionnelle entre le frontend et le backend pour permettre l'interactions natives.
Exemple de code :
// src-tauri/src/main.rs
use tauri::{Builder, Manager};
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
javascript
// frontend/src/App.js
import { invoke } from '@tauri-apps/api/tauri';
const App = () => {
const handleClick = async () => {
const greeting = await invoke('greet', { name: 'World' });
alert(greeting);
};
return (
<div>
<button onClick={handleClick}>Greet</button>
</div>
);
};
export default App;
Mise en pratique : projet fil rouge
Mini-projet : Gestionnaire de tâches
Nous allons créer un simple gestionnaire de tâches où vous pouvez ajouter, supprimer et afficher les tâches.
Étape 1 : Initialiser le projet
npm init -y
npx create-tauri-app my-todo-app
Étape 2 : Ajouter une nouvelle tâche
Nous allons modifier le frontend pour ajouter une nouvelle tâche et l'envoyer au backend.
Exemple de code :
// frontend/src/App.js
import { useState } from 'react';
import { invoke } from '@tauri-apps/api/tauri';
const App = () => {
const [tasks, setTasks] = useState([]);
const [newTask, setNewTask] = useState('');
const handleAddTask = async (e) => {
e.preventDefault();
await invoke('add_task', { task: newTask });
setNewTask('');
fetchTasks();
};
const fetchTasks = async () => {
const tasks = await invoke('get_tasks');
setTasks(tasks);
};
return (
<div>
<h1>Todo List</h1>
<form onSubmit={handleAddTask}>
<input
type="text"
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
placeholder="Add a new task"
/>
<button type="submit">Add</button>
</form>
<ul>
{tasks.map((task, index) => (
<li key={index}>{task}</li>
))}
</ul>
</div>
);
};
export default App;
Exemple de code :
// src-tauri/src/main.rs
use tauri::{Builder, Manager};
use std::fs;
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![add_task, get_tasks])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
#[tauri::command]
fn add_task(task: &str) {
let mut tasks = fetch_tasks();
tasks.push(task.to_string());
save_tasks(&tasks);
}
#[tauri::command]
fn get_tasks() -> Vec<String> {
fetch_tasks()
}
fn fetch_tasks() -> Vec<String> {
if fs::metadata("tasks.json").is_err() {
return vec![];
}
let content = fs::read_to_string("tasks.json").unwrap();
serde_json::from_str(&content).unwrap_or(vec![])
}
fn save_tasks(tasks: &Vec<String>) {
let content = serde_json::to_string(tasks).unwrap();
fs::write("tasks.json", content).unwrap();
}
Étape 3 : Supprimer une tâche
Nous allons ajouter la possibilité de supprimer une tâche.
Exemple de code :
// frontend/src/App.js
import { useState } from 'react';
import { invoke } from '@tauri-apps/api/tauri';
const App = () => {
const [tasks, setTasks] = useState([]);
const [newTask, setNewTask] = useState('');
const handleAddTask = async (e) => {
e.preventDefault();
await invoke('add_task', { task: newTask });
setNewTask('');
fetchTasks();
};
const fetchTasks = async () => {
const tasks = await invoke('get_tasks');
setTasks(tasks);
};
const handleDeleteTask = async (index) => {
await invoke('delete_task', { index });
fetchTasks();
};
return (
<div>
<h1>Todo List</h1>
<form onSubmit={handleAddTask}>
<input
type="text"
value={newTask}
onChange={(e) => setNewTask(e.target.value)}
placeholder="Add a new task"
/>
<button type="submit">Add</button>
</form>
<ul>
{tasks.map((task, index) => (
<li key={index}>
{task}
<button onClick={() => handleDeleteTask(index)}>Delete</button>
</li>
))}
</ul>
</div>
);
};
export default App;
Exemple de code :
// src-tauri/src/main.rs
use tauri::{Builder, Manager};
use std::fs;
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![add_task, get_tasks, delete_task])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
#[tauri::command]
fn add_task(task: &str) {
let mut tasks = fetch_tasks();
tasks.push(task.to_string());
save_tasks(&tasks);
}
#[tauri::command]
fn get_tasks() -> Vec<String> {
fetch_tasks()
}
#[tauri::command]
fn delete_task(index: usize) {
let mut tasks = fetch_tasks();
if index < tasks.len() {
tasks.remove(index);
save_tasks(&tasks);
}
}
fn fetch_tasks() -> Vec<String> {
if fs::metadata("tasks.json").is_err() {
return vec![];
}
let content = fs::read_to_string("tasks.json").unwrap();
serde_json::from_str(&content).unwrap_or(vec![])
}
fn save_tasks(tasks: &Vec<String>) {
let content = serde_json::to_string(tasks).unwrap();
fs::write("tasks.json", content).unwrap();
}
Erreurs frequentes et debugging
Erreur 1 : error[E0433]: failed to resolve: use of undeclared type or module 'tauri'
Code incorrect :
## ❌ Mauvais
use tauri;
Code correct :
## ✅ Correct
use tauri::{Manager, Builder};
Erreur 2 : error: expected a type, found value expression
Code incorrect :
## ❌ Mauvais
#[tauri::command]
fn greet(name) {
format!("Hello, {}!", name)
}
Code correct :
## ✅ Correct
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}!", name)
}
Erreur 3 : error[E0277]: the trait bound 'usize: std::marker::Send' is not satisfied
Code incorrect :
## ❌ Mauvais
#[tauri::command]
fn delete_task(index) {
let mut tasks = fetch_tasks();
if index < tasks.len() {
tasks.remove(index);
save_tasks(&tasks);
}
}
Code correct :
## ✅ Correct
#[tauri::command]
fn delete_task(index: usize) {
let mut tasks = fetch_tasks();
if index < tasks.len() {
tasks.remove(index);
save_tasks(&tasks);
}
}
Pour aller plus loin
1. Ajouter des notifications push
Vous pouvez utiliser l'API de Tauri pour envoyer des notifications push à votre utilisateur.
Exemple de code :
## ✅ Correct
#[tauri::command]
fn show_notification(title: &str, body: &str) {
use tauri::api::notification;
notification::NotificationBuilder::new()
.title(title)
.body(body)
.show();
}
2. Utiliser des services en arrière-plan
Vous pouvez utiliser le service Tauri pour exécuter du code en arrière-plan, même lorsque votre application n'est pas en premier plan.
Exemple de code :
## ✅ Correct
#[tauri::command]
fn run_background_task() {
use tauri::api::process;
process::spawn_cmd("sleep 10 && echo 'Task completed'").unwrap();
}
3. Intégrer des bibliothèques Rust personnalisées
Vous pouvez intégrer vos propres bibliothèques Rust personnalisées à votre application Tauri.
Exemple de code :
## ✅ Correct
#[tauri::command]
fn call_custom_rust_function() {
let result = my_custom_library::custom_function();
println!("{}", result);
}
Défi pratique
Créer une application de messagerie simple
Créez une application de messagerie simple avec Tauri. L'application devrait permettre à l'utilisateur d'envoyer et de recevoir des messages. Utilisez les fonctionnalités natives pour le stockage local et la notification push.
Bon codage !