Construire une application de démarrage de plateau Tauri en utilisant Rust et JavaScript
est similaire à Electron en général. Les principes sont les mêmes : créez des applications rapidement en utilisant JavaScript pour effectuer la majeure partie du travail de l’application / de l’interface utilisateur.
Cependant, Tauri adopte une approche radicalement différente dans sa mise en œuvre et, par conséquent, obtient de meilleurs résultats sur pratiquement tous les critères possibles.
Nous savons déjà qu’il existe deux problèmes majeurs avec les applications basées sur Electron :
- Taille du bundle (taille de téléchargement)
- Utilisation de la RAM et croissance dans le temps
Avec Electron, vous commencez avec une taille de bundle de 85 Mo pour presque aucune fonctionnalité, et la plupart des applications se téléchargent facilement jusqu’à 150 Mo.
En termes d’utilisation de la mémoire, une application Electron utilise au départ 500 Mo de votre RAM pour ne rien faire. C’est pourquoi je n’utilise aucune application Electron (appelée «applications natives») pour diverses applications Web populaires telles que Slack et autres. J’utilise simplement Chrome, en espérant qu’il puisse mieux optimiser l’utilisation de la RAM dans les onglets.
Pour Tauri : 8 Mo de taille groupée pour de nombreuses fonctionnalités et une utilisation maximale de la mémoire de 150 à 200 Mo, principalement en raison du fait qu’un navigateur est un navigateur. Les navigateurs ont tendance à ne consommer que de la mémoire (ils utilisent la vue Web native de chaque système d’exploitation).
En termes de fonctionnalités, eh bien, ce qui a pris des années à Electron (je parle ici en tant que développeur Electron) – vous l’avez disponible dès maintenant dans Tauri, dans sa première version stable. D’un bundler de qualité et d’outils de développement, à un programme de mise à jour intégré, à la prise en charge des icônes de la barre d’état, et plus encore.
Un avantage supplémentaire que vous obtenez des applications Tauri provient directement de son état d’esprit d’ingénierie, que je suppose que Rust a aidé à conduire.
Sécurité en tant que citoyen de première classe
Sécurité en tant que citoyen de première classe. Peu de plates-formes d’applications (en fait : aucune à ma connaissance) commencent par une description détaillée de leurs fonctions de sécurité intégrées, ainsi qu’un implémentation de fonctionnalités avancées à utiliser, en se concentrant sur le côté JS, l’IPC, et plus encore. Cela vient s’ajouter à ce que Rust en tant que langage offre en matière de sécurité et de stabilité. Encore plus impressionnant est le inclusion de la modélisation des menaces et la réflexion sur la manière d’opérer de manière défensive : la sécurité dès la conception.
Performance en tant que citoyen de première classe
Des repères en tant que citoyen de première classe. Encore une fois, peu de plates-formes commencent par ce sujet, fournissant des informations très détaillées, benchmark automatisé et transparent. Lorsque cela se produit dans une bibliothèque, un outil ou une plate-forme, vous savez que la performance est un KPI principal. Et lorsque vous créez des applications, vous voulez vous assurer que les performances de la plate-forme que vous utilisez restent excellentes afin que votre application qui est construite dessus reste excellente. Si la plate-forme a des problèmes de performances, cela signifie automatiquement que votre application a des problèmes de performances, et aussi – que vous, en tant que développeur, n’aurez probablement aucun moyen de le résoudre.
Séparation complète du backend et du frontend
Bien que cela puisse initialement être perçu comme un inconvénient par rapport à Electron, le fait que le processus backend soit construit en Rust et que le frontend soit construit en JavaScript est en réalité extrêmement bénéfique :
- Ça nous oblige à vraiment raisonner sur l’IPC, qui incite à la sécurité et à la stabilité
- Dans le cas de Rust, il vous donne exactement ce dont vous avez besoin dans un processus backend : performances, sécurité, stabilité et une approche systémique lorsque vous devez opérer étroitement avec le système sous-jacent. Vous ne pouvez pas vraiment faire cela avec un processus backend basé sur Node.js (vous devrez utiliser une API native dans ce cas)
Et avec la façon dont Tauri résume IPC avec les macros Rust et l’expérience des développeurs, même si vous ne connaissez pas Rust, tout ira bien.
Construire des applications Electron n’est en aucun cas simple. C’est pratiquement le sommet du paradoxe du choix, comme avec de nombreuses applications JavaScript, mais ici, encore plus. Bien que vous ayez la fatigue JavaScript standard du côté frontal de l’application Electron, vous avez la même chose dans le processus “principal” du backend : vous devez choisir vos outils, bibliothèques et principes JavaScript, mais limités à un processus Node.js pur. (donc certaines bibliothèques ne fonctionneront pas, et d’autres seulement travailler).
Avec Tauri, le backend est écrit en Rust, et l’écosystème Rust est simple et surtout il y a une bonne bibliothèque pour faire quelque chose. Ainsi, vous évitez le paradoxe du choix et avez plus de temps pour construire.
Pour la partie frontale, vous vous retrouvez avec la même expérience qu’avec vos applications Electron. Vous devrez choisir votre bundler, votre framework d’application, votre framework de style, votre framework de gestion d’état, votre linter, etc., que vous optiez pour JavaScript ou TypeScript.
En ce sens, construire sur Tauri est plus simple.
Pour cet exercice, voyons ce que signifie une “application de plateau”. Au départ, c’est en fait beaucoup de choses. C’est un:
- Application de bureau
- Un widget ou un accessoire du système d’exploitation
- Un processus de fond de longue durée
- Un exercice de fenêtrage (par exemple apparaître au bon endroit)
Et donc, cela pourrait être un exercice difficile pour une nouvelle plate-forme d’application, ce qui est parfait pour comprendre comment Tauri le gère. Nous savons déjà que de nombreuses applications de plateau sont implémentées dans Electron aujourd’hui, alors comment Tauri s’en sort-il ?
Décomposons l’application de bureau Docker. Nous allons essayer de reconstruire la plupart de ses parties à Tauri, et voir ce qui fonctionne.
L’exigence ou les spécifications du bureau Docker sont du type :
- Barre d’état avec une icône de mise à jour (lors du chargement de Docker)
- Mise à jour des éléments de menu dynamiques dans la barre d’état, par exemple pour afficher l’état “Docker est en cours d’exécution” et le nom de l’utilisateur actuellement connecté lors de l’ouverture du menu de la barre d’état
- Une fenêtre de bureau qui s’ouvre (le Dashboard), qui a un comportement spécifique :
- Lors de l’affichage de la fenêtre (Plateau → Ouvrir le tableau de bord), la fenêtre doit apparaître dans votre espace de travail actuel et surveiller
- Lorsque vous cliquez en dehors de la fenêtre ou que vous allez faire autre chose, la fenêtre doit se cacher
- C’est une question de goût, nous pouvons faire traîner la fenêtre, et l’utilisateur doit explicitement la masquer en cliquant sur fermer (en fait, la fermeture devrait se fermer, mais nous l’empêchons et nous la cachons à la place)
- L’application ne doit pas être présente sur votre barre des tâches / tremplin. Il ne devrait vivre que dans votre bac.
- C’est une préférence de goût, nous pouvons l’activer/désactiver.
- A une taille spécifique, ne peut pas être redimensionnée
- Peut être déplacé, a une coque de fenêtre (cela peut être retiré, mais laissons la coque)
En termes d’infrastructure et de mécanique, nous avons besoin de ce qui suit :
- Une sorte d’interrogation du statut entre l’application JavaScript et le processus principal de Rust. Nous pouvons aller:
- JavaScript interroge Rust avec setInterval et invoque une commande Tauri, ou
- Côté rouille poussant un événement périodique en faisant un
emit
sur la fenêtre principale pour que le côté JavaScript écoute - Quoi qu’il en soit, nous avons une ligne de communication constante entre les deux parties, ce qui est formidable.
- Nous transformons une application de bureau en un processus puissant de longue durée.
- Nous avons également besoin d’un code d’appel ad hoc de JavaScript vers Rust, ceci est résolu comme par magie par Tauri
command
abstraction - Enfin, nous avons besoin d’un cadre et d’une infrastructure d’applications de bureau à part entière. Ici, nous utiliserons React, Chakra-UI, React Router et Zustand ou Redux pour l’état et plus particulièrement la persistance de l’état (par exemple lors du stockage des paramètres utilisateur).
Vous pouvez essayer le projet de démarrage terminé ici :
Cela peut surprendre, mais comparé à Go ou Node.js, Rust n’a pas une grande bibliothèque de barre d’état système, et Tauri a en fait la meilleure implémentation en ce moment. Ce que nous avons est le suivant :
ez besoin pnpm
et vous voulez probablement exécuter le guide de démarrage pour obtenir les prérequis de base en place)
Chaque application Tauri se divise en frontend et processus principal (noyau Tauri). Dans le projet de démarrage, l’interface comporte les principaux composants suivants :
- Vite pour la construction
- Réagissez en tant que cadre d’interface utilisateur
- Interface utilisateur des chakras comme cadre de composants et pour le style
- Réagir Routeur en tant que cadre de routage à usage général, lorsqu’une application doit charger et décharger des “écrans”
- Boîte à outils Redux ou Zustand pour la gestion de l’état et la persistance des données (les deux sont câblés, choisissez ce que vous préférez)
Voici quelques mécanismes de notre liste de souhaits que nous souhaitons mettre en place dans la partie noyau / processus principal implémentée dans l’API de Rust et Tauri.
- Éviter de fermer depuis la fenêtre car c’est une application de plateau
- Masquer de la barre des tâches
- S’assurer que la fenêtre apparaît devant l’utilisateur
- État – pour être familier, nous pouvons utiliser l’un ou l’autre (nous utiliserons l’état du côté JavaScript)
- Côté JavaScript (gestion de l’état comme d’habitude).
- Côté rouille — le directeur de l’état dans Tauri peut être utilisé pour le stockage d’état à usage général.
- Processus d’arrière-plan en cours d’exécution et de déclenchement
- Côté JavaScript – nous répondrons à la question – une boucle setInterval naïve vivra-t-elle éternellement et cinglera-t-elle le noyau Rust? (oui)
- Et aussi quelle est la meilleure façon d’initier une telle boucle éternelle ?
- Contrôler les fonctionnalités natives du côté JavaScript, telles que la définition d’un élément de menu natif
C’est facile à faire, voici la recette :
Créez un élément et attribuez-lui un identifiant unique :
Créez une commande et demandez le descripteur d’application dans la signature de la fonction
Enregistrez-le en utilisant le morceau de code suivant :
Et appelez-le du côté JS avec invoke
Nous avons deux façons de procéder, dans le diagramme de séquence ci-dessous, les deux sont représentées :
Procssus de longue durée côté JavaScript
Nous voulons exécuter un JavaScript normal setInterval
et j’espère qu’il restera en vie et capable de piloter le processus Rust également via IPC (un Tauri command
).
Bref, ça marche
Mais attention à certains pièges :
Processus de longue durée côté Rust
Nous pouvons avoir un processus de longue durée du côté de Rust, lancer un thread et lui faire faire une boucle éternelle avec un petit sommeil entre les itérations.
Quelques questions ici :
- Comment et quand le démarrer ?
- Qu’est-ce que le modèle de simultanéité ? puisqu’un thread Rust veut cingler le côté JavaScript via IPC, il doit y avoir quelque chose à raisonner ici
Pour démarrer un tel processus, nous pouvons avoir un command
et invoquez-le lorsque nous sommes prêts du côté JavaScript. Il n’y a pas de bien ou de mal ici, c’est une question de ce dont vous avez besoin.
Ou lancez une boucle éternelle dans la phase de configuration de Tauri, où nous avons toujours accès à une application. Pour émettre un événement, il faut le faire via un Window
puisque App
ne sera pas thread-safe.
Essayons de lancer une requête réseau, que le côté Rust exécute. Ici, nous utilisons request, qui utilise async, et tokio
comme temps d’exécution.
Nous savons que cela nécessite une initialisation tokio
runtime dans n’importe quel exécutable que nous produisons. Comme nous le verrons, Tauri utilise tokio
, donc tout fonctionne hors de la boîte. Rendez simplement la fonction asynchrone comme vous le feriez normalement, mais rappelez-vous : vous doit renvoyer une erreur sérialisable.
Pour le rendre intéressant, nous retournons également un Vec
(sérialisable) de Content
(sérialisable).
Une fois cette opération terminée, nous recevrons avec plaisir le contenu sérialisé en tant qu’objet de données JavaScript du côté JS.
Lors de la navigation en dehors d’une fenêtre avec un intervalle de temps long, elle est déchargée et effacée. Et React Router est prêt à l’emploi.
- Rappelez-vous : nous ne naviguons pas vraiment dans un site Web. Ainsi, l’utilisation d’un routeur comme React Router devient un besoin spécialisé. Au lieu d’utiliser un routeur, vous pouvez utiliser une sorte de contrôle Tabs contenant tous les écrans de l’application dont vous avez besoin.
Dans l’ensemble, pour les longs processus, il est préférable de les déposer dans la racine du routeur de réaction, et pour les parties de l’interface utilisateur qui changent, déposez-les dans un <Outlet/>
dans la racine.
Le mélange de correspondance de modèles dans l’API de Rust et Tauri est tout simplement fantastique :
Pour ajouter des éléments, vous devez reconstruire le menu complet avec le nouvel élément inclus, et le réinitialiser via l’API native :