Agrégateur de contenus
Développer une Single Page Application avec Turbo - Partie 1
Rédigé par Benjamin Dreux le 23 janv. 2024
Une application web suit souvent un des deux patterns suivants:
Soit on décide de développer une application multi-pages, basée sur un framework backend tel que Spring MVC, Rails ou Django, soit on opte pour une application Single Page.
Les frameworks backend sont généralement des éléments bien établis et largement acceptés en entreprise. On trouve une abondance de documentation sur ces projets, et il est facile d'embaucher des développeurs expérimentés dans ces stacks. Les performances offertes par ces frameworks sont rarement des contraintes pour les applications de gestion. Souvent, c'est le modèle de données qui nécessite des ajustements ou la manière dont on y accède.
En revanche, une application multi-pages oblige les utilisateurs à créer un nouveau contexte d'exécution pour chaque page. En d'autres termes, à chaque nouvelle page, le serveur est sollicité pour fournir l'ensemble des ressources de l'application. Les headers de cache de l'application peuvent être configurés pour éviter le temps de téléchargement. Cependant, ce qui ne sera pas évité, c'est le temps d'interprétation de ces ressources
Si l'on adopte l'approche des applications à page unique, c'est le code JavaScript qui générera l'interface visuelle de notre application. Cela signifie que le backend contiendra plusieurs services répondant avec du JSON. Cela nous permettra de faire apparaître des éléments visuels selon les besoins spécifiques de l'application que nous développons. Cependant, cela implique également que certains aspects deviennent des problèmes à résoudre. Par exemple, le routage entre les pages, la gestion de l'état client, l'internationalisation et la communication avec le backend. Tous ces éléments sont moins évidents dans une application à page unique utilisant React. Ce ne sont pas des problèmes insurmontables, mais des questions à adresser dans le projet.
Lorsque l'on choisit une application à page unique, il arrive souvent que certains développeurs se retrouvent confinés à un sous-ensemble de l'application, à savoir le frontend. Cela est dû à la complexité croissante du contexte de travail, justifiant ainsi qu'une partie de l'équipe est dédiée à cette problématique.
Ce qui est dommage, c'est que les deux approches ont leurs aspects positifs, et le choix est souvent perçu comme exclusif. Pourtant, la plupart d'entre nous ont probablement expérimenté une troisième voie. Lorsque Ajax a commencé à susciter de l'intérêt, l'idée n'était pas de reconstruire toute l'application en JavaScript. Tout ce que nous voulions, c'était mettre à jour une partie de la page affichée à l'écran, tandis que notre backend continuait à fonctionner de la même manière.
Vous souvenez-vous de ce qui vous a dissuadé de continuer dans cette direction?
Moi oui, c'est le manque de structure dans ce que je faisais. Pour être parfaitement franc, la découverte de cette technique remonte probablement aux alentours de 2004, à l'époque où je m'initiais à la programmation web. Avec plus d'expérience, il aurait probablement été possible de faire mieux.
Notre troisième alternative revient à l'idée de la page partielle, mais avec une touche d'API JavaScript moderne. Des APIs telles que l'API history de JavaScript, dont la méthode pushState a été introduite en 2010 pour Chrome et en 2011 pour Firefox.
Mais alors, qu'est-ce qu'on gagne à adopter cette manière de travailler ?
-
Les assets d'un site ne sont plus téléchargés à chaque changement de page, ni même évalués.
-
Le CSS n'a plus besoin d'être interprété. Cela signifie que l'on ne verra plus de contenu HTML s'afficher à l'écran avant qu'il ne soit mis en forme (Flash Of Unstyled Content).
-
Les vues sont construites uniquement dans le backend, ce qui signifie qu'on a une seule source, que les templates peuvent être mis en cache, voire même le résultat des pages peut être mis en cache.
-
Pas besoin de livrer un volume de JavaScript incroyable pour afficher toutes les pages possibles de notre application.
-
Il n'est même plus évident que ce soit nécessaire de compacter votre JavaScript, puisque l'on se retrouve avec si peu de code.
-
L'équipe de développement va pouvoir choisir, pour chaque écran, le niveau de fidélité qu'elle souhaite avoir. C'est-à-dire placer le curseur entre une page dont le rendu est complété en une passe ou plusieurs mises à jour qui s'effectuent progressivement.
-
La quantité de JavaScript à écrire est grandement réduite ; en effet, la manipulation de la page elle-même peut être faite de manière déclarative avec une bibliothèque
Tout n'est pas parfait ; il y a des compromis à faire
-
C'est une manière différente de travailler qui peut être déstabilisante, même pour ceux qui ont expérimenté les deux approches.
-
Bien que l'on se rapproche de la fidélité d'une SPA, on reste fortement attaché au modèle HTML. Il peut être nécessaire de revoir certaines pratiques de base, telles que les formulaires HTML (voir l'attribut form), ou de faire face à des limitations dans l'imbrication des balises HTML (<a> ne peut pas être le fils direct d'un <tr>).
-
Il se peut encore que nos talents en JavaScript soient requis. Dans certains cas, le comportement souhaité est trop spécifique pour être délégué à une bibliothèque.
Assez tourné autour du pot, ce dont il est question ici, c'est l'utilisation de Hotwire Turbo ou Htmx. Pour le reste de cette série d'articles, je vais me concentrer uniquement sur la version actuelle de Turbo.
Il a été annoncé publiquement que la prochaine version (aka Turbo 8) offrira de nouvelles fonctionnalités qui pourraient changer certains détails que nous n'avons pas encore abordés. De plus, il existe une alternative à Turbo nommée Htmx, récemment promue dans l'accélérateur de GitHub, qui devrait donc attirer beaucoup d'attention.
Principe de base
Pour une application multi-pages, les étapes suivantes se produisent lorsque l'on clique sur un lien:
-
Le navigateur arrête toute activité dans le JavaScript.
-
Le navigateur télécharge la nouvelle page de manière synchrone.
-
Le navigateur vide l’écran, puis affiche la nouvelle page.
-
Le navigateur lance le téléchargement tous les assets (CSS, JS, fonts, images).
-
Au fur et à mesure de leur arrivée, ils sont interprétés et ajoutés au contexte de la nouvelle page.
C'est pour éviter de répéter ces étapes que l'on a préféré les SPA. Dans le cadre d'une application qui utilise Turbo, la démarche est différente :
-
Le téléchargement de la page est déclenché par un appel Ajax de manière asynchrone.
-
Une barre de progression est affichée sur la page lorsqu'elle met du temps à charger.
-
Lorsque le téléchargement de la page est complet, son traitement est divisé en deux parties : le head et le body.
-
Pour le head, les nouveaux assets sont ajoutés dans le head de la page actuelle. Ceux qui sont déjà présents ne seront pas modifiés. Ainsi, le temps d'exécution du JS et du CSS sera réduit à sa plus simple expression.
-
Pour le body, le body actuel est remplacé par celui de la page nouvellement arrivée. Ainsi, on a l'impression que le chargement de la page est presque instantané.
Pour permettre à Turbo de lancer un processus distinct du comportement par défaut du navigateur, Turbo va mettre en place des events listeners sur tous les liens de la page et tous les formulaires.
Les principes fondamentaux sont assez simples, et l'ensemble de la bibliothèque est construit autour de cela : des idées simples qui ont été suffisamment raffinées pour répondre aux problématiques quand elles se posent, sans que l'on ait besoin d'y penser. Par exemple, la barre de progression : il peut arriver qu'une page prenne plus de temps à produire que les autres. Si on ne fait rien, l'utilisateur va se demander s'il a bien cliqué comme il faut.
Une idée simple est de mettre une animation sur le bouton en question. L'équipe de Turbo a préféré désactiver le bouton en question, laissant au CSS l'interprétation visuelle et mettant en place une barre de progression qui n'apparaît que si le chargement de la page prend plus de X secondes. Ainsi, il n'est pas nécessaire de changer quoi que ce soit ni d'ajouter du code à chaque bouton que l'on veut supporter.
Dans le même ordre d'idées, Turbo va conserver une image des pages que l'on vient de visiter. Ainsi, si l'utilisateur décide d'utiliser le bouton Back de son navigateur, une image de la page précédente peut être affichée rapidement, le temps que l'on télécharge la nouvelle version de la page.
La suite
Ce premier article sert d'introduction à l'article suivant, dans lequel nous verrons comment mettre en place Turbo dans une application et comment s'en servir. Dans l'article suivant, nous discuterons de ce qui peut être fait lorsque Turbo ne suffit plus.