Pourquoi on a abandonné Electron
GitSquid v1 était construit sur Electron. Ça fonctionnait. Les utilisateurs pouvaient parcourir des dépôts, indexer des modifications, gérer des branches, manipuler des remotes et utiliser un terminal intégré -- le tout depuis une seule application desktop. Sous le capot, on s'appuyait sur simple-git pour les opérations Git, node-pty et xterm.js pour le terminal, et la stack React habituelle pour l'interface.
Mais au fur et à mesure que l'application mûrissait, les compromis d'Electron devenaient de plus en plus difficiles à ignorer. On avait livré un produit fonctionnel, mais le runtime lui-même jouait contre nous. On a donc réécrit l'intégralité du backend en Rust avec Tauri 2.x -- tout en conservant 95% de notre frontend React.
Voici l'histoire de pourquoi on l'a fait, comment on l'a fait, et ce qui a cassé en chemin.
Le problème avec Electron
Electron est un framework éprouvé. VS Code, Slack, Discord -- des produits majeurs tournent dessus. Mais pour un client Git, la surcharge est disproportionnée par rapport à la tâche.
- Taille de l'app : Electron embarque un navigateur Chromium complet et un runtime Node.js. Notre application packagée pesait plus de 150 Mo. Pour un client Git, c'est difficile à justifier.
- Consommation mémoire : chaque fenêtre Electron lance plusieurs processus Chromium. La consommation mémoire au repos dépassait largement ce qu'une application native nécessiterait pour le même travail.
- Temps de démarrage : lancer Chromium n'est pas instantané. Les utilisateurs remarquaient un délai perceptible avant que l'app devienne interactive, surtout sur du matériel un peu ancien.
- Sensation native : malgré les efforts d'Electron avec les barres de titre et menus natifs, l'application ne donnait jamais vraiment l'impression d'appartenir à l'OS. Des petits détails -- la gestion des fenêtres, le comportement du focus, les raccourcis clavier -- avaient toujours un petit côté web app.
Aucun de ces points n'est rédhibitoire en soi. Mais combinés, ils créaient une friction qui ne correspondait pas à ce qu'un outil de développeur devrait être : rapide, léger et transparent.
Pourquoi Tauri 2.x
On a évalué plusieurs alternatives avant de se décider pour Tauri. Les facteurs déterminants étaient clairs :
- Webview système au lieu d'un Chromium embarqué. Tauri utilise la webview fournie par l'OS (WebKit sur macOS, WebView2 sur Windows, WebKitGTK sur Linux). Ça suffit à éliminer l'essentiel du poids binaire d'Electron.
- Backend Rust. Le backend tourne comme un binaire Rust compilé. Sécurité mémoire, pas de garbage collector, performances prévisibles. Pour un outil qui exécute des dizaines d'opérations Git par interaction, ça compte.
- Binaires radicalement plus petits. Une application Tauri peut peser moins de 10 Mo. À comparer avec les 150 Mo+.
- Multiplateforme avec intégration native. Tauri 2.x fournit des API solides pour les menus natifs, la barre système, les notifications et les boîtes de dialogue. L'application donne l'impression d'appartenir à l'OS.
- Écosystème actif et bonne expérience développeur. Le système de plugins de Tauri, son modèle IPC et sa documentation ont considérablement mûri avec la version 2.x. C'était prêt pour la production.
La stratégie de migration
Une réécriture complète est risquée. On a atténué ce risque avec une séparation claire des responsabilités.
Garder le frontend, réécrire le backend
Notre frontend React était déjà structuré autour d'une frontière IPC. Les composants appelaient des fonctions qui communiquaient avec le processus principal d'Electron ; il suffisait de rediriger ces appels vers l'API invoke de Tauri. Environ 95% du code React a été repris tel quel.
Le backend, c'était une autre histoire. Chaque service Node.js -- opérations Git, surveillance de fichiers, gestion du terminal, authentification, paramètres -- devait être réimplémenté en Rust. Au total, on a écrit environ 7 200 lignes de Rust et migré plus de 120 commandes IPC.
Git CLI plutôt que libgit2
Une question fréquente : pourquoi ne pas utiliser git2-rs (les bindings Rust pour libgit2) ?
On a choisi std::process::Command pour appeler le git CLI directement. Les raisons :
libgit2ne supporte pas toutes les fonctionnalités de Git. Des opérations comme le rebase interactif, certaines stratégies de merge et certaines options de configuration sont soit absentes, soit se comportent différemment.- Les utilisateurs ont déjà Git installé. En appelant leur binaire Git local, on garantit une parité de comportement avec la ligne de commande. Si ça marche dans le terminal, ça marche dans GitSquid.
- Le débogage est plus simple. Les commandes qu'on exécute sont les mêmes que celles qu'un utilisateur taperait. Quand quelque chose plante, les messages d'erreur sont familiers.
Remplacer l'écosystème Node.js
Chaque dépendance Node.js a été remplacée par un équivalent Rust :
simple-gita été remplacé par des appels directs au git CLI viastd::process::Commandnode-ptya été remplacé parportable-ptypour l'émulation de terminal- Le stockage des tokens est passé au crate
keyring, qui utilise le trousseau de l'OS (macOS Keychain, Windows Credential Manager, Linux Secret Service) - La surveillance de fichiers est passée au crate
notify - Les requêtes HTTP sont passées à
reqwest
Migration transparente des paramètres
On a conservé la même structure de répertoire de configuration. Les utilisateurs passant de la v1 à la v2 ne perdent ni leurs paramètres, ni leurs dépôts enregistrés, ni leurs préférences. Le backend Rust lit les mêmes fichiers de configuration que le backend Node.js écrivait.
Les défis rencontrés
La migration n'a pas été un long fleuve tranquille. Plusieurs problèmes nous ont coûté un temps de débogage considérable.
Octets nuls dans le parsing du git log
On utilise des chaînes de format personnalisées avec git log --format pour parser les données de commits. Dans la version Electron, on utilisait des caractères séparateurs spécifiques entre les champs. En portant vers Rust, on a d'abord essayé d'utiliser des octets nuls (\x00) comme séparateurs de champs -- ça semblait être un choix sûr puisqu'ils n'apparaîtraient jamais dans les messages de commit.
Erreur. Rust gère très bien les octets nuls dans les chaînes, mais plusieurs couches de la stack les traitent comme des terminateurs de chaîne C. La sortie du log était tronquée silencieusement au premier octet nul. On est passé à un délimiteur multi-caractères qui ne pourrait jamais apparaître naturellement dans la sortie de Git.
Stockage de tokens dans le trousseau
Le crate keyring stocke les identifiants dans le trousseau de l'OS, ce qui est la bonne approche. Mais on a rencontré un problème similaire avec les octets nuls : on concaténait les données de tokens avec des séparateurs en octets nuls avant de les stocker. À la lecture, certaines implémentations de trousseau tronquaient la valeur au premier octet nul, renvoyant des identifiants incomplets. La solution était la même -- utiliser un séparateur visible et non ambigu.
Complexité de l'authentification BitBucket
GitHub et GitLab ont des flux d'authentification par token relativement simples. BitBucket est plus complexe. Selon l'opération et le type de compte, il peut falloir un nom d'utilisateur, un email, un mot de passe d'application ou un token OAuth -- et les règles pour savoir lequel utiliser où ne sont pas toujours évidentes. Faire fonctionner l'authentification BitBucket de manière fiable pour le clone, le fetch, le push et les appels API a nécessité plusieurs itérations.
Staging ligne par ligne
GitSquid permet d'indexer des lignes individuelles d'un diff, pas seulement des hunks entiers. Dans la version Electron, la génération de patches se faisait en JavaScript. Pour la version Tauri, on a déplacé cette logique dans le backend Rust. Générer un patch au format Git valide à partir de sélections de lignes arbitraires, gérer les cas limites avec les lignes de contexte, et s'assurer que git apply accepte le résultat -- c'était l'une des parties les plus délicates de la réécriture. L'implémentation Rust s'est avérée plus robuste que la version JavaScript originale.
Les apostrophes françaises qui cassent les builds
Celle-là, on s'en souviendra. Certaines de nos chaînes utilisateur contenaient des apostrophes typographiques françaises (la variante courbe, U+2019) au lieu d'apostrophes droites ASCII. Elles provoquaient des problèmes d'encodage pendant le processus de build sur certaines configurations CI. Le correctif était trivial une fois le problème identifié, mais les messages d'erreur ne pointaient absolument pas vers la cause réelle.
Résultats
Après avoir terminé la migration et atteint la parité fonctionnelle complète, voici où on en est :
- Taille de l'app : considérablement réduite par rapport au build Electron. Le bundle Tauri ne représente qu'une fraction des 150 Mo+ qu'on livrait avant.
- Consommation mémoire : baseline plus basse et plus prévisible en charge. Plus d'arbre de processus Chromium qui dévore la RAM en arrière-plan.
- Temps de démarrage : nettement plus rapide. L'application est interactive quasi instantanément.
- Parité fonctionnelle : 100%. Toutes les fonctionnalités de la v1 existent dans la v2. Le terminal intégré, le staging ligne par ligne, le support multi-remote, la gestion des branches -- tout y est.
- Réutilisation du frontend : 95% du code React a été conservé, validant notre décision architecturale de maintenir une frontière IPC propre.
Est-ce que ça valait le coup ?
Oui.
La réécriture a demandé un effort conséquent. Environ 7 200 lignes de Rust, plus de 120 commandes IPC à réimplémenter, et plusieurs bugs non évidents qui n'ont fait surface que parce que Rust et Node.js gèrent les cas limites différemment. Ce n'était pas un projet de week-end.
Mais le résultat est une application fondamentalement meilleure. GitSquid v2 démarre plus vite, consomme moins de mémoire, se distribue en un binaire plus léger, et paraît plus native sur chaque plateforme. Le backend Rust est plus prévisible et plus facile à raisonner que son équivalent Node.js. Et parce qu'on a gardé le même frontend React, les utilisateurs retrouvent la même interface qu'ils connaissent déjà.
Si vous maintenez une application Electron et que la surcharge du runtime vous dérange, Tauri est une option sérieuse. L'écosystème est suffisamment mature pour un usage en production, et le chemin de migration -- surtout si vous avez déjà une séparation propre frontend/backend -- est plus réaliste que ce qu'une réécriture complète pourrait laisser croire.
L'enseignement clé : traitez votre frontière IPC comme un contrat d'API. Si votre frontend sait uniquement faire invoke('get_commits', { path, branch }) et ne se soucie pas de savoir si la réponse vient de Node.js ou de Rust, remplacer le backend devient un projet réaliste plutôt qu'un fantasme.