Gérer une collection avec Eleventy

Avant [...], il y avait les flux RSS.

L'automatisation, ça a du bon

Comme évoqué dans mon premier article sur Eleventy, ce site est généré semi-automatiquement via l'utilisation de templates. Mais ce n'est pas tout ! Je voulais également, en écrivant un article, que celui-ci soit directement référencé dans l'index du blog et dans mon flux RSS. J'ai donc automatisé la récupération desdits articles, leur affichage au bon endroit et la génération d'un flux RSS : on va voir comment mettre ça en place.

Créer la collection

La première chose à faire est de créer une liste des articles. Encore une fois, l'équipe de développement rend les choses faciles, et on a simplement besoin de paramétrer la balise tags dans l'en-tête de nos fichiers. Ainsi, l'en-tête de l'article d'hier ressemble à ça :

---
title: "Eleventy, comment ça marche ?"
updated: 2025-02-21
excerpt: "Gérer des templates, utiliser un plugin de navigation."
tags:
    - posts
    - tutorials
---

On peut évidemment spécifier plusieurs tags, mais la seule chose à garder en tête (!) est de toujours utiliser les mêmes. En effet, le générateur va créer une collection par tag et il faut donc avoir une seule étiquette pour regrouper tous les articles, même si certains peuvent en avoir plusieurs (comme c'est le cas ici).

Il suffit ensuite de récupérer et afficher cette collection dans notre page d'index, comme ceci :

{% set postslist = collections.posts %}
{%- css %}.postlist { counter-reset: start-from {{ (postslistCounter or postslist.length) + 1 }} }{% endcss%}
<ul reversed class="postlist">
    {%- for post in postslist | reverse %}
    <li class="postlist-item{% if post.url == url %} postlist-item-active{% endif %}">
        <time class="postlist-date" datetime="{{ post.date | htmlDateString }}">
            ({{ post.date | htmlDateString }})
        </time>
        <a href="{{ post.url }}" class="postlist-link">
            {% if post.data.title %}{{ post.data.title }}{% else%}<code>{{ post.url }}</code>{% endif %}
        </a>
    </li>
    {%- endfor %}
</ul>

Eleventy se charge de remplacer les balises et on n'a pas besoin de chercher l'URL de l'article : un simple appel à {{ post.url }} suffit à mettre les bonnes infos au bon endroit.

Il n'y a plus qu'à paramétrer le template de l'article et tout est bon : on peut donc regarder du côté du flux RSS.

Les flux RSS, c'est bien

Avant de recevoir des push-notifications sur nos téléphones intelligents, il y avait d'autre moyens de savoir si un site avait ajouter du contenu sans ouvrir son navigateur web : les flux RSS. D'ailleurs, peut-être que ces notifications sont récupérées via un flux RSS, mais je n'en ai aucune idée.

Digression

Les plus averti⋅e⋅s d'entre vous auront peut-être remarqué que je parle de flux RSS alors que je génère un flux Atom. C'est vrai. Les deux formats sont souvent désignés sous le seul terme RSS pour des raisons que j'ignore, mais probablement parce que c'est plus parlant de désigner deux choses très proches par le même terme. Je sais aussi que le format Atom est un peu plus flexible que le format RSS, et que je vois en général des flux Atom et pas RSS. De toute manière, les outils actuels permettent d'utiliser les deux formats de manière transparente.

On reprend

Une fois de plus, on va automatiser les choses (promis, c'est la dernière fois que je le dis) et utiliser ce plugin. Et puisqu'on a déjà créé nos collections d'articles, on a déjà fait la moitié du boulot : on va maintenant créer notre template de flux au format Atom, et y insérer le contenu qu'on souhaite comme on le souhaite. Ici, deux solutions :

  • utiliser le modèle de base et ne spécifier que les métadonnées (URL de base, nom d'auteurice, etc.) ;
  • créer son propre template et gagner en granularité.

Faire confiance, c'est parfois plus simple

La première méthode est très simple, il n'y a qu'une table à fournir au plugin dans le fichier eleventy.config.js :

import feedPlugin from "@11ty/eleventy-plugin-rss";
import pluginNavigation from "@11ty/eleventy-navigation";

export default async function (eleventyConfig) {
	eleventyConfig.addPlugin(feedPlugin, {
		type: "atom", // or "rss", "json"
		outputPath: "/feed.xml",
		templateData: {
			eleventyNavigation: {
				key: "RSS",
				order: 4,
			},
		},
		collection: {
			name: "posts",
			limit: 10,
		},
		metadata: {
			title: "David JULIEN",
			subtitle:
				"Le blog d'un doctorant en informatique... Mais on pourrait parler de cuisine, aussi.",
			base: "https://davidjulien.xyz/",
			language: "fr",
			description:
				"Ce site a surtout vocation à compiler mes travaux de recherches, ainsi qu'à accueillir mon blog.",
			author: {
				name: "David JULIEN",
				email: "swytch@mailo.com",
				academic: "david.julien@univ-nantes.fr",
				url: "https://davidjulien.xyz/contact/",
			},
		},
	});
}

Et voilà. À la génération du site, le plugin feedPlugin se chargera de créer le fichier feed.xml et de le placer à la racine, tandis que le plugin eleventyNavigation se chargera de faire apparaître un lien dans la barre de navigation, en haut.

Un peu de contrôle, ça ne coûte pas très cher

Et donc la deuxième méthode est un poil plus compliquée, puisqu'il faut disposer d'un modèle de flux RSS Atom. Mais l'équipe est (décidément) sympa, et elle nous en fournit un !

On le copie quelque part (je l'ai mis dans le dossier /content/) et on l'adapte à nos besoins. On va d'abord nettoyer l'en-tête et la passer en YAML, pour ne garder que ce qui nous est important :

---
eleventyComputed:
    permalink: "{{ metadata.rss }}"
eleventyNavigation:
    key: RSS
    order: 99
---

Notez l'utilisation des balises, ça fontionne aussi dans l'en-tête ! On spécifie ensuite le corps de notre flux. Personnellement je n'ai pas changé beaucoup de choses, surtout réagencé les propriétés. Ce qui nous intéresse, c'est l'intégration de nos articles :

{%- for post in collections.posts | reverse %}
    {%- set absolutePostUrl %}{{ post.url | htmlBaseUrl(metadata.url) }}{% endset %}
    <entry>
        <title>{{ post.data.title }}</title>
        <link rel="alternate" type="text/html" href="{{ absolutePostUrl }}" />
        <published>{{ post.date | dateToRfc3339 }}</published>
        <updated>{{ (post.data.updated or post.date) | dateToRfc3339 }}</updated>
        <id>{{ absolutePostUrl }}</id>
        <content type="html">{{ post.content | renderTransforms(post.data.page, metadata.url) }}</content>
    </entry>
{%- endfor %}

Encore une fois rien de bien méchant, le plus dur c'est de savoir quelles balises mettre. Vous pouvez aussi voir qu'on peut mettre du « vrai » code qui intègre de la logique, et pas simplement des balises à remplacer :

<updated>{{ (post.data.updated or post.date) | dateToRfc3339 }}</updated>

Avec ce petit bout de code, le générateur vérifie si la balise updated existe pour la formatter et l'insérer dans la balise XML ; si elle n'existe pas, il utilise la date de création de l'article.

On colle tout ça au bon endroit dans notre fichier et on obtient un modèle prêt à l'emploi. Et l'emploi est cette fois-ci encore plus simple : toujours dans eleventy.config.js, on spécifie simplement qu'on appelle notre plugin :

import pluginRss from "@11ty/eleventy-plugin-rss";

export default async function (eleventyConfig) {
	eleventyConfig.addPlugin(pluginRss);
}

Et le tour est joué. Notez que ce n'est pas le même plugin que plus haut : feedPlugin est devenu pluginRss.

That's all, folks!

Je pense qu'on a fait le tour de ce que je voulais dire aujourd'hui.

À plus !