· Blog de veille

Utiliser React comme moteur de templating

Sommaire

    • Les générateurs de sites statiques (en React)
    • Atomic Design : approche par Layout / Zone / Composant
    • Que nous apporte React en tant que moteur de templating
    • Réutilisabilité des composants pour le build et pour les apps
    • Conclusion

    • Les générateurs de sites statiques (en React)

      Ces dernières années, la Jamstack a le vent en poupe, de nombreux avantages (performance web principalement) et une approche séduisante pour beaucoup de cas d’usage, des plus simples aux plus complexes. La plupart des bibliothèques et frameworks JavaScript proposent désormais de faire du Server Side Rendering (SSR), comme React, Vue.js ou encore Angular. Cette possibilité a ouvert la porte aux générateurs de sites statiques, qui se basent sur ces librairies comme moteur de rendu. Nous allons ici parler principalement des possibilités de React en tant que moteur de templating, comme on peut le trouver dans Gatsby, Next ou encore React Static, car le contexte projet et équipe l’impose.

      D’ailleurs, si l’approche de ces différentes solutions ne vous convient pas, vous pouvez, grâce aux méthodes dédiées au SSR (notamment React.renderToStaticMarkup()), créer le votre, ajusté à vos besoins.

      Bien, maintenant que nous sommes au clair sur le fait que nous allons utiliser React comme moteur de template, une bibliothèque basée sur une approche composant, il convient de s’orienter vers une approche Atomic Design pour la conception des pages.

      Atomic Design : approche par Layout / Zone / Composant

      Dès la conception, avoir une approche Atomic Design (en) permet de garder une cohérence dans le design et un découpage permettant une flexibilité lors de la contribution des pages.

      Pour l’un de nos projets, nous avons choisi de mettre à la disposition du contributeur une liste exhaustive de layouts et de composants, basée sur le design et les besoins.

      Le contenu type d’une page construite est donc une succession de layouts, se présentant sous cette forme :

      Les Zones sont les seuls éléments non contribués, induits directement par le layout choisi. On fournit donc une liste exhaustive de layouts (Simple, 2 colonnes, Grille, Accordion, etc.), et de composants au contributeur, afin qu’il puisse construire sa page comme il le souhaite. Il est également recommandé d’avoir un outil visuel permettant la création facile des pages – par exemple une application de back office en React communiquant avec un CMS headless comme Drupal pour gérer les contenus – afin que le contributeur ait un retour visuel immédiat de sa page.

      Il est donc essentiel de prendre en compte le fait que chaque composant peut être inclus dans tout type de layout, et donc dans différents contextes (de page, de zone, etc.). Les composants ne doivent pas être interdépendants mais être autonomes, visuellement et fonctionnellement.

      Que nous apporte React en tant que moteur de templating ?

      React propose un concept appelé Context permettant de transmettre à ses composants enfants des informations sans les passer de composant en composant via les props (en React, ce sont les priorités qui sont passées au composant lors de son instanciation).

      Ainsi, on peut imaginer créer un contexte :

      • De page : dans lequel on aurait toutes les informations relatives à la page courante (slug, titre, langue ou toute autre metadata)
      • De layout : qui fournit notamment le type de layout
      • De zone (taille de zone déduite, couleur de fond…)

      L’utilisation de Context s’avère très utile et ouvre certaines possibilités lors de l’intégration HTML.

      Classes conditionnelles

      Dans notre design, imaginons que l’on puisse contribuer des variations de couleur de fond, pour les zones par exemple, ainsi que d’autres décorateurs (image ou icône de fond, marges, etc.). Le contexte de la zone peut donc contenir ces informations mais également d’autres, déduites à partir des informations de contexte de page ou de layout. Par exemple, on peut déduire le type de zone (petite, grande, pleine largeur) en fonction du type de layout et sa position dans la layout : dans un layout 1/3 – 2/3, la zone à droite sera de type large tandis que celle de gauche sera qualifiée comme petite.

      De cette façon, un composant a accès à son contexte complet dans le document (nombre de colonnes et type de layout (1/2-1/2, 2/3-1/3, …)) et celle de la zone dans laquelle il est (petite/grosse, marges, couleur de fond, …).

      Puisque nous avons accès à ces informations nous pouvons appliquer une classe de comportement (ou modifier) au composant, relative à l’une de ces informations, sans se baser sur une classe que comporte le parent comme on l’aurait fait classiquement. On aurait écrit par exemple :

      .MonComposant {
        flex-direction : row ;
      }
      .Zone.is-small .MonComposant {
        flex-direction : column ;
      ...
      }
      
    • Mais puisque notre composant a déjà l’information de taille de zone dans laquelle il se situe on peut lui appliquer une classe en conséquence (ici pour qu’il soit en colonne et non en ligne). Il gère donc ses états, indépendamment de son contexte.

      .MonComposant.is-small {
      flex-direction : column ;
      ...
      }
      

      Le seul point d’attention est que, puisque le composant réagira seulement à ses classes  ajoutées à la construction de la page, s’il est déplacé dans une autre colonne, son comportement ne sera pas dynamique (un composant avec une classe liée à une zone small n’aura changera pas de modifier si il est déplacé dans une zone large).De cette manière, les classes CSS peuvent suivre parfaitement une méthodologie de type BEM, où le modifier prend tout son sens, et où chaque composant est réellement indépendant.Combinés à des variables CSS (Custom Properties), on peut rendre nos composants complètement agnostiques de leur contexte. Si l’on prend en exemple des couleurs de fond appliquées aux zones, on peut imaginer créer des variables CSS différentes en fonction de ces contextes (ici avec des variables Sass) :

      .Zone {
        --zone-background-color : #{$light} ;
        --zone-foreground-color : #{$dark} ;
      
        // classe à ajouter au DOM avec les informations de contexte
        &.has-darkGreyBg {
      		--zone-background-color : #{$dark} ;
      	  --zone-foreground-color : #{$light} ;
        }
      }
      
      .MonComposant {
      	color : var(--zone-foreground-color) ;
        background-color : var(--zone-background-color) ;
      }
      


      De cette manière, nos composants respectent toujours les bonnes couleurs, dépendant de celles de la zone, sans que l’on ait à leur attribuer de nouvelles classes pour faire les changements nécessaires.

      Rendu conditionnel

      Puisque l’on a le contexte de rendu disponible au build de la page, on peut donc décider de ne pas rendre certains éléments en fonction de son contexte.
      Par exemple, pour un composant de type Image + Citation, se trouvant dans un contexte de colonne small, on peut choisir de ne pas générer le HTML de l’image. Alors que dans une intégration plus classique, le code source contiendrait tout le HTML, mais on masquerait la partie non désirée via CSS. Au lieu d’écrire :

      .is-small .Citation-img {
      	display : none ;
      }
      

      on peut écrire côté React :

      render (
      	<div className="Citation">
      		{ zoneSize === 'large' && (
      			<img src="" className="Citation-img"/>
      		)}
      		<blockquote className="Citation-text">
      			...
      		</blockquote>
      	</div>
      )
      

      Dans nos usages, on économise donc cette condition CSS, en plus de l’HTML inutile dans le DOM.

      Optimisation des images

      Dans un projet Jamstack, il n’est pas anodin d’utiliser un CDN / Asset Manager comme Cloudinary qui permet d’optimiser ses assets (images, vidéo). Cloudinary permet de définir des transformer que l’on peut appliquer dynamiquement à l’URL appelant l’image (redimensionnement, recadrage, effets, overlay, etc). Si vous voulez en savoir plus sur Cloudinary, vous pouvez consulter cette mini conférence (slides en anglais). Mais ces outils doivent être utilisés de façon intelligente et combinés à une bonne gestion des attributs srcset des images côté HTML.

      C’est sur ce point que l’utilisation de contexte peut nous aider. En effet, puisqu’au niveau du composant qui rend l’image, on connait la taille de la colonne dans laquelle il est, on peut décider d’appeler une image plus adaptée à sa largeur.
      Exemple avec un composant Image, composant contribué pour lequel l’utilisateur passe juste la référence d’une image hébergée sur Cloudinary, et se charge de rendre l’HTML associé. Notre composant Image va donc pouvoir mettre dans la propriété srcset de l’élément img des transformer adaptés à la taille de la colonne dans laquelle il se trouve. Ainsi une image dans une zone large sera dimensionnée avec la taille d’une zone large, alors que s’il est dans une petite zone, ses dimensions seront adaptées.

 

const { zoneSize } = useContext(ZoneContext) ;
const transformer = zoneSize === 'large' ? 'largeImage' : 'smallImage' ; 
...

<picture>
  <source
    media="(max-width : 1024px)"
    srcSet={`
      https://res.cloudinary.com/idemo/image/upload/t_${transformer}_1x/sofa_cat.jpg 1x,
      https://res.cloudinary.com/idemo/image/upload/t_${transformer}_2x/sofa_cat.jpg 2x`}
    />
  <img
    srcSet={`
      https://res.cloudinary.com/idemo/image/upload/t_${transformer}_1x/sofa_cat.jpg 1x,
      https://res.cloudinary.com/idemo/image/upload/t_${transformer}_2x/sofa_cat.jpg 2x`}
    src={`https://res.cloudinary.com/idemo/image/upload/${transformer}_1x/sofa_cat.jpg`}
    alt="Cat on a sofa"
    />
</picture>

 

Réutilisabilité des composants pour le build et pour les apps

Un des avantages à utiliser un générateur de site utilisant React (ou tout autre librairie basée sur les composants), c’est la possibilité de réutiliser tous les composants dans vos applications front (ce qui est fait automatiquement par les outils comme Gatsby). On peut imaginer une application de recherche dont les résultats peuvent reprendre des composants déjà existants pour leur affichage (par exemple le même composant image qui sert dans la construction des pages. On s’assure ainsi d’une uniformité du code, de sa centralisation et que le contributeur a devant les yeux le rendu réel de son travail dans son application de back-office.

Conclusion

Il existe beaucoup d’autres avantages à l’utilisation d’un framework comme React en tant que moteur de templating / générateur de site statique. Combiné à une approche de design modulaire, et en tirant partie de toute sa complexité et de sa puissance, il nous permet d’améliorer à la fois notre code, mais aussi le processus de contribution. À noter que cet article traite d’exemples avec React, mais il est certainement possible d’avoir la même approche avec toute autre librairies orientées composant.