Définir une architecture cohérente
Comment faire "le bon choix" ?
Dans les quatre chapitres précédents, nous avons exploré en détail quatre problématiques front-end incontournables lors de la conception de l’architecture d’une application web. Déstructurer les outils les plus communément utilisés et étudier les normes sous-jacentes nous a permis d’acquérir une vision globale de ces de ces quatre concepts fondamentaux du développement web, débarrassée de toute abstraction et de tout dogmatisme.
Ayant à présent acquis les automatismes nécessaires, vous pourrez appliquer cette méthode à l’ensemble des domaines du développement web, au fur et à mesure de vos progressions futures.
Certains de ces domaines, comme la programmation orientée composant, constituent des patrons de conception universels, qui ne sont en rien spécifiques à un framework ou à une bibliothèque, ni même au développement web. En tant que tels, ils permettent donc de définir un référentiel commun associé à un ensemble de bonnes pratiques qui peuvent et doivent être intégrées dès les premières étapes de conception. Ils peuvent ainsi constituer une fondation solide qui demeurera quelle que soit l’option choisie pour la concrétiser.
En nous focalisant sur ces concepts plutôt que sur leur implémentation, nous avons jusque-là présenté des exemples sommaires destinés à exposer pas à pas des idées, des méthodologies et des logiques. La plupart de ces exemples fournissent une base de réflexion, et non des solutions clés en main pouvant être simplement recopiées telles quelles dans des projets.
1. Déterminer l’approche optimale en fonction du contexte
Il est donc à présent temps de nous attaquer à l’application concrète de ces connaissances. En fonction du sujet abordé, nous devrons avant tout déterminer :
-
si un standard officiel a été établi (ou est en cours de discussion, mais malgré tout implémenté par une majorité de navigateurs de manière suffisamment stable),
-
s’il existe un ou plusieurs outils pouvant...
Vanilla Web et les microbibliothèques
1. De Vanilla JS au modern Web
Commençons donc par le commencement, au plus près des fonctionnalités natives.
Le terme "Vanilla JS" est véritablement adopté aux alentours du mois d’août 2012, avec la création du site vanilla-js.com parodiant le discours des bibliothèques principales de l’époque (jQuery, bien entendu, mais également Dojo, Prototype JS, Ext JS, YUI et Mootools) pour mettre en avant la simplicité et les performances d’un code utilisant uniquement les fonctionnalités natives. Suivant ainsi de près la naissance de HTML5, ce courant du développement est surtout le reflet de cette période marquée par l’omniprésence de jQuery, par des applications web peu développées et des bibliothèques comme AngularJS et React encore jeunes ou à naître.
Si un certain dogmatisme antibibliothèque a pu faire sens suite aux changements radicaux en cours en 2012, la situation a depuis radicalement changé. Nous sommes à présent essentiellement face à des projets bien plus complexes qu’alors, tandis que l’évolution récente et en cours de la plateforme web s’avère bien moins radicale.
En termes d’architecture, nous préférons parler de "Vanilla Web", qui nous semble exprimer bien plus clairement l’idée d’éviter l’usage d’outils de build et de minimiser celui de bibliothèques, sans pour autant les exclure strictement.
Nous ferons également ici la différence entre cette approche encore difficilement applicable et le "modern Web", prônant l’usage de microbibliothèques pour des cas bien précis et un usage minimal des outils de build, sans les exclure.
Quelle que soit l’approche choisie - Vanilla ou modern Web -, nous pouvons parfaitement associer solutions "custom" et bibliothèques spécialisées. Faire le choix d’une bibliothèque n’est plus qu’une affaire de rapport coût/bénéfice, comme cela devrait être le cas pour tout projet.
2. Complexité et poids d’un code JavaScript
Toute application web devant être chargée par le réseau...
Bibliothèques de rendering
Pour construire des applications plus complexes, nous avons besoin de faire appel à des solutions qui le sont tout autant. Or, celles-ci sont extrêmement nombreuses, chacune permettant d’aborder le rendering, voire le développement dans son ensemble, en suivant des chemins radicalement différents. Suivant l’option choisie, les performances obtenues peuvent par ailleurs grandement varier (dans un rapport de 1 à 3, voire à 10 pour les cas les plus extrêmes).
Moyenne des principales solutions de rendering sur l’ensemble des tests de performance
Exception faite des équivalents de jQuery, nous pouvons classer les bibliothèques de rendering en trois groupes, qui présentent de nombreuses similarités, en fonction de leur syntaxe :
-
une syntaxe spécifique, par "extension du langage HTML"
-
une syntaxe orientée HyperScript ou similaire, minimaliste
-
une syntaxe utilisant des littéraux de gabarits étiquetés (tagged template literals)
La première syntaxe exigeant des outils de build et des aides au développement, elle n’est généralement employée que dans le cadre d’écosystèmes complets, autrement dit de frameworks, sur lesquels nous reviendrons par la suite (voir la section Frameworks de ce chapitre).
Nous avons étudié en détail la deuxième syntaxe dans la section HyperScript du chapitre Afficher des données dynamiquement et dans la section précédente de ce chapitre. La majorité des bibliothèques "HyperScript" favorisent à présent des solutions de plus haut niveau, comme JSX ou htm (voir le chapitre Adopter une approche par composant, section Liens avec une approche fonctionnelle). De telles solutions de plus haut niveau s’inscrivent généralement dans un écosystème étendu et complexe dépassant largement le cadre d’une simple microbibliothèque. Nous les étudierons donc en conclusion de cette section (voir React est-il vraiment une bibliothèque de rendering ?).
Il nous reste donc à explorer la dernière syntaxe basée sur les étiquettes de gabarits.
1. Principe des étiquettes de gabarits
Pour effectuer le rendering avec...
Modularité et programmation orientée composant
Plusieurs microbibliothèques de rendering permettent d’optimiser nos applications tout en offrant une expérience de développement améliorée. La structure de tels projets peut cependant rapidement devenir complexe, réduisant du même coup leur maintenabilité.
Pour créer des projets conséquents, nous devons donc impérativement les modulariser.
Que nous utilisions ou non un bundler, le premier niveau de cette modularisation est naturellement porté par des ES modules, matériellement représentés par chaque fichier source JavaScript et si besoin rassemblés dans un même dossier en effectuant l’export via un fichier index.js.
// lib/index.js
export * from "./module-1.js";
export { foo, bar } from "./module-1.js";
// app.js
import { foo } from "./lib/index.js";
Dans ce domaine comme dans d’autres, les guides de style de programmation dédiés aux principaux frameworks et écosystèmes de plus haut niveau (Angular67, Airbnb React/JSX68, etc.) constituent une excellente base de travail pour définir celui d’un projet moderne. La plupart recommandent notamment de ne définir qu’un élément par fichier, dans la continuité du principe de responsabilité unique69, ce qui doit pouvoir s’appliquer dans la grande majorité des cas.
La principale difficulté est alors de définir concrètement ce concept d’’’éléments’’. Ici, "élément" peut désigner une très large variété de structures, logiques et algorithmes couvrant ensemble une responsabilité unique. Il peut s’agir d’une classe, d’une fonction, ou d’un mix de classes, fonctions et variables. Dans les deux premiers cas, il est plus judicieux d’adopter une programmation orientée composant. Ce qui nous amène alors à une règle simple : un composant = une fonction ou classe = un fichier = un ES module. Le terme "composant" a lui même une définition claire, commune et éprouvée. Cette règle nous permet donc de développer...
Programmation réactive
1. Notions fondamentales
Par essence, le développement front-end est fortement évènementiel. Réagissant à des comportements utilisateur non prédictibles, toute interface graphique est composée d’éléments permettant des interactions. Pour réagir à celles-ci, chaque développeur doit donc observer ces évènements, pour y associer une longue chaîne d’actions.
Ce type de difficulté a tout d’abord été partiellement résolu grâce au patron de conception Observer, rapidement devenu le modèle de référence pour la représentation de ce type d’échange de signaux. Ce patron de conception permet de créer une classe observable enregistrant une liste d’observateurs, auxquels elle notifiera un changement de son état par l’appel à une méthode définie par une interface commune (typiquement notify()).
Diagramme UML du patron de conception Observer.
Le DOM lui-même utilise ce patron dans le cadre de la gestion des évènements utilisateur, via les interfaces EventTarget et EventListener.
Or, cette approche tend à promouvoir les effets de bord.
document.querySelector("button")
.addEventListener("click", async (event) => {
const params = parseClickEvent(event);
const data = await callAPI(params);
const html = dataToHTML(data);
document.querySelector(".view").innerHTML = html;
});
De plus, la plupart des parcours utilisateur s’avèrent bien plus complexes que l’exemple précédent. Ils impliquent un grand nombre d’interactions pouvant se faire concurrence. De très nombreux problèmes, qu’il sera difficile de modéliser, se posent alors.
Imaginons que nous souhaitons offrir une complétion automatique reposant sur une API, comme dans l’exemple naïf suivant.
document
.querySelector("input[type=text].auto-complete")
.addEventListener("keyup", async (event) => {
const data = await callAPI(event.value);
const...
Gestion d’état
N’importe quelle application web doit gérer un grand nombre de variations d’état, à tous les niveaux : route utilisée, données récupérées depuis les API, état interne de chaque composant, etc.
Il est donc généralement préférable d’adopter un cadre dans lequel seront définis ces états et leurs transitions. Bien entendu, cela peut être fait, comme pour tout programme, sans l’aide d’aucun outil, en suivant simplement les patrons de conception les plus communs. Compte tenu de la grande complexité du développement front-end, il est malgré tout courant d’opter pour un certain patron de conception, dont la mise en place est généralement appuyée par une bibliothèque dédiée.
Quand nous utilisons le terme "gestion d’état" dans le cadre du développement d’applications web, il est, du fait de la popularité de cette approche, souvent résumé à l’utilisation d’une bibliothèque de programmation réactive type "conteneur d’état prédictible", comme Redux.
1. Créer un conteneur d’état avec Redux
Dan Abramov définissait, en 2016, le concept de "predictable state container" en ces termes :
« C’est un "conteneur d’état", car il contient tout l’état de votre application. Il ne vous permet pas de changer cet état directement. Au lieu de cela, il vous oblige à décrire chaque changement via des objets simples appelés "actions". Les actions peuvent être enregistrées pour être ensuite rejouées, ce qui rend prévisible la gestion d’état. Avec les mêmes actions dans le même ordre, vous aboutirez au même état. »91
À présent un des membres les plus suivis et respectés de la communauté React, Abramov a forgé l’expression "predictable state container" en 2015 pour résumer le concept de la bibliothèque de gestion d’état qu’il venait de créer : Redux. Redux concrétise, dans le cadre de la programmation réactive, l’idée...
Routeurs
Comme nous avons pu le constater au chapitre Développer une application monopage, les routeurs sont dans leurs principes de base des éléments assez simples. Ils peuvent cependant rapidement gagner en complexité et s’avérer complexes à maintenir, tant leurs connexions avec les autres modules d’une application sont nombreuses et tendent à influencer l’ensemble de l’architecture.
Pour cette raison, la plupart des routeurs disponibles sont liés à l’usage de l’outil - à savoir, la bibliothèque ou le framework - pour lequel ils ont été spécialement conçus, afin de permettre de répondre efficacement aux besoins spécifiques de cet outil.
À ce jour, il n’existe malheureusement aucune microbibliothèque permettant de véritablement répondre à tous les besoins potentiels d’une application web moderne. Ces fonctionnalités doivent donc généralement être développées avec l’application.
Deux bibliothèques peuvent cependant aider au démarrage d’un tel projet ou, a minima, vous servir d’inspiration.
1. Page.js
Page.js97 est sans doute, encore à ce jour, la plus connue et utilisée des microbibliothèques de routage. Après l’avoir créée en 2012 en même temps qu’une centaine d’autres projets, TJ Holowaychuk cesse très rapidement (dès 2013) d’y contribuer. Elle est depuis très ponctuellement maintenue par quelques développeurs de passage (aucune nouvelle fonctionnalité n’a été ajoutée depuis la version 1.11.0...
Framework
1. Définition
Aucun livre traitant du développement et de l’architecture des applications web ne saurait être complet sans aborder la solution actuellement la plus populaire, à savoir les frameworks web. Avant de s’attaquer à ce sujet complexe, il est indispensable de s’accorder sur une définition de ce terme.
En premier lieu, tout framework offre avant tout une abstraction importante d’éléments de bas niveau, permettant ainsi de nous focaliser sur les questions de haut niveau, via un cadre strict.
Contribuer à un projet faisant appel à un framework ne demande ainsi ni de connaître les détails les plus complexes de la plateforme ni d’effectuer une R&D importante. Les abstractions utilisées doivent cependant être maîtrisées, ce qui peut nécessiter un temps d’apprentissage et d’adaptation important.
De plus, la mise en place de ces abstractions demande nécessairement aux développeurs du framework de faire des choix qui peuvent ou non s’avérer payants sur le long terme. L’importance de ces choix est évidemment d’autant plus grande que le framework est ambitieux, et permet de couvrir un large spectre de besoins.
Citons par exemple l’utilisation d’un DOM Wrapper (JQLite) et du "dirty checking" par AngularJS, ou encore l’utilisation intensive du standard HTML Imports par Polymer.
Bien que parfaitement justifiés au moment de la création de ces bibliothèques, ces choix fondamentaux ont avec le temps amené à une impasse nécessitant la création d’une solution de remplacement (Angular pour AngularJS et le "projet Polymer" pour Polymer).
De tels changements radicaux sont depuis devenus plus rares, en grande partie grâce à une meilleure utilisation du versionnage sémantique (qui permet d’introduire des changements majeurs brisant la rétrocompatibilité plus progressivement) et une meilleure modularisation de ce type de frameworks, ainsi qu’une généralisation des outils d’aide à la migration.
Vue, React et Angular ont ainsi permis de donner l’impression d’une certaine continuité, bien qu’ayant radicalement changé depuis leurs premières...
Synthèse
Après plus de dix ans d’évolution, le choix de bibliothèques, d’outils et de frameworks de développement web est extrêmement large, et toujours croissant.
Si le trio Angular-React-Vue semble encore détenir une place privilégiée quasi hégémonique, il existe heureusement de nombreuses alternatives qui permettent de définir des architectures adaptées à chaque contexte.
Dans ce chapitre, nous avons exploré pas à pas différents types d’architectures d’un niveau de complexité et d’abstraction croissant. Nous pouvons classer celles-ci en huit catégories, partagées en trois groupes :
La caractéristique majeure associée à cette complexité croissante des abstractions est de toujours plus éloigner le code applicatif final des problématiques de bas niveau. Plus la solution utilisée est haut placée dans cette hiérarchie, plus notre projet est dépendant de cette solution.
Cette dépendance a longtemps fait parfaitement sens pour la quasi-intégralité des projets, tant les capacités natives de la plateforme web empêchent de réaliser un développement efficace, performant et maintenable. Depuis plusieurs années, de nombreuses innovations ont cependant permis de réduire...
Références
1. ADAM Q. La dette technique : une supercherie mythologique pour galériens de l’IT [En ligne]. Medium. 26 septembre 2019. Disponible sur : https://medium.com/@waxzce/la-dette-technique-une-supercherie-mythologique-pour-gal%C3%A9riens-de-lit-7ed0dfbf39c7 (consulté le 9 janvier 2021).
2. Les coûts irrécupérables - Crétin de cerveau #3 [En ligne]. [s.l.] : [s.n.], 2016. Disponible sur : https://www.youtube.com/watch?app=desktop&v=GCmfXMMhRzk (consulté le 13 décembre 2020).
3. ROTH S., ROBBERT T., STRAUS L. « On the sunk-cost effect in economic decision-making: a meta-analytic review ». Bus Res [En ligne]. 1er octobre 2015. Vol. 8, n° 1, p. 99-138. Disponible sur : https://doi.org/10.1007/s40685-014-0014-8 (consulté le 9 janvier 2021).
4. WANG J., KEIL M. « A Meta-Analysis Comparing the Sunk Cost Effect for IT and Non-IT Projects ». IRMJ [En ligne]. 1er juillet 2007. Vol. 20, p. 1-18. Disponible sur : https://doi.org/10.4018/irmj.2007070101.
5. MCAFEE R. P., MIALON H. M., MIALON S. H. Do Sunk Costs Matter? [En ligne]. Rochester, NY : Social Science Research Network, 2007. Disponible sur : https://doi.org/10.2139/ssrn.1000988 (consulté le 9 janvier 2021).
6. WAGNER J. Reduce JavaScript Payloads with Tree Shaking | Web Fundamentals [En ligne]. Google Developers. 27 octobre 2020. Disponible sur : https://developers.google.com/web/fundamentals/performance/optimizing-javascript/tree-shaking?hl=fr (consulté le 13 décembre 2020).
7. « Tree Shaking ». In : webpack [En ligne]. [s.l.] : [s.n.], [s.d.]. Disponible sur : https://webpack.js.org/guides/tree-shaking/ (consulté le 13 décembre 2020).
8. « Tree-Shaking ». In : Rollup.js [En ligne]. [s.l.] : [s.n.], [s.d.]. Disponible sur : https://rollupjs.org/guide/en/#tree-shaking (consulté le 13 décembre 2020)
9. TETLOCK P. E. Expert political judgment: How good is it? How can we know? 6. pr., and 1. paperback pr. Princeton, N.J. : Princeton Univ. Press, 2006. 321 p. ISBN : 978-0-691-12871-9.
10. SCHNAARS S. P. Megamistakes: forecasting and the myth of rapid technological change. New York : London : Free Press ; Collier Macmillan, 1989. 202 p. ISBN : 978-0-02-927952-6.
11. TST. #8/25 : Megamistakes [En ligne]....