Cet article est le dernier d'une série consacrée aux langages de templating côté serveur.
L'article précédent montrait comment injecter des données dans les pages HTML, et comment gérer efficacement les dispositions de pages grâce à la relation d'héritage de template.
Je te propose maintenant d'aborder un dernier sujet très intéressant pour les sites modernes : comment créer des composants réutilisables à l'aide de templates.
L'objectif est de garder une architecture propre et maintenable côté serveur, même lorsqu'on utilise un framework serveur non orienté composants. Tu vas voir en effet que les langages de templating permettent de relever ce défi avec brio !
La problématique
La relation d'héritage est une façon de partager du code entre templates qui est efficace lorsque tous les templates dérivés ont besoin de la totalité du code du template parent. Dans l'article précédent, nous avons vu l'exemple typique d'un template parent contenant un en-tête et un pied de page commun à toutes les pages du site.
Mais prenons maintenant l'exemple d'un blog sur lequel plusieurs auteurs publient des articles. Supposons qu'on veuille afficher une courte présentation de l'auteur au bas de chaque article, et créer une page affichant tous les auteurs du blog.
Créer un ancêtre commun aux templates d'article et de liste d'auteurs ne répondrait pas au besoin d'afficher plusieurs auteurs dans la même page.
On a donc ici clairement besoin d'un template indépendant modélisant un auteur pour pouvoir l'intégrer dans les templates d'article et de liste d'auteurs. On utilise dans ce cas une relation de composition.
Tip
La composition de templates convient bien dans les cas suivants :
- pour des morceaux de templates répétés plusieurs fois dans une même page
- pour des morceaux de templates présents dans plusieurs types de pages, mais pas tous.
- pour remplacer l'héritage dans les langages de templating qui n'implémentent pas cette relation.
Il y existe 2 types de templates servant à faire de la composition :
- les templates partiels, qui restent une solution limitée, comme tu vas le voir bientôt
- les macros, qui sont très similaires à la notion de composant
Les templates partiels
Un template partiel n'est ni plus ni moins qu'un morceau de template stocké dans un fichier indépendant. Voici l'exemple du template partiel author.njk représentant un auteur :
<div class="author">
<img src="/public/img/" alt="" class="author-photo"/>
<div>
<p class="author-name"></p>
<p class="author-bio"></p>
</div>
</div>
Pour intégrer ce template partiel dans le template d'article, j'ajoute le code suivant à l'endroit souhaité dans le template d'article (début ou fin par exemple) :
{% set author=metadata.authors | getAuthorById(authorId) %}
{% include "components/author.njk" %}
La première ligne récupère l'auteur en filtrant la collection metadata.authors
sur l'id de l'auteur représenté par la propriété authorId
de l'article. Avec l'instruction set
elle stocke l'auteur dans la variable author
, qui est bien celle qu'on utilise dans le composant author.njk.
Important
Le point important à bien noter ici est que le template d'auteur a accès au contexte du template d'article et donc à l'auteur. À la différence d'un composant, un template partiel ne possède pas son propre contexte de données, mais partage celui de son hôte.
Lors de la génération des pages HTML à partir des templates, chaque page d'article intégrera le code généré par le template partiel.
Warning
Les templates partiels sont une technique simple pour centraliser du contenu statique. Mais le fait qu'ils partagent le contexte de leur hôte et ne soient pas paramétrables, en réduit beaucoup l'intérêt. En effet, cela crée un couplage fort entre les templates partiels et leurs hôtes, ce qui va à l'encontre d'une architecture modulaire et facile à maintenir sur le long terme.
Les macros (= composants)
Pour éviter le problème de couplage entre les templates partiels et leurs hôtes, certains langages de templates modernes proposent la notion de macro, qui est très semblable à celle de composants qu'on trouve dans les frameworks comme Nuxt.js.
Exemple
Reprenons notre template partiel author.njk représentant un auteur, et transformons-le en macro :
{%- macro Render(author) %}
<div class="author">
<img src="/public/img/{{ author.photo }}" alt="{{ author.name }}" class="author-photo"/>
<div>
<p class="author-name">{{ author.name }}</p>
<p class="author-bio">{{ author.bio }}</p>
</div>
</div>
{% endmacro %}
Une macro est équivalente à une fonction et peut donc recevoir des paramètres. Ici, la macro Render
reçoit l'objet représentant l'auteur en paramètre.
Pour utiliser cette macro dans le template d'article :
{% set author=metadata.authors | getAuthorById(authorId) %}
{% import "../components/author.njk" as Author %}
{{ Author.Render(author) }}
- La première ligne récupère l'auteur dont l'identifiant est fourni par l'article courant.
- La seconde ligne importe le contenu du fichier de la macro et lui donne l'alias
Author
. Cet alias désigne en fait l'espace de noms associé au fichier, qui peut éventuellement contenir plusieurs macros. - La troisième ligne appelle la macro via cet alias, en lui passant l'auteur en paramètre.
Important
Le point clé : la macro ne partage pas le contexte du template hôte, mais reçoit en paramètre les données dont elle a besoin. Il n'y a donc aucun couplage entre les deux.
Remarque : on peut placer le code CSS du composant dans un fichier author.css et l'importer dans le template de base avec la syntaxe classique :
{% include "../components/author.css" %}
Ainsi le code CSS du composant sera partagé entre toutes ses instances si l'on utilise le composant à plusieurs endroits.
Avantages des macros par rapport aux templates partiels
Les macros :
- définissent un contexte isolé pour les variables, évitant tout couplage avec le code externe.
- peuvent recevoir des paramètres.
De plus, comme les templates partiels, les macros peuvent être imbriquées, c'est-à-dire s'appeler les unes les autres.
Ainsi, même avec des frameworks serveur sans notion de composant native, comme Flask (Python), Symfony (PHP), Jekyll (Ruby), Express.js et Eleventy (basés sur Node.js), il est possible de créer du code propre et modulaire grâce aux macros.
Note
Les macros sont particulièrement adaptées au rendu serveur statique. Elles ne remplacent pas la notion de composant côté client introduite par React.
Quels langages de templating permettent de créer des composants ?
Les langages de templating les plus évolués possèdent presque tous la notion de composant, qu'elle soit nommée composant, macro, mixin ou fonction, comme le montre le tableau suivant :
Langage de template |
Framework | Implémentation des composants |
Syntaxe type |
---|---|---|---|
Nunjucks | Node.js | macros | {% macro %} /{{ macro() }} |
Jinja2 | Flask, Django | macros | identique à Nunjucks |
Twig | Symfony | macros | identique à Nunjucks |
Blade | Laravel | composants | <x-component> |
Pug | Node.js | mixins | mixin /+mixin() |
EJS | Express.js | fonctions JS | <% function() { %> |
Conclusion
Maîtriser un langage de templating puissant est un atout majeur pour créer du code propre, modulaire et facile à lire et à maintenir. En particulier, les composants permettent de construire une architecture de code modulaire avec les frameworks serveur qui ne fournissent pas nativement la notion de composant.
Pour mon blog statique, j'utilise le langage de templating Nunjucks avec le framework Eleventy. J'ai découvert les deux en même temps, et il m'a fallu quelques jours pour apprivoiser la syntaxe Nunjucks, mais j'apprécie maintenant beaucoup sa puissance et son expressivité.
J'utilise l'héritage de template pour les layouts, et des composants pour les fils d'Ariane et les résumés d'articles (sur les pages de catégories d'articles). Mon architecture de code reste simple et propre, et je peux gérer des problématiques complexes telles que la traduction et la taxonomie de façon bien plus simple, personnalisable et performante qu'avec WordPress. C'est un vrai régal !
Pour finir, je serais curieux de savoir quels langages de templating tu utilises et si tu en es satisfait. Dis-moi tout en commentaire ! Et si cet article t'a été utile, merci de le liker pour aider à le faire connaître. 😉