La première fois que quelqu'un m'a décrit Tauri, il a dit que tu construis une application de bureau en utilisant HTML, CSS et JavaScript. Ma réaction a été le scepticisme. Ça ressemble à un site web dans une fenêtre, pas à une vraie application. Ensuite il a fait remarquer que VS Code, Slack et Discord sont tous construits de la même façon, avec Electron, qui est la version plus ancienne et plus populaire de cette approche. Ce sont de vraies applications, utilisées par des millions de personnes chaque jour. L'architecture a un nom : les applications de bureau hybrides. Ce n'est pas un hack, et ce n'est pas nouveau.
Ça fonctionne comme ça :
Couche UI (technologies web)
↓
Pont natif
↓
Capacités de l'OSTon interface est construite avec la plateforme web. Un pont la connecte au système d'exploitation. Le pont est ce qui transforme une page web en une application de bureau.
Electron a prouvé que ça fonctionne
Tu as probablement utilisé des applications Electron sans t'en rendre compte. VS Code, Slack, Discord et une longue liste d'autres outils de bureau sont tous construits avec. Electron a pris la position que livrer un moteur de navigateur complet dans chaque application était un prix raisonnable pour permettre aux développeurs web de livrer des logiciels de bureau sans apprendre une nouvelle stack.
HTML / CSS / JS
↓
Pont Chromium + Node
↓
OSLe compromis, c'est la taille. Chaque application Electron embarque sa propre copie de Chromium et de Node. Une petite application Electron peut occuper 150 mégaoctets sur le disque parce qu'elle contient un navigateur entier. L'utilisation mémoire suit le même schéma : chaque fenêtre Electron apporte son propre processus Chromium.
Tauri prend la même philosophie mais fait des compromis différents :
HTML / CSS / JS
↓
Tauri IPC
↓
Rust
↓
OSAu lieu d'embarquer un navigateur, Tauri utilise le WebView qui est déjà installé sur le système de l'utilisateur. Sur Windows c'est WebView2, sur macOS c'est WKWebView, sur Linux c'est WebKitGTK. Au lieu de Node, le backend est Rust.
| Electron | Tauri | |
|---|---|---|
| Moteur de navigateur | Chromium embarqué | WebView système |
| Backend | Node.js | Rust |
| Taille de l'app | Grande (100+ MB) | Petite (typique 2-10 MB) |
| Modèle de sécurité | Node a un accès complet au système | Le frontend n'est pas fiable par défaut |
| Mémoire | Plus élevée (Chromium complet par app) | Plus basse (partage le WebView système) |
La différence de taille est ce que la plupart des gens remarquent en premier. Le modèle de sécurité est la distinction la plus intéressante, et j'y arrive.
Un processus, pas deux
Quand j'ai commencé à lire sur Tauri, je cherchais sans cesse la partie où le frontend se connecte au backend. J'avais le modèle web en tête, où le navigateur parle à un serveur sur HTTP, et je supposais que Tauri aurait quelque chose de similaire. Une API localhost, peut-être, ou une sorte de serveur embarqué auquel le WebView parlerait.
Ce n'est pas ce qui se passe.
Quand tu lances :
pnpm tauri devun exécutable Rust démarre. Cet exécutable fait quatre choses :
- Crée une fenêtre native de l'OS
- Crée un WebView dans cette fenêtre
- Charge ton frontend dans le WebView
- Crée un pont IPC entre le WebView et le code Rust
Exécutable Rust
├── fenêtre native
├── WebView (ton app React/Vue/Svelte)
└── pont IPCIl n'y a pas de serveur frontend séparé. Il n'y a pas d'API localhost. Il n'y a pas de processus backend séparé. Le processus Rust est l'orchestrateur. Il crée la fenêtre, charge l'UI web dedans, et reste prêt à gérer les messages qui en proviennent.
C'est la partie avec laquelle j'ai dû rester un moment. Dans une application web, le navigateur et le backend sont deux programmes séparés qui tournent sur des machines différentes. Le navigateur découvre le backend via une URL. Dans Tauri, ils sont un seul programme. Le "backend", c'est juste le code Rust qui tourne dans le même processus qui a créé le WebView en premier lieu. Si tu as utilisé Next.js, c'est plus proche de ça. Ton frontend et tes routes d'API sont un projet, un processus, avec le framework qui gère le routage. Tu ne déploies pas un frontend et un backend séparés. Tauri a la même forme.
Comment JavaScript parle à Rust
Venir du développement web, tu t'attendrais à simplement importer quelque chose et l'appeler. C'est comme ça que tout fonctionne en JavaScript. Tu installes un paquet, tu l'importes, tu l'utilises.
Tauri te donne un helper qui ressemble exactement à ça :
import { invoke } from "@tauri-apps/api/core";
const result = await invoke("greet", { name: "John" });Et en surface, c'est bien ça. Tu appelles quelque chose, tu récupères un résultat. Mais en dessous, rien de tout ça ne fonctionne comme tu le reconnaîtrais du web. Il n'y a pas de module partagé. Il n'y a pas de fonction importée. Tu envoies un message à un autre processus et tu attends qu'il réponde.
JavaScript
|
| message : { command: "greet", args: { name: "John" } }
|
pont IPC
|
dispatcher de commandes Rust
|
fn greet(name: String) -> String
|
| retourne : "Hello, John"
|
La Promise JavaScript se résoutDu côté Rust, la fonction qui reçoit ça ressemble à :
#[tauri::command]
fn greet(name: String) -> String {
format!("Hello, {}", name)
}L'attribut #[tauri::command] enregistre la fonction auprès du dispatcher IPC. Quand JavaScript appelle invoke("greet", ...), le dispatcher trouve cette fonction, passe les arguments et retourne le résultat. Le côté JavaScript obtient une Promise. Le côté Rust exécute une vraie fonction compilée.
Le nom pour ça, c'est IPC, Communication Inter-Processus. La raison pour laquelle ça existe est que deux processus ne peuvent pas directement accéder à la mémoire de l'autre. Ils doivent communiquer par des messages. Ce que tu vois au-dessus, c'est une façon structurée pour deux processus d'échanger des données sans partager de mémoire.
Il y a une partie là-dedans qui m'a fait trébucher. Dans une application web, le navigateur parle au backend sur HTTP, ce qui signifie qu'il a besoin d'un port, quelque chose comme localhost:3000. Dans Tauri, il n'y a pas de port. Je cherchais sans cesse où la connexion était établie et je ne la trouvais pas, parce qu'il n'y a pas de connexion. Le pont IPC est créé en interne par le runtime Tauri. C'est comme deux pièces dans le même bâtiment. Tu n'appelles pas quelqu'un au téléphone quand il est dans la pièce d'à côté. Tu utilises le vide-courrier interne.
Le pont est déjà là
Une fois que j'ai compris qu'il n'y avait pas de port, la question suivante était évidente : s'il n'y a pas de serveur, comment JavaScript trouve-t-il Rust ?
La réponse, c'est qu'il ne le trouve pas. Il n'a pas à le faire.
Quand le processus Rust crée le WebView, il injecte le pont de communication dedans. Conceptuellement :
Runtime Rust
|
| injecte
|
window.__TAURI__Quand ton JavaScript appelle invoke(), il utilise ce pont. Le frontend ne découvre pas Rust. Il n'y a pas d'URL à pointer, pas de port à connecter. Rust a construit l'environnement dans lequel le frontend tourne, et le canal de communication faisait partie de cet environnement avant que la première ligne de JavaScript ne soit chargée.
C'est une différence nette avec le modèle web. Sur le web, le navigateur découvre le backend via une URL. Dans Tauri, le backend a créé l'environnement dans lequel le frontend tourne, et le canal de communication faisait partie de cet environnement dès le départ.
Où vit la limite de confiance
Le frontend n'est pas fiable. C'est vrai pour chaque frontend, et ce n'est pas la partie intéressante. La partie intéressante, c'est où chaque framework place la limite de confiance et ce qu'il te permet de délimiter.
Dans Electron, le backend Node a un accès complet au système par défaut. Que le frontend obtienne cet accès est un flag de config, nodeIntegration. Mets-le à true et ton renderer peut appeler child_process, fs, tout ce que Node peut faire. Mets-le à false et le renderer est en bac à sable, mais le processus principal garde un accès complet, et tu exposes typiquement ce dont tu as besoin via ipcMain.handle. La limite est « est-ce que le frontend a l'API Node ou non. » C'est un portail binaire. Il n'y a pas de système de capacités qui dit que le frontend peut appeler exportReport mais pas deleteFile. Tu écris cette logique toi-même dans chaque gestionnaire IPC.
Dans Tauri, le frontend n'a aucun accès par défaut. Chaque commande que le frontend peut appeler doit être explicitement enregistrée comme un #[tauri::command], et le système de capacités contrôle quelles commandes le frontend est autorisé à invoquer. Seul le côté Rust a accès à l'OS. Le système de capacités délimite ce que le frontend peut demander. Tu n'écris pas de garde dans chaque gestionnaire. Le framework l'applique.
| Electron | Tauri | |
|---|---|---|
| Accès OS du backend | Complet par défaut | Complet, mais seulement Rust l'a |
| Accès du frontend à l'OS | Flag de config (nodeIntegration) | Aucun par défaut |
| Ce que le frontend peut appeler | Ce que tu exposes via ipcMain.handle | Seulement les commandes enregistrées avec #[tauri::command] |
| Délimitation par commande | Tu écris la logique de garde toi-même | Le système de capacités délimite chaque commande |
L'analogie du restaurant tient toujours. Le personnel de la salle pose les commandes sur un rail à tickets. La cuisine décide quoi préparer et comment. Mais dans Electron, la porte de la cuisine a une serrure, et tu dois te souvenir de la fermer et de décider qui reçoit une clé. Dans Tauri, la porte de la cuisine commence fermée, et chaque plat spécifique que la salle peut commander doit être ajouté au menu explicitement. Le défaut, c'est « rien n'est au menu. » Tu ajoutes des plats un par un, et le framework l'applique.
Le build
Pendant le développement, deux choses tournent en même temps :
Frontend : serveur dev Vite (hot reload, fast refresh)
Backend : Cargo (compilateur Rust, surveille les changements)Tu édites tes composants React, Vite les recharge à chaud. Tu édites ton code Rust, Cargo recompile. La fenêtre native reste ouverte pendant que les deux côtés se mettent à jour.
En production, le frontend est construit et le Rust est compilé en un binaire de release. Les deux sont emballés ensemble :
Build du frontend
+
Build de release Rust
|
v
Application nativeLe résultat dépend de la cible :
| Plateforme | Résultat |
|---|---|
| Windows | .exe / .msi |
| macOS | .app / .dmg |
| Linux | AppImage / .deb |
| Android | .apk / .aab |
| iOS | .ipa |
Tauri mobile mérite une note ici. Sur mobile, l'architecture est la même : ton UI web tourne dans le WebView de la plateforme, Android WebView ou iOS WKWebView, et Rust est compilé nativement pour la plateforme mobile. Ce n'est pas React Native. Il n'y a pas de pont JavaScript vers UI native. L'UI est un WebView, et Rust gère le côté natif.
Navigateur vs Tauri
Ton app React peut tourner dans un navigateur simple avec pnpm dev. Mais invoke() ne fonctionnera pas, parce qu'il n'y a pas de runtime Rust ni de pont IPC. La fonction existe dans le paquet JavaScript, mais quand elle essaie d'envoyer un message à travers le pont, il n'y a rien à l'autre bout.
Dans Tauri, le runtime Rust tourne, le pont existe, et invoke() transporte les messages vers de vraies fonctions Rust. C'est la différence entre ouvrir ton app dans Chrome et la faire tourner via Tauri. Le code est le même. Le runtime est différent.
Quand ça convient
Tauri fonctionne bien pour :
- Applications de productivité de bureau
- Outils de développeur
- Logiciels d'entreprise
- Applications de prise de notes
- Utilitaires de fichiers
- Outils internes d'entreprise
- Utilitaires multiplateformes qui doivent tourner sur Windows, macOS et Linux
C'est moins adapté pour :
- Jeux 3D haute performance
- Applications gourmandes en graphismes où tu as besoin de chaque détail d'UI native
- Applications qui nécessitent une intégration profonde spécifique à la plateforme avec des frameworks d'UI au niveau OS
Les bons cas partagent tous quelque chose : l'interface est le travail, et le système d'exploitation fournit des capacités de support comme l'accès aux fichiers, les notifications et la gestion des fenêtres. Les mauvais cas sont l'inverse : les graphismes ou le système d'UI de la plateforme native sont le travail, et une UI web gêne.
Le changement de mentalité
La question avec laquelle j'ai commencé était de savoir si nous concevons vraiment des interfaces plutôt que des applications de bureau. La réponse est oui, mais le cadrage compte.
Tu ne mets pas un site web dans une fenêtre de bureau. Tu construis une application native où la technologie d'UI se trouve être la plateforme web. Cette distinction est ce qui fait fonctionner l'architecture. Le côté Rust est un vrai backend natif avec un vrai accès OS. Le côté web est la couche de présentation. Le pont entre les deux est où vit la limite de confiance.
Un site web dans une fenêtre serait un navigateur pointant vers une URL, sans capacités natives. Ce que Tauri te donne, c'est une application native compilée qui se trouve rendre son interface avec des technologies web. La différence, c'est le backend Rust, le pont IPC et le modèle de sécurité autour.
Lire la suite
Comment fonctionnent les applications mobiles
Les trois façons de construire une app mobile, ce que chacune fait réellement sous le capot, et le schéma d'architecture partagé derrière React Native, Flutter et Tauri mobile.
Une base de code, Web et Bureau
Comment concevoir une app Tauri pour que le même code React tourne dans le navigateur et sur le bureau, en mettant les différences de runtime derrière une interface de service.
Le pooling de clés API avec Bifrost
Un guide pratique pour configurer le pooling de clés API dans Bifrost et comment le scaler en toute sécurité sur plusieurs nœuds en externalisant vos rate limits.
