Ludovic Frank - Développeur indépendant

Retour d'expérience sur Symfony-UX Turbo après une utilisation intensive en production

ionicons-v5-k Ludovic Frank 16 janv. 2023
4549 lectures Niveau :

Bonjour bonjour 😁,

Pour les habitués de ce blog, vous savez que depuis quelques mois, je parle beaucoup de Symfonfony-UX/Turbo, Mais je n'ai jamais donné de retour d'expérience dessus.

La raison est simple, avant de donner un avis, il est nécessaire de bien tester la chose... et le mieux est de l'utiliser avec de vrai utilisateurs sur un minimum de temps.

À ce jour ma première application avec Turbo et Stimulus est en production depuis maintenant un peu plus d'un mois et a vu passé quelques milliers d'utilisateurs, j'ai pu profiter de quelques retours sur l'expérience utilisateur (merci beaucoup à eux 😁).

Symfony-UX, késako ?

Faisons simple, et jetons tout d'abord un œil au site de présentation

Symfony-UX est un ensemble de librairies, JavaScript et PHP permettant au développeur de créer une superbe expérience utilisateur.

Au cœur de tout cela, on retrouve Stimulus, le framework JavaScript super simple qui nous vient du monde de Ruby on Rails.

Autour de Stimulus, on retrouve différentes librairies avec chacune leur utilité, il est par exemple possible d'intégrer React, VueJS ou encore ChartJS.

Puis, pour aller plus loin, il y a Turbo qui permet de donner une expérience "SPA" (Single Page Application) à notre application Symfony (bon ici, c'est Symfony, mais Turbo fonctionne avec n'importe quel back en fait).

Turbo et Stimulus font partie du même ensemble de bibliothèques, mais sont totalement indépendant l'un de l'autre, cet ensemble c'est Hotwired

Selon moi, la grande force de cet ensemble de composants, c'est sa simplicité, on prend plus de temps à réfléchir à ce qu'on développe, plutôt qu’à comment on le développe...

Comment fonctionne Turbo ?

Dans son utilisation la plus basique, Turbo "supprime le chargement des pages" dans le navigateur...

Je m'explique :
Lorsque l'on clique sur un lien, au lieu de charger la page, Turbo va charger uniquement le contenu de la page, puis remplacer le contenu de la page dans le DOM.

La conséquence à cela c'est que le navigateur n'a pas besoin de réinterpréter le JavaScript et le CSS (et s'ils ne sont pas en cache, de les re-télécharger) et si le serveur répond rapidement (dans mon cas 40ms) alors il y a une instantanéité dans l'affichage de l'interface.

Il est donc possible d’utiliser les outils de templating côté serveurs (sur Symfony, Twig) pour créer une application type "SPA".

Il faut savoir également, qu'il y a un système de cache dans l'historique de ce que fait Turbo, afin de revenir rapidement sur une page précédente, Turbo, va brièvement présenter la version "en cache" de cette page avant de récupérer la réponse du serveur, cela peu avoir des conséquences, par exemple si vous avez laissé un modal ouvert alors le comportement risque d'être ... bizarre, un autre cas peut être quand une alerte est restée dans la version en cache, elle se présentera à nouveau brièvement avant d'être remplacé par la version renvoyée par le serveur.

Turbo ne nécessite pas d'écrire de JavaScript pour fonctionner
C'est un des intérêts de Turbo, en fait vous n'avez pas besoin d'écrire de JavaScript pour profiter de tout ce dont je vous parle ici.

Bien sûr, il est possible d'enrichir l'expérience utilisateur avec du JavaScript, c'est là que Stimulus intervient.

Les Turbo Frame

Imaginez ça comme une bonne vielle iframe, ce qui se passe dans une Turbo frame, reste dans une Turbo frame, et ne touche pas le reste de la page.
Vous pouvez donc découper votre page en plusieurs petits morceaux, lorsque vous cliquez sur un lien dans un de ces morceaux alors, seulement cette partie de la page sera mise à jour, le reste ne bougera pas

Imaginez par exemple un formulaire pour ajouter des commentaires, lorsque vous avez validé votre commentaire, le serveur renvoie une alerte en HTML disant : Votre commentaire a bien été ajouté, et bien seule cette partie de la page sera mise à jour... Et si vous avez l'œil, dans ce que je viens de vous décrire là, à aucun moment vous n'avez écrit de JavaScript, c'est juste du HTML et le langage de votre back (PHP dans le cas de Symfony).

Les Turbo frame, c'est top, mais maintenant imaginons qu'en même temps il y ait besoin de mettre à jour une autre partie de la page ... Comment faire ??? 😛

Les Turbo Stream

En plus des Turbo Frame, il y a les Turbo Stream...

Qu'est-ce qui les différencie ?
Les Turbo Frame servent à changer un bout de l'interface, mais que se passe-t-il si par exemple, on veut en plus de ça changer un élément de la navbar par exemple, mettre à jour en temps réel le nombre de commentaires que l'utilisateur a posté ? Dans sa petite barre d’utilisateur en haut de l'interface ?

C'est là qu'on utilise les Turbo Stream, plutôt que de renvoyer dans la réponse seulement le contenu qui remplace le formulaire d'ajout de commentaire Et bien dans la même réponse on renvoie deux Turbo stream, un pour remplacer le formulaire et un autre pour mettre à jour le nombre de commentaires en navbar.

C'est simplifié, mais en gros, c'est ça...

Allez plus loin avec les Turbo stream
Ce qui serait fou, c'est que quand un événement se produit côté serveur, par exemple, l'ajout d'un commentaire de la part d'un autre utilisateur, la page se mettra à jour chez tous les utilisateurs actuellement sur cette interface... mais comment faire ?
Turbo stream est totalement compatible avec des technologies telles que Mercure.

Il suffit de "subscribe" à une URL, et dès que le black émettra un événement, les utilisateurs concernés qui ont souscrit à cet événement verront leurs interfaces se mettre à jour en temps réel.

Il est interdit d'écrire de JavaScript dans le <body>

Il est très important de souligner ici qu'avec Turbo, il est interdit d'écrire du JavaScript dans le body de la page.

Tout simplement, car le body est remplacé par Turbo et que si du JavaScript se retrouve dans la réponse du serveur alors le navigateur ne fera que l'exécuter a chaque fois.

Au bout d'un moment, vous imaginez bien qu'il y aura un problème de fuite mémoire 😮.

(Sur les sites et applications classiques, l'état JavaScript est nettoyé à chaque changement de page du coup, il est difficile de faire n'importe quoi puisque de toute façon, au prochain chargement de la page, tout sera « reset »)

Alors, que fait-on pour ajouter du JavaScript ???

Stimulus, la base de Symfony-UX

Stimulus en plus d'êtres la base de Symfony-UX, c'est le meilleur ami de Turbo, il va nous permettre de faire du JavaScript afin d'améliorer complétement notre application.

Le truc super avec Stimulus, c'est qu'il permet de faire de belle chose sans écrire des montagnes de JS.

Le fonctionnement de Stimulus

En fait, c'est très simple, il ne fait que regarder le DOM en permanence, quand il voit un élément qui contient un "stimulus-controller" alors il charge le contrôleur en question depuis l'HTML on peut passer des variables au contrôleur (ce qu'on appellerait des « props » dans le monde de React)

Un contrôleur peut avoir des "target", ce sont des éléments qui se situe dans l'élément du contrôleur (des enfants) qu'il sera facile de cibler depuis le contrôleur avec une variable prédéfinie pour nous, la variable se retrouve avec un nom comme celui-ci :  "this.targetNameTarget"

Cette variable contient tout simplement l'élément du DOM en question, ni plus ni moins... (sans JQuery 😛)

Quand Turbo met à jour le DOM ou une partie de celui-ci, Stimulus regarde quel contrôler il doit charger, une fois chargé, il va chercher la méthode connect() puis l’exécute

Vous vous demandez à quoi peut bien ressembler un contrôleur Stimulus ?

Comme je vous l'ai précisé, c'est d'une simplicité... on verra ce que ce contrôleur fait plus bas dans l'article. 🙂

Ma première application Symfony-UX/Turbo

Maintenant qu'on a vu comment fonctionnent Turbo et Stimulus, que fait-on ?

Je n'ai jamais été un grand fan de « faire du code pour faire du code », il me faut un projet concret...
Après avoir dévoré les formations Turbo et Stimulus de SymfonyCast.
Je me suis demandé « quel projet utile, qui résout un problème réel, je pourrais créer ? »

Dans mon entourage, j'ai la chance d'avoir des entrepreneurs comme amis, et dans ces amis, il y a Patrice Marchand, propriétaire du restaurant des Frères Marchand à Nancy.
Au cours de nos longues discussions, il m'a parlé de comment son restaurant prend les réservations, et nous sommes tombés d'accord sur le fait qu'on pouvait faire mieux ...

L'idée d'un outil de réservation qui permet d'éviter les « no-show » aux restaurateurs est née, et cette application était pour moi parfaite pour se mettre à Symfony-UX/Turbo.

En me promenant dans le parc de la pépinière à Nancy, le projet germait dans ma tête... puis, en juillet 2022, le début de l'écriture du code a commencé, en août de la même année nous voilà avec une première version fonctionnelle, mais perfectible, mais pour ça, il fallait déjà que l'application soit utilisée en production, avec de vrais clients et un vrai restaurant, afin d'avoir les retours, ou ça coince ? Qu'est-ce qu'on améliore ? Qu'est-ce qu'on modifie ? Bref "la méthode agile" comme elle est souvent appelée.

L'idée est très simple, le restaurant après avoir configuré quelques informations comme les horaires d'ouverture disposent d'un lien permettant à ses clients de réserver, lors du processus de réservation, un numéro de carte bancaire est demandé, afin de protéger le restaurant des "no-show".

(Quand un client réserve sans se présenter et sans prévenir, aucun paiement n'est effectué pendant la procédure de réservation)

Depuis le 10 décembre 2022, cette application est la méthode pour réserver au restaurant des Frères Marchand... À ce jour des milliers de personnes ont réservé leurs tables avec cet outil 😍.

(comme quoi, une idée peut très vite être adoptée, si c'est bien conçu)

Maintenant, je vous propose de voir quelques interfaces et les déconstruire, afin de comprendre comment ça fonctionne.

Une turbo frame pas comme les autres

Délimitation de Turbo Frame

De base, une Turbo frame, comme expliqué plus haut, est un comme une iFrame, elle permet de changer une partie de la page, mais cela n'affecte pas l'URL principale du navigateur
Sur la capture d'écran ci-dessus, la Turbo frame est en bleu, et dans ce cas là j'ai besoin de modifier l'URL du navigateur quand elle change, car c'est en fait l'élément central de la page.

Ce genre de cas particulier peut être géré avec Turbo, ici j'ai mis l'attribut "data-turbo-action" sur "advance" et en plus de ça, il a fallu écouter des événements de Turbo afin de voir quand il fait une restauration (retour en arrière vers une page déjà visitée) afin de modifier son comportement.

En effet, Turbo émets des événements, JavaScript, tel que

  • turbo:before-cache : Émit avant que Turbo mette en cache une "page" afin de passer a l'autre, cet événement permet par exemple de fermer les alertes laissées ouvertes et donc ne pas les afficher si l'utilisateur revient à cet endroit
  • turbo:before-fetch-request : Qui permet de savoir quand Turbo s'apprête à faire une requête au serveur, utile, par exemple pour ajouter des leaders a la requête

Et bien d'autres... disponibles dans la documentation de Turbo

La partie en vert, n'est qu'une simple « div », en fait elle ne change que quand l'utilisateur change la langue, pour cela, dans la turbo frame (en bleu) il y a un du HTML faisant appel à un contrôleur Stimulus qui va changer le texte de la div en vert, ce qui me permet d'utiliser le système de translation de Symfony 🙂

La partie en rouge, elle, est un contrôleur Stimulus qui entoure un formulaire, dans ce formulaire, il y a deux "input" visibles.
Quand un client choisit la date et le nombre de couverts, le contrôleur Stimulus s'occupe de donner cette information au serveur qui répond le le HTML à afficher (la partie en jaune), ce qui est intéressant ici, c'est que la partie en jaune est tout simplement insérée dans le dom avec un simple innerHTML, rien de bien éxotique.

Enfin, une fois que l'utilisateur a cliqué sur un bouton pour définir l'heure qu'il souhaite, un troisième "input" invisible est défini et le formulaire complet est envoyé au serveur.
Ce qui a pour effet de remplacer l'ensemble de la turbo Frame (la partie en bleu).

Il y a des animations apportées en plus par le contrôleur Stimulus du formulaire, mais il est possible de faire sans, donc je ne m'y attarde pas trop.

Un contrôleur stimulus qui coche des cases

Dans la partie, back-office, le restaurant peut choisir les jours de la semaine où il est ouvert, et les horaires d'ouverture de chaque jour.

Checbox avec contrôleur Stimulus

Cette interface fonctionne avec des checkbox, mais vous vous voyez vous : cocher les cases une par une ? 😅

Sur cette interface, vous pouvez voir des boutons du type "tout cocher" ou encore "tout décocher", et bien en fait derrière ces boutons il y a le contrôleur Stimulus dont je vous ai parlé plus haut

On voit que ce contrôleur prend une "valeur".

Cette valeur est la classe des cases qu'il faut cocher, ou décocher et on retrouve la définition de cette valeur dans le HTML

Contrôleur Stimulus avec valeurs

Ce simple contrôler Stimulus, permet d'éviter de cliquer plusieurs fois partout sans cesse, tout en gardant la possibilité d'être très pointilleux dans les horaires d'ouverture.

Des boutons qui sont en fait des Turbo Frame

Dans l'interface permettant de fermer certains horaires pour certains jours, il fallait quelque chose de très simple et très visuel, voici donc l'interface :

Bouton dans une turbo framea

Ici, le formulaire entouré en orange est en fait un contrôleur Stimulus, quand un élément est changé, alors il y a un "RequestSubmit" qui permet de mettre à jour la page immédiatement (sans la recharger)

Dans ce contrôleur Stimulus il y a "action", le "change" des éléments du formulaire, à savoir le jour de départ et le nombre de jour.

Ici, chaque bouton (même ceux pas entourés en vert) est un Turbo frame, quand on clique dessus, le serveur effectue la modification en base et renvoie le HTML du bouton en rouge ou vert en fonction de si cet horaire et fermé ou ouvert.
Dans le HTML hormis la Turbo frame, le bouton n'est qu'un anchor classique avec pour URL la route qui met à jour l'horaire en base de données

Voilà ce que cela donne une fois les boutons cliqués :

Bouton avec horraire fermée (réservation restaurant)

Stripe et Stimulus

Stripe avec Stimulus

Comme expliqué plus haut, dans ce genre d'application, tout le JavaScript est sous forme de contrôleur Stimulus.

Stripe ne fait pas exception, pour l'intégrer j'ai simplement utilisé le wrapper fournis par Stripe pour avoir accès a Stripe Element depuis un module webpack

Dans ce genre de cas, il faut juste bien faire attention que la bibliothèque externe n'ajoute pas de JavaScript dans la page et également penser au fait que quand Stimulus charge un contrôleur, il appelle sa méthode connect() et quand il le décharge, il utilise la méthode disconnect()

Utile si la lib externe nécessite qu'une méthode soit appelée lors de la destruction de l'élément qu'elle a créé.

Un autre cas spécial avec Stimulus

Ici, je ne vais pas trop m'y attarder, car le cas est l'utilisation de Intl-input avec Stimulus, et ce cas je l'ai déjà traité dans cet article

Par défaut la fuite mémoire est énorme avec ces deux libraires, du coup le navigateur va très vite à Crash, c'est pourquoi à l'époque j'y avais consacré un article

Conclusion

Turbo et Stimulus sont des technologies récentes, de ce fait l'idée de base est superbe et ça fonctionne parfaitement la majorité du temps.
Les soucis peuvent arriver lors de l'utilisation de librairie externe, cela demande un peu de temps parfois pour trouver une solution ais dans mon cas, il y en à toujours eu une.

J'ai particulièrement apprécié ces technologies, car elles m'ont permis, une fois la documentation parcourue et les principes bien compris, qu'une idée passe du statut d'idée à "projet en production" en seulement quelques mois.

(le développement initial à durée un mois, mais, le monde de l'entreprise est lent et pour former tout le monde a l'utilisation de l'outil avant de le mettre en production, ça prend du temps)

Pour résumer, cela m’a permis de plus passer de temps à me demander quelles fonctionnalités seraient top plutôt que me demander "comment je vais faire" et le temps de développement a drastiquement été réduit.

Je pense que pour tous les projets de ce genre, Turbo et Stimulus auront une grande place dans ma boite à outils.

Passez une très bonne semaine 🙂