Blog ENI : Toute la veille numérique !
🐠 -25€ dès 75€ 
+ 7 jours d'accès à la Bibliothèque Numérique ENI. Cliquez ici
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici
  1. Livres et vidéos
  2. Développement et architecture des Applications Web Modernes
  3. Afficher des données dynamiquement
Extrait - Développement et architecture des Applications Web Modernes Retrouver les fondamentaux
Extraits du livre
Développement et architecture des Applications Web Modernes Retrouver les fondamentaux Revenir à la page d'achat du livre

Afficher des données dynamiquement

Optimiser le chargement initial

1. Faire primer l’expérience utilisateur

Quelle que soit la plateforme employée, un seul et unique objectif doit primer sur tous les autres lors du développement d’une application : la satisfaction des utilisateurs. Car rien ne pourrait être d’une quelconque importance pour une application en l’absence d’utilisateur. Or, cette satisfaction s’avère toujours difficile à atteindre, sur le Web plus que nulle part ailleurs, tant le nombre d’éléments à prendre en compte est grand.

Dans ce domaine, faire appel à l’ergonomie, ou plus spécifiquement, à l’étude de l’expérience utilisateur (UX design) doit être un réflexe, afin de guider le développement de l’interface. Souvent confondu avec sa concrétisation en interfaces utilisateur (UI design), l’UX design constitue pourtant bien plus qu’un simple ensemble de principes de design dictant les formes et comportements que doivent adopter nos composants graphiques. Son objectif principal est bien de replacer l’utilisateur au cœur du processus de développement, suivant les principes de la conception orientée utilisateur (user-centered design) initiés par Donald A. Norman1.

Bien au-delà des techniques de développement et d’architecture, aucune application web ne peut atteindre une qualité satisfaisante sans adopter ces principes. Disposer d’une connaissance, au moins basique, des principes et méthodes de ce domaine peut constituer un atout majeur pour n’importe quelle personne confrontée au développement web. Nous vous recommandons donc de consulter quelques-uns des nombreux ouvrages et ressources en ligne traitant de ces techniques2,3,4,5, en complément de ce livre.

Pour autant, si l’UX design permet d’assurer la prise en compte des besoins utilisateur relatifs à une application spécifique, la plateforme web en elle-même impose déjà de se focaliser sur différents aspects qui, s’ils sont correctement traités, permettent d’améliorer radicalement l’expérience utilisateur.

2. Capturer l’attention des utilisateurs

La rapidité et la facilité d’accès à...

Comparer différentes approches

Comme souvent en informatique, il n’existe pas de solution absolue pour effectuer un rendering. Il s’agit donc plutôt de trouver le bon équilibre entre maintenabilité, lisibilité et performance. L’importance respective de chacun de ces points variant suivant le contexte, il en est de même pour l’approche à adopter.

Pour nous assurer que nous faisons le bon choix, nous avons cependant besoin de savoir quel impact chacune de ces approches a sur les performances. Or, effectuer ce type de test s’avère souvent complexe, voire trompeur. Car la seule évaluation du temps de traitement du code JavaScript effectuant ce rendu ne peut suffire ici.

Du point de vue de notre code JavaScript, la DOM API fonctionne de manière synchrone. Lorsque, par exemple, nous exécutons le code suivant, aucune promise ni aucune callback n’est susceptible de nous indiquer que ces opérations peuvent prendre un temps plus long pour être traitées que les lignes de code elles-mêmes.

const el = document.createElement("div"); 
el.textContent = "Hello World!"; 
document.querySelector(".container").appendChild(el); 

Et pour cause : l’évaluation de ces instructions elles-mêmes est bel et bien synchrone. Seul l’affichage (paint) est, lui, asynchrone.

Soit l’opération suivante (où heavyHTML génère une chaîne de caractères représentant un code HTML composé de plus de 2 000 éléments) :

const container = document.createElement("div"); 
document.querySelector("main").appendChild(container); 
container.innerHTML = heavyHTML(); 
container.innerHTML = "<p>Hello World</p>"; 

Son "évaluation" est rapide, comme nous pouvons le voir en nous rendant dans les outils de développement Chrome, sous l’onglet Performance.

L’onglet Performance des outils de développement Chrome utilisant fortement les couleurs, plusieurs des illustrations suivantes, imprimées en noir et blanc, peuvent mal refléter le texte les accompagnant. Pour une meilleure compréhension, nous vous encourageons vivement à reproduire cette analyse sur votre ordinateur, en vous aidant des ressources...

Bien utiliser la DOM API

Si l’on part de l’essentiel et que l’on fait abstraction de toute bibliothèque, la première question à se poser en matière de rendering est : "comment créer et afficher un ensemble d’éléments HTML ?". Il s’agit en effet de la première base de tout développement web, à tel point qu’il est possible de l’illustrer par un simple "Hello World!" :

const el = document.createElement("div"); 
el.textContent = "Hello World"; 
document.body.appendChild(el); 

Un tel exemple est cependant loin de représenter une réponse suffisante, sans aucune évaluation de l’expérience de développement ni des performances.

1. Créer un ensemble d’éléments

Pour mieux évaluer les forces et faiblesses de la DOM API, nous avons besoin d’un projet plus complet. Ce qui est donc pour nous l’occasion de créer une première application test (telle que présentée dans la section Implémentation), en commençant par la création d’une ligne.

Une ligne sera créée via une méthode createRow projetant les valeurs d’un objet data en un élément HTML <tr>. Ce qui demandera de définir par la même occasion les attributs et le contenu de cet élément.

En utilisant la seule DOM API, nous obtenons ainsi le code suivant :

createRow(data) { 
  const tr = document.createElement('tr'); 
  const tds = [ '', '', '', '' ].map(() => { 
    return document.createElement('td') 
  }); 
 
  tds[0].classList.add('col-md-1'); 
  tds[0].textContent = data.id; 
 
  tds[1].classList.add('col-md-4'); ...

Littéraux de gabarit et innerHTML

Simple, efficace et comparable dans sa forme aux templates Angular, JSX ou Vue, l’association de littéraux de gabarit et de la propriété innerHTML est omniprésente dans les articles et livres traitant de développement web moderne. Nous l’avons d’ailleurs utilisée à de nombreuses reprises dans les chapitres précédents, afin de simplifier nos exemples. Du fait même de sa simplicité (et malgré ses très nombreux défauts), elle est (bien trop) souvent recommandée et adoptée dans le cadre d’applications Vanilla JS "réelles", en production.

Nous pouvons pourtant exposer ses limites par des exemples simples. Commençons donc par créer un code minimaliste comparable à celui présenté au chapitre Adopter une approche par composant, section Liens avec une approche fonctionnelle. Commençons donc par créer un code minimaliste similaire (mais non orienté composant), incrémentant et décrémentant un compteur count suite à un clic sur des boutons et affichant en continu sa valeur. 

{ 
  let count = 0; 
 
  const increment = () => { 
    count++; 
    render(); 
  }; 
 
  const decrement = () => { 
    count--; 
    render(); 
  }; 
 
  const container = document.querySelector("main"); 
 
  function render() { 
    container.innerHTML = ` 
      <p>${count}<p> 
      <div> 
        <button onclick="increment()">++</button> 
        <button onclick="decrement()">--</button> 
      </div> 
    `; 
  } 
 
  window.increment = increment; 
  window.decrement = decrement; 
 
  render(); 
} 

Ce code pose cependant plusieurs...

HyperScript

Nous sommes à ce stade face à un choix difficile :

  • opter pour la performance, en utilisant la seule DOM API, au détriment de l’expérience de développement

  • ou opter pour la lisibilité, en utilisant des template strings et innerHTML, au détriment de la maintenabilité et des performances.

Nous aurions donc besoin d’une nouvelle interface de programmation associant lisibilité et performance tout en permettant, idéalement, d’accéder efficacement et simplement aux éléments enfants afin d’effectuer des mises à jour ciblées.

Un grand nombre de bibliothèques offrent une syntaxe déclarative spécifique afin de répondre à ce type d’attentes. Toute syntaxe non standard exige malheureusement d’effectuer une analyse syntaxique. Analyse qui demande souvent d’effectuer de nombreux traitements, ralentissant d’autant plus l’application. 

L’usage de template strings et innerHTML offre de meilleures performances que de nombreuses bibliothèques, précisément pour cette raison. Ne reposant sur aucune syntaxe particulière, les template strings HTML ne demandent aucune analyse syntaxique supplémentaire.

Afin d’éviter de tels ralentissement, de nombreuses bibliothèques permettent d’effectuer cette analyse en amont, hors navigateur. Un template déclaratif est alors traduit en un ensemble d’instructions impératives moins coûteuses. React permet notamment d’utiliser JSX à cette fin, tandis qu’Angular propose une compilation AOT (Ahead-of-time).

Certaines bibliothèques offrent, cela dit, une interface purement programmatique "au-dessus" de la DOM API, ne nécessitant aucune analyse syntaxique.

1. Une bibliothèque, une fonction et une syntaxe

HTML est un langage fortement hiérarchisé : un élément "contient" un ou plusieurs éléments enfants, eux-mêmes pouvant en contenir, et ainsi de suite. Un des principaux obstacles à la lisibilité d’un code utilisant createElement et les différentes méthodes de HTMLElement réside donc dans son éloignement d’une telle logique. Créer chacun des éléments...

Minimiser les accès au DOM

En matière de rendering, le DOM constitue le principal goulot d’étranglement.

Le DOM étant fortement hiérarchisé, chaque modification est susceptible d’entraîner deux opérations : un repaint, calcul de la visibilité de l’ensemble des éléments du DOM, quand seul l’aspect des éléments est affecté (en modifiant par exemple background-color, opacity ou visibility), ou un reflow, encore plus coûteux, puisque devant réévaluer les dimensions et les positions de l’ensemble des éléments suite à la modification d’un seul.

Le navigateur doit systématiquement effectuer un calcul lourd, bloquant toute autre opération. Ce qui peut, dans le pire des cas, figer la page de manière visible.

Afin de minimiser ce coût, plusieurs bonnes pratiques sont à respecter :

  • Diminuer autant que possible le nombre de règles CSS (sans oublier de supprimer tout code CSS non utilisé).

  • Éviter les sélecteurs CSS trop complexes.

  • Exclure du reflow les opérations de rendering complexes (comme les animations), en utilisant position: absolute ou position: fixed.

  • Éviter de trop hiérarchiser les éléments dans un document HTML (la limite absolue étant conventionnellement fixée à une profondeur maximale de 32 éléments imbriqués).

  • Garder le nombre de nodes le plus bas possible (et toujours en dessous de 1 500 éléments).

Cela dit, diminuer autant que possible le déclenchement de nouveaux repaints ou reflows constitue toujours la meilleure optimisation. En servant de "tampon", un VDOM vise précisément à ne déclencher une mise à jour du DOM (par réconciliation) qu’au moment le plus opportun, tout en offrant l’illusion d’une modification continue, justifiant ainsi largement sa popularité. Nous avons vu, en prenant l’exemple de RE:DOM, qu’il est parfaitement possible d’obtenir de très bonnes performances sans VDOM. Optimiser les accès au DOM demeure malgré tout indispensable. Ce qui pourra nécessiter des stratégies similaires à celles mises en œuvre dans le cadre d’un...

Optimiser pour la répétition

Nos benchmarks, afin de pousser le navigateur dans ses retranchements, répètent un très grand nombre de fois (1 000 et 10 000, suivant les tests) les mêmes opérations. Le composant "ligne" est donc lui-même instancié à de nombreuses reprises. Or, pour l’heure, ce composant a été essentiellement pensé pour n’être utilisé qu’une seule fois dans la page (comme peuvent l’être la plupart des vues associées à des routes). Dans ces conditions, les différences de performance jusque-là constatées ne sont pas significatives.

Or, si un même template HTML est reproduit à de nombreuses reprises, créer un élément via document.createElement (ce qui comprend donc notre exemple HyperScript) a un défaut majeur : le même code étant exécuté à chaque itération, générer 1 000 éléments augmente d’autant le temps d’exécution et la quantité de ressources consommées. En d’autres termes : nous ne faisons aucune économie d’échelle.

Il est donc préférable, dans ce cas, d’optimiser la création de l’élément en effectuant un maximum de traitements en amont. Ce que nécessite la définition d’un "patron" représentant l’ensemble de la structure statique de l’élément à générer.

1. Optimiser la mise en cache des éléments avec cloneNode()

Nous pouvons nous inspirer des exemples précédents pour créer et mettre en cache l’ensemble des éléments avant l’instanciation du composant.

Tentons, pour valider cette idée, de définir une fonction counterChildren(). Cette fonction effectue la création de l’ensemble des enfants de notre Counter au premier appel, puis les met en cache dans une WeakMap() (afin d’éviter toute fuite de mémoire), pour, lors des appels suivants, ne renvoyer que le contenu de ce cache.

const counterChildrenCache = new WeakMap(); 
 
function counterChildren() { 
  if (counterChildrenCache.has(this)) { 
    return...

Conclusion

Nous avons vu dans ce chapitre plusieurs approches permettant d’effectuer un rendering plus ou moins efficace sans faire appel à des bibliothèques. Toutes comprennent cependant de nombreux défauts. En particulier, l’expérience de développement obtenue semble très loin de celle offerte par la plupart des frameworks modernes.

Nos exemples sont simples, voire simplistes. Les développer au-delà de ces simples concepts aurait demandé des centaines de pages supplémentaires, voire un livre entier dédié à ce seul sujet. Ils démontrent cependant assez clairement que le rendering est toujours complexe et demande toujours une réflexion poussée.

À mesure qu’elle gagne en complexité, une application purement Vanilla JS risque de contraindre l’équipe de développement à "réinventer la roue", en demandant la création de sa propre bibliothèque de rendering généraliste (ce qui n’est jamais l’objectif), susceptible d’être reprise intégralement à mesure que l’équipe gagne en compétences et doit répondre à des questions nouvelles. La seule "bonne" solution dans ce genre de cas est donc de très peu mutualiser ce code, divers composants étant susceptibles de se reposer...

Références

1. NORMAN D. Le design des objets du quotidien [En ligne]. Revised and expanded edition. [s.l.] : Librairie Eyrolles, 2013. 347 p. Disponible sur : https://www.eyrolles.com/Entreprise/Livre/le-design-des-objets-du-quotidien-9782212678833/ (consulté le 30 novembre 2020) ISBN : 978-0-465-05065-9.

2. « Basics of UX | Web Fundamentals ». In : Google Developers [En ligne]. [s.l.] : [s.n.], [s.d.]. Disponible sur : https://developers.google.com/web/fundamentals/design-and-ux/ux-basics?hl=fr (consulté le 30 novembre 2020).

3. CASIMIR M. « UX/UI For The Modern Web Developer ». In : Medium [En ligne]. [s.l.] : [s.n.], 2018. Disponible sur : https://medium.com/@maloriecasimir/ux-ui-for-web-developers-d5547a4d9b24 (consulté le 30 novembre 2020).

4. KRUG S. Don’t make me think, revisited : a common sense approach to Web usability. Third edition. Berkeley, Calif. : New Riders, 2014. 200 p. ISBN : 978-0-321-96551-6.

5. GARRETT J. J. The elements of user experience : user-centered design for the Web and beyond. 2nd ed.Berkeley, CA : New Riders, 2011. 172 p. (Voices that matter). ISBN : 978-0-321-68368-7.

6. « Paint Timing 1 editor’s draft ». In : W3C [En ligne]. [s.l.] : [s.n.], [s.d.]. Disponible sur : https://w3c.github.io/paint-timing/ (consulté le 1er décembre 2020).

7. « High Resolution Time Level 2 ». [s.l.] : [s.n.], [s.d.]. Disponible sur : https://www.w3.org/TR/hr-time-2/ (consulté le 1er décembre 2020).

8. « Navigation Timing Level 2 ». [s.l.] : [s.n.], [s.d.]. Disponible sur : https://w3c.github.io/navigation-timing/ (consulté le 1er décembre 2020).

9. « User Timing Level 3 »....