Hotwire Turbo: Drive, Frames et Streams pour des projets en Ruby on Rails
Le créateur de Rails, DHH (David Heinemeier Hansson), a toujours montré son mécontentement envers JavaScript et TypeScript en particulier, ces dernières années. Il suggère que les difficultés d’apprendre un nouveau langage de programmation, qui te déconcentre de ton travail sur les règles de ton entreprise, peuvent être désavantageuses à long terme. Pour ce cas, dans leur propre entreprise Basecamp, il a créé un framework qui utilise JavaScript en interne à travers Ruby, ainsi tu ne le dois pas faire. L’objectif est de se concentrer principalement sur ton code Backend (Ruby dans ce cas), ta logique d’entreprise, au lieu de configurations complexes sur JavaScript, spécialement depuis une perspective pour faire des SPAs (Applications à page unique).
J’ai fait une présentation sur ce sujet en mai 2024 lors du meetup de Montreal.rb. Comme certaines personnes n’aiment pas regarder des vidéos ou des diapositives, ou préfèrent une version écrite, voici une version résumée. Tu peux la mettre en favoris et y revenir si tu oublies certains concepts. Cependant, ce post est étroitement lié à l’application démo RoR que j’ai préparée pour la présentation, télécharge-la et essaie-la. Je parie que les concepts seront beaucoup plus clairs une fois que tu l’auras testée et exécutée.
Liens intéressants
Pourquoi éviter l’usage de JavaScript. Mon opinion en tant que développeur NodeJS
Je dirais que c’est vrai, en tant que développeur fréquent en TypeScript, tu verras que chaque jour tu te trouveras dans le dilemme de comment organiser ou ajouter les types à la logique d’entreprise. Je dis que JavaScript n’a pas une courbe d’apprentissage facile pour un développeur débutant, en particulier pour commencer à partir de l’étape de build.
Comme TypeScript n’est pas supporté dans le navigateur ou le runtime Node par défaut, tu dois configurer ton environnement de développement, en te concentrant sur les étapes de build et de watch pour supporter TypeScript. Il y a beaucoup de projets préconfigurés là-bas, mais malgré cela, il n’y a pas d’options pour le développeur débutant qui pourrait vouloir essayer TypeScript. Et on ne va même pas commencer à parler des tuples, des génériques, etc.
Alors, on sacrifie la vitesse de développement pour ajouter un typage fort à JavaScript. Ne me comprends pas mal, je suis d’accord qu’à long terme c’est mieux d’avoir un typage fort, surtout pour des objets JSON grands et complexes. Mais, comme un point de départ, ça peut décourager les débutants de continuer à apprendre la programmation, à écrire du code fonctionnel qui résout des problèmes réels, et à l’avenir, ils peuvent devenir experts dans leur domaine. Ils devraient éviter de se créer des problèmes inutiles.
Pourquoi Turbo?
Turbo est la solution pour créer des expériences SPA en utilisant principalement tes connaissances en Ruby. Tu peux écrire des applications qui peuvent remplacer de nombreuses fonctionnalités qu’une SPA aurait, comme le chargement rapide des pages et la modification de parties spécifiques du DOM.
L’un des avantages des versions modernes de Ruby on Rails, le framework d’application web pour créer des applications web en Ruby, c’est qu’il est livré avec Turbo installé par défaut.
Turbo Drive : Précharge les pages
Un des facteurs clés pour choisir une application à page unique (SPA), c’est la vitesse à laquelle chaque page se charge. Après que tu as envoyé la première requête au serveur, il renvoie tout le JavaScript nécessaire pour s’exécuter dans ton navigateur. C’est là qu’intervient Turbo Drive. Il aide à précharger n’importe quelle page à partir d’un lien que tu veux visiter et il garde également une trace historique en même temps.
Bien sûr, tu peux étendre cette fonctionnalité pour des liens spécifiques, ce qui est utile s’il y a des liens qui sont vraiment lourds à charger. Sinon, comme ils se chargent de manière asynchrone, cela va donner à tes clients l’impression d’une navigation rapide.
Recharger des styles ou du JavaScript lorsqu’ils changent
Étant donné que tu fais souvent des modifications à ton code JavaScript, tu peux indiquer à Turbo Drive de recharger du code JavaScript ou CSS lorsque des modifications sont apportées à l’un d’eux. Assure-toi simplement d’ajouter les balises HTML requises.
Barre de chargement en Turbo
Tu peux habiliter la barre de chargement encore, comme les dernières versions de Rails. T’as besoin de faire des modifications a ton code.
La barre apparaît sur les pages qui mettent plus de 500 millisecondes à charger, on peut configurer le minimum à 0
. Comme ça, on va toujours la montrer.
On va utiliser notre fichier principal de JS pour le montrer.
Aussi, tu peux changer la couleur de la barre de chargement. Il suffit de mettre à jour la classe .turbo-progress-bar
.
Utilisation en JavaScript
On peut utiliser les fonctions et les propriétés de Turbo Drive depuis JavaScript. Voici un exemple.
Utilisation en HTML
Et voici deux exemples en HTML. Ils sont compatibles avec ERB.
Morphing
On peut spécifier comment on met à jour la page et comment le contenu est renouvelé en utilisant le code suivant.
Conservation du déplacement vertical
On peut aussi garder la position du défilement pendant la navigation. Par exemple, si on revient à un long article, en tant qu’utilisateur, tu aimerais revenir à la même position où tu as arrêté de lire avant de visiter le lien dans la publication.
Exclure le contenu du morphing
C’est une caractéristique très intéressante si tu veux garder des sections exclues du morphing. On pourrait dire, par exemple, pour garder les messages d’alerte ou les informations qui persistent entre les pages.
Méthodes d’aide avec le gemme de Ruby
De plus, quand on utilise la gemme de Turbo Drive, on peut profiter des fonctions et des helpers que la gemme nous donne. Voici une liste d’exemples avec quelques méthodes les plus utilisées.
Écrire des tests pour Turbo Drive
Pour tester ton code, tu peux utiliser la même approche que celle que tu utiliserais pour tester ton code Ruby on Rails.
Pour beaucoup d’applications CRUD là-bas, on doit s’assurer que notre application exécute les 4 actions principales (Créer, Mettre à jour, Lire, Effacer).
Regarde le code suivant, il n’y a rien de nouveau avec Turbo Drive.
Turbo Frames : chargement d’autres pages dans la même page, comportement des composants
Travailler avec Turbo Frames, c’est comme gérer des composants dans un Framework Frontend.
On peut créer des partiels dans une autre vue, puis utiliser la vue parente pour les charger.
Une fois que le changement est fait, tu peux lancer un ordre de rafraîchir ou le recharger dans le même conteneur.
Exemple de Turbo Frame
Un Turbo Frame déclaré seul en HTML ressemblera au code suivant.
Propriétés d’un Turbo Frame
Les Turbo Frames nous permettent de modifier leur comportement avec les propriétés suivantes :
-
src
: une URL avec un “path” qui contrôle la navigation d’un élément. -
loading
: a deux valeurs,eager
etlazy
.loading="eager"
chargera le frame immédiatement, tandis queloading="lazy"
chargera le frame quand il devient visible. -
busy
: C’est un attribut booléen qui indique si le frame est en train de charger. C’est géré par Turbo. -
disabled
: On l’utilise pour désactiver la navigation dans le frame. -
complete
: C’est un attribut booléen qui indique si le frame a fini de charger. C’est géré par Turbo. -
autoscroll
: C’est un attribut booléen qui indique si on doit faire défiler automatiquement le frame en haut de la page après le chargement. C’est géré par Turbo.
De plus, comme on peut utiliser Turbo depuis JavaScript, n’oublie pas qu’on a accès à toutes ces propriétés aussi.
-
FrameElement.disabled
-
FrameElement.loading
-
FrameElement.loaded
-
FrameElement.complete
-
FrameElement.autoscroll
-
FrameElement.isActive
-
FrameElement.isPreview
En utilisant la gemme
Si tu utilises la gemme, tu peux également utiliser les helpers pour Turbo Frame.
Deux Turbo Frames dans la même page
On peut utiliser plusieurs Turbo Frames sur la même page. Je vais utiliser une version faite avec des balises HTML pour générer deux frames dans la même vue. Les objectifs à résoudre sont :
- Afficher un formulaire
<turbo-frame/>
qui stocke des balises, qui sera pris d’un partiel. - Afficher la liste des balises à côté, qui sera rafraîchie quand on envoie le formulaire.
- On évite de rafraîchir la page.
Le premier <turbo-frame>
montre une propriété src
, qui est la vue/partielle d’où on prend le contenu. Dans ce cas, c’est la route qui rend la vue new
. Voici le contenu.
Comme tu peux le voir, on a une propriété data: { turbo_frame: TagFrameController::TAG_FRAME_ID }
dans le formulaire. Cela signifie qu’une fois que le formulaire est traité, on va afficher le contenu dans ce frame cible. Donc, notre backend ressemblera à ça :
Quelques trucs intéressants sur Turbo Frames
Je n’ai pas couvert cela dans ma présentation, mais tu peux ajouter des actions supplémentaires avec Turbo Frames, y compris des fonctionnalités intéressantes comme:
- Charger des frames en différé (lazy loading)
- Mise en cache
- Protection contre la falsification de requête sur des sites croisés (Cross-Site Request Forgery, CSRF)
- Navigation à partir d’un Frame
Réalisation de tests sur Turbo Frames
Les tests fonctionnent de la même manière que pour n’importe quelle application Rails classique. Mais, dans ce cas, vérifie qu’on utilise la même URL pour exécuter les actions de création et de liste, parce que les deux actions sont exécutées depuis la même route.
Turbo Frames: On charge seulement les données mises à jour
Dans Turbo Frame, on charge des grands morceaux de vues. Avec Turbo Stream, tu ne rends que les données dont tu as vraiment besoin, en spécifiant leur comportement, comme l’ajout, la suppression, le remplacement, etc.
Cette caractéristique peut être combinée optionnellement avec Websockets. Du coup, toute nouvelle info stockée dans ta base de données apparaîtra en temps réel pour tout le monde.
Cependant, la documentation de Turbo Stream affirme ce qui suit :
C’est une bonne pratique de commencer ton design sans Turbo Streams. Développe ton application complète comme si Turbo Streams n’était pas disponible, ensuite, ajoute-le au fur et à mesure que tu grandis. Ça signifie qu’on ne dépendra pas lourdement des mises à jour pour les flux qui doivent fonctionner avec des applications natives ou ailleurs.
De plus, si tu l’utilises avec Ruby on Rails, tu peux tirer parti d’Action Cable et d’Active Jobs pour rendre le contenu comme nécessaire. Pour cet exemple, on va utiliser la gemme dans un projet Ruby on Rails.
Fonctionnalité supplémentaire lors du rendu
Si tu veux déclencher des effets secondaires lorsque tu réalises un rendu en Turbo Stream, peut-être que tu voudrais utiliser des contrôleurs Stimulus. Pour cela, tu devrais travailler sur des fichiers JavaScript.
Les actions
Avec Turbo Streams, on a huit actions (ou comportements) disponibles. Ces spécifient comment ton contenu s’ajoutera au contenu déjà rendu.
Ces actions sont:
-
append
: On ajoute le contenu à la fin de l’élément spécifié par l’objectif. -
prepend
: On ajoute le contenu au début de l’élément spécifié par l’objectif. -
replace
: Remplace tout le contenu de l’élément cible avec le contenu donné. -
update
: Remplace l’élément cible avec le contenu donné. -
remove
: On efface l’élément cible du DOM. -
before
: On insère le contenu donné juste avant l’élément cible. -
after
: On insère le contenu donné juste après l’élément cible. -
morph
: On remplace le contenu en utilisant la technique de morphing sur l’élément cible. -
refresh
: On rafraîchit le contenu à l’intérieur de l’élément cible.
Structure d’un rendu en Turbo Stream
Maintenant, pour permettre à Turbo Streams d’insérer du contenu dans le HTML, on doit pré-rendre un élément avec un id cible, comme dom_id
par exemple.
Ça signifie que, si on veut remplacer ou transformer un élément spécifique, ton HTML ressemblera à ça :
Mais, si on veut ajouter quelque chose au début, ajouter à la fin ou faire une action similaire à l’élément parent, on doit ajouter un identifiant unique.
Ensuite, si on veut créer un nouvel enregistrement et l’ajouter à notre propre tableau, le code du contrôleur ressemblera à ceci :
C’est pourquoi tu as besoin d’un identifiant parent. On va voir un exemple où on doit enlever un élément spécifique du tableau.
L’utilisation de la gemme Ruby nous donne beaucoup d’avantages et elle offre des méthodes déjà établies pour interagir avec Turbo.
Écrire des tests pour Turbo Streams
On doit être très précis sur le type de réponse qu’on attend du contrôleur. Selon le cas, on doit indiquer à notre outil de test qu’on attend un objet “stream”.
Pour changer ta façon de faire des tests, tu ajoutes as: :turbo_stream
à la fin de ton appel de route. Regarde l’exemple suivant.
Conclusions
Souviens-toi que la majorité du contenu de cet article a été résumée pour être présentée en diapositives pendant une heure. Bien sûr, je ne vais pas couvrir chaque aspect, mais l’objectif est de te montrer les concepts de base pour que tu puisses commencer ta propre aventure en pratiquant et en apprenant Turbo, et l’utiliser dans ton prochain projet.
Utilise Turbo quand tu as l’occasion, surtout si tu travailles avec beaucoup de programmeurs Ruby talentueux. Le changement de contexte et de connaissance sera minimisé et le temps sera consacré à créer plus de fonctionnalités pour les utilisateurs au lieu de chercher comment transpiler correctement ton code TypeScript et quels outils utiliser.
Est-ce que je recommanderais aux développeurs d’arrêter d’apprendre JavaScript parce qu’on a des outils comme Turbo ? Pas du tout. Au contraire, je les encouragerais à continuer à apprendre, mais une fois qu’ils ont créé quelque chose, que ce soit au travail ou dans leurs propres projets. JavaScript est largement utilisé partout dans le monde et probablement, pour n’importe quel travail que tu cherches, on va te demander une connaissance de base à intermédiaire de JavaScript. De plus, souviens-toi que si tu as besoin de faire des choses supplémentaires avec Turbo, tôt ou tard tu devras travailler avec des contrôleurs Stimulus, qui sont écrits en JavaScript.