<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="https://jamstatic.fr/xsl/atom.xsl" media="all"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="fr">
  <id>https://jamstatic.fr/</id>
  <title>Jamstatic</title>
  <subtitle><![CDATA[Jamstatic, la communauté des sites statiques et des architectures découplées.]]></subtitle>
  <link href="https://jamstatic.fr/feed.xml" rel="self" type="application/atom+xml" />
  <link href="https://jamstatic.fr/" rel="alternate" type="text/html" />
  <updated>2026-03-28T02:48:36+00:00</updated>
  <author>
    <name>Jamstatic</name>
    <uri>https://jamstatic.fr/</uri>
  </author>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2023/08/28/migration-site-cecil/</id>
    <title>Pourquoi et comment j’ai migré Jamstatic.fr de Hugo vers Cecil</title>
    <published>2022-08-28T00:00:00+00:00</published>
    <updated>2026-01-28T00:00:00+00:00</updated>
    <link href="https://jamstatic.fr/2023/08/28/migration-site-cecil/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>À la fin de l’année dernière j’avais entrepris de pérenniser le travail de refonte du site, engagé avec <a href="https://frank.taillandier.me" target="_blank" rel="noopener noreferrer">Frank</a> : nouveau logo et nouvelle charte graphique, impliquant la modification des templates et de la feuille de styles.</p>
<p>C’est la version sur laquelle vous lisez cette article 😊</p>
<p>Pour rappel, avant le site ressemblait à ça :</p>
<figure>
<picture title="Capture d’écran de la v1 de Jamstatic.fr">
<source type="image/webp" srcset="/thumbnails/768x/images/jamstatic-v1-screenshot.d51a114fac3326bfb6b8fc8b463cbdee.webp 768w, /images/jamstatic-v1-screenshot.d51a114fac3326bfb6b8fc8b463cbdee.webp 890w" width="890" height="587" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/jamstatic-v1-screenshot.d51a114fac3326bfb6b8fc8b463cbdee.avif 768w, /images/jamstatic-v1-screenshot.d51a114fac3326bfb6b8fc8b463cbdee.avif 890w" width="890" height="587" sizes="100vw">
<img src="/images/jamstatic-v1-screenshot.d51a114fac3326bfb6b8fc8b463cbdee.png" alt="Capture d’écran de la v1 de Jamstatic.fr" loading="lazy" decoding="async" class="dark:brightness-90" width="890" height="587" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/jamstatic-v1-screenshot.d51a114fac3326bfb6b8fc8b463cbdee.png 768w, /images/jamstatic-v1-screenshot.d51a114fac3326bfb6b8fc8b463cbdee.png 890w" sizes="100vw">
</picture>
<figcaption><a href="https://web.archive.org/web/20201012071702/https://jamstatic.fr/" target="_blank" rel="noopener noreferrer">Capture d’écran de la v1 de Jamstatic.fr</a></figcaption>
</figure>
<h2 id="pourquoi-changer-de-solution">Pourquoi changer de solution ?</h2>
<p>Au tout début je m’étais concentré sur la modification des feuilles de styles en m’appuyant sur <a href="https://tailwindcss.com/" target="_blank" rel="noopener noreferrer">Tailwind CSS</a> (comme <a href="https://github.com/jamstatic/jamstatic-fr/pull/255" target="_blank" rel="noopener noreferrer">souhaité par Frank</a>), puis sur la modification des <a href="https://gohugo.io/templates/introduction/" target="_blank" rel="noopener noreferrer">templates Go</a>.<br>
Puis, je me suis rapidement arraché les cheveux sur le moteur de templates d’Hugo... En effet, ne le pratiquant pas régulièrement, j’étais systématiquement en train de consulter la documentation pour savoir comment réaliser des actions pourtant basiques : comment manipuler une variable, gérer les héritages, les inclusions, etc.<br>
Bref, une perte de temps importante et une motivation se réduisant de jour en jour.</p>
<p>J’ai alors décidé de <a href="https://github.com/jamstatic/jamstatic-fr/pull/343" target="_blank" rel="noopener noreferrer">sauter le pas</a> et de migrer vers <a href="https://cecil.app/" target="_blank" rel="noopener noreferrer">Cecil</a>, pour 2 raisons majeures :</p>
<ol>
<li>Je connais la solution sur le bout des doigts (et pour cause puisque j’en suis <a href="https://arnaudligny.fr/blog/cecil-mon-generateur-de-site-statique/" target="_blank" rel="noopener noreferrer">le créateur</a> ^^) ;</li>
<li>Et surtout je suis à l’aise, et donc efficace, avec le moteur de template <a href="https://twig.symfony.com/" target="_blank" rel="noopener noreferrer">Twig</a>.</li>
</ol>
<h2 id="comment">Comment ?</h2>
<p>Après le "pourquoi ?" intéressons nous maintenant à la partie la plus intéressante de ce billet, à savoir le "comment ?" 😊</p>
<p>Le principe de génération du site, la structure des contenus et l’organisation des templates étant relativement proches entre Hugo et Cecil, j’ai décidé de procéder par itérations successives plutôt que de repartir d’une page blanche, selon la boucle suivante :</p>
<ol>
<li>J’effectue une modification ;</li>
<li>Je lance un nouveau build ;</li>
<li>J’effectue les ajustements nécessaires (en m’appuyant sur les messages d’erreur retournés) ;</li>
<li>Je recommence jusqu’à ce que le build soit valide.</li>
</ol>
<h3 id="gestion-de-contenu">Gestion de contenu</h3>
<p>Nous pouvons séparer les contenus en 2 catégories :</p>
<ol>
<li>Les <em>pages</em>, c’est à dire les articles rédigés dans le format <a href="https://fr.m.wikipedia.org/wiki/Markdown" target="_blank" rel="noopener noreferrer">Markdown</a></li>
<li>Les <em>assets</em>, c’est à dire les illustrations et autres vidéos au sein des articles</li>
</ol>
<p>Aussi, je me suis tout d’abord concentré sur la réorganisation de ces contenus :</p>
<ol>
<li>Déplacement des fichiers <em>*.md</em> du dossier <code>content/</code> de Hugo vers le dossier <code>pages/</code> de Cecil</li>
<li>Renommage des fichiers dans la section <em>post</em> de manière à les préfixer avec la date de l’article (<code>YYYY-MM-DD_Titre.md</code>) et ainsi faciliter leur tri</li>
<li>Déplacement des fichiers médias dans le dossier dédié de Cecil (<code>asset/</code>)</li>
</ol>
<h3 id="mise-en-forme-et-templates">Mise en forme et templates</h3>
<p>Le changement de moteur de templates aura certainement été le plus gros du travail, mais qui aura permis d’optimiser le rendu, via :</p>
<ul>
<li>La rationalisation et la factorisation des templates de manière à ne pas/plus dupliquer du code</li>
<li>La suppression des templates inutiles, c’est à dire disponibles en natif avec Cecil (tel que celui du flux RSS)</li>
</ul>
<h3 id="dependances-et-scripts">Dépendances et scripts</h3>
<p>Cecil supportant nativement (entre autre) la minification des scripts et des feuilles de styles, il n’est plus nécessaire d’installer certaines dépendances requises par Hugo. J’ai pu de fait, réduire le fichier <code>package.json</code> au strict minimum : à savoir le support de Tailwind CSS et de All Contributors.</p>
<p>De plus, il existe des <a href="https://cecil.app/themes/components/" target="_blank" rel="noopener noreferrer"><em>Themes Components</em></a> pour Cecil, c’est à dire des thèmes utilitaires, dont <a href="https://github.com/Cecilapp/theme-pwa#readme" target="_blank" rel="noopener noreferrer">PWA</a> permettant de transformer n’importe quel site web généré avec Cecil en Progessive Web App.</p>
<h3 id="image-de-partage">Image de partage</h3>
<p>L’image de partage sur les réseaux sociaux était créée de manière semi-automatique via l’<a href="https://cloudinary.com/documentation/transformation_reference#l_text" target="_blank" rel="noopener noreferrer">API HTTP de manipulation d’image de Cloudinary</a>, en paçant le lien (fabriqué à la main) dans le front matter de chaque post.</p>
<p>Même si ça fonctionnait plutôt bien en pratique, cette méthode était contraignante puisqu’il fallait recréer l’URL pour chaque nouvel article, en veillant à l’encoder correctement.</p>
<p>De plus le service de Cloudinary était sollicité à chaque affichage du post concerné, alors même que l’image n’était plus modifiée.</p>
<p>Aussi, j’ai délégué la construction de cette URL au template <a href="https://github.com/jamstatic/jamstatic-fr/blob/master/layouts/post/page.html.twig#L1" target="_blank" rel="noopener noreferrer"><code>post/page.html.twig</code></a> :</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">set</span></span> cloudinary_url = 'https://res.cloudinary.com/%s/image/upload/f_auto,q_auto/w_1100,c_fit,co_white,g_north_west,x_80,y_120,l_text:poppins_80_ultrabold_line_spacing_-30:%s/%s'|<span class="hljs-keyword">format</span>(site.cloudinary.account, page.title|<span class="hljs-keyword">url_encode</span>|<span class="hljs-keyword">replace</span>({'%2C':'%252C'}), 'jamstatic/twitter-card.png') %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">set</span></span> image = asset(cloudinary_url, {filename: 'cards/%s.png'|<span class="hljs-keyword">format</span>(page.id)}) %}</span></code></pre>
<p>En pratique :</p>
<ul>
<li>Création d’une image « modèle » (en veillant à garder de la place pour y intégrer le titre du billet)</li>
<li>Utilisation de la fonction <a href="https://cecil.app/documentation/templates/#asset" target="_blank" rel="noopener noreferrer"><code>asset()</code></a> pour manipuler l’image générée par Cloudinary :<ol>
<li>Format automatique et qualité automatique</li>
<li>Définition de la largeur maximum du texte avant retour à la ligne (1100 px) et positionnement du texte aux coordonnées 80:120</li>
<li>Utilisation de la police de caractère Poppins 80 ultra bold interligne -30</li>
<li>Remplacement des virgules présentes dans le titre par des espaces</li>
<li>Encodage de l’URL obtenue</li>
<li>Enfin, enregistrement de l’asset avec un nom de fichier "slugifié" afin d'être compatible avec les contraintes de nommage Windows, au format PNG</li>
</ol>
</li>
</ul>
<p>Exemple :</p>
<figure>
<picture title="Exemple d’une Twitter Card">
<source type="image/webp" srcset="/thumbnails/768x/images/twitter-card-example.de9a9418abb32b1e49805c7925f88556.webp 768w, /thumbnails/1024x/images/twitter-card-example.de9a9418abb32b1e49805c7925f88556.webp 1024w" width="1024" height="512" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/twitter-card-example.de9a9418abb32b1e49805c7925f88556.avif 768w, /thumbnails/1024x/images/twitter-card-example.de9a9418abb32b1e49805c7925f88556.avif 1024w" width="1024" height="512" sizes="100vw">
<img src="/images/twitter-card-example.de9a9418abb32b1e49805c7925f88556.png" alt="Exemple d’une Twitter Card" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="512" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/twitter-card-example.de9a9418abb32b1e49805c7925f88556.png 768w, /thumbnails/1024x/images/twitter-card-example.de9a9418abb32b1e49805c7925f88556.png 1024w" sizes="100vw">
</picture>
<figcaption>Exemple d’une Twitter Card</figcaption>
</figure>
<h2 id="conclusion">Conclusion</h2>
<p>Pour conclure rapidement, je dirais que l’important ici n’est pas la solution utilisée pour générer le site : c’est la source de la donnée, le contenu, en l’occurrence les articles. Et, dans le cas de cette migration technique, ce qui a fait la différence c’est le fait que les articles ont été rédigés dans un format standardisé, <a href="https://fr.wikipedia.org/wiki/Markdown" target="_blank" rel="noopener noreferrer">Markdown</a>, indépendant de la solution technique.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://arnaudligny.fr/blog/moteur-de-recherche-algolia-site-statique/</id>
    <title>Un moteur de recherche sur un site statique grâce à Algolia</title>
    <published>2021-07-26T00:00:00+00:00</published>
    <link href="https://arnaudligny.fr/blog/moteur-de-recherche-algolia-site-statique/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Billet initialement publié sur le <a href="https://arnaudligny.fr/blog/moteur-de-recherche-algolia-site-statique/" target="_blank" rel="noopener noreferrer">blog d’Arnaud Ligny</a>.</p></aside>
<p>Quand je travaillais à enrichir la <a href="https://cecil.app/documentation/" target="_blank" rel="noopener noreferrer">documentation</a> de <a href="https://cecil.app" target="_blank" rel="noopener noreferrer">Cecil</a>, je me suis dit qu’il serait pertinent d’offrir un moteur de recherche <em><a href="https://fr.m.wikipedia.org/wiki/Recherche_plein_texte" target="_blank" rel="noopener noreferrer">full text</a></em> aux utilisateurs.</p>
<figure>
<picture title="Exemple de résultat de recherche">
<source type="image/webp" srcset="/thumbnails/768x/images/2021-07-26-moteur-de-recherche-algolia-site-statique/cecil.app_documentation_templates_search.f1a7905828dddde1d0179aec8e2b10df.webp 768w, /images/2021-07-26-moteur-de-recherche-algolia-site-statique/cecil.app_documentation_templates_search.f1a7905828dddde1d0179aec8e2b10df.webp 783w" width="783" height="427" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/2021-07-26-moteur-de-recherche-algolia-site-statique/cecil.app_documentation_templates_search.f1a7905828dddde1d0179aec8e2b10df.avif 768w, /images/2021-07-26-moteur-de-recherche-algolia-site-statique/cecil.app_documentation_templates_search.f1a7905828dddde1d0179aec8e2b10df.avif 783w" width="783" height="427" sizes="100vw">
<img src="/images/2021-07-26-moteur-de-recherche-algolia-site-statique/cecil.app_documentation_templates_search.f1a7905828dddde1d0179aec8e2b10df.png" alt="Exemple de résultat de recherche" loading="eager" decoding="async" class="dark:brightness-90" width="783" height="427" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAE9klEQVR4nO1bW5LcIAyUUnP/86UqlbMoH8NDEhIgjJlHtqu82BgEVrtBMF78/ecvgQEEAMSaAiBL8wGA8CyU01wbsxFeL+XpOmba1BeWnbZke7KcQnpqInJSiKXpT3Ymd6rpYAe/unddS9TcJOBZZFSd75ZVu7lXukDd8p+GPiEdEKWj5ihSeOGI4TaDckOFi8+hINrPOUIGSqEXKeMbMSakqMB2AOVhY7My6MOVsYrYkBXyxv+lDByks5gnxFMKj6BY2Yhdefm5yuAkWMcMgpM6iUQQgarMlLXvUUa+4M5fIeYR7gEBEAJgcRCW/LaHfTu8LCUJ8nUGpUJR2Z8G7x/x5U/nHfJuLYa919YCtyjjsIDEyIAyX9+LKGV5HQJlmKeYM75sziiH8rS4ZMSMsE4IAERfy/3KCL4MG+ApQ2QZqrHKWQRdJASST2gqldWuK+NVKuopA6AlTfez1+/rhFzEfXPGXrpWlKF7MdOjeJSVu4BWOsaOaEoFaMegQ1y2Y1TuEy/IGCAn1XAVQiBGE7N7clu+6XIX960z9s4rq8pYRVAhCMB+C9FkyN8lJP5XZYRsw2AOKSoZvnTtj0S+zR9l9LA4h2gQECBgeYUJsnROrcDvUM9JZWQMoyyhEiJ2PO+S96YbYa4w+KMME4sKoeIHhLS3RQiESQ2IP8pwDI/6Ok0IyT+dNjEPYIUE5E+FO1yY1TYfbo/Qc3LJWhiaoljb7c0n5THK4zRASMrZpYzU3LcpIyO8Us8ieaak0t62ycbXatrUeH5p54w6QZyYMzQ2RVkc9/b8nDJQKYN6A4FrONrXoULQSsuOCToplDnkNVDK6G05TCvjzMPEFTIkQ321uKmjEUgVsavedKdm7jp/GBtS+tywtfrcIUIQnyFtdnxNHIWk83MgGXmJyE7lqWqeOUhR42p3opgnBKGQYKrAVEg9OcELV0YJt/UNXcHLIvu6e86bUfVnuZkiBNXRNO962/oq5Q7YyiCk+jo4HiH3os2PBHfd6w66hHgvVTM+u6GPvnEPKz1lyC9kONB++3XWolI82yNuHrMuyovB/LAWB888udFYtkt4hW28zClj5gXdqRSX6ImOLH1K2vwPBWurbDqqDsi+7FlpyRdUNlZ3E/TRsWMUKVmTStHfOOv6Ol8fcwphhWrk+FRL3TCsgwXRc/4nGGxdXeFEjlPPxFMGqnoDs8OyNyrlEXIK8uEKG58gTzkpvsFA4xXhOYPm2to5p1hKsWzrpqbDXmT/V8bPt2DV3KQyzLXgmyrlEXWuJAZArzPW5u04I0J5A2UEos5aTZxktJHZbqUEV+q2Sq6RwRuIFW9IUXam1oJvppSwQgAqGXWbpFw1TjiqFJ4JNSK2opxZ28WQe8++1uc9pRDLC28ucjIslXDvnFRKtPq7KIWTAbSqEGDENNXtHd67lcLhRXbXlTK+ZzqbZKrrEit/6QcqQx8vVcoKdiqFE+6R0ezmkzx/GK94F+4GI28Eq05Ozykz2K2URhUDdVjELSskG9DbzDLTzvsWpVjEjIgYEpJwaciy+tz67juU4s4VwJy9ogx1vkTISBQmdijlALw5wpqsSz4Z5x4hVjsMywrJkYwX0bTYoZR70FOKGIr0hJ1Tw9MrP8UDADyOu+QDlOJO0nrdoMryNEICxw3fZY3wGUoxiZghRNsL4gWEJLyxUrpk9EjZgH9BSi2hz4ct7wAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/2021-07-26-moteur-de-recherche-algolia-site-statique/cecil.app_documentation_templates_search.f1a7905828dddde1d0179aec8e2b10df.png 768w, /images/2021-07-26-moteur-de-recherche-algolia-site-statique/cecil.app_documentation_templates_search.f1a7905828dddde1d0179aec8e2b10df.png 783w" sizes="100vw">
</picture>
<figcaption>Exemple de résultat de recherche</figcaption>
</figure>
<!-- break -->
<div id="toc"><ul>
<li><a href="#quelle-solution-technique">Quelle solution technique ?</a><ul>
<li><a href="#google-cse">Google CSE</a></li>
<li><a href="#algolia">Algolia</a></li>
</ul>
</li>
<li><a href="#etapes-clefs">Étapes clefs</a><ul>
<li><a href="#creer-un-index">Créer un index</a></li>
<li><a href="#transmettre-l-index">Transmettre l’index</a></li>
<li><a href="#parametrer-l-index">Paramétrer l’index</a></li>
</ul>
</li>
<li><a href="#mise-en-艙uvre">Mise en œuvre</a><ul>
<li><a href="#creation-de-l-index">Création de l’index</a></li>
<li><a href="#transmission-de-l-index">Transmission de l’index</a></li>
</ul>
</li>
<li><a href="#conclusion">Conclusion</a></li>
</ul></div>
<p>La documentation de Cecil est composée de moins de 10 pages : une par thématique (configuration, gestion des contenus, création des templates, etc.) et chacune d’elle contient de nombreuses sections, accessibles par des ancres.</p>
<p>Aussi, il est important que les résultats retournés par un moteur de recherche soient granulaires, c’est à dire qu’ils ciblent ces sections au sein d’une page.</p>
<figure>
<picture title="Exemple de page de documentation">
<source type="image/webp" srcset="/thumbnails/768x/images/2021-07-26-moteur-de-recherche-algolia-site-statique/cecil.app_documentation_templates.179f85d51726ce73ae4d2796e89b0b05.webp 768w, /thumbnails/1024x/images/2021-07-26-moteur-de-recherche-algolia-site-statique/cecil.app_documentation_templates.179f85d51726ce73ae4d2796e89b0b05.webp 1024w" width="1024" height="521" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/2021-07-26-moteur-de-recherche-algolia-site-statique/cecil.app_documentation_templates.179f85d51726ce73ae4d2796e89b0b05.avif 768w, /thumbnails/1024x/images/2021-07-26-moteur-de-recherche-algolia-site-statique/cecil.app_documentation_templates.179f85d51726ce73ae4d2796e89b0b05.avif 1024w" width="1024" height="521" sizes="100vw">
<img src="/images/2021-07-26-moteur-de-recherche-algolia-site-statique/cecil.app_documentation_templates.179f85d51726ce73ae4d2796e89b0b05.png" alt="Exemple de page de documentation" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="521" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/2021-07-26-moteur-de-recherche-algolia-site-statique/cecil.app_documentation_templates.179f85d51726ce73ae4d2796e89b0b05.png 768w, /thumbnails/1024x/images/2021-07-26-moteur-de-recherche-algolia-site-statique/cecil.app_documentation_templates.179f85d51726ce73ae4d2796e89b0b05.png 1024w" sizes="100vw">
</picture>
<figcaption>Exemple de page de documentation</figcaption>
</figure>
<h2 id="quelle-solution-technique">Quelle solution technique ?</h2>
<h3 id="google-cse">Google <abbr title="Custom Search Engine">CSE</abbr></h3>
<p>Dans un premier temps j’ai expérimenté le <a href="https://cse.google.com/" target="_blank" rel="noopener noreferrer">moteur de recherche personnalisé de Google</a> (<abbr title="Custom Search Engine">CSE</abbr>) qui permet de présenter les résultats indexés par Google pour un site donné (comme avec le préfixe <code>site:</code>).<br>
Si les résultats sont pertinents pour un site contenant de nombreuses pages, il ne semble pas possible de personnaliser les résultats en fonction de sections au sein d’une même page, ce qui ne correspondant pas à mon besoin.</p>
<h3 id="algolia">Algolia</h3>
<p>Aussi, après plusieurs comparatifs, j’ai finalement retenu la solution <a href="https://www.algolia.com/" target="_blank" rel="noopener noreferrer">Algolia</a> pour les raisons suivantes :</p>
<ul>
<li>Pertinence des résultats</li>
<li><a href="https://www.algolia.com/pricing/" target="_blank" rel="noopener noreferrer">Tarifs abordables</a> (dont un plan gratuit)</li>
<li><a href="https://www.algolia.com/doc/" target="_blank" rel="noopener noreferrer">Documentation riche</a></li>
<li>Nombreuses bibliothèques de code <a href="https://github.com/algolia" target="_blank" rel="noopener noreferrer">open source</a></li>
</ul>
<h2 id="etapes-clefs">Étapes clefs</h2>
<p>Je souhaitais que le champ de recherche soit disponible sur chacune des pages et qu’il montre immédiatement un extrait des résultats lors de la saisie d’un ou plusieurs mots clefs, et laissant le choix à l’utilisateur de sélectionner la section à consulter : j’ai donc opté pour l’approche <a href="https://www.algolia.com/doc/ui-libraries/autocomplete/introduction/what-is-autocomplete/" target="_blank" rel="noopener noreferrer"><em>Autocomplete</em></a> (cf. la capture d’écran en début de billet).</p>
<h3 id="creer-un-index">Créer un index</h3>
<p>Algolia s’appuie sur un <a href="https://www.algolia.com/doc/guides/sending-and-managing-data/prepare-your-data/#algolia-index" target="_blank" rel="noopener noreferrer">index</a>, c’est à dire une collection d’enregistrements dans laquelle la recherche va être effectuée et dont le résultat permet d’afficher un certain nombre d’informations et de pointer vers la page Web correspondante.</p>
<p>Cet index est une <a href="https://www.algolia.com/doc/guides/sending-and-managing-data/prepare-your-data/#algolia-records" target="_blank" rel="noopener noreferrer">structure JSON</a> relativement libre, composée de couples clef-valeur, permettant d’avoir de la matière dans laquelle chercher et également ajuster les critères de recherche.</p>
<h3 id="transmettre-l-index">Transmettre l’index</h3>
<p>Algolia propose <a href="https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/" target="_blank" rel="noopener noreferrer">plusieurs méthodes</a> afin de transmettre ou de mettre à jour l’index :</p>
<ul>
<li>à la main, <a href="https://www.algolia.com/doc/guides/sending-and-managing-data/send-and-update-your-data/how-to/importing-from-the-dashboard/" target="_blank" rel="noopener noreferrer">via le dashboard</a>, en uploadant le fichier JSON</li>
<li>en ligne de command, via <a href="https://www.algolia.com/doc/tools/cli/get-started/overview/" target="_blank" rel="noopener noreferrer">Algolia CLI</a></li>
<li>programmatiquement, en <a href="https://www.algolia.com/doc/api-client/getting-started/install/php/" target="_blank" rel="noopener noreferrer">PHP</a>, en <a href="https://www.algolia.com/doc/api-client/getting-started/install/javascript/" target="_blank" rel="noopener noreferrer">JavaScript</a>, etc.</li>
</ul>
<h3 id="parametrer-l-index">Paramétrer l’index</h3>
<p>Le paramétrage de l’index, c’est à dire déterminer les attributs dans lesquels rechercher, le classement et l’ordonnancement, etc. est relativement simple à réaliser depuis le <a href="https://algolia.com/dashboard" target="_blank" rel="noopener noreferrer">dashboard</a>.</p>
<p>Je dis <em>relativement</em> car il peut être nécessaire d’effectuer quelques tests avant de maitriser les règles de priorisation des résultats.</p>
<figure>
<picture title="Dashboard Algolia">
<source type="image/webp" srcset="/thumbnails/768x/images/2021-07-26-moteur-de-recherche-algolia-site-statique/image-20221017142612522.e09546834795b634b29aba44b283b4e6.webp 768w, /thumbnails/1024x/images/2021-07-26-moteur-de-recherche-algolia-site-statique/image-20221017142612522.e09546834795b634b29aba44b283b4e6.webp 1024w" width="1024" height="526" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/2021-07-26-moteur-de-recherche-algolia-site-statique/image-20221017142612522.e09546834795b634b29aba44b283b4e6.avif 768w, /thumbnails/1024x/images/2021-07-26-moteur-de-recherche-algolia-site-statique/image-20221017142612522.e09546834795b634b29aba44b283b4e6.avif 1024w" width="1024" height="526" sizes="100vw">
<img src="/images/2021-07-26-moteur-de-recherche-algolia-site-statique/image-20221017142612522.e09546834795b634b29aba44b283b4e6.png" alt="Dashboard Algolia" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="526" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAACSUlEQVR4nO2b3XKEIAxGkw7v/4Q700ehFwLyqygJhG3OjVtnutB8HEWk+Pn8WrgCARAAABEQ3RGiI0RHfHOkJ/9edCeQtMGo/6EWx/lQM0jby/sR+oOuighgbtu1xfee56M+SMK6vuX1t9dDr4uuTK0rz1nvbu4DyRp63IIoRhNBsPYMJYzVKICkNC/qdRtIdURsFswKM96W5jKQuBPhY+sSJpqRRI4/9sqMIqyBGjUDiW+AXDdfbqaZQUg1kMSMxud/DeNVwgBCYnQZABbnFT5Ma26ulqzBeAksQDClHgDfg5xyYnyVa7WuBaOh8GKS+mamhNNJGJoIJyYf8tgxg1BL+DCYzbJs9nMbTYUD41cZw2rIgzm2mkJPNO091gH6DfFoKpSEJ3UEBIvPDAm/q5mQ4Qyxx+IZjCzDaSoUOENoFhLVlBEOFZ69oOpCU3mNBTAci4dqSi/lDYLBEI+m0o09o2mu9lKgpjyH0RCPptKi9kaT1RCPmtLPBEM8mkoPUwxR+vlZ3QElRQ0RhhoiDCZDCHaoDbCz9GqIMIgNOc2g2MY5yo6mTHwOWcF+iRS7Tt5hqx+lsJMpX26IZ59EzFhn2zq0RuXKe8sOpkw3BJEvlPuCz0lkJHj6QPL/gHVHCbMuj2RTmAwp969wmlG0fltxuaZMfoUrSBOQaQpxIGM7u6jZ0ZRJ24DkhFRDkilEgcgyI2cnUwgN2c+MHAmmDAYi24wcKaZcQWDI/mZIYsla1upLg2RTJgey/pIgHdJAbsedsDwkbvD4A0EKWDJcsJheAAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/2021-07-26-moteur-de-recherche-algolia-site-statique/image-20221017142612522.e09546834795b634b29aba44b283b4e6.png 768w, /thumbnails/1024x/images/2021-07-26-moteur-de-recherche-algolia-site-statique/image-20221017142612522.e09546834795b634b29aba44b283b4e6.png 1024w" sizes="100vw">
</picture>
<figcaption>Dashboard Algolia</figcaption>
</figure>
<h2 id="mise-en-艙uvre">Mise en œuvre</h2>
<p>Dans le cas de la documentation de <a href="/tags/cecil">Cecil</a>, il faut donc :</p>
<ol>
<li>créer un fichier d’index au format JSON</li>
<li>le transmettre à application Algolia</li>
<li>afficher un champs de recherche avec auto-complétion</li>
</ol>
<h3 id="creation-de-l-index">Création de l’index</h3>
<p>Avec <a href="/tags/cecil">Cecil</a> il est plutôt aisé de créer un fichier JSON puisque, par définition, c’est son job de générer des fichiers statiques 😊</p>
<p>Ainsi, l’objectif est de :</p>
<ol>
<li>collecter le contenu des pages de la documentation (fichiers au format Markdown dans <code>pages/documentation</code>), converti en HTML</li>
<li>découper ce contenu de manière cohérente (l'objectif n’est pas de pointer sur la page, mais bien sur une section de la page), via un <a href="https://cecil.app/documentation/templates" target="_blank" rel="noopener noreferrer">template Twig</a> sur mesure</li>
<li>générer un fichier <code>algolia.json</code> grâce aux <a href="https://cecil.app/documentation/configuration#formats" target="_blank" rel="noopener noreferrer">formats de sortie</a></li>
</ol>
<h4 id="resultat-cible">Résultat cible</h4>
<p>Le fichier d’index va ressembler à ça :</p>
<pre><code class="language-json hljs json">[
  {
    <span class="hljs-attr">"objectID"</span>: <span class="hljs-string">"documentation/quick-start#download-cecil"</span>,
    <span class="hljs-attr">"page"</span>: <span class="hljs-string">"Quick Start"</span>,
    <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Download Cecil"</span>,
    <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Download cecil.phar from your terminal:"</span>,
    <span class="hljs-attr">"content"</span>: <span class="hljs-string">"..."</span>,
    <span class="hljs-attr">"date"</span>: <span class="hljs-string">"2020-12-19T00:00:00+00:00"</span>,
    <span class="hljs-attr">"href"</span>: <span class="hljs-string">"documentation/quick-start/#download-cecil"</span>
  },
  ...
]</code></pre>
<h4 id="creation-du-template">Création du template</h4>
<p>Comme indiqué précédemment, dans le contexte de Cecil, pour créer ce fichier il est nécessaire de créer un template Twig qui va collecter les donner et les rendre au format JSON.</p>
<p>S’agissant de rechercher dans la documentation, j’aurais pu créer ce template dans le dossier « documentation » (<code>layouts/documentation/list.algolia.twig</code>).<br>
Mais comme je souhaite potentiellement étendre la recherche à plusieurs types de contenus (tels que les « news ») je préfère créer un template applicable à l’ensemble des contenus du site, donc une liste par défaut : <code>layouts/_default/list.algolia.twig</code>.</p>
<p>Ainsi, au sein du template, il suffit de boucler sur les contenus de la section « documentation », à l’aide du tag <code>for</code> :</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">for</span></span> p in site.pages|<span class="hljs-keyword">filter</span>(p =&gt; p.section == 'documentation')|sort_by_weight %}</span><span class="xml">
...
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> %}</span></code></pre>
<p>Ensuite, toute l’astuce consiste à « jouer » sur les header HTML, en l’occurence le « H3 » afin de découper le contenu d’une page en sous sections :</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">set</span></span> sections = p.content|preg_split('/&lt;h3[^&gt;]*&gt;/') %}</span></code></pre>
<blockquote>
<p>Le filtre Twig <a href="https://cecil.app/documentation/templates/#preg-split" target="_blank" rel="noopener noreferrer"><code>preg_split</code></a> à été créé pour l’occasion afin de permettre le découpage d’une chaine de caractère en un tableau, selon une expression régulière.</p>
</blockquote>
<p>De là, il suffit ensuite d’extraire les contenus cibles de chaque section, via de la manipulation de chaines de caractères, pour alimenter un « dataset » :</p>
<pre><code class="language-json hljs json">{
  <span class="hljs-attr">"objectID"</span>: <span class="hljs-string">"Un ID unique"</span>,
  <span class="hljs-attr">"page"</span>: <span class="hljs-string">"Le nom de la page de documentation"</span>,
  <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Le titre de section"</span>,
  <span class="hljs-attr">"description"</span>: <span class="hljs-string">"Le premier paragraphe de la section (utilisé pour illustrer l’aperçu des résiltats)"</span>,
  <span class="hljs-attr">"content"</span>: <span class="hljs-string">"Le contenu de la section, dans laquelle la recherche est effectuée"</span>,
  <span class="hljs-attr">"date"</span>: <span class="hljs-string">"La date de la page, utilisée pour pondérer les résultats"</span>,
  <span class="hljs-attr">"href"</span>: <span class="hljs-string">"Le lien vers la page de la documentation, combinée à une ancre afin d’emmener l’internaute à la bonne section"</span>,
}</code></pre>
<blockquote>
<p>Voir le <a href="https://github.com/Cecilapp/website/blob/master/layouts/_default/list.algolia.twig" target="_blank" rel="noopener noreferrer">template complet sur GitHub</a>.</p>
</blockquote>
<h4 id="associer-ce-template-a-un-format-de-sortie">Associer ce template à un format de sortie</h4>
<p>En l’état, Cecil ne sait pas qu’il faut utiliser ce template et surtout à quel type de contenu il doit être associé. C’est embêtant 😅</p>
<p>Pour régler ce soucis il suffit de compléter la configuration de la manière suivante :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">output:</span>
  <span class="hljs-attr">formats:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">algolia</span>
      <span class="hljs-attr">mediatype:</span> <span class="hljs-string">'application/json'</span>
      <span class="hljs-attr">filename:</span> <span class="hljs-string">'algolia'</span>
      <span class="hljs-attr">extension:</span> <span class="hljs-string">'json'</span>
  <span class="hljs-attr">pagetypeformats:</span>
    <span class="hljs-attr">homepage:</span> <span class="hljs-string">['html',</span> <span class="hljs-string">'atom'</span><span class="hljs-string">,</span> <span class="hljs-string">'algolia'</span><span class="hljs-string">]</span></code></pre>
<p>Maintenant Cecil sait que :</p>
<ol>
<li>Les pages dont la variable <code>format</code> a pour valeur le nom du format « algolia » peuvent utiliser un template de la forme <code>&lt;layout&gt;.algolia.twig</code></li>
<li>Enregistrer le fichier généré sous <code>filename.extension</code>, soit « algolia.json »</li>
<li>La page de type <code>homepage</code> (listant toutes les pages du site) doit être générée dans le format « algolia » (en plus de « html » et « atom »)</li>
</ol>
<p>Et voilà, l’index est maintenant généré et disponible à la racine du site généré : <a href="https://cecil.app/algolia.json" target="_blank" rel="noopener noreferrer">https://cecil.app/algolia.json</a>.</p>
<h3 id="transmission-de-l-index">Transmission de l’index</h3>
<p>Comme <a href="#transmettre-l-index">indiqué précédemment</a>, Algolia offre plusieurs méthodes d’envoi de l’index.</p>
<p>Dans un premier temps j’ai déposé manuellement le fichier généré localement directement depuis le dashboard : c’est un moyen simple et rapide de faire des tests d’intégrité de l’index.</p>
<p>J’ai ensuite cherché à automatisé cette procédure, et j’ai donc opté pour le client d’API JavaScript.<br>
<a href="https://cecil.app" target="_blank" rel="noopener noreferrer">Cecil.app</a> étant hébergé par Netlify, j’ai utilisé un plugin pour me simplifier la mise en oeuvre : <a href="https://github.com/reima-ecom/netlify-plugin-refresh-algolia" target="_blank" rel="noopener noreferrer"><em>Algolia Index Refresh Build Plugin</em></a> :</p>
<pre><code class="language-toml hljs ini"><span class="hljs-section">[[context.production.plugins]]</span>
  package = "netlify-plugin-refresh-algolia"
  <span class="hljs-section">[context.production.plugins.inputs]</span>
    appId = "APP_ID"
    indexName = "INDEX"
    filePath = "_site/algolia.json"</code></pre>
<h4 id="formulaire-de-recherche">Formulaire de recherche</h4>
<figure>
<picture title="Exemple de résultat de recherche">
<source type="image/webp" srcset="/thumbnails/768x/images/2021-07-26-moteur-de-recherche-algolia-site-statique/cecil.app_documentation_templates_search.f1a7905828dddde1d0179aec8e2b10df.webp 768w, /images/2021-07-26-moteur-de-recherche-algolia-site-statique/cecil.app_documentation_templates_search.f1a7905828dddde1d0179aec8e2b10df.webp 783w" width="783" height="427" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/2021-07-26-moteur-de-recherche-algolia-site-statique/cecil.app_documentation_templates_search.f1a7905828dddde1d0179aec8e2b10df.avif 768w, /images/2021-07-26-moteur-de-recherche-algolia-site-statique/cecil.app_documentation_templates_search.f1a7905828dddde1d0179aec8e2b10df.avif 783w" width="783" height="427" sizes="100vw">
<img src="/images/2021-07-26-moteur-de-recherche-algolia-site-statique/cecil.app_documentation_templates_search.f1a7905828dddde1d0179aec8e2b10df.png" alt="Exemple de résultat de recherche" loading="lazy" decoding="async" class="dark:brightness-90" width="783" height="427" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAE9klEQVR4nO1bW5LcIAyUUnP/86UqlbMoH8NDEhIgjJlHtqu82BgEVrtBMF78/ecvgQEEAMSaAiBL8wGA8CyU01wbsxFeL+XpOmba1BeWnbZke7KcQnpqInJSiKXpT3Ymd6rpYAe/unddS9TcJOBZZFSd75ZVu7lXukDd8p+GPiEdEKWj5ihSeOGI4TaDckOFi8+hINrPOUIGSqEXKeMbMSakqMB2AOVhY7My6MOVsYrYkBXyxv+lDByks5gnxFMKj6BY2Yhdefm5yuAkWMcMgpM6iUQQgarMlLXvUUa+4M5fIeYR7gEBEAJgcRCW/LaHfTu8LCUJ8nUGpUJR2Z8G7x/x5U/nHfJuLYa919YCtyjjsIDEyIAyX9+LKGV5HQJlmKeYM75sziiH8rS4ZMSMsE4IAERfy/3KCL4MG+ApQ2QZqrHKWQRdJASST2gqldWuK+NVKuopA6AlTfez1+/rhFzEfXPGXrpWlKF7MdOjeJSVu4BWOsaOaEoFaMegQ1y2Y1TuEy/IGCAn1XAVQiBGE7N7clu+6XIX960z9s4rq8pYRVAhCMB+C9FkyN8lJP5XZYRsw2AOKSoZvnTtj0S+zR9l9LA4h2gQECBgeYUJsnROrcDvUM9JZWQMoyyhEiJ2PO+S96YbYa4w+KMME4sKoeIHhLS3RQiESQ2IP8pwDI/6Ok0IyT+dNjEPYIUE5E+FO1yY1TYfbo/Qc3LJWhiaoljb7c0n5THK4zRASMrZpYzU3LcpIyO8Us8ieaak0t62ycbXatrUeH5p54w6QZyYMzQ2RVkc9/b8nDJQKYN6A4FrONrXoULQSsuOCToplDnkNVDK6G05TCvjzMPEFTIkQ321uKmjEUgVsavedKdm7jp/GBtS+tywtfrcIUIQnyFtdnxNHIWk83MgGXmJyE7lqWqeOUhR42p3opgnBKGQYKrAVEg9OcELV0YJt/UNXcHLIvu6e86bUfVnuZkiBNXRNO962/oq5Q7YyiCk+jo4HiH3os2PBHfd6w66hHgvVTM+u6GPvnEPKz1lyC9kONB++3XWolI82yNuHrMuyovB/LAWB888udFYtkt4hW28zClj5gXdqRSX6ImOLH1K2vwPBWurbDqqDsi+7FlpyRdUNlZ3E/TRsWMUKVmTStHfOOv6Ol8fcwphhWrk+FRL3TCsgwXRc/4nGGxdXeFEjlPPxFMGqnoDs8OyNyrlEXIK8uEKG58gTzkpvsFA4xXhOYPm2to5p1hKsWzrpqbDXmT/V8bPt2DV3KQyzLXgmyrlEXWuJAZArzPW5u04I0J5A2UEos5aTZxktJHZbqUEV+q2Sq6RwRuIFW9IUXam1oJvppSwQgAqGXWbpFw1TjiqFJ4JNSK2opxZ28WQe8++1uc9pRDLC28ucjIslXDvnFRKtPq7KIWTAbSqEGDENNXtHd67lcLhRXbXlTK+ZzqbZKrrEit/6QcqQx8vVcoKdiqFE+6R0ezmkzx/GK94F+4GI28Eq05Ozykz2K2URhUDdVjELSskG9DbzDLTzvsWpVjEjIgYEpJwaciy+tz67juU4s4VwJy9ogx1vkTISBQmdijlALw5wpqsSz4Z5x4hVjsMywrJkYwX0bTYoZR70FOKGIr0hJ1Tw9MrP8UDADyOu+QDlOJO0nrdoMryNEICxw3fZY3wGUoxiZghRNsL4gWEJLyxUrpk9EjZgH9BSi2hz4ct7wAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/2021-07-26-moteur-de-recherche-algolia-site-statique/cecil.app_documentation_templates_search.f1a7905828dddde1d0179aec8e2b10df.png 768w, /images/2021-07-26-moteur-de-recherche-algolia-site-statique/cecil.app_documentation_templates_search.f1a7905828dddde1d0179aec8e2b10df.png 783w" sizes="100vw">
</picture>
<figcaption>Exemple de résultat de recherche</figcaption>
</figure>
<p>La mise en œuvre est relativement simple :</p>
<ol>
<li>intégrer un champ de saisi (élément <code>input</code>)</li>
<li>lui associer la bibliothèque <em><a href="https://github.com/algolia/autocomplete/tree/v0" target="_blank" rel="noopener noreferrer">Autocomplete.js</a></em></li>
</ol>
<p><strong>Champ de saisie :</strong></p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"search-input"</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"{% trans %}Search the Docs [Alt+S]{% endtrans %}"</span> <span class="hljs-attr">accesskey</span>=<span class="hljs-string">"s"</span> /&gt;</span></code></pre>
<p><strong><em>Autocomplete.js</em> :</strong></p>
<pre><code class="language-javascript hljs javascript"><span class="hljs-comment">// client Algolia</span>
<span class="hljs-keyword">var</span> client = algoliasearch(<span class="hljs-string">'APP_ID'</span>, <span class="hljs-string">'API_KEY'</span>);
<span class="hljs-comment">// index dans lequel rechercher</span>
<span class="hljs-keyword">var</span> index = client.initIndex(<span class="hljs-string">'INDEX'</span>);
<span class="hljs-comment">// fonction de recherche</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">newHitsSource</span>(<span class="hljs-params">index, params</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">doSearch</span>(<span class="hljs-params">query, cb</span>) </span>{
    index
      .search(query, params)
      .then(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">res</span>) </span>{
        cb(res.hits, res);
      })
      .catch(<span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">err</span>) </span>{
        <span class="hljs-built_in">console</span>.error(err);
        cb([]);
      });
  };
}
<span class="hljs-comment">// association de la lib au champ "search-input"</span>
autocomplete(<span class="hljs-string">'#search-input'</span>, { <span class="hljs-attr">hint</span>: <span class="hljs-literal">false</span> }, [
  {
    <span class="hljs-attr">source</span>: newHitsSource(index, {
      <span class="hljs-comment">// paramètres de mise en valeur des résultats suggérés</span>
      <span class="hljs-attr">hitsPerPage</span>: <span class="hljs-number">4</span>,
      <span class="hljs-attr">attributesToHighlight</span>: [<span class="hljs-string">'description'</span>, <span class="hljs-string">'page'</span>, <span class="hljs-string">'title'</span>],
      <span class="hljs-attr">highlightPreTag</span>: <span class="hljs-string">'&lt;strong&gt;'</span>,
      <span class="hljs-attr">highlightPostTag</span>: <span class="hljs-string">'&lt;/strong&gt;'</span>,
      <span class="hljs-attr">attributesToSnippet</span>: [<span class="hljs-string">'description:25'</span>],
      <span class="hljs-attr">snippetEllipsisText</span>: <span class="hljs-string">'…'</span>
    }),
    <span class="hljs-comment">// clef d'affichage principale (ici le titre de la section)</span>
    <span class="hljs-attr">displayKey</span>: <span class="hljs-string">'title'</span>
  }
]).on(<span class="hljs-string">'autocomplete:selected'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">event, suggestion, dataset, context</span>) </span>{
  <span class="hljs-comment">// au clic sur une suggestion on envoie l'internaute vers la page#ancre correspondante</span>
  <span class="hljs-built_in">window</span>.location.href = <span class="hljs-string">'{{ url() }}'</span> + suggestion.href;
});</code></pre>
<blockquote>
<p>Voir le <a href="https://github.com/Cecilapp/website/blob/master/layouts/partials/search-box.html.twig" target="_blank" rel="noopener noreferrer">template complet sur GitHub</a>.</p>
</blockquote>
<p>Et voilà ! 🎉</p>
<p><strong>Notes :</strong></p>
<ol>
<li>Il s’agit ici de la version 0 de <em>Autocomplete.js</em> qui reste fonctionnelle mais commence à vieillir</li>
<li>La personnalisation de l’apparence des suggestions est un peu pénible car il faut arriver à « retrouver » les classes CSS générées à la volée via JavaScript, ce qui n’est pas toujours évident…</li>
</ol>
<h2 id="conclusion">Conclusion</h2>
<p>Je me suis bien amusé à créer ce moteur de recherche, et je suis plutôt satisfait de la fonctionnalité, qui est fonctionnelle et surtout très utile.</p>
<p>Pour tester, ça se passe par ici : <a href="https://cecil.app/documentation/" target="_blank" rel="noopener noreferrer">https://cecil.app/documentation/</a></p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://arnaudligny.fr/blog/generer-et-heberger-un-site-statique-avec-github/</id>
    <title>Générer et héberger un site web statique avec GitHub</title>
    <published>2021-06-29T00:00:00+00:00</published>
    <link href="https://arnaudligny.fr/blog/generer-et-heberger-un-site-statique-avec-github/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Billet initialement publié sur le <a href="https://arnaudligny.fr/blog/generer-et-heberger-un-site-statique-avec-github/" target="_blank" rel="noopener noreferrer">blog d’Arnaud Ligny</a>.</p></aside>
<p><a href="https://github.com" target="_blank" rel="noopener noreferrer">GitHub</a> fourni l’outillage nécessaire pour générer un site statique et pour l’héberger – gratuitement – grâce à <a href="https://pages.github.com/" target="_blank" rel="noopener noreferrer">GitHub Pages</a> et <a href="https://github.com/features/actions" target="_blank" rel="noopener noreferrer">GitHub Actions</a>.</p>
<p>Dans cet article j’explique comment mettre en pratique GitHub Pages et GitHub Actions, en utilisant <a href="https://cecil.app" target="_blank" rel="noopener noreferrer">Cecil</a> comme générateur de site statique.</p>
<!-- break -->
<div id="toc"><ul>
<li><a href="#qu-est-ce-que-github-pages">Qu’est-ce que GitHub Pages ?</a></li>
<li><a href="#qu-est-ce-que-github-actions">Qu’est-ce que GitHub Actions ?</a></li>
<li><a href="#en-pratique-comment-faire">En pratique, comment faire ?</a><ul>
<li><a href="#creation-du-workflow">Création du workflow</a></li>
<li><a href="#definition-des-taches-et-des-etapes">Définition des  tâches et des étapes</a></li>
<li><a href="#parametrage-de-github-pages">Paramétrage de GitHub Pages</a></li>
</ul>
</li>
</ul></div>
<h2 id="qu-est-ce-que-github-pages">Qu’est-ce que GitHub Pages ?</h2>
<picture>
<source type="image/webp" srcset="/images/2021-06-29-generer-et-heberger-un-site-statique-avec-github/github-pages.0df99148f84014ec9d2fba6cb01c2e38.webp" width="512" height="205">
<source type="image/avif" srcset="/images/2021-06-29-generer-et-heberger-un-site-statique-avec-github/github-pages.0df99148f84014ec9d2fba6cb01c2e38.avif" width="512" height="205">
<img src="/images/2021-06-29-generer-et-heberger-un-site-statique-avec-github/github-pages.0df99148f84014ec9d2fba6cb01c2e38.png" alt="GitHub Pages" loading="lazy" decoding="async" class="dark:brightness-90" width="512" height="205" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAD7UlEQVR4nO2aa5KjMAyEsT05yNz/ihj2V6c6SksIwtR4d91VrkAGv/RZssykfH9/78vUMKq/PYCpV00gg2kCGUwTyGCaQAbTBDKYJpDBNIEMpglkME0gg2kCGUxfZyuUUuT3+z5fid2hNJBSyrPgnrXv+0dQuO7/DPcQCIMopSy11hcwLBgyMqhXj+tyUXW5jajPvxFsCIQhcMF3/NyyxMbxQh2et2XbNglEeSa3ocZh2xkZlAsEk2+tLbXWt08bwjKy4CAFQnlJ5F32ea+Nfd+XUkrKozz4P6kQCDyitbZ8fX0trbVnsaErA0YBYQMqGJERPK9S97YvO44jAaRX7y5YEgiHKsBAwT2HrwyUoz0n4yFch+tt2/asZ68977FtRfISDjWWM+0qHQKxUB6PxxNMFLqO7u1EMkCUARhC7/157YHhdjLJh+fNdhxqLlfAuCGLYaA8Ho9nAZCMl0R/82Dwpn605wBGrfUFTO/9Zc/IAMlkcupTLSSVmBzpDYhKc2utLx4CIJkNnr/zJhvFf564rYPneu9L731Z13Xpvb/0sW3bUkqR7fG+YMeXzeZQAJ29EgvkDJQwZKnQhcJhKzqb2Inyp52UAsPGsAaCd6zrGq5oQOHxwIgWQubwq8YM6Krfj4HwwNVg7aZ/BES1ZyeJ61prKgazd/A9FoiCgOd4TnytvJ3rsaE9GPi7tVUWSgjkzs2K2zv6LpO1WYgwZvS81xfDsAdfCwSexaFIAThzPmNJIMolOVZbA1j3v6ozoL3wocKfDYPKc3DNIdoLrTYUfTpv1hsQNRnAWNf1DQTCxJ2DyrTFIWtd1+eGzsWmwqjHiwfjz4zFWzBe0nFFoYdwOomNk7OJ3vvLHvKpsqd+L8PiYsFYI7EnoC01Hhuy+HyjosjZtw1W7h7CE7bhCB1n0t7sYKJMTLVpgXDay0CskdA2n02U0awnecZX5ZP91vUQ3qgABR201t72kih3zyqCwjFbGcZC4XClDAMPscZrrblhWIXyo9c2Z3XoISzrGSjKiNmMClKHMs9L1D6n9gzlHdwfvMBCUWmz7Tf77uwWD4EYCA+UvcMa007A6ghKJnRF8TsykO3HAmHP8DxEZW93gIAO/2NoXwWoXD0ynPedWrFZIKhvDaSMFNW1ewnOF1h43L8KlwrCVRBQ6n/q3BkGnTl3RAZRugLEju+MUWx99O/Nz+vnDhDQqV+d2EzlqiIg6jrTzlWDsFGjBWH78jz9U53+GRB090Bsmz/RfqZ/3lei535Kl4H8y/qNxQDNXy4OpglkME0gg2kCGUwTyGCaQAbTBDKYJpDBNIEMpglkME0gg2kCGUwTyGD6A4lqOVNX3G8OAAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<p><a href="https://pages.github.com/" target="_blank" rel="noopener noreferrer">GitHub Pages</a> est une solution d’hébergement de pages web statiques permettant de créer rapidement un site web associé à un projet GitHub.\
Historiquement, cette branche dédiée d’un dépôt, utilise <a href="https://jekyllrb.com/" target="_blank" rel="noopener noreferrer">Jekyll</a> par défaut pour générer à la volée des pages web à partir de son contenu (fichiers Markdown et HTML).</p>
<p>Aujourd’hui de nombreux développeurs ont abandonnés Jekyll au profit d’autres générateurs de site statique, plus riches en fonctionnalités, afin de profiter de cette solution d’hébergement gratuite.</p>
<p>GitHub Pages est très facile à utiliser, puisqu’il suffit de commiter des fichiers dans un dépôt GitHub pour obtenir un site web, mais reste limité et ne propose que les réglages suivants :</p>
<ol>
<li>Choix de la branche (et du dossier : racine ou <code>/docs</code>) à utiliser</li>
<li>Possibilité de choisir un domaine personnalisé (via un enregistrement <code>CNAME</code>)</li>
<li>Activation de HTTPS</li>
</ol>
<p>Comme je viens de l’indiquer, l’idée ici de s’appuyer sur le <abbr title="Static Site Generator (Générateur de site statique)">SSG</abbr> de son choix : mais dans ce cas, comment automatiser la génération en cas de modification d’une page de contenu ou d’un template ? C’est là que GitHub Actions intervient  ! 😀</p>
<h2 id="qu-est-ce-que-github-actions">Qu’est-ce que GitHub Actions ?</h2>
<picture>
<source type="image/webp" srcset="/images/2021-06-29-generer-et-heberger-un-site-statique-avec-github/github-actions.fcd55e2dec8c410824a47d220e6ea036.webp" width="700" height="216">
<source type="image/avif" srcset="/images/2021-06-29-generer-et-heberger-un-site-statique-avec-github/github-actions.fcd55e2dec8c410824a47d220e6ea036.avif" width="700" height="216">
<img src="/images/2021-06-29-generer-et-heberger-un-site-statique-avec-github/github-actions.fcd55e2dec8c410824a47d220e6ea036.png" alt="GitHub Actions" loading="lazy" decoding="async" class="dark:brightness-90" width="700" height="216" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAE2UlEQVR4nO2ba3bjIAyFr1z2v9YuoBPNDyQh8XCcBCfuKfeM60diLPi4CNwpfX9/M5Yuo8S8eFxJ26cDWIpK2SDLJVfRtmBcSymmkAXn00p5t0BcRYNZ1gL0bhERAHOIaoH4tFwOWU75hNQZOlINcsgC8SmlNoUsp7xDtTNUd3LIAvFudRyiWk45QyNnqFL/cusUmhnVHxYzG5Se0r8OEQrH+QsMgEgOllNOUwOE3J7AYHKAxGaRy5XhkP67lPZ+5ZF+bvEC+Y38cSnE3hAzwLKvwTyUmmbJGl9BaIxXVuwuAYiHsUnl8i9M3LDlADADYDYobcV593S6CCAQQAxiiu7GRQzd2LWa9tZANtkzZSgg7xSWSnEGYWDkPDyDx4DOaBSiXFciibcdrpyxn4vhDWOfAVFnaPJmlJgJwMYZEmVbgNk5xK4h7ItOBGESAA0QoaId5hUg8ph44gq6V+YBoAHIBnFGVcYG4Aarlz2cmW3rgYF9vx66ZpKRWiqIB4A8FcUrQA6UmX5uzh0dZ+iItikN5xA4IF0wFmQ7nM2TpPEuEMo5T9ot5LqrA9kogmA4V/hn1XljsMVkX4asMj0YVWBUq5HfKxBEeQNyctfxN0xCEIAcaUdqDvSkBXKUS6/MdLvJgg+x8UOwVD3EjDIGcgjKkZlZG351tQKhkxBiEMhWxjr39xMQfqABfeMVDCcAYSqF9Db9jPxz5UO2bQeKzyldGH0wtnjS3j6oZgbiQcjwmsfYBkYGkjuITkz2GlDbyqbQTO77XOpQLu1EG8vlcJBPkk1pqd0suYTIXKkNQY6b//49GD0okDJ3pyfxLh2hiPNJ7ZB8B8fOVDVUKN2D0MUmSvVKBGMo/tk6nPZgAIz0NQDig6vZgEo7xQo01YEMHLYPFess3nxZvesll7HETWETz1glyO21YXTx2I+5cyXEqM4rUORKia4ayexOX3CTi7IaIJvu3TVzy6jwYZMW80izgIlBLBWQdtHesueDKF+iH7I8nBZGecuq95M5qhf78CJLzRoGrnxzFmJj9Wg7pa/NOUCA2GsTKiv36BDtYTsg5AdLVDaCMdttR5PfPcXErsOCAK+hAGCi3BF24tjtHKTeH1XikZrVQCgCMSgoYMwppZ/nSlWPDiDqXAGU5G7Hs9QDgjIhkPGFdS9h+ElLW+K+/Kyx9+kjsXtFh6APRPc+iXirFxBiW3YBA9b6Zfyf5w6vu0AklpJsX3na3s2zHALnElQOAdwYDPAm479NgfMwVkzgkXBvN5/Kg0Be6xgnAUlbuRygwANxryDk2z495ullPgsQqrh4eDJRXSBAWT/wJIeeU4H0JS2rDRwdUe2BMGPIyTFbhNmXUg9Z8aHnOQQNCOs6pDHRtYGk6k92/GxKnVEqGVO4VpqZqmlwq7e4Q0XBzO7yvVT9eZlDvKjeWydzA1Wvxjui4cnJorBcuLy6QGIVKmdQ29PuueMK+gUhAgDSI/3b/0eH16Vl+unnb+nHJ0h6dWpfgXDn2H8e7p8ZC35PPz5Paf/jMLktVye1W1jXrD/PBojUIced0SljUizLHcDw79T95Hes1YbzleL4fdwZtRacOdpZFh7XgjFPKY/dzzuj1oLzmqpXi69pwXhdnXXIHC04z2k7YzG2YDwvtw45RwvOY/oP23XZBb9do2oAAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<p><a href="https://github.com/features/actions" target="_blank" rel="noopener noreferrer">GitHub Actions</a> est la solution de GitHub pour automatiser des workflows de type intégration ou déploiement continue.</p>
<p>Le principe est très proche de ce que propose des outils comme <a href="https://www.jenkins.io/" target="_blank" rel="noopener noreferrer">Jenkins</a>, <a href="https://www.travis-ci.com/" target="_blank" rel="noopener noreferrer">Travis CI</a> ou encore <a href="https://about.gitlab.com/stages-devops-lifecycle/continuous-integration/" target="_blank" rel="noopener noreferrer">GitLab CI</a>.</p>
<p>En pratique c’est plutôt simple : un fichier de configuration permet de déterminer quelles actions déclencher, selon quels évènements, dans un ou plusieurs environnements donnés, dans un certain ordre et de produire un « livrable » ou un résultat (positif ou négatif).</p>
<p>Ce qui fait la puissance de GitHub Actions c’est à la fois la rapidité de mise à disposition de ses machines virtuelles et surtout, comme son nom l’indique, de ses (très nombreuses) <a href="https://github.com/marketplace?type=actions" target="_blank" rel="noopener noreferrer"><strong>Actions</strong> mis à disposition par la communauté sur la marketplace</a>.</p>
<h2 id="en-pratique-comment-faire">En pratique, comment faire ?</h2>
<p>Le principe semble simple mais, en pratique, est-ce que c’est aussi facile à mettre en œuvre ?\
La réponse est oui bien sûr ! 😀</p>
<p>Comme je l’indiquais en introduction, je vais illustrer mon propos en expliquant comment automatiser la génération d’un site statique avec <a href="https://cecil.app" target="_blank" rel="noopener noreferrer">Cecil</a> et comment le déployer.</p>
<h3 id="creation-du-workflow">Création du workflow</h3>
<p>Les fichiers de configuration de workflow de GitHub Actions doivent être déposés dans un dossier spécifique à la racine du dépôt qui contient les fichiers source du site à générer :</p>
<pre><code class="language-plaintext hljs plaintext">.github/workflows/</code></pre>
<p>De là il suffit de créer un nouveau fichier avec l’extension <code>yml</code> ou <code>yaml</code> (par exemple <code>build-and-deploy.yml</code>) qui sera automatiquement reconnu comme un nouveau workflow accessible depuis l’onglet « Actions » du dépôt.</p>
<h3 id="definition-des-taches-et-des-etapes">Définition des  tâches et des étapes</h3>
<p>Avant de définir les tâches il est nécessaire de déterminer l’évènement déclencheur :</p>
<pre><code class="language-yml hljs yaml"><span class="hljs-attr">on:</span>
  <span class="hljs-attr">push:</span>
    <span class="hljs-attr">branches:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">master</span></code></pre>
<p>Ici on considère que la branche de production est <code>master</code> et que la génération doit être déclenchée à chaque modification de code (<code>push</code>).</p>
<p>En pratique nous devons définir <strong>2 tâches</strong> (<code>jobs</code>) :</p>
<h4 id="1-build-generation-du-site-statique">1. <code>build</code> : génération du site statique</h4>
<p>C’est là que la marketplace montrent tout son intérêt. En effet, pour chacune des étapes (<code>steps</code>) ci-dessous, j’utilise une action clef en main :</p>
<ol>
<li>Obtention du code source via <a href="https://github.com/marketplace/actions/checkout" target="_blank" rel="noopener noreferrer"><code>checkout</code></a></li>
<li>Génération du site via <a href="https://github.com/marketplace/actions/cecil-action" target="_blank" rel="noopener noreferrer"><code>Cecil-Action</code></a></li>
<li>« Mise de côté » des fichiers générés via <a href="https://github.com/marketplace/actions/upload-a-build-artifact" target="_blank" rel="noopener noreferrer"><code>upload-artifact</code></a></li>
</ol>
<blockquote>
<p>Note : la directive <code>with</code> permet de passer des options à l’action.</p>
</blockquote>
<pre><code class="language-yml hljs yaml"><span class="hljs-attr">build:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span>
  <span class="hljs-comment"># Utilisation d'une image Linux Ubuntu</span>
  <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
  <span class="hljs-attr">steps:</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Checkout</span> <span class="hljs-string">source</span>
    <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/checkout@v2</span>
    <span class="hljs-attr">with:</span>
      <span class="hljs-comment"># Inutile de récupérer tout l'historique : la dernière version suffit</span>
      <span class="hljs-attr">fetch-depth:</span> <span class="hljs-number">1</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Build</span> <span class="hljs-string">site</span> <span class="hljs-string">with</span> <span class="hljs-string">Cecil</span>
    <span class="hljs-attr">uses:</span> <span class="hljs-string">Cecilapp/Cecil-Action@v3</span>
    <span class="hljs-attr">with:</span>
      <span class="hljs-comment"># Pour éviter les conflits, utilisation d'un nom spécifique</span>
      <span class="hljs-attr">config:</span> <span class="hljs-string">'cecil.yml'</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Upload</span> <span class="hljs-string">site</span> <span class="hljs-string">to</span> <span class="hljs-string">Artifacts</span>
    <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/upload-artifact@v2</span>
    <span class="hljs-attr">with:</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">_site</span>
      <span class="hljs-attr">path:</span> <span class="hljs-string">_site</span>
      <span class="hljs-comment"># La tâche ne sera pas exécutée si aucun fichier n'est généré</span>
      <span class="hljs-attr">if-no-files-found:</span> <span class="hljs-string">error</span></code></pre>
<h4 id="2-deploy-deploiement-des-fichiers-generes">2. <code>deploy</code> : déploiement des fichiers générés</h4>
<ol>
<li>Récupération des fichiers générés via <a href="https://github.com/marketplace/actions/download-a-build-artifact" target="_blank" rel="noopener noreferrer"><code>download-artifact</code></a></li>
<li>Déploiement du site via <a href="https://github.com/marketplace/actions/gh-pages-deploy" target="_blank" rel="noopener noreferrer"><code>GitHub-Pages-deploy</code></a></li>
</ol>
<pre><code class="language-yml hljs yaml"><span class="hljs-attr">deploy:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span>
  <span class="hljs-comment"># La tâche 'deploy' est exécutée après la tâche 'build'</span>
  <span class="hljs-attr">needs:</span> <span class="hljs-string">build</span>
  <span class="hljs-attr">runs-on:</span> <span class="hljs-string">ubuntu-latest</span>
  <span class="hljs-attr">steps:</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Download</span> <span class="hljs-string">site</span> <span class="hljs-string">from</span> <span class="hljs-string">Artifacts</span>
    <span class="hljs-attr">uses:</span> <span class="hljs-string">actions/download-artifact@v2</span>
    <span class="hljs-attr">with:</span>
      <span class="hljs-attr">name:</span> <span class="hljs-string">_site</span>
      <span class="hljs-attr">path:</span> <span class="hljs-string">_site</span>

  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Deploy</span> <span class="hljs-string">site</span> <span class="hljs-string">to</span> <span class="hljs-string">GitHub</span> <span class="hljs-string">Pages</span>
    <span class="hljs-attr">uses:</span> <span class="hljs-string">Cecilapp/GitHub-Pages-deploy@v3</span>
    <span class="hljs-attr">env:</span>
      <span class="hljs-comment"># Accès en écriture à la branche cible (`gh-pages`)</span>
      <span class="hljs-attr">GITHUB_TOKEN:</span> <span class="hljs-string">${{</span> <span class="hljs-string">secrets.GITHUB_TOKEN</span> <span class="hljs-string">}}</span>
    <span class="hljs-attr">with:</span>
      <span class="hljs-comment"># Adresse e-mail valide sur GitHub</span>
      <span class="hljs-attr">email:</span> <span class="hljs-string">arnaud@ligny.org</span></code></pre>
<blockquote>
<p>Note : par défaut <em>GitHub-Pages-deploy</em> utilise le dossier <code>_site</code> comme source et le déploie dans la branche <code>gh-pages</code>.</p>
</blockquote>
<h3 id="parametrage-de-github-pages">Paramétrage de GitHub Pages</h3>
<p>Enfin, il reste à activer <em>GitHub Pages</em> au sein du dépôt via <code>Settings</code> &gt; <code>Pages</code>.</p>
<picture>
<source type="image/webp" srcset="/images/2021-06-29-generer-et-heberger-un-site-statique-avec-github/github-settings-pages-before.d31b641b777fcb0291e81b9147a7641a.webp" width="662" height="363">
<source type="image/avif" srcset="/images/2021-06-29-generer-et-heberger-un-site-statique-avec-github/github-settings-pages-before.d31b641b777fcb0291e81b9147a7641a.avif" width="662" height="363">
<img src="/images/2021-06-29-generer-et-heberger-un-site-statique-avec-github/github-settings-pages-before.d31b641b777fcb0291e81b9147a7641a.png" alt="GitHub Pages settings" loading="lazy" decoding="async" class="dark:brightness-90" width="662" height="363" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAABzUlEQVR4nO2aQXKDMAxFrUxP0k13vf/t3EUh2KptZCzDh/43k8nEAkz0RnICyOfXdwwEhtfVJ0ByKAQMCgGDQsCgEDAoBIyP86cUh2M895f6CUJaAnrlrCL0fs8RNEFILckjYvZEPEeQk5BSQkcESMiT2itCCmP3YECIToZ3ZZRiRyqldRw8OoVYJIxWhmZGpeC2OIOQEQkeC3oreZaEW9aZ2rmcL6ohZE+EZytqxXRSZPJ47za+GFvWzFZlbWO1hOrYUTEp1rn8MQrRi2NpsbT26L3+bf3ytZjXeO82PjSE6N5b+5yO6X17Yzo+GruHhBRDhegEWtrDSKw0T+ucZoxfR+fP3pacday0bU+sFD+zMq5l4I/hGZXSmnNkHBenSydelTIy5+jxMJh0tdejGo7Oc29OvB/yvOTNgHcMwaAQMCgEDAoBg0LAoBAwKAQMCgGDQsCgEDAoBAwKAYNCwKAQMCgEDAoBg0LAoBAwKAQMCgFj5yEHnMf0/wsVIRIyGbI8pfjHA8V4UxAi20vC9h4lBFkERP0YKcV4oYSsIjYh7zoRqRQIZXiSCFHrhYQgmZEQJP5+iBKXigmJD4rx4JWvF0l1JMg7lo8Qf34Abv5xHJVdsI0AAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<ol>
<li>Sélectionner la branche cible <code>gh-pages</code> (ainsi que le dossier <code>/</code>)</li>
<li>Puis le domaine (si vous souhaitez le personnaliser)</li>
<li>Et enfin activer HTTPS (si le domaine de référence est personnalisé)</li>
</ol>
<picture>
<source type="image/webp" srcset="/images/2021-06-29-generer-et-heberger-un-site-statique-avec-github/github-settings-pages-after.15dd00d2b1779561c43650072c06804f.webp" width="660" height="466">
<source type="image/avif" srcset="/images/2021-06-29-generer-et-heberger-un-site-statique-avec-github/github-settings-pages-after.15dd00d2b1779561c43650072c06804f.avif" width="660" height="466">
<img src="/images/2021-06-29-generer-et-heberger-un-site-statique-avec-github/github-settings-pages-after.15dd00d2b1779561c43650072c06804f.png" alt="GitHub Pages settings" loading="lazy" decoding="async" class="dark:brightness-90" width="660" height="466" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAlElEQVR4nO3RMRHAIADAwFIpLGz4d0fnKiDDv4LcZcy1z0PGezuAP0NiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQGENiDIkxJMaQmA8IygHaM1MH3wAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<p>Et voilà comment générer et déployer automatiquement un site web statique, hébergé gratuitement ! 🎉</p>
<p>Remarques :</p>
<ul>
<li>Dans cet article j’ai utilisé <a href="https://github.com/marketplace/actions/cecil-action" target="_blank" rel="noopener noreferrer">Cecil Action</a> mais j’aurais également pu effectuer la même démonstration avec une action <a href="https://github.com/marketplace?type=actions&amp;query=hugo" target="_blank" rel="noopener noreferrer">Hugo</a> ou <a href="https://github.com/marketplace?type=actions&amp;query=eleventy" target="_blank" rel="noopener noreferrer">Eleventy</a> ;</li>
<li>Si vous souhaitez tester par vous même, en moins d’une minute, je vous invite à essayer avec le template <a href="https://github.com/Cecilapp/Single-GitHub-Page" target="_blank" rel="noopener noreferrer"><code>Single-GitHub-Page</code></a>.</li>
</ul>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://arnaudligny.fr/blog/un-site-e-commerce-avec-cecil-et-snipcart/</id>
    <title>Créer un site e-commerce avec Cecil et Snipcart</title>
    <published>2021-06-24T00:00:00+00:00</published>
    <link href="https://arnaudligny.fr/blog/un-site-e-commerce-avec-cecil-et-snipcart/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Billet initialement publié sur le <a href="https://arnaudligny.fr/blog/un-site-e-commerce-avec-cecil-et-snipcart/" target="_blank" rel="noopener noreferrer">blog d’Arnaud Ligny</a>.</p></aside>
<p>En début d’année ma chérie terminait la <a href="https://fr.ulule.com/paysages-a-velo/" target="_blank" rel="noopener noreferrer">campagne Ulule de son projet <strong><em>Paysages à vélo</em></strong></a> et se posait la question de continuer la vente de ses créations via une boutique en ligne.</p>
<p>Elle m’a alors sollicité pour l’aider à concevoir et construire cette boutique. Elle hésitait entre une solution clef en main telle que <em>Shopify</em> ou une solution basée sur un framework e-commerce tel <em>WooCommerce</em>.<br>
Néanmoins, la première solution reste onéreuse pour un petit projet (peu de ventes) et la seconde demande beaucoup d’énergie y compris pour un petit catalogue.</p>
<p>Je lui ai alors proposé de créer un site web statique avec <a href="https://cecil.app" target="_blank" rel="noopener noreferrer"><strong>Cecil</strong></a> auquel nous brancherions la solution e-commerce <a href="https://snipcart.com" target="_blank" rel="noopener noreferrer"><strong>Snipcart</strong></a> afin de dynamiser les interactions utilisateur.</p>
<!-- break -->
<div id="toc"><ul>
<li><a href="#pourquoi-un-site-statique">Pourquoi un site statique ?</a></li>
<li><a href="#pourquoi-snipcart">Pourquoi Snipcart ?</a></li>
<li><a href="#mise-en-艙uvre">Mise en œuvre</a><ul>
<li><a href="#creation-du-catalogue">Création du catalogue</a></li>
<li><a href="#templates-et-integration-snipcart">Templates et intégration Snipcart</a></li>
<li><a href="#personnalisation-du-tunnel-d-achat">Personnalisation du tunnel d’achat</a></li>
</ul>
</li>
<li><a href="#gestion-de-contenu-cms">Gestion de contenu (CMS)</a></li>
<li><a href="#conclusion">Conclusion</a></li>
</ul></div>
<h2 id="pourquoi-un-site-statique">Pourquoi un site statique ?</h2>
<p>Je suis un fervent promoteur de l’approche statique pour la diffusion de sites web de contenu pour les raisons suivantes (entre autres) :</p>
<ul>
<li><strong>Performance</strong> : une fois généré, le site n’a pas plus besoin d’être interprété par le serveur, juste d’être servi ;</li>
<li><strong>Simplicité</strong> : pas de base de données à maintenir car les données sont stockées dans des fichiers plats (Markdown + YAML) ;</li>
<li><strong>Portabilité</strong> : peut être hébergé sur n’importe serveur web et peut donc être migré facilement selon les besoins.</li>
</ul>
<p>Dans le cas de ce projet j’ai donc utilisé <a href="https://arnaudligny.fr/blog/cecil-mon-generateur-de-site-statique/" target="_blank" rel="noopener noreferrer">mon propre générateur de site statique</a> : <a href="https://cecil.app" target="_blank" rel="noopener noreferrer">Cecil</a>.</p>
<h2 id="pourquoi-snipcart">Pourquoi Snipcart ?</h2>
<p><a href="https://snipcart.com" target="_blank" rel="noopener noreferrer">Snipcart</a> n’est pas une solution e-commerce clef en main mais plutôt un « checkout » (tunnel d’achat) à ajouter à n’importe quel site web.</p>
<p>Il est donc nécessaire d’avoir préalablement créé un site catalogue pour charger les articles (produits) dans sa boutique Snipcart, puis de placer un bouton d’ajout au panier sur chaque fiche produit.</p>
<p>Le reste, à savoir le panier et les étapes de la commandes (saisie de l’adresse de facturation, choix du mode de livraison, paiement, etc.), est injecté automatiquement via JavaScript par le composant Snipcart.</p>
<p>Intérêt de cette approche et de Snipcart en particulier :</p>
<ul>
<li><strong>Indépendance</strong> concernant la gestion du catalogue ;</li>
<li><strong>Peu ou pas de développement</strong> spécifique, principalement de la personnalisation ;</li>
<li><strong>Tarif</strong> honnête (2% des ventes) ;</li>
<li><strong>Sécurisation</strong> des transactions portée par la solution de paiement (ici <a href="https://stripe.com/fr" target="_blank" rel="noopener noreferrer">Stripe</a>).</li>
</ul>
<h2 id="mise-en-艙uvre">Mise en œuvre</h2>
<h3 id="creation-du-catalogue">Création du catalogue</h3>
<p>Le catalogue de <a href="https://shop.cecillie.fr/" target="_blank" rel="noopener noreferrer">Paysages à vélo</a> est très simple : il s’agit de proposer moins d’une dizaine d’affiches dans 2 formats d’impression (A3 et A5).<br>
En pratique nous avons 6 modèles composés d’1 variant « format ».</p>
<p>Les attributs et le texte de la fiche produit sont définis dans un fichier <a href="https://daringfireball.net/projects/markdown/" target="_blank" rel="noopener noreferrer">Markdown</a> (avec un « <a href="https://cecil.app/documentation/content/#front-matter" target="_blank" rel="noopener noreferrer">front matter</a> ») :</p>
<pre><code>pages/products
|_ index.md
|_ 1.pink-gravel.md
|_ 2.purple-cargo.md
|_ 3.yellow-longtail.md
|_ 4.blue-folding-bike.md
|_ 5.ultra-violet-gang.md
|_ 6.lemon-lovers.md</code></pre>
<p>Les produits partagent les mêmes caractéristiques de base, qui peuvent donc être mutualisées à la racine de la section <em>products</em> via le fichier <code>pages/products/index.md</code>.<br>
Les attributs sont définis via des variables au format <a href="https://fr.m.wikipedia.org/wiki/YAML" target="_blank" rel="noopener noreferrer">YAML</a> :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-meta">---</span>
<span class="hljs-attr">cascade:</span>
  <span class="hljs-attr">price:</span> <span class="hljs-number">23</span>
  <span class="hljs-attr">variants:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Format</span>
    <span class="hljs-attr">options:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">value:</span> <span class="hljs-string">A3</span>
      <span class="hljs-attr">html:</span> <span class="hljs-string">"Affiche A3 - 23 €"</span>
      <span class="hljs-attr">price:</span> <span class="hljs-number">0</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">value:</span> <span class="hljs-string">A5</span>
      <span class="hljs-attr">html:</span> <span class="hljs-string">"Carte A5 - 8 €"</span>
      <span class="hljs-attr">price:</span> <span class="hljs-number">-15</span>
<span class="hljs-meta">---</span></code></pre>
<p>J’ai utilisé la variable spéciale <a href="https://cecil.app/documentation/content/#cascade" target="_blank" rel="noopener noreferrer"><code>cascade</code></a> qui permet de faire hériter à toutes pages de la section les variables qu’elle contient :</p>
<ul>
<li><code>price</code> : le prix de référence</li>
<li><code>variants</code> : qui caractérise les déclinaisons pour chacun des produits, en l’occurrence le <em>format</em> d’impression<ul>
<li><code>options</code> :<ul>
<li><code>value</code> : la valeur du format (ex : « A3 »)</li>
<li><code>html</code> : le texte affiché dans la liste déroulante</li>
<li><code>price</code> : le prix modifié par rapport au prix de référence (qui peut être négatif)</li>
</ul>
</li>
</ul>
</li>
</ul>
<p>Ensuite chacun des produits est caractérisé via son propre fichier Markdown, par exemple <code>pages/products/1.pink-gravel.md</code> :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-meta">---</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">"Pink gravel"</span>
<span class="hljs-attr">description:</span> <span class="hljs-string">"Femme ridant en toute liberté au pieds des montagnes."</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">"#01 Pink gravel"</span>
<span class="hljs-attr">image:</span> <span class="hljs-string">"/images/products/01-Pink-gravel-A3_S.png"</span>
<span class="hljs-attr">gallery:</span>
<span class="hljs-bullet">-</span> <span class="hljs-string">"/images/products/01-Pink-gravel-A5_S.png"</span>
<span class="hljs-bullet">-</span> <span class="hljs-string">"/images/products/01-Pink-gravel-ZOOM_S.png"</span>
<span class="hljs-attr">published:</span> <span class="hljs-literal">true</span>
<span class="hljs-meta">---</span>
<span class="hljs-string">**Gavarnie,</span> <span class="hljs-string">Hautes-Pyrénées.**</span>  
<span class="hljs-string">**Femme</span> <span class="hljs-string">ridant</span> <span class="hljs-string">en</span> <span class="hljs-string">toute</span> <span class="hljs-string">liberté</span> <span class="hljs-string">au</span> <span class="hljs-string">pieds</span> <span class="hljs-string">des</span> <span class="hljs-string">montagnes.**</span>

<span class="hljs-string">_Impression</span> <span class="hljs-string">numérique</span> <span class="hljs-string">sans</span> <span class="hljs-string">bordure</span> <span class="hljs-string">sur</span> <span class="hljs-string">papier</span> <span class="hljs-string">couché</span> <span class="hljs-string">premium</span> <span class="hljs-string">semi</span> <span class="hljs-string">mat</span> <span class="hljs-number">200</span> <span class="hljs-string">g</span> <span class="hljs-string">(carte</span> <span class="hljs-string">A5</span> <span class="hljs-number">300</span> <span class="hljs-string">g).</span> 
<span class="hljs-string">Les</span> <span class="hljs-string">affiches</span> <span class="hljs-string">sont</span> <span class="hljs-string">toutes</span> <span class="hljs-string">signées</span> <span class="hljs-string">à</span> <span class="hljs-string">la</span> <span class="hljs-string">main._</span></code></pre>
<h3 id="templates-et-integration-snipcart">Templates et intégration Snipcart</h3>
<h4 id="liste-des-produits">Liste des produits</h4>
<p>La volumétrie du catalogue étant très faible il n’est pas nécessaire de construire une arborescence complexe : afficher l’ensemble des produits sur la page d’accueil est suffisant.</p>
<p>Ainsi le template <a href="https://twig.symfony.com/" target="_blank" rel="noopener noreferrer">Twig</a> <code>layouts/index.html.twig</code> liste l’ensemble des « pages » contenus dans la section <em>products</em> triées par « poids » inverse (donc la dernière création en première position) :</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">extends</span></span> 'page.html.twig' %}</span><span class="xml">

</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">block</span></span> content %}</span><span class="xml">
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hero"</span>&gt;</span>
  </span><span class="hljs-template-variable">{{~ page.content ~}}</span><span class="xml">
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"products"</span>&gt;</span>
  </span><span class="hljs-template-tag">{%- <span class="hljs-name"><span class="hljs-keyword">for</span></span> product in site.pages.all|filter_by('section', 'products')|sort_by_weight|<span class="hljs-keyword">reverse</span> ~%}</span><span class="xml">
    </span><span class="hljs-template-tag">{%~ <span class="hljs-name"><span class="hljs-keyword">include</span></span> 'components/product.html.twig' with {'product': product, 'home': true} only ~%}</span><span class="xml">
  </span><span class="hljs-template-tag">{%- <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> ~%}</span><span class="xml">
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endblock</span></span> %}</span></code></pre>
<h4 id="fiche-produit">Fiche produit</h4>
<p>La fiche produit (un <a href="https://github.com/cecillie/eshop/blob/main/layouts/components/product.html.twig" target="_blank" rel="noopener noreferrer">composant Twig</a> réutilisable) va afficher :</p>
<ol>
<li>Les informations : nom, description, photos, etc.</li>
<li>Le choix du format (options du variant <em>format</em>), la saisie de la quantité souhaitée et le bouton « <strong>Ajouter au panier</strong> »</li>
</ol>
<p>Concentrons nous sur le cœur de la fiche produit, à savoir l’ajout au panier :</p>
<figure>
<picture title="Formulaire d’ajout au panier">
<source type="image/webp" srcset="/images/2021-06-24-un-site-e-commerce-avec-cecil-et-snipcart/add-to-cart.092ce8f0182e5a948a49c5d1cce512ab.webp" width="321" height="150">
<source type="image/avif" srcset="/images/2021-06-24-un-site-e-commerce-avec-cecil-et-snipcart/add-to-cart.092ce8f0182e5a948a49c5d1cce512ab.avif" width="321" height="150">
<img src="/images/2021-06-24-un-site-e-commerce-avec-cecil-et-snipcart/add-to-cart.092ce8f0182e5a948a49c5d1cce512ab.png" alt="Formulaire d’ajout au panier" loading="lazy" decoding="async" class="dark:brightness-90" width="321" height="150" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Formulaire d’ajout au panier</figcaption>
</figure>
<pre><code class="language-twig hljs twig"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"product__details"</span>&gt;</span>
  </span><span class="hljs-template-tag">{%- <span class="hljs-name"><span class="hljs-keyword">if</span></span> product.variants is defined ~%}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    </span><span class="hljs-template-tag">{%- <span class="hljs-name"><span class="hljs-keyword">for</span></span> variant in product.variants ~%}</span><span class="xml">
      </span><span class="hljs-template-tag">{%- <span class="hljs-name"><span class="hljs-keyword">if</span></span> variant.options is defined ~%}</span><span class="xml">
    <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ productId }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">-</span></span></span><span class="hljs-template-variable">{{ variant.name|<span class="hljs-keyword">lower</span> }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ variant.name }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">select</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ productId }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">-</span></span></span><span class="hljs-template-variable">{{ variant.name|<span class="hljs-keyword">lower</span> }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ variant.name|<span class="hljs-keyword">lower</span> }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span>
        </span><span class="hljs-template-tag">{%- <span class="hljs-name"><span class="hljs-keyword">for</span></span> option in variant.options ~%}</span><span class="xml">
      <span class="hljs-tag">&lt;<span class="hljs-name">option</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ option.value }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ option.html }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">option</span>&gt;</span>
        </span><span class="hljs-template-tag">{%- <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> ~%}</span><span class="xml">
    <span class="hljs-tag">&lt;/<span class="hljs-name">select</span>&gt;</span>
      </span><span class="hljs-template-tag">{%- <span class="hljs-name"><span class="hljs-keyword">endif</span></span> ~%}</span><span class="xml">
    </span><span class="hljs-template-tag">{%- <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> ~%}</span><span class="xml">
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  </span><span class="hljs-template-tag">{%- <span class="hljs-name"><span class="hljs-keyword">endif</span></span> ~%}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ productId }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">-qty"</span>&gt;</span>Quantité<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"number"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ productId }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">-qty"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"qty"</span> <span class="hljs-attr">min</span>=<span class="hljs-string">"1"</span> <span class="hljs-attr">max</span>=<span class="hljs-string">"10"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"1"</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  </span><span class="hljs-template-tag">{%~ <span class="hljs-name"><span class="hljs-keyword">include</span></span> 'components/add-item.html.twig' with {'productId':productId,'product':product} only ~%}</span><span class="xml">
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span></code></pre>
<p>Cette portion du template <code>layouts/components/product.html.twig</code> est composée de 3 parties :</p>
<ol>
<li>La liste déroulantes des formats, en faisant une boucle sur l‘ensemble des variants disponibles (ici uniquement <em>format</em>), puis une autre boucle sur l’ensemble des options (<em>A3</em> et <em>A5</em>) ;</li>
<li>Le champ de saisi de la quantité ;</li>
<li>Le bouton d’ajout au panier (représenté par le composant <code>add-item.html.twig</code>).</li>
</ol>
<p>C’est le composant bouton qui porte les attributs permettant l’ajout du produit au panier Snipcart.</p>
<h4 id="integration-snipcart">Intégration Snipcart</h4>
<p>L’intégration de Snipcart est simple, et nécessite :</p>
<ol>
<li>La feuille de style de référence ;</li>
<li>Un ou plusieurs boutons d’ajout au panier, portant les attributs du produit : identifiant, URL, nom, prix, etc. ;</li>
<li>Une balise <code>&lt;div&gt;</code> invisible (permettant affichage du panier) portant la clef d’API Snipcart ;</li>
<li>L’applicatif à proprement parlé via un fichier JavaScript.</li>
</ol>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://cdn.snipcart.com/themes/v3.0.11/default/snipcart.css"</span> /&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"snipcart-add-item"</span>
  <span class="hljs-attr">data-item-id</span>=<span class="hljs-string">"product-1"</span>
  <span class="hljs-attr">data-item-url</span>=<span class="hljs-string">"/"</span>
  <span class="hljs-attr">data-item-name</span>=<span class="hljs-string">"Product #1"</span>
  <span class="hljs-attr">data-item-price</span>=<span class="hljs-string">"10.99"</span>
&gt;</span>Add to cart<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">hidden</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"snipcart"</span> <span class="hljs-attr">data-api-key</span>=<span class="hljs-string">"MzMxN2Y0ODMtOWNhMy00YzUzLWFiNTYtZjMwZTRkZDcxYzM4"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.snipcart.com/themes/v3.0.11/default/snipcart.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></code></pre>
<ul>
<li>Démo : <a href="https://codepen.io/thatfrankdev/pen/xxwRXQw?editors=1000" target="_blank" rel="noopener noreferrer">https://codepen.io/thatfrankdev/pen/xxwRXQw?editors=1000</a></li>
<li>Template de <em>Paysages à vélo</em> : <a href="https://github.com/cecillie/eshop/blob/main/layouts/components/add-item.html.twig" target="_blank" rel="noopener noreferrer">layouts/components/add-item.html.twig</a></li>
</ul>
<h3 id="personnalisation-du-tunnel-d-achat">Personnalisation du tunnel d’achat</h3>
<p>J’ai également pris le temps de personnaliser le tunnel d’achat à la fois au niveau du rendu graphique et des étapes.</p>
<figure>
<picture title="Exemple de panier">
<source type="image/webp" srcset="/thumbnails/768x/images/2021-06-24-un-site-e-commerce-avec-cecil-et-snipcart/shop.cecillie.fr-cart.013690ef32b556676f1f5e3084dbacf2.webp 768w, /images/2021-06-24-un-site-e-commerce-avec-cecil-et-snipcart/shop.cecillie.fr-cart.013690ef32b556676f1f5e3084dbacf2.webp 800w" width="800" height="527" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/2021-06-24-un-site-e-commerce-avec-cecil-et-snipcart/shop.cecillie.fr-cart.013690ef32b556676f1f5e3084dbacf2.avif 768w, /images/2021-06-24-un-site-e-commerce-avec-cecil-et-snipcart/shop.cecillie.fr-cart.013690ef32b556676f1f5e3084dbacf2.avif 800w" width="800" height="527" sizes="100vw">
<img src="/images/2021-06-24-un-site-e-commerce-avec-cecil-et-snipcart/shop.cecillie.fr-cart.013690ef32b556676f1f5e3084dbacf2.png" alt="Exemple de panier" loading="lazy" decoding="async" class="dark:brightness-90" width="800" height="527" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/2021-06-24-un-site-e-commerce-avec-cecil-et-snipcart/shop.cecillie.fr-cart.013690ef32b556676f1f5e3084dbacf2.png 768w, /images/2021-06-24-un-site-e-commerce-avec-cecil-et-snipcart/shop.cecillie.fr-cart.013690ef32b556676f1f5e3084dbacf2.png 800w" sizes="100vw">
</picture>
<figcaption>Exemple de panier</figcaption>
</figure>
<h4 id="personnalisation-du-rendu">Personnalisation du rendu</h4>
<p>Concernant le rendu graphique, ce n’est pas le plus commode à réaliser : il est en effet nécessaire d’inspecter l’ensemble des composants HTML afin d’identifier les classes CSS, de les dupliquer et de les modifier selon ses besoins.</p>
<p>Par exemple, pour remplacer la police de caractère :</p>
<pre><code class="language-css hljs css"><span class="hljs-selector-class">.snipcart</span> {
  <span class="hljs-attribute">font-family</span>: <span class="hljs-string">'Roboto Condensed'</span>, sans-serif;
}</code></pre>
<p>La feuille de style Sass de <em>Paysages à vélo</em> disponible sur <a href="https://github.com/cecillie/eshop/blob/main/static/css/main.scss#L418" target="_blank" rel="noopener noreferrer">GitHub</a>.</p>
<blockquote>
<p>Remarque : depuis la version 3.2, Snipcart a introduit la notion de « <a href="https://docs.snipcart.com/v3/setup/theming" target="_blank" rel="noopener noreferrer">Theming</a> » qui facilite grandement la personnalisation via des propriétés CSS.</p>
</blockquote>
<h4 id="personnalisation-des-textes">Personnalisation des textes</h4>
<p>Les textes de l’interface de Snipcart sont disponibles en français (à laquelle j’ai d’ailleurs apporté <a href="https://github.com/snipcart/snipcart-l10n/blob/master/locales/fr-FR.json" target="_blank" rel="noopener noreferrer">ma contribution</a>) sans paramétrage particulier (autre qu’en définissant l’attribut <code>lang</code> de la balise <code>&lt;html&gt;</code>) mais si vous souhaitez personnaliser les textes, ça reste possible en chargeant son propre fichier de langue :</p>
<pre><code class="language-javascript hljs javascript"><span class="hljs-built_in">document</span>.addEventListener(<span class="hljs-string">'snipcart.ready'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{
  fetch(<span class="hljs-string">'/snipcart/{{ language }}.json'</span>)
    .then(<span class="hljs-function"><span class="hljs-params">response</span> =&gt;</span> response.json())
    .then(<span class="hljs-function"><span class="hljs-params">translation</span> =&gt;</span> Snipcart.api.session.setLanguage(<span class="hljs-string">'{{ language }}'</span>, translation))
});</code></pre>
<h4 id="personnalisation-des-formulaires">Personnalisation des formulaires</h4>
<p>Snipcart permet également de modifier et d’enrichir les étapes du tunnel d’achat via des <a href="https://docs.snipcart.com/v3/setup/customization" target="_blank" rel="noopener noreferrer">templates Vue.js</a> :</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">hidden</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"snipcart"</span> <span class="hljs-attr">data-api-key</span>=<span class="hljs-string">"{{ site.snipcart.apikey }}"</span> <span class="hljs-attr">data-templates-url</span>=<span class="hljs-string">"/snipcart/templates.tpl"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></code></pre>
<p>Ainsi, dans le cas de <em>Paysages à vélo</em> j’ai :</p>
<ol>
<li>Modifié l’affichage des lignes du panier afin d’y indiquer le format d’impression sélectionné à côté du nom du produit ;</li>
<li>Désactivé la suggestion d’adresse (qui n’est pas très fiable sur le territoire français) ;</li>
<li>Ajouté un champ de saisi d’un message cadeau.</li>
</ol>
<p>Par exemple, dans le cas du champ de saisi du message cadeau, le code ressemble à ça :</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">shipping-address</span> <span class="hljs-attr">section</span>=<span class="hljs-string">"bottom"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">fieldset</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"snipcart-form__set"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">hr</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"snipcart-form__separator"</span> /&gt;</span>
    <span class="hljs-comment">&lt;!-- Gift message --&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"snipcart-form__field"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">snipcart-label</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"snipcart__font--tiny"</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"Message cadeau"</span>&gt;</span>Message cadeau<span class="hljs-tag">&lt;/<span class="hljs-name">snipcart-label</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">snipcart-input</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"Message cadeau"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">snipcart-input</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"snipcart__font--tiny snipcart-form__footer"</span>&gt;</span>
        (Votre message sera écrit à la main sur une carte, ajoutée au colis)
      <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">fieldset</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">shipping-address</span>&gt;</span></code></pre>
<blockquote>
<p>Si vous voulez en voir plus le <a href="https://github.com/cecillie/eshop/blob/main/static/snipcart/templates.tpl" target="_blank" rel="noopener noreferrer">code source est disponible sur GitHub</a>.</p>
</blockquote>
<h2 id="gestion-de-contenu-cms">Gestion de contenu (CMS)</h2>
<p>La configuration du site, les fiches produit et les pages de contenu sont administrables à la main en éditant les fichiers correspondant, ce qui est suffisant dans la plupart des cas.</p>
<p>Néanmoins il peut s’avérer plus commode et plus agréable de pouvoir s’appuyer sur un CMS : dans le cas de <em>Paysages à vélo</em>, j’ai retenu <a href="https://forestry.io" target="_blank" rel="noopener noreferrer"><strong>Forestry</strong></a> pour sa simplicité de mise en œuvre et d’utilisation.</p>
<p>De plus Forestry offre un fonctionnalité de prévisualisation, en contexte, très efficace !</p>
<!--
<video controls preload="none" poster="/images/2021-06-24-un-site-e-commerce-avec-cecil-et-snipcart/forestry-preview-demo.mp4_poster.webp">
  <source src="/images/2021-06-24-un-site-e-commerce-avec-cecil-et-snipcart/forestry-preview-demo.webm" type="video/webm">
  <source src="/images/2021-06-24-un-site-e-commerce-avec-cecil-et-snipcart/forestry-preview-demo.mp4" type="video/mp4">
</video>
-->
<p><figure>
<video title="Démo Forestry" controls="" preload="none" poster="/images/2021-06-24-un-site-e-commerce-avec-cecil-et-snipcart/forestry-preview-demo.mp4_poster.8e0a97b54f905f6accbc8c2bce609f02.webp" src="/images/2021-06-24-un-site-e-commerce-avec-cecil-et-snipcart/forestry-preview-demo.cdbf001f3202b16a01d6bf9d798dd1b5.webm" style=";max-width:100%;height:auto;background-color:#d8d8d8;">
<figcaption>Démo Forestry</figcaption>
</figure></p>
<h2 id="conclusion">Conclusion</h2>
<p>J’ai pris beaucoup de plaisir à réaliser ce petit site e-commerce, principalement grâce à Snipcart qui m’a permis d’être libre sur la création du site web catalogue tout en offrant des options de personnalisation du tunnel d’achat relativement simples à mettre en œuvre (j’aurais d’ailleurs pu également parler de la possibilité de <a href="https://docs.snipcart.com/v3/webhooks/shipping" target="_blank" rel="noopener noreferrer">personnaliser les frais de port via <em>webhook</em></a>).</p>
<p><strong>Et surtout :</strong> l’utilisatrice du site est autonome sur la gestion des contenus, la création de nouveaux produits et la gestion des commandes, ce qui est finalement le plus important pour la réussite d’un site e-commerce ! 🛒😊</p>
<p>Enfin, je vous invite à :</p>
<ul>
<li>Étudier le <a href="https://github.com/cecillie/eshop" target="_blank" rel="noopener noreferrer">code source du projet</a> si vous souhaitez en savoir plus et vous inspirer ;</li>
<li>Jeter un œil à mon générateur de site statique : <a href="https://cecil.app/" target="_blank" rel="noopener noreferrer">Cecil</a> ;</li>
<li>Consulter le site officiel de <a href="https://snipcart.com/fr" target="_blank" rel="noopener noreferrer">Snipcart</a>.</li>
</ul>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2021/03/09/11000-pages-statiques/</id>
    <title>Un site statique de 11 000 pages, c&#039;est possible ?</title>
    <published>2021-03-09T14:00:00+00:00</published>
    <link href="https://jamstatic.fr/2021/03/09/11000-pages-statiques/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>Les sites statiques pour un site de média c'est l'idéal. Rapide, léger, sécurisé, tout semble coller sur le papier. Mais est-il possible de créer un site statique avec un grand nombre de pages ?</p>
<h2 id="le-contexte">Le contexte</h2>
<p>Tout débute par un client. Une entreprise qui gère des documents et des articles sur tout ce qui concerne les textes légaux sur le droit du travail dans les sociétés françaises.  Elle existe depuis des décennies et comme toutes les entreprises de média et de documentation, elle produit beaucoup de contenu chaque année.  </p>
<p>Au fil des années, par la création de plusieurs sites web, ils ont une accumulation de divers technologies et langages. Le client souhaite aller de l'avant, réduire la dette technique et simplifier toute son infrastructure. Et réduire les coûts si possible.<br>
Il désire donc basculer une bonne partie des sites actuels sur des sites statiques.  </p>
<p>Mais avant d'investir de l'énergie humaine et de l'argent dans cette grosse migration, il est indispensable de tester les possibilités des solutions, vérifier que la solution matche bien avec le besoin et les enjeux business.</p>
<h2 id="choix-des-generateurs-et-du-cms-headless">Choix des générateurs et du CMS Headless</h2>
<p>Côté CMS, le choix est déjà arrêté sur <a href="https://craftcms.com" target="_blank" rel="noopener noreferrer">Craft CMS</a>. C'est un CMS qui propose une très bonne expérience pour l'édition et surtout, il permet un mode headless avec des fonctionnalités très intéressantes par défaut. On y retrouve notamment: un mode prévisualisation, une API GraphQL et des webhooks. Parfait pour fonctionner avec un site statique.</p>
<p>Le mode prévisualisation offre une bonne expérience aux responsables du contenu. Il est très important pour une complète adoption par l'équipe de pouvoir éditer du contenu et vérifier le rendu immédiatement.</p>
<p>Comme le mode prévisualisation est un prérequis, cela élimine déjà tous les GSS (générateur de site statique) pur HTML (<a href="/categories/hugo/">Hugo</a>, <a href="/categories/eleventy/">Eleventy</a>, etc.) sur lequel on ne bénéficie pas de cette fonctionnalité.<br>
On s'oriente donc sur deux solutions qui offrent la possibilité de fonctionner dans ce mode. C'est donc Nuxt.js et Next.js qui sont retenus.</p>
<p>Gastby aurait pu être utilisé aussi grâce à la nouvelle route API mais nous souhaitons conserver des process de build assez rapide. D'expérience, je connais bien Gatsby et je sais qu'il est le moins performant — ce prototype précède l'annonce de la version 3.0 annoncée comme plus rapide.</p>
<p>Pour être 100% honnête, cet <a href="/2020/10/31/comparatif-performance-generateurs-de-site-statique/">article</a> a mis de côté Gastby au profit de Next.js dès le départ.</p>
<aside class="note note-info"><h3 id="next-js">Next.js</h3>
<p><a href="https://nextjs.org" target="_blank" rel="noopener noreferrer">Next.js</a> est un framework JavaScript basé sur React. Il permet de générer des applications React sous trois modes de rendu : SSR (rendu serveur), hybride (statique dynamique) et 100% statique (export des pages sous forme HTML).<br>
Uniquement dans le mode hybride, il offre les fonctionnalités suivantes : le mode prévisualisation des pages non générées, la regénération statique incrémentale (la (re)génération de pages existantes ou non, sous forme de fichiers statiques).</p></aside>
<aside class="note note-info"><h3 id="nuxt-js">Nuxt.js</h3>
<p><a href="https://fr.nuxtjs.org" target="_blank" rel="noopener noreferrer">Nuxt.js</a> est un framework JavaScript basé sur Vue.js. Il permet de générer des applications Vue.js sous trois modes de rendu : SSR (rendu serveur), SPA (single page application) et 100% statique (export des pages HTML).<br>
Il propose également un système de prévisualisation, mais dans le mode 100% statique. À ce jour, il ne propose pas de système de génération de page dynamique comme Next.js.<br>
Rien de confirmé pour le moment, mais la version 3.0 de Nuxt.js devrait proposer une fonctionnalité équivalente.</p></aside>
<h2 id="commencons-avec-next-js">Commençons avec Next.js</h2>
<p>Nous commençons avec Next.js, et nous avons hâte de découvrir le mode incrémental. La vitesse de publication d'une nouvelle version du site est également un point important. En effet, Next.js a implémenté deux modes intéressants dans les dernières versions : le mode incrémental et la régénération des pages.</p>
<p>Néanmoins ces deux fonctionnalités et le mode prévisualisation ne sont pas disponibles dans le mode entièrement statique (export HTML). Donc <strong>pour Next.js, le mode hybride est obligatoire</strong> et demande donc un serveur Node.js pour le faire tourner. Pas de soucis aujourd'hui, car Vercel est très accessible en termes d'hébergement et Netlify propose aussi la possibilité de faire tourner Next.js en mode hybride sur son service.</p>
<h2 id="generation-des-11-000-pages-et-premier-blocage">Génération des 11 000 pages et premier blocage</h2>
<p>Durant le développement, je travaillais avec un échantillon d'une centaine de pages. Jusque là, tout va bien. Je fais mon développement, j'ajoute les pages, la pagination. Tout fonctionne parfaitement.</p>
<p>Il est temps de tester avec les 11 000 pages. Et là, patatras !  L'API a du mal à suivre les requêtes. Eh oui, 11 000 pages, c'est autant de requêtes pour générer chaque page dans un temps très court. Évidemment, à ce stade, rien n'est optimisé du côté du CMS mais c'est un premier blocage dans la génération des pages.</p>
<p>Pour optimiser les appels vers l'API, je fais un système de cache qui permet d'utiliser les fichiers du cache au lieu d'appeler l'API. Je génère le cache juste avant le build.<br>
Tout fonctionne parfaitement. Les 11 000 pages prennent entre 5 et 8 minutes pour être créées.</p>
<h2 id="la-surprise-ou-le-blocage-qu-on-attendait-pas">La surprise ou le blocage qu'on attendait pas</h2>
<p>Bon OK, le temps de build des 11 000 pages est un petit peu long. Si on doit attendre à chaque fois entre 5 et 10 minutes pour avoir une nouvelle version en ligne, ce n’est pas top.</p>
<p>Mais là où nous avons été surpris, c'est quand nous avons testé le déploiement sur Vercel.<br>
Une chose que j'avais oubliée, c'est que 11 000 pages, c'est entre 400 et 500 Mb. Et en upload, ça prend du temps. Beaucoup de temps !</p>
<p>Clairement, au début, j'ai cru a un bug. J'ai coupé le déploiement au bout de 39 minutes. Oui, vous avez bien lu, 39 minutes. Et non, ce n’était pas un bug : j'ai testé sur Netlify, via FTP avec une connexion fibre, ça prend bien une bonne quarantaine de minutes !</p>
<figure>
<picture title="39 minutes de temps de déploiement">
<source type="image/webp" srcset="/images/post/11000-pages-statiques/39mindeploy.db86559c1af22952417cd298357497f6.webp" width="356" height="74">
<source type="image/avif" srcset="/images/post/11000-pages-statiques/39mindeploy.db86559c1af22952417cd298357497f6.avif" width="356" height="74">
<img src="/images/post/11000-pages-statiques/39mindeploy.db86559c1af22952417cd298357497f6.png" alt="39 minutes de temps de déploiement" loading="lazy" decoding="async" class="dark:brightness-90" width="356" height="74" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAADoUlEQVR4nO1a25aDMAiMcf//Z/us7hNdlh3IRcC265yTY9UaUiZDCHZ5PB7HcRyllFJmjzf8UK8ewI3f+PLo5FaKH26FvBhcFEK4lXIet0JeDK4KIWhKuRXURgghEne63I9QQjgB1Ph5Js7aW5bFaSQ2whXCyZAt0mbPtREgQiJICiOEO37f91RSbkIUSOcjYrzsWOfatRFYhFjEjNoNy7KsJonxsGeda9dGIJ0uyeD3ua2XIITjE0jhzuYELMtSjuN4HpGt0cwyJe0tBRMzQooWMqzZaJHSa4/b4ARQH2hco9Vxfj2NEG5cI0WDdAS/Rn3y/qU9bQzovmYDqQEpQ9rtIYWP561KJ5rjUUjRzj3CoxXCzvS5LEu+QtBAZr5rZTjazEUhRrOPFm1tIbcmhBxT6/deTgihhxiLEJnloDg/o44WCfKorTe9uJQQT3VoaSc6n4EMSS1CZnEZIT2LY48DUAiKqABYk8Bzxx5OCM0s2XrSR3peO2oOqdU/VxlZL84ghBBrwyTRk36i/nsX2d4+LbR26Z4IfUHVKqFY+wEELaOSqumdxbNr2OizI7h8p77v+/M+hxaieDiSjpcN9TOCHqV5E5NeyyIStm0zSydo1tday77vpdb6Kyy2Gu8P2ehFlCo4Ut6HaGTwZhHCCeAKqbX+2bFLJaEw1oPe77+VQshZ0vmclG3boEq4I0kVGmk8S+POJyIz1OGVaqeGLI0QVGCUs10La8jhI2HLC159pxBCR0QKD18WIeu6/rmPsjV+n/cjEUGOh0rS/3Ui1xJJCN+9c3UQ+OKO7nNEb+Ise7O4/H2ItS9Baa0WvrRyvAUvkjxLNemEaPF9ph903tp/RCjFs89QQtBegmY5NakKGbLk9yMW69nnI4qYacVFSQIt0jy11bIoem5d17Ku65MYjaQeB3vM6gi1hSsEZUsctMewCEGkEDGSFPns6Hh7EKEMQhghaB+B7qMMC/XBCeVEcLXMrEszszwyYwsvv1MJAzmblDFCiFQLIudMskB2ESKVQQhVCB1RXYlIaP0NSPYj1w4vIrLWnRbCCdEKgDzDst6JWJkaClWeTqO+MpRBSEl7S/kp9FHFduQFlUZKBBGonwxlEELXEPpM5xoJI28LJTny87sjPGRp18+EAZneWvY8bGTClRCuDHRPKsfL5ifBXSEtB72bA7PH6/IHpndz8ivDTSGfTkrW7zulkE8n4QqcVsh/IyX6904p5L+RkIlvQjj5N2cxLSoAAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>39 minutes de temps de déploiement</figcaption>
</figure>
<p>Le constat est simple. Impossible de faire du full statique sur un très grand nombre de pages. Le statique ne s'y prête pas du tout. D'autant que les futurs sites doivent atteindre le double, voire le triple en quantité de pages.</p>
<h2 id="la-regeneration-incrementale-a-la-rescousse">La régénération incrémentale à la rescousse</h2>
<p>C'est alors que la fonctionnalité de <a href="https://nextjs.org/docs/basic-features/data-fetching#incremental-static-regeneration" target="_blank" rel="noopener noreferrer">régéneration incrementale</a> nous est apparu comme la solution ultime ! Next.js est en effet capable de générer une page sous forme statique lors de la visite. Générer <em>ou</em> régénérer.</p>
<p>On peut utiliser le mode <code>fallback</code> de la fonction <code>getStaticPaths</code> avec la valeur <code>true</code> ou <code>blocking</code> pour afficher une page non générée lors de la phase de build.</p>
<p>Ensuite, vous donnez un tableau vide comme valeur <code>paths</code> de la fonction <code>getStaticPaths</code> pour ne générer aucune page lors du build. Les pages seront générées à la demande en cas de visite.</p>
<p>Résultat, un <strong>temps de génération entre deux et quatre minutes</strong> pour un site statique hybride de 10 991 articles !</p>
<figure>
<picture title="10991 articles">
<source type="image/webp" srcset="/images/post/11000-pages-statiques/10991articles.d1fa6683eef339c38d45565a0a52064d.webp" width="508" height="143">
<source type="image/avif" srcset="/images/post/11000-pages-statiques/10991articles.d1fa6683eef339c38d45565a0a52064d.avif" width="508" height="143">
<img src="/images/post/11000-pages-statiques/10991articles.d1fa6683eef339c38d45565a0a52064d.png" alt="10991 articles" loading="lazy" decoding="async" class="dark:brightness-90" width="508" height="143" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>10991 articles</figcaption>
</figure>
<figure>
<picture title="4 minutes de temps de déploiement">
<source type="image/webp" srcset="/images/post/11000-pages-statiques/4mindeploy.c523cd79283c87f56c565c1e51905018.webp" width="413" height="72">
<source type="image/avif" srcset="/images/post/11000-pages-statiques/4mindeploy.c523cd79283c87f56c565c1e51905018.avif" width="413" height="72">
<img src="/images/post/11000-pages-statiques/4mindeploy.c523cd79283c87f56c565c1e51905018.png" alt="4 minutes de temps de déploiement" loading="lazy" decoding="async" class="dark:brightness-90" width="413" height="72" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAADsklEQVR4nO1c7ZLCIAwE6vu/qY9ge7+iMeazDcI57EwHrC2ELBsC3ly93+9HWZgGt+NYfMyENtqAhXcshUyGpZDJsBQyGZZCJsMNKkspc+BmP6JjEZmLD0I4B3ucfpaYWQittU7RjqoQcNZxHKLjog6dhQCKLEKuQiQESKCX9vxZWO9mkKg5fAQZUp8sIZSEfd9dpFg4G/p6EGJ95pCp7hAh0DkmI4sU2sfZe1FoBHgVMoQQKVT1IgX3i0vuuyuQCIA65yDa71CFgAGZpFAjpAFnO0IjoNZaaq0uW3oTUmv1rSEZpFhhQRr4VYdwjscltIvrtD+pPANNqcdx+DaGXlIkp3MzED8bJUMjSQtBkk247i29dtDP2kQQFQIPRC7PwLV73sF6ZqtXCbQemfnUJq8PLJtOH514MhMpTnL1qOM0NdLSU4cyEoK1yCApxEouThHiTRMjqaY0W61w5+nfQwT3Lp0A0kTxwiKjFIUQMJy7sjq3Us2sDMdDRnShvwLNH2GFREKVle9rbfVyglVaKXc2IfQzS0hUHVoMtUrpvd6EaLZo/Q0hRGqEc1aUDC8xGN8kxOqrByEYKiFYGVKGY+0DOIVxxGiG9o7bPfrz2kLhPjqBz9x9+o5EhpeYCLLe47K5EXgjRDII78zpDl0LX3Qm9iQmCi3UZrYZRegsa9/358UphC78XK5+NZ2GNjLwLfIjUH9TpyQ8Ho8PpWDUWktr7VkCadJ6cYUUDTM62gu3QjAxVCXgACChtdefe0Fsxs/SUAXPex3Z0+HZ60jUVtfhIiUCK4U7NKPrihSvpXVkJEbbEP49hFNJKS9C8Gyn6gBIocuD/6QOQMRmV9oLdUkl0Cmtcxc2UsvKRmB0/6U4d+raQk+PoPG6oO1XMM44ItN539p/eGy+/KekPfDtmTqDMgDm0Qkt8ewvpbwpBFJeawM4S4o7amd+6uiEOq+1VvZ9f0tpMTH4uW3bSmvt45I2iWcMz8BMygCIx+9Q4rVg27bnfbxw43sSEb02gdRmD2Y4syqFt9mlEKyEUspTLdLRCUcKJibD8CuYURmAD0Lo7plLVSHTkr7HxyeWYrKhtTmLMiiwzTfuCxqy8PdAyLZt7AAlUjLC1VUCZ1YGQF1DuF03l2HR97iEoOf6oY2Bs3NW1Cr8kQMeDBBAF3FP470W8mg7/0EZAHNRl3419IDbgyzoYBd17t4V2dO1KQu/SLB5dJIx6F90XC88CbGcNrNTZ7YtivWfHCbD7Zdm1y+M5Q9gJSYfp6j0FwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>4 minutes de temps de déploiement</figcaption>
</figure>
<p>Cerise sur le gâteau, en utilisant le paramètre <code>revalidate</code>, on peut régénérer la page (temps en secondes) sans relancer un build. Cela veut dire qu'en cas de mise à jour de contenu, il n'est pas nécessaire de relancer un build !</p>
<p><div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;">
<iframe src="https://www.youtube-nocookie.com/embed/Dj-rKHmLp5w" loading="lazy" width="640" height="360" frameborder="0" allow="accelerometer;autoplay;encrypted-media;gyroscope;picture-in-picture;fullscreen;web-share;" allowfullscreen="" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background-color:#d8d8d8;"></iframe>
</div></p>
<h2 id="la-solution-retenue">La solution retenue</h2>
<p>Bien que Nuxt.js ne propose pas de mode hybride comme Next.js pour le moment, nous sommes allés au bout du prototype. Nous avons fait la même application en mode full statique, car nous ne souhaitons pas utiliser le mode SSR (Server Side Rendering). Et comme vous devez vous en douter, nous avons rencontré les mêmes soucis de temps de build et de déploiement.</p>
<p>Nous avons hâte de découvrir plus en détail la version 3 de Nuxt.js avec le moteur Nitro, qui doit normalement permettre un mode hybride.</p>
<p>Donc pour le moment, Next.js est retenu pour la migration des premiers sites. On utilisera le mode hybride avec une génération des pages à la demande en utilisant un <code>fallback</code> en <code>blocking</code> pour être 100% SEO compatible.</p>
<p>L'idée de générer au build les <em>X</em> derniers articles pour accélérer le chargement des articles récents est évoqué. Nous n’excluons pas que certaines pages très anciennes ne soient peut-être jamais visitées et donc jamais générées.</p>
<p>Finalement, pour remplir le périmètre requis pour le projet : le mode prévisualisation, un processus de publication simple et automatique, aucune gestion de serveur et certaines pages accessibles uniquement par des abonnés ; Next.js semble être pour le moment la meilleure solution.</p>
<h2 id="prototype-prive">Prototype privé</h2>
<p>Le projet est confidentiel et le code privé. Je ne peux pas fournir les données pour faire des tests.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2020/12/08/hebergement-statique-en-france/</id>
    <title>Héberger du statique en France, avec Matthias Dugué et Hubert Sablonnière</title>
    <published>2020-12-08T00:00:00+00:00</published>
    <updated>2021-09-10T00:00:00+00:00</updated>
    <link href="https://jamstatic.fr/2020/12/08/hebergement-statique-en-france/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>Dans ce deuxième épisode de <em>Génération Statique</em> Frank Taillandier et Arnaud Ligny reçoivent Matthias Dugué, en charge des relations avec les développeurs chez <a href="https://alwaysdata.com" target="_blank" rel="noopener noreferrer">Alwaysdata</a>, et Hubert Sablonnière, développeur web chez <a href="https://www.clever-cloud.com" target="_blank" rel="noopener noreferrer">Clever Cloud</a>. Avec eux nous évoquons la différence entre les plate-formes françaises et les géants américains du Cloud, quand il s’agit de déployer des sites Jamstack.</p>
<iframe src="https://anchor.fm/jamstatic/embed/episodes/Hberger-du-statique-en-France-enhc1t" height="174px" width="100%" frameborder="0" scrolling="no"></iframe>
<p>L’émergence de solutions dédiées au déploiement automatisé de sites statiques est principalement l’apanage de sociétés américaines (Netlify, Vercel, Amazon, Microsoft, Digital Ocean, etc.). La bataille a commencé outre-atlantique pour s’arroger les parts de marché, maintenant que Netlify revendique plus d’un million d’utilisateurs. Il existe des hébergeurs en France, mais pas uniquement dédié à du déploiement automatisé de statique sur des CDNs.</p>
<p>Les offres dédiées Jamstack sont un terreau d’innovation, on a vu ainsi arriver les développements atomiques (une URL pour chaque commit), les intégrations au workflow Git avec mise à disposition d’un lien de prévisualisation lors de la soumission d’une modification sur le dépôt Git, l’ajout simplifié de fonctions Lambda, etc.</p>
<p>Matthias et Hubert nous confient que le statique ne représente pas plus de 5% des projets actuellement chez les hébergeurs plus généralistes, et rappellent que le besoin applicatif représente encore leur cœur de leur activité. Les documentations respectives des ces services sont générées avec Hugo et les deux hébergeurs permettent de pousser via Git. L’automatisation et l’expérience utilisateur n’est pas aussi poussée mais le workflow est similaire. On commence néanmoins à réfléchir à l’amélioration de la DX (<em>Developer Experience</em>), via l’amélioration des outils en ligne de commande dans un premier temps.</p>
<p>L’UX n’est cependant pas le premier facteur de choix des entreprises françaises, qui veulent avant tout pouvoir héberger au plus près de leurs utilisateurs quand elles visent le marché français.</p>
<p>Il est difficile de comparer des services spécialisés qui focalisent leurs efforts sur le frein à l’adoption et qui se content de recopier des fichiers statiques. Les services plus génériques qui gèrent de l’infrastructure et des serveurs ont beaucoup d’autres aspects à gérer, ne serait-ce que pour s’assurer de la sécurité de leur infrastructure.</p>
<p>Hubert précise que des fonctionnalités très prisées comme la prévisualisation automatisée de branche Git sont possibles pour des sites statiques chez Clever Cloud à condition de connaître leur API. Ce genre de fonctionnalité est beaucoup plus complexe à mettre en place pour des applications dynamiques.</p>
<p>Matthias confesse que l’importance de la Developer Experience (DX) est grandissante dans la communauté et que le déploiement de sites Jamstack est quelque chose qu’on voit se développer aussi chez des acteurs majeurs comme Amazon Web Services, Microsoft Azure, Digital Ocean ou Cloudflare récemment.</p>
<p>Il n’est pas aisé de pour nos hébergeurs français plus modestes de rivaliser avec les géants du secteur. Force est de constater q’il est plus facile aujourd’hui de déployer un projet statique chez les "gros". Les outils en ligne de commande, les APIs, les outils de monitoring continuent d’être au centre de l’attention des hébergeurs traditionnels, c’est leur cœur de métier. Il y a bien d’autres paramètres à prendre en compte, comme par exemple la qualité et la réactivité d’un support technique à visage humain, qui accompagnent les clients pour s’assurer de répondre à leurs besoins au plus vite.</p>
<p>Les gros acteurs comme AWS ne sont pas non plus à l’abri de pannes, comme <a href="https://www.theverge.com/2020/11/25/21719396/amazon-web-services-aws-outage-down-internet" target="_blank" rel="noopener noreferrer">on a pu le voir encore recemment</a>.</p>
<p>Chez Clever Cloud, on peut déjà déployer des fonctions en mode PaaS (Platform as a Service), voire des <em>functions as a service</em> (FaaS) avec support de Web Assembly. Lors du développement de sa documentation en mode Jamstack Alwaysdata a pu expérimenté le besoin en fonctions dynamiques comme l’envoi de mail, un besoin qu’il est toujours possible de combler en développant soi-même la fonction manquante ou en se reposant sur des services et des API externes.</p>
<p>Arnaud souligne qu’il est peut-être plus rassurant pour des entreprises de pouvoir stocker leurs fonctions métier dans son Cloud privé afin de maîtriser son environnement. "La valeur ajoutée de notre métier c’est l’accompagnement et la gestion de la complexité" commente Matthias.</p>
<p>Cette complexité est aussi présente chez des acteurs comme AWS, et on est pas à l’abri pour autant d’un certain degré d’enfermement propriétaire lorsqu’on développe son application pour tourner sur ces plate-formes.</p>
<p>Les problématiques de protections des données, la volonté de ne pas dépendre d’entreprises étrangères qui ne sont pas soumises aux mêmes règles fiscales, des choix éthiques liés aux politiques énergétiques sont autant de critères qui entrent de plus en plus dans le processus de décision.</p>
<p>Le déploiement automatisé systématique c’est très pratique dans plein de cas, mais au prix de quel coût énergétique ? Il reste encore aux différents outils (CMS, plate-forme de déploiement) à donner plus de maîtrise (et de métriques) pour nous permettre de pouvoir estimer correctement l’empreinte énergétique de son <em>workflow</em> et de l’infrastructure utilisée.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2020/11/19/jamstack-legere-et-performante/</id>
    <title>Vers une Jamstack légère et performante, avec Nicolas Goutay</title>
    <published>2020-11-19T00:00:00+00:00</published>
    <link href="https://jamstatic.fr/2020/11/19/jamstack-legere-et-performante/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Dans ce premier épisode de <em>Génération Statique</em> Frank Taillandier et Arnaud Ligny reçoivent Nicolas Goutay, expert performance web, actuellement développeur chez <a href="https://orbit.love" target="_blank" rel="noopener noreferrer">Orbit</a>, conférencier et organisateur des <a href="https://jamstack.paris" target="_blank" rel="noopener noreferrer">meetups Jamstack parisiens</a>. Fort d’une expérience avec React et Gatsby, le coût des frameworks JavaScript côté client a poussé Nicolas à privilégier des outils plus légers et plus adaptés aux sites de contenus.</p></aside>
<iframe src="https://anchor.fm/jamstatic/embed/episodes/Vers-une-Jamstack-lgre-et-performante--avec-Nicolas-Goutay-emunhp/a-a3um11n" width="100%" frameborder="0" scrolling="no"></iframe>
<h2 id="introduction">Introduction</h2>
<p><a href="https://jamstatic.fr" target="_blank" rel="noopener noreferrer">Jamstatic</a> a été lancé avec la volonté de partager autour de la génération de sites statiques, nous suivons avec attention cet écosystème depuis près de 2015.</p>
<p>Près de 80 articles ont été publiés depuis sur le site web, plusieurs meetups ont eu lieu notamment sur Paris. Au vu des nombreuses discussions sur le <a href="https://jamstatic.fr/slack/">Slack</a>, nous avons décidé de nous essayer au format <a href="https://anchor.fm/jamstatic/" target="_blank" rel="noopener noreferrer">podcast</a>, afin de partager toujours plus sur le développement de sites web modernes et performants.</p>
<h2 id="bataille-d-ego-de-ceo">Bataille d'égo de CEO</h2>
<p><a href="https://jamstackconf.com/2020/october/" target="_blank" rel="noopener noreferrer">La dernière Jamstack Conf</a> a eu lieu en ligne, on en retiendra surtout la joute verbale entre Matt Biilmann le CEO de <a href="https://www.netlify.com" target="_blank" rel="noopener noreferrer">Netlify</a>, à l’origine de l’appellation Jamstack, et Matt Mullenweg, le CEO d’<a href="https://automattic.com" target="_blank" rel="noopener noreferrer">Automattic</a> venu défendre WordPress. Richard McManus de The New Stack <a href="https://thenewstack.io/jamstack-vs-wordpress-round-2-the-two-matts-debate/" target="_blank" rel="noopener noreferrer">revient sur cet échange</a> qui ne manquait pas de piquant. Pour se démarquer et faire augmenter l'adoption de leur plate-forme pour le déploiement de sites Jamstack, la stratégie de Netlify est d’attaquer les faiblesses bien connues de WordPress, notamment en matière de maintenance et de sécurité, mais c’était sans compter sur le répondant de Matt Mullenweg qui a très bien défendu l’approche intégrée de WordPress.com, qui répond justement à cette problématique, tout en gardant un CMS open source.</p>
<h2 id="l-api-de-wordpress-pour-le-blog-de-gatsby">L'API de WordPress pour le blog de Gatsby</h2>
<p>Gatsby, un des deux frameworks React majeurs qui permet de générer des applications web, a d’ailleurs <a href="https://www.gatsbyjs.com/blog/gatsby-blog-wordpress" target="_blank" rel="noopener noreferrer">annoncé avoir finalement opté pour WordPress en mode headless pour son blog</a>. Ils permettent ainsi à plus de 130 contributeurs de rester dans leur zone de confort, tout en contournant les tarifs de CMS leaders sur le marché comme Contentful, qui dans sa formule actuelle à $489 dollars n’intègre que 25 utilisateurs. Une stratégie moins frontale et plus intelligente de Gatsby qui par la même occasion montre qu’on peut très bien avoir un site React en front tout en gardant son WordPress en back et en requétant les données en GraphQL.</p>
<h2 id="les-annonces-de-la-premiere-next-js-conf">Les annonces de la première Next.js conf</h2>
<p>L’autre framework React qui a le vent en poupe c’est <a href="https://nextjs.org" target="_blank" rel="noopener noreferrer">Next.js</a>. La première <a href="https://nextjs.org/conf/schedule" target="_blank" rel="noopener noreferrer">conférence</a> du framework hybride qui permet de faire du statique <em>et</em> du SSR a eu lieu fin octobre.</p>
<p>Beaucoup d’annonces de la part de Vercel, notamment la mise à disposition d’un outil de monitoring de métriques de performance (les <a href="https://web.dev/vitals/#core-web-vitals" target="_blank" rel="noopener noreferrer">Core Web Vitals</a>) mesurées depuis leur CDN auprès des utilisateurs, sans avoir à ajouter la moindre ligne de JS à son site.</p>
<p>Ces <a href="https://web.dev/metrics/" target="_blank" rel="noopener noreferrer">métriques</a> poussées par Google permettent de démontrer la performance de sites développés avec Next.js et déployés sur les CDN de Vercel. Ils ont d’ailleurs aussi à l’occasion sorti un premier exemple de site e-commerce et annonce bientôt supporter de s'interfacer avec l'API de Shopify. Quand on connaît l’importance de la performance sur les taux de conversions en e-commerce, on se dit que la bataille va faire rage dans ce secteur. On notera que la plate-forme de déploiement Gatsby Cloud permet aussi de mesurer son score Lighthouse standard, des indicateurs "un peu fourre-tout", selon Nicolas.</p>
<figure>
<picture title="Le tableau de bord des métriques de performance utilisateur de Vercel affiche un score d&#039;expérience réelle, ici pour les 3/4 des utilisateurs de téléphone mobile sur un site généré avec Next.js.">
<source type="image/webp" srcset="/thumbnails/768x/images/post/2020-11-19_jamstack-legere-et-performante/vercel-analytics.9e4c6b47b12e4a6acbb7072c2d81a1b2.webp 768w, /thumbnails/1024x/images/post/2020-11-19_jamstack-legere-et-performante/vercel-analytics.9e4c6b47b12e4a6acbb7072c2d81a1b2.webp 1024w" width="1024" height="640" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/post/2020-11-19_jamstack-legere-et-performante/vercel-analytics.9e4c6b47b12e4a6acbb7072c2d81a1b2.avif 768w, /thumbnails/1024x/images/post/2020-11-19_jamstack-legere-et-performante/vercel-analytics.9e4c6b47b12e4a6acbb7072c2d81a1b2.avif 1024w" width="1024" height="640" sizes="100vw">
<img src="/images/post/2020-11-19_jamstack-legere-et-performante/vercel-analytics.9e4c6b47b12e4a6acbb7072c2d81a1b2.png" alt="Le tableau de bord des métriques de performance utilisateur de Vercel affiche un score d&#039;expérience réelle, ici pour les 3/4 des utilisateurs de téléphone mobile sur un site généré avec Next.js." loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="640" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/post/2020-11-19_jamstack-legere-et-performante/vercel-analytics.9e4c6b47b12e4a6acbb7072c2d81a1b2.png 768w, /thumbnails/1024x/images/post/2020-11-19_jamstack-legere-et-performante/vercel-analytics.9e4c6b47b12e4a6acbb7072c2d81a1b2.png 1024w" sizes="100vw">
</picture>
<figcaption>Le tableau de bord des métriques de performance utilisateur de Vercel affiche un score d'expérience réelle, ici pour les 3/4 des utilisateurs de téléphone mobile sur un site généré avec Next.js.</figcaption>
</figure>
<p>Également développé en partenariat avec les équipes de Google, Next.js dispose maintenant d’un <a href="https://vercel.com/docs/next.js/image-optimization" target="_blank" rel="noopener noreferrer">composant Image</a> qui va grandement aider les développeurs dans la gestion des images responsive pour les servir dans des formats optimisés sur leurs CDN. Gatsby dispose déjà d’un composant image qui retaille les images au build et les charge automatiquement au scroll. On voit que la concurrence est rude entre les deux frameworks et que c’est la performance ressentie côté utilisateur qui est moteur dans ces innovations. C’est une très bonne chose pour le web et pour faciliter le travail les développeurs qui ont maintenant à leur disposition des abstractions dont le but est d’éviter de servir des images non optimisées.</p>
<p>Devant la popularité croissante de Next.js, Netlify n’a d’autre choix pour retenir les développeurs que de devoir <a href="https://www.netlify.com/blog/2020/10/27/preview-mode-for-next.js-now-fully-supported-on-netlify/" target="_blank" rel="noopener noreferrer">supporter le SSR et le mode preview via des fonctions lambda</a>. Ils offrent désormais quelques cours en ligne dont un pour <a href="https://explorers.netlify.com/learn/nextjs" target="_blank" rel="noopener noreferrer">apprendre les bases de Next.js</a>. Le framework semble au centre de toutes les attentions en ce moment. L'enjeu n'est pas négligeable pour Netlify, puisque Vercel est un concurrent direct. Nous parlerons plus en détail du déploiement et de l'hébergement de sites statiques dans le prochain épisode.</p>
<h2 id="gatsby-simplifie-la-generation-de-pages">Gatsby simplifie la génération de pages</h2>
<p>Gatsby de son côté n'est pas en reste et continue de progresser, le framework vient d'annoncer <a href="https://www.gatsbyjs.com/blog/fs-route-api" target="_blank" rel="noopener noreferrer">une nouvelle API de routing basée sur le système de fichiers</a>… à la Next.js ou Nuxt.js. Votre fichier <code>/about.js</code> sera servi en tant que <code>/about/</code>. Nicolas précise que Gatsby demande un peu plus d'investissement initial mais que le résultat final sera similaire.</p>
<h2 id="nuxt-js-de-plus-en-plus-statique">Nuxt.js de plus en plus statique</h2>
<p>Nuxt.js, est désormais capable de <a href="https://nuxtjs.org/blog/nuxt-static-improvements" target="_blank" rel="noopener noreferrer">générer un site entièrement statique</a> et permet de requêter le système de fichiers grâce à <a href="https://content.nuxtjs.org/" target="_blank" rel="noopener noreferrer">son API de contenu</a> très simple d’utilisation, avec rechargement à chaud, insertion de composants Vue dans le Markdown, un queryBuilder, une recherche textuelle, et bien plus…</p>
<p>Le projet initié par les deux frères Chopin, Alexandre et Sébastien, a annoncé sa première levée de fonds et propulse maintenant des sites majeurs comme lequipe.fr. On se réjouit de la progresssion du framework Vue.js.</p>
<p>Stocker ses contenus sous forme de fichiers Markdown, JSON, YAML, est très courant et c’est une bonne chose que tous les framework JS continuent de faciliter le travail à ce niveau-là. Frank rappelle que des CMS basés sur Git comme <a href="https://forestry.io" target="_blank" rel="noopener noreferrer">Forestry</a> interagissent avec votre dépôt et proposent une interface de rédaction épurée ainsi que la possibilité de modéliser ses contenus, via du front matter ou des fichiers de données. Une flexibilité vraiment appréciable, et accessible au plus grand nombre.</p>
<p>Des frameworks bien pratiques, de plus en plus versatiles, capables de travailler avec des fichiers locaux comme avec des API distantes. Ces frameworks JavaScript facilitent le développement par composition, ce paradgime explique en partie leur adoption de plus en plus massive.</p>
<h2 id="hugo-de-mieux-en-mieux-outille-pour-le-javascript">Hugo de mieux en mieux outillé pour le JavaScript</h2>
<p>Enfin Hugo, <a href="/2020/10/31/comparatif-performance-generateurs-de-site-statique/">le générateur de loin le plus rapide</a> pour transformer ses fichiers source en site web, <a href="https://gohugo.io/news/0.78.0-relnotes/" target="_blank" rel="noopener noreferrer">continue d'intégrer</a> des outils issus de l'écosystème <a href="https://gohugo.io/hugo-pipes/js/" target="_blank" rel="noopener noreferrer">JavaScript</a>, après la transpilation via <a href="https://gohugo.io/hugo-pipes/babel/" target="_blank" rel="noopener noreferrer">Babel</a>, la gestion de dépendances <a href="https://gohugo.io/news/0.75.0-relnotes/" target="_blank" rel="noopener noreferrer">npm</a>, on peut aussi maintenant empaqueter son JavaScript à la vitesse de la lumière avec <a href="https://esbuild.github.io" target="_blank" rel="noopener noreferrer">esbuld</a>. Les développeurs peuvent découper leurs projets en modules réutilisables et bénéficier d'un <em>bundler</em> lui aussi bien plus rapide que webpack ou parcel.</p>
<h2 id="tailwind-2-0">Tailwind 2.0</h2>
<p><a href="https://blog.tailwindcss.com/tailwindcss-v2" target="_blank" rel="noopener noreferrer">La version 2.0 de Tailwind CSS est sortie</a>.<a href="https://tailwindcss.com" target="_blank" rel="noopener noreferrer">Le site fait peau neuve</a> pour l'occasion, il est développé avec Next.js, déployé sur Vercel avec une recherche propulsée par Algolia.</p>
<h2 id="eleventy-en-route-vers-le-million-de-telechargements">Eleventy en route vers le million de téléchargements</h2>
<p>De son côté, mine de rien <a href="page/post/eleventy-generateur-statique-simple">Eleventy le générateur volontairement simple</a> de Zach Leatherman se rapproche doucement du million de téléchargements npm, l’engouement est réel chez les développeurs front adeptes des choses bien faites, et souhaitant garder un contrôle total sur le JavaScript livré. C'est d'ailleurs ce qui a plu à Nicolas Goutay, qui détaille <a href="https://orbit.love/blog/towards-a-lightweight-jamstack" target="_blank" rel="noopener noreferrer">l'approche plus légére privilégiée pour développer le nouveau site d'Orbit</a>, avec Eleventy, Tailwind CSS et Alpine.js.</p>
<h2 id="comment-ne-pas-faire-subir-le-cout-des-frameworks-a-ses-visiteurs">Comment ne pas faire subir le coût des frameworks à ses visiteurs ?</h2>
<p>Dans son article sur <a href="https://orbit.love/blog/towards-a-lightweight-jamstack" target="_blank" rel="noopener noreferrer">https://orbit.love/blog/towards-a-lightweight-jamstack</a> (prochainement disponible en français), Nicolas rappelle que l'expérience de développement proposée par les frameworks JavaScript se fait au détriment de la performance expérimentée par vos visiteurs.</p>
<p>Nicolas a longtemps utilisé React pour beaucoup de projets en agence. Très attentif à la performance finale ressentie par les visiteurs, Nicolas sait que cela demande d'implémenter beaucoup de bonnes pratiques: la gestion des images, l'implémentation de Service Workers, le rendu côté serveur. "Encore faut-il en avoir le temps, l'expertise et la connaissance" précise Nicolas. Gatsby s'est positionné comme un framework React qui peut se connecter à n'importe quelle source de données via son API GraphQL et qui implémente les bonnes pratiques de webperf par défaut, générant une Progressive Web App en sortie.</p>
<p>Gatsby peut aussi précharger les pages dès qu'un visiteur s'apprête à cliquer sur un lien, ce qui va donner une impression d'instantanéité du chargement de la page. C'est ce parti-pris sur la performance qui a plu à Nicolas, qui fait que le développeur est incité à servir un site optimisé.</p>
<p>Nicolas rappelle néanmoins que la taille du JavaScript livré sur le site a un impact énorme sur l'expérience utilisateur (et de plus en plus sur le SEO). Même 30 kilobits de JavaScript vont impacter le temps que met un site à être interactif. Cela peut se mesurer en terme de “clics rageux” (<em>rage clicks</em>) où les utilisateurs croient pouvoir interagir avec la page qui semble chargée, alors que le navigateur est toujours en train d'interpréter le code JavaScript embarqué.</p>
<p>Tout le monde ne dispose pas du dernier modèle de téléphone ultra-puissant et un site comme celui du Washington Post peut mettre près de 40 secondes à s'afficher sur un téléphone d'entrée de gamme.</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Even worse, the Washington Post.<br><br>This video is 30 seconds long, because that's how long it took the Redmi to load an article. The iPhone did it in 1-2 seconds. <a href="https://t.co/gfaK676SHo">pic.twitter.com/gfaK676SHo</a></p>— Josh W. Comeau 🎃 (@JoshWComeau) <a href="https://twitter.com/JoshWComeau/status/1322556145626718208?ref_src=twsrc%5Etfw">October 31, 2020</a></blockquote>
<p>On surveillera le <a href="https://web.dev/fid/" target="_blank" rel="noopener noreferrer">First Input Delay</a> dans ses métriques, même si comme le dit Nicolas "cela reste une approximation synthétique et imparfaite". Idéalement testez sur autre chose que le dernier iPhone avec une connexion 4G qui dépote.</p>
<p>Tout le monde ne maintient pas une application comme Facebook, Airbnb, ou Twitter. Si pour les applications, l'utilisation de frameworks ne fait plus débat, elle demande donc de peser le pour et le contre quand il s'agit de développer des sites de contenu avec peu d'interactivité côté client.</p>
<p>Malgré son appétence pour React, Nicolas a décidé de s'affranchir de ce coût non négligeable pour les utilisateurs finaux et s'est intéressé à Eleventy, pour se recentrer sur le contenu, l'accessibilité et la performance.</p>
<p>Les langages de templating proposés par les générateurs sont néanmoins moins versatiles que React ou Vue. On utilisera des fichiers partiels pour tendre vers un développement par composition, mais c'est une expérience de développement différente.</p>
<p>Le changement de philosophie peut se résumer à qui pilote, en React c'est le JavaScript, avec Alpine.js ce sont les fichiers HTML, dans lequel on ajoute des attributs qui permettent de piloter l'interactivité.</p>
<p><a href="https://github.com/orbit-love/orbit-web" target="_blank" rel="noopener noreferrer">Le code du site d'Orbit est open source</a>, vous pouvez donc aller voir comment Nicolas a utilisé <a href="https://github.com/alpinejs/alpine" target="_blank" rel="noopener noreferrer">Alpine</a> pour ajouter de l'interactivité à certaines parties de l'interface comme la navigation, l'inscription à la newsletter ou les extraits de code.</p>
<blockquote>
<p>L'approche est très rafraichissante, elle permet de se recentrer sur une des primitives du web, le HTML, et de pouvoir soigner la qualité du code servi à l'utilisateur — Nicolas Goutay</p>
</blockquote>
<p>Mozilla adopte <a href="https://hacks.mozilla.org/2020/10/to-eleventy-and-beyond/" target="_blank" rel="noopener noreferrer">cette approche légère</a> sur <a href="https://extensionworkshop.com/" target="_blank" rel="noopener noreferrer">https://extensionworkshop.com/</a>.</p>
<p>On notera que certains sites Eleventy utilisent les Web Components et que GitHub a développé <a href="https://github.com/github/view_component" target="_blank" rel="noopener noreferrer">un framework pour embarquer des Web Components dans les applications Ruby on Rails</a>. Là encore, il faudrait dédier une émission complète sur ce sujet. C'est quelque chose à surveiller de près.</p>
<p>Il est possible d'utiliser Vue avec Eleventy, c'est d'ailleurs <a href="https://www.netlify.com/blog/2020/09/18/eleventy-and-vue-a-match-made-to-power-netlify.com/" target="_blank" rel="noopener noreferrer">ce qu'a fait Zach Leatherman sur netlify.com</a>.</p>
<p>Le statique moderne est toujours en plein évolution, beaucoup d'outils dans le seul but de produire des sites à la fois modernes, accessibles et performants.</p>
<p>La discussion continue sur <a href="/slack">Slack</a> et <a href="https://twitter.com/jamstatic_fr" target="_blank" rel="noopener noreferrer">Twitter</a>…</p>
<h2 id="intervenants">Intervenants</h2>
<ul>
<li><a href="https://frank.taillandier.me" target="_blank" rel="noopener noreferrer">Frank Taillandier</a>, Customer Success Manager chez <a href="https://forestry.io" target="_blank" rel="noopener noreferrer">Forestry.io</a>, initiateur de la communauté <a href="https://jamstatic.fr" target="_blank" rel="noopener noreferrer">jamstatic.fr</a></li>
<li><a href="https://arnaudligny.fr" target="_blank" rel="noopener noreferrer">Arnaud Ligny</a>, consultant technique web &amp; e-commerce, créateur du générateur de site statique <a href="https://cecil.app" target="_blank" rel="noopener noreferrer">Cecil</a></li>
<li><a href="https://twitter.com/phacks" target="_blank" rel="noopener noreferrer">Nicolas Goutay</a>, développeur web chez <a href="https://orbit.love/" target="_blank" rel="noopener noreferrer">Orbit</a>, organisateur des meetups <a href="https://jamstack.paris/" target="_blank" rel="noopener noreferrer">Jamstack Paris</a>, et de la conférence francophone <a href="https://www.welovespeed.com/2020/" target="_blank" rel="noopener noreferrer">We Love Speed</a></li>
</ul>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2020/10/31/comparatif-performance-generateurs-de-site-statique/</id>
    <title>Comparaison des temps de compilation des générateurs de site statique</title>
    <published>2020-10-31T07:38:15+00:00</published>
    <updated>2020-10-31T13:38:15+00:00</updated>
    <link href="https://jamstatic.fr/2020/10/31/comparatif-performance-generateurs-de-site-statique/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Le temps de compilation d’un site est un critère parmi tant d’autres. Si ces premiers tests comparatifs de performance pure ne sont pas représentatifs de ce que à quoi vous pouvez vous attendre dans le contexte de vos projets, ils vous donneront quand même un premier ordre d’idée. Partir sur un framework a un coût en temps de compilation.
Jekyll n’est intrinsèquement pas plus lent qu’Eleventy, tout dépend de votre projet. Gatsby sera le plus pénalisant sur de gros sites, Next.js est le framework qui s’en sort le mieux, et Hugo l’emporte haut la main et demeure intouchable dès qu’il s’agit de vitesse de compilation.</p></aside>
<p>Il y a tant de <a href="https://jamstack.org/generators/" target="_blank" rel="noopener noreferrer">générateurs de sites statiques (SSG)</a>. C'est fatiguant de devoir décider par où commencer. Bien qu'une abondance d'articles utiles puisse aider à se repérer dans les options (populaires), ils ne facilitent pas la décision comme par magie.</p>
<p>Je me suis efforcé de faciliter cette décision. Un de mes collègues a construit une <a href="https://www.ample.co/blog/questions-to-ask-before-choosing-a-static-site-generator" target="_blank" rel="noopener noreferrer">fiche d'évaluation du générateur de site statique</a>. Elle donne un très bon aperçu de nombreux choix de SSG populaires. Ce qui manque, c'est la façon dont ils fonctionnent réellement dans la pratique.</p>
<figure>
<picture title="Comparatif des principaux générateurs">
<source type="image/webp" srcset="/thumbnails/768x/images/post/2020-10-31_comparatif-performance-generateurs-de-site-statique/ssg-comparison-cheatsheet.b1fbf5c8b032161cc9909a987593ed0c.webp 768w, /thumbnails/1024x/images/post/2020-10-31_comparatif-performance-generateurs-de-site-statique/ssg-comparison-cheatsheet.b1fbf5c8b032161cc9909a987593ed0c.webp 1024w" width="1024" height="369" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/post/2020-10-31_comparatif-performance-generateurs-de-site-statique/ssg-comparison-cheatsheet.b1fbf5c8b032161cc9909a987593ed0c.avif 768w, /thumbnails/1024x/images/post/2020-10-31_comparatif-performance-generateurs-de-site-statique/ssg-comparison-cheatsheet.b1fbf5c8b032161cc9909a987593ed0c.avif 1024w" width="1024" height="369" sizes="100vw">
<img src="/images/post/2020-10-31_comparatif-performance-generateurs-de-site-statique/ssg-comparison-cheatsheet.b1fbf5c8b032161cc9909a987593ed0c.png" alt="Comparatif des principaux générateurs" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="369" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAABcSAAAXEgFnn9JSAAADUklEQVR4nO1cW5LjIAyUXNz/iDsfcxDtB28FMAjhMAldlcKOY5DVtAQ2Dv7+/CNoALMSAREBEAGvKy/R/dKXByKYFhsIAMRLsj4nokiCO243mvwe3MC0DqZkxG/8AUcGUVDRUUcbtq+2O2yXQvg2AAASARDZ76xk5JZ+CF5dUPZJy1O3CgHgxFBomLw6mCX4ZUrxPb9HAXfdtkkIr4SrxBNBadjaBhoW9ameEzITK7oICQ27MlWJt8jnmwyIGxKlhxciniYkGOJKJLftFLJHFsHYM9QMquSCXQgJBrmmAzE1fJhSqkS8mxBrnTcBAV0ZAxgvF2NlE0nC3psQawOAH/k6/6MriY2dR322Rxi08BPjHjKkdusQ4i1IxEAJKYDOVDtpGa72FovFp56SGjDy5mRhaOaixs5NB+vjeFfOM3nTK/rAnGPuatXCLoOOK24iyMx6PsrfzoXpbtiXYxcyAAAuOREVhKoI9krJZexEBkCW1CWhK8kj+NDQtoXs7mf/aTuRcumbE5Wxuz5WEiGNOyaePjn+IcEkQwtCZfCfrzB/1LNXvoswl1PGkulO0CKj5L0Rj+pNDP840gzYcl67v8U5nTTmXOWvlUdeG0NL0G7th3Ob3ZB4kc1D0lIRVN25PS397Ivo+sCHw6jdpp123qeSvQmICGs9ANyd1LAHksFSI4coPHJKA/PENGXKilKbamyznMFclj1U7ayxkkP0ML7uQnYhT6OUM5Kj4nqXE/KZqOeMEkY61c2w970jrR3VoZ0zOMyXjG6V0M4ZGjghqxfZTQy9nMFRCVlHNjlYaFq45ukoZAjrlOFxckgTTy5vsGAh67BTxnPEnLu9D2Ckmx9ClmMs6piuqeaBCBOPcA/0Ievkh5AFmAk65oQrbcz58yhEERrp+Dtn6hsHha9ViH9VQoMbDBXO408pZOVamF1Eo6oQ7YsqLTjjeOdDLE1leKgREhYRMfs0HFZboMTXUMzWV/vuSagQ4kcXCDkhBG5RPMgc1rufvk3XW/f0KrRFzJn0T2QkSC+Mq8MTFRwmaKJUbwC9vufbakKNjIWICpn5A5nk0WYpZKF7dXr0QVuTjORYDynojuxOipn+Jx9M+Lj74Vi1+T4nOjGZk7HwCety/Ae+WSFtw/PmwAAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/post/2020-10-31_comparatif-performance-generateurs-de-site-statique/ssg-comparison-cheatsheet.b1fbf5c8b032161cc9909a987593ed0c.png 768w, /thumbnails/1024x/images/post/2020-10-31_comparatif-performance-generateurs-de-site-statique/ssg-comparison-cheatsheet.b1fbf5c8b032161cc9909a987593ed0c.png 1024w" sizes="100vw">
</picture>
<figcaption><a href="https://www.ample.co/blog/questions-to-ask-before-choosing-a-static-site-generator" target="_blank" rel="noopener noreferrer">Comparatif des principaux générateurs</a></figcaption>
</figure>
<p>Tous les générateurs de sites statiques ont en commun le fait qu'ils prennent des données en entrée, les font passer par un moteur de template et produisent des fichiers HTML. Nous appelons généralement ce processus « la compilation ».</p>
<p>Il y a trop de nuances, de contexte et de paramètres à considérer pour pouvoir comparer les performances des différents générateurs pendant le processus de compilation pour les afficher sur une feuille de calcul - et c'est ainsi que commence notre test de comparaison des temps de compilation des générateurs de sites statiques les plus courants.</p>
<p>Il ne s'agit pas seulement de déterminer quel générateur est le plus rapide. <a href="https://gohugo.io" target="_blank" rel="noopener noreferrer">Hugo</a> a déjà cette réputation. Je veux dire, ils l'écrivent sur leur site web - Le framework le plus rapide au monde pour le développement de sites web - donc ça doit être vrai !</p>
<p>Il s'agit d'une comparaison des temps de compilation de plusieurs SSG populaires et, plus important encore, d'analyser en détail ces temps de compilation. Choisir aveuglément le plus rapide ou discréditer le plus lent serait une erreur. Voyons ensemble pourquoi.</p>
<h2 id="les-tests">Les tests</h2>
<p>Le processus de test est conçu pour démarrer de manière simple - avec seulement quelques générateurs populaires et un format de données simple. Une base sur laquelle on pourra s'appuyer pour tester d'autres générateurs et affiner les données. Pour le moment, le test comprend six des générateurs les plus populaires :</p>
<ul>
<li><a href="https://www.11ty.dev" target="_blank" rel="noopener noreferrer">Eleventy</a></li>
<li><a href="https://www.gatsbyjs.com" target="_blank" rel="noopener noreferrer">Gatsby</a></li>
<li><a href="https://gohugo.io" target="_blank" rel="noopener noreferrer">Hugo</a></li>
<li><a href="https://jekyllrb.com" target="_blank" rel="noopener noreferrer">Jekyll</a></li>
<li><a href="https://nextjs.org" target="_blank" rel="noopener noreferrer">Next</a></li>
<li><a href="https://nuxtjs.org" target="_blank" rel="noopener noreferrer">Nuxt</a></li>
</ul>
<p>Chaque test utilise l'approche et les conditions suivantes :</p>
<ul>
<li>La source de données pour chaque génération est constituée de fichiers Markdown avec un titre front matter et un corps de texte (contenant trois paragraphes de contenu) générés de manière aléatoire.</li>
<li>Le contenu ne contient pas d'images.</li>
<li>Les tests sont exécutés en série sur une seule machine, ce qui rend les valeurs réelles moins pertinentes que la comparaison relative entre les lots.</li>
<li>La sortie est un texte en clair sur une page HTML, exécutée par le starter par défaut, en suivant le guide de démarrage respectif de chaque générateur.</li>
<li>Chaque test est un essai à froid. Les caches sont effacés et les fichiers Markdown sont régénérés pour chaque test.</li>
</ul>
<p>Ces tests sont considérés comme des tests de <em>référence</em>. Ils utilisent des fichiers Markdown de base et produisent du HTML non stylisé dans la sortie intégrée.</p>
<p>En d'autres termes, le résultat est techniquement un site web qui pourrait être déployé pour la production, bien que ce ne soit pas vraiment un scénario de la vraie vie. Cependant, cela permet une première comparaison entre ces frameworks. Les choix que vous faites en tant que développeur utilisant l'un de ces frameworks impacteront les temps de compilation de différentes manières (<a href="https://css-tricks.com/make-jamstack-slow-challenge-accepted/" target="_blank" rel="noopener noreferrer">généralement en les ralentissant</a>).</p>
<p>Par exemple, contrairement au monde réel, nous testons des générations à froid. Dans la vraie vie, si vous avez 10 000 fichiers Markdown comme source de données et que vous utilisez Gatsby, vous allez utiliser le cache de Gatsby, ce qui réduira considérablement les temps de génération (jusqu'à près de la moitié).</p>
<p>On peut en dire autant des générations incrémentielles, qui sont liées à des passages à chaud par rapport aux passages à froid, dans la mesure où elles ne génèrent que les fichiers qui ont changé. Pour le moment, nous ne testons pas l'approche incrémentale dans ces tests.</p>
<h2 id="les-deux-types-de-generateurs-de-sites-statiques">Les deux types de générateurs de sites statiques</h2>
<p>Avant cela, considérons d'abord qu'il existe en réalité deux types de générateurs de sites statiques. Appelons-les <em>basique</em> et <em>avancé</em>.</p>
<ul>
<li><strong>Les générateurs de base</strong> (qui ne sont pas basiques sous le capot) sont essentiellement une interface en ligne de commande (CLI) qui prend des données en entrée et produit du HTML, et peut souvent être étendue pour traiter divers ressources (ce que nous ne faisons pas ici).</li>
<li><strong>Les générateurs avancés</strong> offrent quelque chose en plus de la sortie d'un site statique, comme le rendu côté serveur, des fonctions <em>serverless</em> et l'intégration d'un framework. Ils ont tendance à être configurés pour être plus dynamiques par défaut.</li>
</ul>
<p>J'en ai délibérément choisi trois de chaque type dans ce test. Les trois premiers à tomber dans le panier de base sont Eleventy, Hugo et Jekyll. Les trois autres sont basés sur un framework frontend et sont livrés avec des quantités d'outils variés. Gatsby et Next sont bâtis sur React, tandis que Nuxt est construit par dessus Vue.</p>
<table>
<thead>
<tr>
<th style="text-align: left;">Générateurs Basiques</th>
<th style="text-align: left;">Générateurs Avancés</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">Eleventy</td>
<td style="text-align: left;">Gatsby</td>
</tr>
<tr>
<td style="text-align: left;">Hugo</td>
<td style="text-align: left;">Next</td>
</tr>
<tr>
<td style="text-align: left;">Jekyll</td>
<td style="text-align: left;">Nuxt</td>
</tr>
</tbody>
</table>
<h2 id="mon-hypothese">Mon hypothèse</h2>
<p>Appliquons <a href="https://en.wikipedia.org/wiki/Scientific_method" target="_blank" rel="noopener noreferrer">la méthode scientifique</a> à cette approche car la science est amusante (et utile) !</p>
<p>Mon hypothèse est que si un générateur est avancé, alors il fonctionnera moins vite qu'un générateurs de base. Je pense que les résultats en témoigneront, car les générateurs avancés ont davantage de coûts fonctionnels que les générateurs de base. Ainsi, il est probable que nous voyons les deux groupes de générateurs - de base et avancés - regroupés ensemble, dans les résultats avec des générateurs de base se déplaçant beaucoup plus rapidement.</p>
<p>Permettez-moi de développer un peu plus cette hypothèse.</p>
<h3 id="lineaire-et-rapide">Linéaire et rapide</h3>
<p>Hugo et Eleventy domineront les tests avec des volumes de données plus petits. Ce sont des processus (relativement) simples dans Go et Node.js, respectivement, et leur temps de génération devrait le refléter. Bien que les deux générateurs soient moins rapides au fur et à mesure que le nombre de fichiers augmente, je m'attends à ce qu'ils restent en tête, bien que Eleventy soit peut-être un peu moins linéaire à l'échelle, simplement parce que Go a tendance à être plus performant que Node.</p>
<h3 id="lent-puis-rapide-mais-toujours-lent">Lent, puis rapide, mais toujours lent</h3>
<p>Les générateurs avancés, ou liés à un framework, démarreront et sembleront lents. Je soupçonne qu'un test sur fichier unique contient une différence significative - des millisecondes pour les tests de base, contre plusieurs secondes pour Gatsby, Next et Nuxt.</p>
<p>Les générateurs basés sur un framework font chacun appel à webpack pour la génération, ce qui entraîne une quantité importante de frais fonctionnels, quelle que soit la quantité de contenu qu'ils traitent. C'est le contrat tacite que nous passons en utilisant ces outils (nous y reviendrons plus tard).</p>
<p>Mais, à mesure que nous ajouterons des milliers de fichiers, je pense que nous verrons l'écart entre les deux catégories se réduire, même si le groupe <em>avancé</em> restera plus loin derrière d'une manière significative.</p>
<p>Dans le groupe des générateurs avancés, je m'attends à ce que Gatsby soit le plus rapide, uniquement parce qu'il n'a pas de composant côté serveur dont il faut se soucier - mais c'est juste une intuition. Next et Nuxt ont peut-être optimisé cela au point que, si nous n'utilisons pas cette fonctionnalité, cela n'affectera pas les temps de génération. Et je pense que Nuxt battra Next, uniquement parce que Vue a un peu moins d'impact que React.</p>
<h3 id="jekyll-le-vilain-petit-canard">Jekyll : le vilain petit canard</h3>
<p>Ruby est tristement célèbre pour sa lenteur. Il est devenu plus performant avec le temps, mais je ne m'attends pas à ce qu'il rivalise avec Node, et certainement pas avec Go. Et pourtant, dans le même temps, il n'a pas le bagage d'un framework.</p>
<p>Au début, je pense que nous verrons Jekyll comme étant assez rapide, peut-être même impossible à distinguer de Eleventy. Mais au fur et à mesure que nous arriverons aux milliers de fichiers, la performance en prendra un coup. Mon sentiment est qu'il peut y avoir un moment où Jekyll devient le plus lent des six. Nous allons pousser jusqu'à la barre des 100 000 pour en être sûrs.</p>
<figure>
<picture title="Les résultats auxquels on pourrait s&#039;attendre, Hugo le plus rapide et Next.js le plus lent">
<source type="image/webp" srcset="/thumbnails/768x/i2.wp.com/css-tricks.com/wp-content/uploads/2020/10/jekyll-hand-chart.jpg-w-862-ssl-1.f0c6f236dca9c5706a7cf2979cd26a99.webp 768w, /i2.wp.com/css-tricks.com/wp-content/uploads/2020/10/jekyll-hand-chart.jpg-w-862-ssl-1.f0c6f236dca9c5706a7cf2979cd26a99.webp 862w" width="862" height="687" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/i2.wp.com/css-tricks.com/wp-content/uploads/2020/10/jekyll-hand-chart.jpg-w-862-ssl-1.f0c6f236dca9c5706a7cf2979cd26a99.avif 768w, /i2.wp.com/css-tricks.com/wp-content/uploads/2020/10/jekyll-hand-chart.jpg-w-862-ssl-1.f0c6f236dca9c5706a7cf2979cd26a99.avif 862w" width="862" height="687" sizes="100vw">
<img src="/i2.wp.com/css-tricks.com/wp-content/uploads/2020/10/jekyll-hand-chart.jpg-w-862-ssl-1.f0c6f236dca9c5706a7cf2979cd26a99.jpg" alt="Les résultats auxquels on pourrait s&#039;attendre, Hugo le plus rapide et Next.js le plus lent" loading="lazy" decoding="async" class="dark:brightness-90" width="862" height="687" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A9jc8VGp5pjPSK3NAFsHigtUQanCpuBIKDSClPNSAw0w080w1SQXGNUR61KRmgJzVoTFQVYWo0XFSCkxklFA6UUgMs09KaRUkfWnYRKqU8LSgjFIWxRYYuaTNM3UoNFhDjTMZp2aC2KAExim7gKRmzTDTJbJlbNSrVdKnWkNMkFFIKKBlFhikU4NSSDiqrPg0AWvM4pC1VfN96PN96LgWtwpd9VfMpQ9JsLFndSbs1EHpd1K47D6KaDmnCkwsiRKmFQLUymmgJKKQUUwK0nSqEvWiimxEYoooqRjhThRRSAeKkFFFAx4pwoooAkFSLRRVIRIKKKKAP//Z);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/i2.wp.com/css-tricks.com/wp-content/uploads/2020/10/jekyll-hand-chart.jpg-w-862-ssl-1.f0c6f236dca9c5706a7cf2979cd26a99.jpg 768w, /i2.wp.com/css-tricks.com/wp-content/uploads/2020/10/jekyll-hand-chart.jpg-w-862-ssl-1.f0c6f236dca9c5706a7cf2979cd26a99.jpg 862w" sizes="100vw">
</picture>
<figcaption>Les résultats auxquels on pourrait s'attendre, Hugo le plus rapide et Next.js le plus lent</figcaption>
</figure>
<h2 id="les-resultats-sont-arrives">Les résultats sont arrivés</h2>
<p>Le code de ces tests se trouve sur <a href="https://github.com/seancdavis/ssg-build-performance-tests" target="_blank" rel="noopener noreferrer">GitHub</a>. Il y a aussi <a href="https://ssg-build-performance-tests.netlify.app" target="_blank" rel="noopener noreferrer">un site qui montre les résultats relatifs</a>.</p>
<p>Après de nombreuses itérations pour établir les bases sur lesquelles ces tests pourraient être effectués, j'ai fini par réaliser une série de 10 tests dans trois ensembles de données différents :</p>
<ul>
<li><strong>Basique</strong> : Un seul fichier, pour comparer les temps de génération de base</li>
<li><strong>Petits sites</strong> : De 1 à 1024 fichiers, en doublant le nombre de fichiers à chaque fois (pour faciliter la détermination de l'échelle linéaire des générateurs)</li>
<li><strong>Grands sites</strong> : De 1 000 à 64 000 fichiers, en doublant le nombre de fichiers à chaque passage. Au départ, je voulais aller jusqu'à 128 000 fichiers, mais j'ai rencontré des problèmes avec certains des frameworks. 64 000 ont fini par suffire pour donner une idée de la manière dont les différents acteurs allaient évoluer avec des sites toujours plus importants.</li>
</ul>
<figure>
<picture title="Performance de base, Hugo est largement vainqueur">
<source type="image/webp" srcset="/i1.wp.com/css-tricks.com/wp-content/uploads/2020/10/base-build-times.eccf4cae52b1423e6f67af656122455f.webp" width="697" height="716">
<source type="image/avif" srcset="/i1.wp.com/css-tricks.com/wp-content/uploads/2020/10/base-build-times.eccf4cae52b1423e6f67af656122455f.avif" width="697" height="716">
<img src="/i1.wp.com/css-tricks.com/wp-content/uploads/2020/10/base-build-times.eccf4cae52b1423e6f67af656122455f.jpg" alt="Performance de base, Hugo est largement vainqueur" loading="lazy" decoding="async" class="dark:brightness-90" width="697" height="716" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A9rNRSfdNStUUn3TQI5XX/wDUtXl95/x+H616hr/+pavL7z/j8P1rtobGsTtfC/Ra9AtvuCvP/C/Ra9AtvuCsK25L3LPaq1z9w1Z7VWufuGso7ks4fxB0auQg/wCPj8a6/wAQdGrkIP8Aj4/Gvew/8I8uv8RvwfcFaNl9+s6D7grRsvv14df4zn+2jfj+4KKI/uCipR6C2OiaopPumpWqKT7ppHYcrr/+pavL7z/j8P1r1DX/APUtXl95/wAfh+tdtDY0idr4Y6LXoFt9wV5/4Y6LXoFt9wVhW+Il7lntVa5+4as9qrXP3DWUdyWcP4g6NXIQf8fH411/iDo1chB/x8fjXvYf+EeXX+I34PuCtGy+/WdB9wVo2X368Ov8Zz/bRvx/cFFEf3BRUo9BbHRNUUn3TUpqKT7ppHWcrr/+pavL7z/j8P1r1DX/APUtXl95/wAfh+tdtDY1idr4Y6LXoFt9wV5/4Y6LXoFt9wVhW3Je5Z7VWufuGrPaq1z9w1lHclnD+IOjVyEH/Hx+Ndf4g6NXIQf8fH4172H/AIR5df4jfg+4K0bL79Z0H3BWjZffrw6/xnP9tG/H9wUUR/cFFSj0FsdEaik+6aKKR1nK6/8A6lq8vvP+Pw/Wiiu2hsaxO18MdFr0C2+4KKKwrbkvcs9qrXP3DRRWUdyWcP4g6NXIQf8AHx+NFFe9h/4R5df4jfg+4K0bL79FFeHX+M5/to34/uCiiipR6C2P/9k=);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Performance de base, Hugo est largement vainqueur</figcaption>
</figure>
<figure>
<picture title="Génération sur des petits sites (&amp;lt; 1024 fichiers) : Hugo est de loin le plus rapide, Gatsby devient plus lent dès 128 fichiers">
<source type="image/webp" srcset="/i2.wp.com/css-tricks.com/wp-content/uploads/2020/10/build-small-sites.cfb968aa7a4432f741659f10e2f28709.webp" width="697" height="716">
<source type="image/avif" srcset="/i2.wp.com/css-tricks.com/wp-content/uploads/2020/10/build-small-sites.cfb968aa7a4432f741659f10e2f28709.avif" width="697" height="716">
<img src="/i2.wp.com/css-tricks.com/wp-content/uploads/2020/10/build-small-sites.cfb968aa7a4432f741659f10e2f28709.jpg" alt="Génération sur des petits sites (&lt; 1024 fichiers) : Hugo est de loin le plus rapide, Gatsby devient plus lent dès 128 fichiers" loading="lazy" decoding="async" class="dark:brightness-90" width="697" height="716" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A9pzRmoyaUGgCQGlzTM0ZoAfmjNMzSZoAkzSE1Huo3UAKTimFqGamUCuFKKSgUxEgNFJRSGNzQDSUUwH7qN1MooAdupN1JRQIXJpKKKACijFGKAsFKKMUoFAWFFFLiigY0jFJin4pQKQxmKMU/FGKAGYoxUmKNtAEeKNtSYoxQAzbShafilAoAZilxT8UYoAbiin4ooAZRRRQAtFFFABS0UUAFFFFABQKKKAHCloooAKKKKAP/9k=);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Génération sur des petits sites (&lt; 1024 fichiers) : Hugo est de loin le plus rapide, Gatsby devient plus lent dès 128 fichiers</figcaption>
</figure>
<figure>
<picture title="Génération de gros sites (entre 1000 et 64000 fichiers): Hugo est de loin le plus rapide, Gatsby est exponentiellement plus lent">
<source type="image/webp" srcset="/i1.wp.com/css-tricks.com/wp-content/uploads/2020/10/build-large-sites.88423cc45655c96a03d56cb3e7ebee4b.webp" width="697" height="716">
<source type="image/avif" srcset="/i1.wp.com/css-tricks.com/wp-content/uploads/2020/10/build-large-sites.88423cc45655c96a03d56cb3e7ebee4b.avif" width="697" height="716">
<img src="/i1.wp.com/css-tricks.com/wp-content/uploads/2020/10/build-large-sites.88423cc45655c96a03d56cb3e7ebee4b.jpg" alt="Génération de gros sites (entre 1000 et 64000 fichiers): Hugo est de loin le plus rapide, Gatsby est exponentiellement plus lent" loading="lazy" decoding="async" class="dark:brightness-90" width="697" height="716" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A9sooooAKeKZThQBIKcaaKCaAENMNOJphoAaaaTSmm0AGaM0UUAGaKKKAJKKKKACnCm04UAPFBNHamk0AITTSaUmmE0AIabmlJptAC5ozTaUUAOooFFAElFFFABThTacKAH9qYaf2pjUAMJppNKaYTQAhNJRRTAKKKKAHUUUUhEtFFFAwp4oooAd2pjUUUARtUZoooASiiimAUUUUAOooopCP/9k=);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Génération de gros sites (entre 1000 et 64000 fichiers): Hugo est de loin le plus rapide, Gatsby est exponentiellement plus lent</figcaption>
</figure>
<h2 id="synthese-des-resultats">Synthèse des résultats</h2>
<p>Certains résultats m'ont surpris, alors que d'autres étaient prévisibles. Voici les points les plus importants :</p>
<ul>
<li>Comme prévu, <strong>Hugo est le plus rapide</strong>, quelle que soit la taille du site. Ce à quoi je ne m'attendais pas, c'est qu'il <em>loin</em> devant tous les autres générateurs, même sur une génération de base (il n'est pas non plus linéaire, mais nous reviendrons sur ce point.)</li>
<li>Les groupes de générateurs de base et avancés sont assez évidents quand on regarde les résultats pour les petits sites. C'était prévu, mais il était surprenant de voir que <strong>Next est plus rapide que Jekyll avec 32 000 fichiers, et plus rapide que Eleventy et Jekyll avec 64 000 fichiers</strong>. Il est également surprenant que Jekyll soit plus rapide que Eleventy avec 64 000 fichiers.</li>
<li>Aucun des générateurs ne suit une échelle linéaire. Next.js est celui qui s'en rapproche le plus cependant. Hugo donne l'apparence d'être linéaire, mais c'est seulement parce qu'il est beaucoup plus rapide que les autres.</li>
<li>Je pensais Gatsby serait le plus rapide parmi les générateurs avancés, et je me suis dit que c'était celui qui se rapprocherait le plus des générateurs de base. Mais <strong>Gatsby s'est avéré être le plus lent</strong>, produisant la courbe la plus exponentielle.</li>
<li>Bien que cela ne soit pas spécifiquement mentionné dans l'hypothèse de départ, <strong>l'échelle des différences était plus grande que je ne l'aurais imaginé</strong>. Pour un seul fichier, Hugo est environ 170 fois plus rapide que Gatsby. Mais à 64 000 fichiers, il est plus proche - environ 25 fois plus rapide. Cela signifie que, si Hugo reste le plus rapide, il a en fait la forme de croissance exponentielle la plus spectaculaire parmi le lot. Il semble simplement linéaire à cause de l'échelle du graphique.</li>
</ul>
<h2 id="qu-en-conclure">Qu'en conclure ?</h2>
<p>Lorsque j'ai partagé mes résultats avec les créateurs et les mainteneurs de ces générateurs, j'ai généralement eu la même réponse. Pour paraphraser :</p>
<blockquote>
<p>Les générateurs qui prennent plus de temps à générer en font davantage. Ils apportent plus aux développeurs, alors que les générateurs plus rapides (c'est-à-dire les outils "de base") concentrent leurs efforts en grande partie sur la conversion des modèles en fichiers HTML.</p>
</blockquote>
<p>Nous sommes d'accord.</p>
<p>Pour résumer : <em>La mise à l'échelle des sites Jamstack est difficile.</em></p>
<p>Les défis qui se présenteront à vous, développeur, au fur et à mesure que vous que la taille de votre site augmentera, varieront en fonction du site que vous essayez de construire. Ces données ne sont pas saisies ici car elles ne peuvent l'être - chaque projet est unique d'une certaine manière.</p>
<p>Ce qui compte vraiment, c'est <em>votre niveau de tolérance à l'attente en échange de l'expérience de développement</em>.</p>
<p>Par exemple, si vous allez générer un grand site à forte charge d'images avec Gatsby, vous allez le payer avec des délais de génération plus longs, mais on vous donne aussi un immense ensemble de plugins et une base sur laquelle construire un site solide, organisé et basé sur des composants. Faites de même avec Jekyll, et il vous faudra beaucoup plus d'efforts pour rester organisé et efficace tout au long du processus, même si vos générations peuvent être plus rapides.</p>
<p>Au <a href="https://www.ample.co" target="_blank" rel="noopener noreferrer">boulot</a>, je développe généralement <a href="https://www.ample.co/blog/the-case-for-gatsby" target="_blank" rel="noopener noreferrer">des sites avec Gatsby</a> (ou Next, selon le niveau d'interactivité dynamique requis). Nous avons travaillé avec le framework Gatsby pour construire un noyau sur lequel nous pouvons rapidement créer des sites web très personnalisés, riches en images, avec une abondance de composants. Nos temps de génération augementent au fur et à mesure que les sites se développent, mais c'est à ce moment que nous devenons créatifs en mettant en place des <a href="https://micro-frontends.org" target="_blank" rel="noopener noreferrer">micro frontend</a>, en déchargeant le traitement des images, en mettant en place des aperçus de contenu, ainsi que de nombreuses autres optimisations.</p>
<p><a href="https://www.seancdavis.com" target="_blank" rel="noopener noreferrer">De mon côté</a>, je préfère travailler avec Eleventy. En général, je ne fais qu'écrire du code, et mes besoins sont beaucoup plus simples. (J'aime me considérer comme un bon client pour moi-même.) J'ai le sentiment d'avoir plus de contrôle sur les fichiers en sortie, ce qui me permet d'obtenir plus facilement les performances de 💯 côté client, et c'est important pour moi.</p>
<p>En fin de compte, il ne s'agit pas <em>seulement</em> de ce qui est rapide ou lent. Il s'agit de savoir ce qui fonctionne le mieux pour vous et combien de temps vous êtes prêt à attendre.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Ce n'est que le début ! L'objectif de cet effort était de créer une base sur laquelle nous pouvons, ensemble, comparer les temps de génération relatifs des générateurs de sites statiques les plus populaires.</p>
<p>Quelles sont vos idées ? Quels sont les faiblesses du processus actuel ? Que pouvons-nous faire pour améliorer ces tests ? Comment pouvons-nous les rendre plus proches des scénarios du monde réel ? Devrions-nous transférer le traitement sur une machine dédiée ?</p>
<p>Voilà les questions auxquelles j'aimerais que vous m'aidiez à répondre. <a href="https://github.com/seancdavis/ssg-build-performance-tests/issues" target="_blank" rel="noopener noreferrer">Parlons-en</a>.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2020/10/05/la-jamstack-n-est-rapide-que-si-vous-la-rendez-rapide/</id>
    <title>La Jamstack n&#039;est rapide que si vous y veillez</title>
    <published>2020-10-05T00:00:00+00:00</published>
    <link href="https://jamstatic.fr/2020/10/05/la-jamstack-n-est-rapide-que-si-vous-la-rendez-rapide/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p><a href="https://nicolas-hoizey.com" target="_blank" rel="noopener noreferrer">Nicolas Hoizey</a> partage ici sa vision de la Jamstack et le fait que celle-ci n’est performante que si vous faites véritablement en sorte qu’elle le soit. À noter que depuis la parution de son article, Netlify a supprimé les majuscules de Jamstack afin de moins insister sur l'acronyme à l'origine de cette appelation qui suscite encore trop souvent une incompréhension. Encore heureux qu'en 2020 on puisse encore générer du HTML sans passer par un framework JS !</p></aside>
<p>La Jamstack se présente souvent comme un excellent moyen de fournir des sites performants. C'est même le premier avantage répertorié sur <a href="https://jamstack.wtf" target="_blank" rel="noopener noreferrer">jamstack.wtf</a>, un guide<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref">1</a></sup> pour "comprendre le concept de Jamstack simplement, de manière à encourager d'autres développeurs à adopter ce workflow". Mais trop de sites Jamstack sont encore trop lents.</p>
<p>Vous avez peut-être vu les diatribes fréquentes d'<a href="https://infrequently.org" target="_blank" rel="noopener noreferrer">Alex Russell</a> à propos de Gatsby :</p>
<blockquote class="twitter-tweet"><p lang="en" dir="ltr">Looking across the full set of traces, modern Gatsby seems to produce pages that take 2-3x as long as they should to become interactive. <br><br>This is not OK. Gatsby/NPM/React regressively tax access to content.<br><br>In less generous moments, I'd go as far as to say it's unethical.</p>— Alex Russell (@slightlylate) <a href="https://twitter.com/slightlylate/status/1184959830819106816">October 17, 2019</a></blockquote>
<p>Gatsby est une cible facile (parmi tant d'autres) car il n'est actuellement pas optimisé pour être performant par défaut, malgré ce qui est <a href="https://store.gatsbyjs.org/product/gatsby-sticker-6-pack" target="_blank" rel="noopener noreferrer">présenté</a>. Il est possible de corriger cela, notamment avec <a href="https://www.gatsbyjs.org/packages/gatsby-plugin-no-javascript/" target="_blank" rel="noopener noreferrer">ce plugin</a>, et je pense que de bons développeurs React peuvent améliorer les choses, mais cela devrait être le cas <strong>par défaut</strong>, et non après coup.</p>
<p>Eleventy est très différent, comme Zach Leatherman nous le rappelle dans <a href="https://www.zachleat.com/web/performance-dashboard/" target="_blank" rel="noopener noreferrer"><em>Eleventy’s New Performance Leaderboard</em></a> :</p>
<blockquote>
<p>Eleventy n'effectue aucune optimisation particulière pour rendre vos sites plus rapides. Cela ne vous empêchera pas de créer un site lent. Mais Eleventy n’ajoute <strong>rien</strong> qui ralentisse votre site.</p>
</blockquote>
<p>Le problème avec la plupart des sites Jamstack lents est qu'ils chargent tout un tas de JavaScript. N'oubliez pas que tout code JavaScript ajouté doit être envoyé au navigateur, qui nécessitera davantage de ressources pour le traiter. Cela impactera inéxorablement les performances.</p>
<p>Parfois, la génération côté serveur suffit pour obtenir des données depuis une API et servir le HTML à tous les visiteurs, ce qui est nettement plus performant.</p>
<p>Par exemple, <a href="https://www.swyx.io" target="_blank" rel="noopener noreferrer">swyx</a> a écrit <a href="https://www.swyx.io/writing/clientside-webmentions/" target="_blank" rel="noopener noreferrer"><em>Clientside Webmentions</em></a> à propos de l’implémentation de Webmention avec <a href="https://svelte.dev" target="_blank" rel="noopener noreferrer">Svelte</a>. Tout article faisant la promotion de <a href="https://indieweb.org/Webmention" target="_blank" rel="noopener noreferrer">Webmention</a> et facilitant son adoption est le bienvenu ! Mais même si c’est une bonne démo de Webmention et Svelte, je ne recommanderais pas de le faire côté client.</p>
<h2 id="privilegier-le-cote-serveur">Privilégier le côté serveur</h2>
<p>Je préfère <a href="https://nicolas-hoizey.com/articles/2017/07/27/so-long-disqus-hello-webmentions/#how-does-it-work-on-this-site" target="_blank" rel="noopener noreferrer">le faire sur le serveur</a>.</p>
<p>Cela permet :</p>
<ul>
<li>D'appeler l’API <a href="http://webmention.io" target="_blank" rel="noopener noreferrer">webmention.io</a> seulement au moment de générer le site, ce qui devrait être moins fréquent que la consultation des pages par les visiteurs.</li>
<li>De mettre en cache le résultat des requêtes à <a href="http://webmention.io" target="_blank" rel="noopener noreferrer">webmention.io</a> et l’horodatage de la dernière, afin que la prochaine requête demande uniquement les nouvelles webmentions.</li>
</ul>
<p>Cela sollicite moins <a href="http://webmention.io" target="_blank" rel="noopener noreferrer">webmention.io</a>, avec une unique requête simple par génération, alors que le client effectue une requête bien plus volumineuse (voire plusieurs, avec pagination) pour chaque page vue.</p>
<p>Par exemple :</p>
<ul>
<li>Mon site web a reçu 75 Webmentions en avril 2020. Je l’ai probablement généré une centaine de fois durant la même période, ce qui correspond à <strong>100 requêtes à Webmention.io avec des réponses peu volumineuses</strong>.</li>
<li>Pendant la même période, 3 746 pages de mon site web ont été vues (sous estimé, je continue à utiliser Google Analytics 🤷‍♂️), ce qui équivaudrait à <strong>3 746 requêtes à Webmention.io avec des réponses volumineuses</strong>.</li>
</ul>
<p>Utiliser la génération côté serveur pour récupérer les Webmentions offre de multiples avantages :</p>
<ul>
<li>La performance pour les utilisateurs est largement meilleure, avec du HTML déjà compilé sur le serveur et servi de manière statique.</li>
<li>Beaucoup moins d’appels d’API, ce qui requiert beaucoup moins de temps de compilation et d’énergie.</li>
<li>Tout le monde devrait savoir qu'<a href="https://aaronparecki.com" target="_blank" rel="noopener noreferrer">Aaron Parecki</a> propose l’impressionnant service <a href="http://webmention.io" target="_blank" rel="noopener noreferrer">webmention.io</a> <strong>gratuitement</strong>, et la majorité des utilisateurs de Webmention l’utilisent aujourd’hui, et ne pas surcharger son API donne bien meilleure conscience.</li>
</ul>
<h2 id="ameliorer-le-cote-client-s-il-est-indispensable">Améliorer le côté client, s’il est indispensable</h2>
<p>Si vous savez que vous recevez beaucoup de Webmentions très utiles que vous devez afficher à vos visiteurs, vous pouvez améliorer la liste générée côté serveur via le côté client.</p>
<p>Mais rappelez-vous que chaque JavaScript ajouté à la page a un coût, donc les quelques Webmentions supplémentaires doivent être vraiment utiles.</p>
<p>Alors, au lieu de le faire pour chaque page vue :</p>
<ul>
<li>Essayez d’<strong>attendre un peu après la génération du site</strong> avant de faire les appels API côté client. Garder l’horodatage de génération du site côté client via JavaScript, et attendez une heure, une journée, en fonction de la fréquence des Webmentions. Vous pouvez même utiliser l’« âge » de la page pour moins requêter <a href="http://webmention.io" target="_blank" rel="noopener noreferrer">webmention.io</a> pour le contenu plus ancien, qui reçoit probablement moins de Webmentions, comme l’a fait <a href="https://aarongustafson.github.io/jekyll-webmention_io/performance-tuning" target="_blank" rel="noopener noreferrer">Aaron Gustafson pour les appels côté serveur dans son plugin Jekyll</a>.</li>
<li>Gardez une trace des appels API, pour un utilisateur, dans le <em>localStorage</em> ou l’<em>IndexDB</em>, afin que vous ne répétiez pas ces appels tout de suite. Vous pouvez même utiliser un Service Worker pour mettre en cache les requêtes et leur horodatage.</li>
</ul>
<h2 id="les-appels-a-l-api-uniquement-cote-client-ont-parfois-plus-de-sens">Les appels à l’API uniquement côté client ont parfois plus de sens</h2>
<p>Je suis d’accord que les Webmentions ne sont pas le cas d’usage le plus complexe pour expliquer que la plupart du temps vous devez appeler les API côté serveur au moment de la génération plutôt que côté client :</p>
<ul>
<li>Les Webmentions à afficher sont les mêmes pour tous les visiteurs.</li>
<li>Ne pas générer les plus récentes n’est sûrement pas un problème.</li>
</ul>
<p>Alors oui je comprends bien que de nombreux autres cas d’usage rendent les appels côté client nécessaires, ou meilleurs que ceux côté serveur.</p>
<p>Ce que je dis c’est que <strong>ça ne devrait pas être le cas par défaut</strong>.</p>
<h2 id="promouvoir-la-ajmstack-mstack">Promouvoir la <del>AJMstack</del> Mstack</h2>
<p>C’est aussi quelque chose que je n’aime pas vraiment dans la tendance actuelle de la Jamstack, promouvoir <strong>J</strong>avaScript et les <strong>A</strong>PI bien plus que le balisage (NDT : « balisage » peut se traduire par « <strong>M</strong>arkup » en anglais).</p>
<p>Voici pour l’exemple ce que vous pouvez voir sur <a href="https://jamstack.wtf/" target="_blank" rel="noopener noreferrer">jamstack.wtf</a> (simplifié) :</p>
<figure>
<img src="/images/post/2020-10-05_la-jamstack-n-est-rapide-que-si-vous-la-rendez-rapide/jamstack-horizontal.cb75428480e15291ef03268a1c4fa2ef.svg" alt="Jamstack version aplatie" title="Jamstack version aplatie" loading="lazy" decoding="async" class="dark:brightness-90" width="492" height="139">
<figcaption>Jamstack version aplatie</figcaption>
</figure>
<p>Comme suggéré par <a href="https://twitter.com/yann_yinn" target="_blank" rel="noopener noreferrer">Yann</a>, j’aimerais commencer par utiliser cette meilleure présentation :</p>
<figure>
<img src="/images/post/2020-10-05_la-jamstack-n-est-rapide-que-si-vous-la-rendez-rapide/jamstack-vertical.ab7be3c6640c6190e02ce014568ebf49.svg" alt="Jamstack version empilée" title="Jamstack version empilée" loading="lazy" decoding="async" class="dark:brightness-90" width="489" height="212">
<figcaption>Jamstack version empilée</figcaption>
</figure>
<p>Cela rend plus évident qu’il s’agit d’une pile de choses, très utile pour une « pile » (NDT : « stack » peut être traduit « pile » en français).</p>
<p>Mais j’aimerais suggérer cette modification :</p>
<figure>
<img src="/images/post/2020-10-05_la-jamstack-n-est-rapide-que-si-vous-la-rendez-rapide/ajmstack.2456fae4d40741f3c0c9f382b0b95303.svg" alt="JavaScript fait la liaison" title="JavaScript fait la liaison" loading="lazy" decoding="async" class="dark:brightness-90" width="489" height="212">
<figcaption>JavaScript fait la liaison</figcaption>
</figure>
<p>Bien sûr, cela se lit <strong>AJMstack</strong> au lieu de Jamstack, donc je parie que je n’aurais pas de succès dans la promotion… 🤷‍♂️</p>
<p>Mais au final ça semble plus adéquat, et cela montre que JavaScript fait le lien entre les APIs et le balisage.</p>
<p>Cela permet de présenter cela comme une excellente plate-forme d’amélioration progressive, car nous pouvons commencer avec du bon vieux (ai-je entendu « ennuyeux » ?) HTML…</p>
<p>Voici la <strong>Mstack</strong> :</p>
<figure>
<img src="/images/post/2020-10-05_la-jamstack-n-est-rapide-que-si-vous-la-rendez-rapide/mstack.9e208a65e9487d30a516b35dfd5ce1f4.svg" alt="Mstack" title="Mstack" loading="lazy" decoding="async" class="dark:brightness-90" width="489" height="66">
<figcaption>Mstack</figcaption>
</figure>
<p>Assurez-vous déjà que cette « couche » soit au top, et ensuite améliorez-la avec du JavaScript et des APIs au besoin.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn:1">
<p>Il existe une <a href="/2019/02/07/c-est-quoi-la-jamstack/">version française</a> de ce guide.&#160;<a href="#fnref1:1" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
</ol>
</div>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2020/09/05/tout-savoir-sur-les-modules-hugo/</id>
    <title>Tout ce que vous devez savoir sur les modules Hugo</title>
    <published>2020-09-05T11:55:22+00:00</published>
    <updated>2020-09-05T11:55:22+00:00</updated>
    <link href="https://jamstatic.fr/2020/09/05/tout-savoir-sur-les-modules-hugo/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>Les modules Hugo, apparus dans la version <a href="https://gohugo.io/news/0.56.0-relnotes/" target="_blank" rel="noopener noreferrer">0.56.0</a>, ajoutent un puissant système de dépendances dont vous auriez tort de vous passer. Ils permettent de rappatrier des fichiers stockés dans des dépôts Git dans vos projets. Les cas d'utilisation vont du simple usage d'un thème complet, à celui de sélection de composants de thèmes distants, comme des icônes, ou au partage de composants réutilisables (modèles, fichiers partiels, shortcodes, etc.) entre plusieurs projets.</p>
<p>Cet article vous propose de vous mettre la main à la pâte et après avoir vu comment <strong>importer</strong> un ou plusieurs modules dans votre site, nous développerons notre propre module!</p>
<h3 id="initialiser-votre-projet-en-tant-que-module">Initialiser votre projet en tant que module</h3>
<aside class="note note-note"><p><strong>Tout est module</strong><br>
Il est important de comprendre qu'avant d'importer un module Hugo, votre projet doit lui même être un module !</p></aside>
<p>Pour initialiser votre projet en tant que module, vous devez faire reférence à une URL de dépôt Git.</p>
<p>Partons du principe que votre projet Hugo est déjà sur GitHub à l'adresse <code>https://github.com/chez-moi/mon-depot</code>.</p>
<p>Dans un terminal, à la racine de votre projet, lancez :</p>
<pre><code class="language-bash hljs bash">hugo mod init github.com/chez-moi/mon-depot</code></pre>
<p>☝️ Cette commande génère un fichier <code>go.mod</code> à la racine du projet. Il ressemble à quelque chose comme :</p>
<pre><code class="language-go hljs go">module github.com/chez-moi/mon-depot

<span class="hljs-keyword">go</span> <span class="hljs-number">1.15</span></code></pre>
<p>Un fichier <code>go.sum</code> est également généré, mais ne nous en préoccupons pas pour le moment.</p>
<h3 id="importer-un-depot-distant">Importer un dépôt distant</h3>
<p>Prenons un exemple simple et ajoutons à notre projet les icônes mis à disposition par l'équipe de Bootstrap dans le dépôt <a href="https://github.com/twbs/icons" target="_blank" rel="noopener noreferrer">https://github.com/twbs/icons</a>.</p>
<p>La déclaration se fait dans votre fichier de configuration à l'aide du mot clé <code>module</code> et de sa liste d'<code>imports</code>:</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">module:</span>
  <span class="hljs-attr">imports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">path:</span> <span class="hljs-string">github.com/twbs/icons</span></code></pre>
<p>Maintenant lancez la commande <code>hugo</code> et vous pouvez remarquer que le fichier <code>go.mod</code> comporte une nouvelle ligne:</p>
<pre><code class="language-go hljs go">module github.com/chez-moi/mon-depot

<span class="hljs-keyword">go</span> <span class="hljs-number">1.15</span>

require github.com/twbs/icons v1<span class="hljs-number">.0</span><span class="hljs-number">.0</span> <span class="hljs-comment">// indirect</span></code></pre>
<p>C'est bien, mais cela ne dit pas à Hugo ce qu'il doit faire de ces fichiers.</p>
<p>Grâce à la clé <code>mounts</code>, relative à notre import de Bootstrap, donnons plus de directives à Hugo :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">module:</span>
  <span class="hljs-attr">imports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">path:</span> <span class="hljs-string">github.com/twbs/bootstrap</span>
      <span class="hljs-attr">mounts:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">source:</span> <span class="hljs-string">icons</span>
          <span class="hljs-attr">target:</span> <span class="hljs-string">assets/icons</span></code></pre>
<p>Comme pour les <code>imports</code>, on peut utiliser plusieurs <code>mounts</code>, pour le moment nous contenterons d'un seul avec les paramètres suivants:</p>
<ul>
<li>le paramètre <code>source</code> désigne la location des fichiers dans le dépôt distant. Ici nous voulons juste le réperetoire <code>icons</code> situé à la racine du dépôt.</li>
<li>le paramètre <code>target</code> désigne l'endroit où Hugo doit monter les fichiers dans notre systéme de fichier Hugo unifié.</li>
</ul>
<p>Une fois le montage effectué, nous pouvons accéder aux icônes SVG situées dans ce dossier comme à n'importe quel autre fichier de notre projet :</p>
<pre><code class="language-html hljs xml">{{ with resources.Get "icons/cart.svg" }}
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"w-4 fill-current"</span>&gt;</span>
    {{ .Content | safeHTML }}
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
{{ end }}</code></pre>
<p>Et voilà !</p>
<p>Nous pouvons afficher cette icône de panier SVG sans avoir à la recopier dans notre projet.</p>
<p>Dans l'éventualité où vous voulez personnaliser cette icône de panier nous pouvons compter sur le système de fichiers unifié d'Hugo !</p>
<p>Tout ce que nous avons à faire est de créer un fichier d'icône du même nom et de le placer au même emplacement <code>assets/icons/cart.svg</code> dans notre projet pour qu'il soit utilisé à la place de l'icône de panier de Bootstrap.</p>
<p>Ou si nous voulions, nous pourrions aussi importer le fichier d'un autre dépôt distant pour une simple icône 🤪 :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">path:</span> <span class="hljs-string">github.com/refactoringui/heroicons</span>
    <span class="hljs-attr">mounts:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">source:</span> <span class="hljs-string">src/solid/shopping-cart.svg</span>
      <span class="hljs-attr">target:</span> <span class="hljs-string">assets/icons/cart.svg</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">path:</span> <span class="hljs-string">github.com/twbs/icons</span>
    <span class="hljs-attr">mounts:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">source:</span> <span class="hljs-string">icons</span>
      <span class="hljs-attr">target:</span> <span class="hljs-string">assets/icons</span></code></pre>
<p>☝️ Ici nous importons deux dépôts, chacun avec son propre point de montage.</p>
<aside class="note note-tip"><p>Peu importe le nombre de fichiers montés, Hugo téléchargera quand même l'intégralité du dépôt, donc pensez-y à deux fois avant d'importer un dépôt de plusieurs MB pour un simple fichier SVG.</p></aside>
<h3 id="mise-a-jour">Mise à jour</h3>
<p>Et si le dépôt distant est mis à jour ? Par défaut lors du premier import Hugo va télécharger la dernière version publiée, à défaut le commit de tête de la branche par défaut. C'est pour cela qu'Hugo a ajouté <code>v1.0.0</code> à la fin du <code>require</code> dans le fichier <code>go.mod</code>.</p>
<p>Si <a href="https://github.com/twbs/icons/" target="_blank" rel="noopener noreferrer"><code>github.com/twbs/icons</code></a> sort une version <code>v1.1.0</code> et que vous souhaitez faire la mise à jour :</p>
<pre><code class="language-bash hljs bash">hugo mod get -u github.com/twbs/bootstrap</code></pre>
<p>Cette commande mettra votre fichier <code>go.mod</code> à jour avec la dernière version publiée.</p>
<h3 id="cibler-une-version">Cibler une version</h3>
<p>Si maintenant vous souhaitez utiliser une version bien précise d'un dépôt Git, ciblez son tag (prenons un autre dépôt pour cet exemple):</p>
<pre><code class="language-bash hljs bash">hugo mod get github.com/twbs/bootstrap@v3.4.1</code></pre>
<p>Si vous souhaitez utiliser un commit bien précis, pointez vers son hash avec <code>@</code> comme ceci :</p>
<pre><code class="language-bash hljs bash">hugo mod get github.com/twbs/bootstrap@394812b61d4dc80bfb2e090de925ae0dfc4cc29b</code></pre>
<aside class="note note-tip"><p>Vous devez bien entendu versionner les fichiers <code>go.mod</code> et <code>go.sum</code> afin que tous les collaborateurs du projet utilisent les mêmes versions !</p></aside>
<aside class="note note-note"><p><strong>Développer un module en local</strong><br>
Cet article ne couvre pas le développemeent local d'un module, nous vous invitons à <a href="https://www.thenewdynamic.com/note/develop-hugo-modules-locally/" target="_blank" rel="noopener noreferrer">lire notre note sur le sujet</a> avant d'embarquer pour le joyeux monde des modules Hugo.</p></aside>
<h2 id="creer-un-module-hugo">Créer un module Hugo</h2>
<p>Bon c'est intéressant de savoir importer n'importe quel dépôt et d'intégrer ses fichiers dans notre projet, mais la vraie puissance vient de l'utilisation de modules Hugo complets, qui peuvent gérer des fichiers de modèles, des assets, des fichiers de données, voire des fichiers de <strong>contenu</strong> !</p>
<p>Et quel meilleur moyen d'apprendre que de créer nous-mêmes notre propre module ?</p>
<p>Créons pour l'exemple un petit module d'icônes. Pour cela nous voulons :</p>
<ol>
<li>Importer les fichiers SVG d'un dépôt distant</li>
<li>Créer une page qui liste toutes les icônes disponibles</li>
<li>Utiliser notre propre fichier partiel <code>icon</code> pour faciliter l'affichage de n'importe quelle icône du projet.</li>
</ol>
<p>Créons d'abord un dossier sur notre ordinateur et nommons-le <code>assets/hugo-icons</code> pour éviter tout conflit avec un dossier <code>assets/icons</code> existant.</p>
<h3 id="1-les-imports">1. Les imports</h3>
<p>Nous avons tout d'abord besoin de lister les <code>imports</code> des fichiers de notre module dans un fichier <code>config.yaml</code>.</p>
<p>En effet, n'importe quel projet Hugo, que ce soit un site web, un thème ou un composant peut importer d'autres modules ou d'autres dépôts. Il n'y a pas de limite dans l'arborescence de dépendances, les modules sont une vraie de gestion de dépendences !</p>
<p>Nos imports seront très similaires à ce que nous avons vu plus haut, la seule différence est que nous importons les fichiers dans un dossier différent pour éviter tout conflit :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># config.yaml</span>
<span class="hljs-attr">module:</span>
  <span class="hljs-attr">imports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">path:</span> <span class="hljs-string">github.com/twbs/bootstrap</span>
      <span class="hljs-attr">mounts:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">source:</span> <span class="hljs-string">icons</span>
          <span class="hljs-attr">target:</span> <span class="hljs-string">assets/hugo-icons/icons</span></code></pre>
<h3 id="2-la-page-de-listing">2. La page de listing</h3>
<p>Pour cela nous avons besoin de deux fichiers :</p>
<ol>
<li>Un fichier de contenu ;</li>
<li>Un fichier de modèle pour qu'Hugo puisse effectuer le rendu de notre fichier.</li>
</ol>
<p>Puisque nous utilisons la directive <code>mounts</code>, nous n'avons pas besoin de nous respecter l'arborescence classique d'un projet Hugo. Nous sommes libres d'organiser les fichiers de notre module comme bon nous semble :</p>
<ul>
<li><code>page/layout.html</code></li>
<li><code>page/content.md</code></li>
</ul>
<p>Mettons à jour notre fichier <code>config.yaml</code>, vous pouveez noter que les paramètres <code>mounts</code> se situent à la racine de notre map <code>module</code>.</p>
<p>Les points de montage ne sont en effet pas réservés aux seuls <code>imports</code>. Vous pouvez attribuer des points de montage au module en question à l'aide de sa propre directive <code>mounts</code> :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># config.yaml</span>
<span class="hljs-attr">module:</span>
  <span class="hljs-attr">mounts:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">source:</span> <span class="hljs-string">page/index.md</span>
      <span class="hljs-attr">target:</span> <span class="hljs-string">content/hugo-icons-listing.md</span>
      <span class="hljs-attr">lang:</span> <span class="hljs-string">en</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">source:</span> <span class="hljs-string">page/template.html</span>
      <span class="hljs-attr">target:</span> <span class="hljs-string">layouts/_default/hugo-icons-listing.html</span>
  <span class="hljs-attr">imports:</span> <span class="hljs-string">[...]</span></code></pre>
<aside class="note note-info"><p>Le paramètre <code>lang</code> n'a d'importance que pour les sites multilingues et même si vous l'omettez la page sera montée pour la langue par défaut du site.</p></aside>
<p>Nos deux fichiers eux ressemblent à ça :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># page/index.md</span>
<span class="hljs-meta">---</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">Liste</span> <span class="hljs-string">des</span> <span class="hljs-string">icônes</span>
<span class="hljs-attr">layout:</span> <span class="hljs-string">hugo-icons-listing</span>
<span class="hljs-meta">---</span>
</code></pre>
<pre><code class="language-html hljs xml">{{/* page/template.html */}}
{{ define "main" }}
  {{ range resources.Match "hugo-icons/icons/*.svg" }}
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"fill:currentColor;width:3rem;margin:1rem 0"</span>&gt;</span>
    {{ .Content | safeHTML }}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  {{ end }}
{{ end }}</code></pre>
<aside class="note note-info"><p>Notez qu'ici nous partons du principe que votre modèle <code>baseof.html</code> contient un block <code>main</code>, sans quoi Hugo affichera une erreur.</p></aside>
<h3 id="3-le-fichier-partiel">3. Le fichier partiel</h3>
<p>Plaçons notre fichier dans <code>partials/icon.html</code> et déclarons un nouveau point de montage :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># config.yaml</span>
<span class="hljs-attr">module:</span>
  <span class="hljs-attr">mounts:</span>
    <span class="hljs-string">[...]</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">source:</span> <span class="hljs-string">partials</span>
      <span class="hljs-attr">target:</span> <span class="hljs-string">layouts/partials/hugo-icons</span>
  <span class="hljs-attr">imports:</span>
  <span class="hljs-string">[...]</span></code></pre>
<p>Ici nous choississons de monter notre fichier dans un répertoire nommé pour éviter tout conflit avec un fichier partiel <code>icon</code> existant. Les utilisateurs pourront ainsi appeler <code>{{ partial "hugo-icons/icon" "cart" }}</code> en toute sécurité.</p>
<p>Notre fichier partiel :</p>
<pre><code>{{/*
  icon
  Affiche l'icône correspondant à la chaîne passée comme contexte

  @author @regisphilibert

  @context String (.)

  @access public

  @example - Go Template
    {{ partial "hugo-icons/icon" "cart" }}
*/}}</code></pre>
<pre><code class="language-html hljs xml">{{- with resources.Get (print "hugo-icons/icons/" .) -}}
  {{- .Content | safeHTML -}}
{{- end -}}</code></pre>
<h3 id="finitions-de-notre-module">Finitions de notre module</h3>
<p>Notre module comporte maintenant les fichiers nécessaires. Il nous faut encore ajouter quelque chose de très important à sa configuration: sa compatibilité avec les versions d'Hugo.</p>
<p>Nous utilisons la fonction <code>resources.Match</code> introduite dans <a href="https://gohugo.io/news/0.57.0-relnotes/" target="_blank" rel="noopener noreferrer">Hugo 0.57.0</a>. Quant au montage dans les sous-dossiers, il n'est supporté que depuis <a href="https://gohugo.io/news/0.64.0-relnotes/#other-1" target="_blank" rel="noopener noreferrer">Hugo 0.64.0</a>.</p>
<p>Il nous faut donc indiquer que notre module ne marchera qu'avec une version d'Hugo au moins égale à 0.64.0 ou sinon… patatra !</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># config.yaml</span>
<span class="hljs-attr">module:</span>
  <span class="hljs-attr">hugoVersion:</span>
    <span class="hljs-comment"># La version extended (Sass) n'est pas requise</span>
    <span class="hljs-attr">extended:</span> <span class="hljs-literal">false</span>
    <span class="hljs-comment"># Il n'y a pas de version maximale</span>
    <span class="hljs-attr">max:</span> <span class="hljs-string">""</span>
    <span class="hljs-comment"># Par contre il y a une version minimale</span>
    <span class="hljs-attr">min:</span> <span class="hljs-string">"0.64.0"</span></code></pre>
<p>Notre fichier <code>config.yaml</code> final :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">module:</span>
  <span class="hljs-attr">hugoVersion:</span>
    <span class="hljs-attr">min:</span> <span class="hljs-string">"0.64.0"</span>
  <span class="hljs-attr">mounts:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">source:</span> <span class="hljs-string">page/index.md</span>
      <span class="hljs-attr">target:</span> <span class="hljs-string">content/hugo-icons-listing.md</span>
      <span class="hljs-attr">lang:</span> <span class="hljs-string">en</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">source:</span> <span class="hljs-string">page/template.html</span>
      <span class="hljs-attr">target:</span> <span class="hljs-string">layouts/_default/hugo-icons-listing.html</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">source:</span> <span class="hljs-string">partials</span>
      <span class="hljs-attr">target:</span> <span class="hljs-string">layouts/partials/hugo-icons</span>
  <span class="hljs-attr">imports:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">path:</span> <span class="hljs-string">github.com/twbs/icons</span>
      <span class="hljs-attr">mounts:</span>
        <span class="hljs-bullet">-</span> <span class="hljs-attr">source:</span> <span class="hljs-string">icons</span>
          <span class="hljs-attr">target:</span> <span class="hljs-string">assets/hugo-icons/icons</span></code></pre>
<p>Vous pouvez consulter <a href="https://github.com/regisphilibert/hugo-module-icons" target="_blank" rel="noopener noreferrer">le dépôt de notre module d'exemple pour cet article</a>.</p>
<aside class="note note-info"><h4 id="pense-bete">Pense-bête</h4>
<p>Nous avons vu les commandes <code>hugo mod init</code> et <code>hugo mod get -u</code>. Ces deux commandes sont aussi très utiles:
<code>hugo mod clean</code>
Cette commande va supprimer le cache du module. Je l'utiliss dès que quelque chose de marche pas comme prévu.
<code>hugo mod tidy</code>
Cette commande supprimera les entrées inutilisées dans votre fichier <code>go.sum</code>.</p></aside>
<h2 id="conclusion">Conclusion</h2>
<p>Les modules Hugo sont la méthode à priviléger dès qu'il s'agit d'importer des fichiers issus de n'importe quel dépôt Git public dans vos projets et de contrôler leur versionnement.</p>
<p>Et maintenant que vous savez comment créer un module Hugo, vous devriez vous en servir pour gérer les composants réutilisables de vos projets et les <a href="https://www.thenewdynamic.com/open-source/" target="_blank" rel="noopener noreferrer">publier</a> pour enrichir l'écosystème d'Hugo. :smile:</p>
<h2 id="ressources-complementaires">Ressources complémentaires</h2>
<ul>
<li><a href="https://gohugo.io/hugo-modules/" target="_blank" rel="noopener noreferrer">Documentation officielle</a></li>
<li><a href="https://dev.to/craftsmandigital/hugo-modules-for-dummies-42j9" target="_blank" rel="noopener noreferrer">Les modules Hugo pour les nuls</a></li>
<li><a href="https://www.hugofordevelopers.com/series/master-hugo-modules/" target="_blank" rel="noopener noreferrer">Maîtriser les modules Hugo</a></li>
</ul>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2019/12/27/webmentions-eleventy/</id>
    <title>Guide complet des Webmentions avec Eleventy</title>
    <published>2019-12-27T07:48:32+00:00</published>
    <updated>2019-12-27T16:05:32+00:00</updated>
    <link href="https://jamstatic.fr/2019/12/27/webmentions-eleventy/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>Je suis toujours une grande fan du générateur de site statique <a href="https://www.11ty.dev/" target="_blank" rel="noopener noreferrer">Eleventy</a>, et j'étais impatiente de gérer les <a href="https://indieweb.org/Webmention" target="_blank" rel="noopener noreferrer">Webmentions</a> avec.</p>
<blockquote>
<p>Webmention est un standard web pour les mentions et les conversations sur le web, un puissant élément constitutif d'un réseau fédéré croissant de commentaires, d'appréciations, de rediffusions et d'autres riches interactions  sur le web social décentralisé.
— <a href="https://indieweb.org/Webmention" target="_blank" rel="noopener noreferrer">IndieWeb.org</a></p>
</blockquote>
<p>C'est un outil très cool qui vous permet d'avoir des interactions sociales quand vous hébergez votre propre contenu. Max Böck a écrit un excellent article qui détaille son implémentation, <a href="https://mxb.dev/blog/using-webmentions-on-static-sites/" target="_blank" rel="noopener noreferrer">Indieweb statique 2e partie : utiliser les Webmentions</a>. Il a également crée un starter pour Eleventy, <a href="https://github.com/maxboeck/eleventy-webmentions" target="_blank" rel="noopener noreferrer">eleventy-webmentions</a>, un modèle de départ avec un support basique des webmentions.</p>
<p>Alors pourquoi écrire cet article ? Malheureusement pour moi j'ai commencé à développer mon site avec le <a href="https://github.com/11ty/eleventy-base-blog" target="_blank" rel="noopener noreferrer">blog de base pour Eleventy</a> et j'avais déjà terminé quand j'ai découvert <a href="https://github.com/maxboeck/eleventy-webmentions" target="_blank" rel="noopener noreferrer">eleventy-webmentions</a>. J'ai dû lutter pour développer pleinement cette fonctionnalité, car je débute encore avec Eleventy. J'ai donc voulu partager en détail les étapes que j'ai dû mener à bien, en espérant que ça aide davantage d'entre vous à rejoindre l'IndieWeb.</p>
<p>Le but de cet article est d'ajouter les webmentions à un site Eleventy, après coup. Les fichiers, les dossiers, et l'architecture du site sont les mêmes que dans <code>eleventy-base-blog</code>, mais vous pouvez vous en servir comme point de départ pour un site Eleventy. Simplement faites attention aux endroits où votre architecture pourrait différer.</p>
<p>Le code de cet article est un mélange de l'article de Max Böck, son <a href="https://github.com/maxboeck/mxb" target="_blank" rel="noopener noreferrer">site perso</a>, du modèle d'amorçage <a href="https://github.com/maxboeck/eleventy-webmentions" target="_blank" rel="noopener noreferrer">eleventy-webmentions</a>, du <a href="https://github.com/zachleat/zachleat.com" target="_blank" rel="noopener noreferrer">site de Zach Leatherman</a>, et des modifications effectuées pendant ma propre implémentation. Je leur suis très reconnaissante pour leur travail, car je n'aurais jamais pu arriver à ce résultat sans eux.</p>
<h2 id="etape-1-s-inscrire-sur-webmentions-io">Étape 1 : s'inscrire sur webmentions.io</h2>
<p>Il nous faut d'abord s'inscrire sur webmention.io, le service tiers qui nous permet de profiter du pouvoir des webmentions sur les sites statiques.</p>
<ol>
<li>Configurer IndieAuth de manière à ce que webmention.io sache que vous êtes bien le propriétaire de votre domaine. Suivez les instructions données <a href="https://indieauth.com/setup" target="_blank" rel="noopener noreferrer">sur leur site</a>.</li>
<li>Allez sur <a href="https://webmention.io/" target="_blank" rel="noopener noreferrer">webmention.io</a>.</li>
<li>Entrez l'URL de votre site web dans le champ "Web Sign-In" , et cliquez sur "Sign in".</li>
</ol>
<p>Si la validation est réussie, vous devriez être redirigé·e vers le tableau des webmentions où sont affichées deux balises <code>&lt;link&gt;</code> que vous devez insérez dans la balise <code>&lt;head&gt;</code> de votre site web :</p>
<pre><code class="language-html hljs xml"><span class="hljs-comment">&lt;!-- _includes/layouts/base.njk --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"webmention"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://webmention.io/&lt;your.domain&gt;/webmention"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"pingback"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://webmention.io/&lt;your.domain&gt;/xmlrpc"</span> /&gt;</span></code></pre>
<p>Vous disposez aussi d'un token d'API personnel. Nous voulons pouvoir le stocker de manière sécurisée dans nos variables d'environnement locales. Installez <code>dotenv</code> pour définir et accéder facilement à vos variables d'environnement :</p>
<pre><code class="language-bash hljs bash">npm install dotenv</code></pre>
<p>Créez un fichier <code>.env</code> à la racine de votre projet et ajoutez votre token d'API pour webmention.io.</p>
<pre><code class="language-bash hljs bash">WEBMENTION_IO_TOKEN=v07r370k3n1c1.</code></pre>
<p>N'oubliez pas d'ajouter votre fichier <code>.env</code> dans votre fichier <code>.gitignore</code>. Tant que nous y sommes, ajoutons également le dossier <code>_cache</code> qui sera crée lors du premier rapatriement des webmentions :</p>
<pre><code class="language-bash hljs bash">_cache/
_site/
node_modules/
.env</code></pre>
<p>Vous aimeriez probablement récupérer quelques webmentions. Si vous utilisez Twitter, <a href="https://brid.gy/" target="_blank" rel="noopener noreferrer">Bridgy</a> est un excellent moyen de récupérer vos mentions depuis Twitter. Assurez d'abord qu'un lien vers votre site web est présent dans votre profil, puis connectez-le.</p>
<h2 id="comment-tout-ca-va-marcher">Comment tout ça va marcher</h2>
<p>Quand nous lançons une génération avec <code>NODE_ENV=production</code>, nous allons récupérer les nouvelles webmentions publiées depuis la fois précédente. Celles-ci seront sauvées dans le fichier <code>_cache/webmentions.json</code>.  Ces mentions proviennent de l'<a href="https://github.com/aaronpk/webmention.io#api" target="_blank" rel="noopener noreferrer">API de webmention.io</a>.</p>
<p>Lors de chaque génération, pour chaque page :</p>
<ul>
<li>Depuis le cache des webmentions <code>_cache/webmentions.json</code>, ne garder que les webmentions qui correspondent à l'URL de la page en cours (dans mon cas, celle de l’article de blog).</li>
<li>Faire appel à la fonction <code>webmentionsByType</code> pour les filtrer par type (par exemple des <em>likes</em> ou des réponses)</li>
<li>Utiliser la fonction <code>size</code> pour calculer le nombre de mentions par type</li>
<li>Afficher le total avec le type de mention sous forme d'entête (ex: "7 réponses")</li>
<li>Afficher la liste des mentions de ce type (par exemple sous forme d'avatar avec un lien vers le profil Twitter pour chaque <em>like</em>.)</li>
</ul>
<h2 id="recuperation-des-webmentions">Récupération des webmentions</h2>
<p>Tout d'abord, nous devons ajouter notre nom de domaine en tant que propriété dans notre fichier <code>_data/metadata.json</code>. Ajoutons-y également l'URL racine qui nous sera utile par la suite :</p>
<pre><code class="language-json hljs json"><span class="hljs-comment">// _data/metadata.json</span>
{
  <span class="hljs-comment">//...other metadata</span>
  <span class="hljs-attr">"domain"</span>: <span class="hljs-string">"example.com"</span>,
  <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://example.com"</span>
}</code></pre>
<p>Ensuite, installons quelques dépendances supplémentaires :</p>
<pre><code class="language-bash hljs bash">npm install lodash node-fetch</code></pre>
<p>Et mettons à jour notre script de <code>build</code> pour y préciser la variable d'environnement <code>NODE_ENV</code> dans notre <code>package.json</code> :</p>
<pre><code class="language-json hljs json"><span class="hljs-comment">// package.json</span>
{
  <span class="hljs-comment">// …config</span>
  <span class="hljs-attr">"scripts"</span>: {
    <span class="hljs-attr">"build"</span>: <span class="hljs-string">"NODE_ENV=production npx eleventy"</span>,
    <span class="hljs-comment">// scripts…</span>
}</code></pre>
<p>Nous pouvons maintenant nous concentrer sur la partie récupération. Oui, je sais que le fichier qui suit est beaucoup trop long, mais je pense qu'il n'est pas facile à comprendre hors contexte. Voici les grandes étapes qui constituent le code :</p>
<ol>
<li>Lire les mentions depuis le cache enregistré dans <code>_cache/webmentions.json</code>.</li>
<li>Si notre environnement est <code>production</code>, récupérer les nouvelles webmentions depuis la dernière génération. Les fusionner avec celles en cache et sauvegarder le fichier de cache. Retourner les mentions ajoutées.</li>
<li>Si notre environnement n’est pas <code>production</code>, retourner les mentions depuis le cache.</li>
</ol>
<pre><code class="language-javascript hljs javascript"><span class="hljs-comment">// _data/webmentions.js</span>
<span class="hljs-comment">// Déclaration des dépendances</span>
<span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>)
<span class="hljs-keyword">const</span> fetch = <span class="hljs-built_in">require</span>(<span class="hljs-string">'node-fetch'</span>)
<span class="hljs-keyword">const</span> unionBy = <span class="hljs-built_in">require</span>(<span class="hljs-string">'lodash/unionBy'</span>)
<span class="hljs-keyword">const</span> domain = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./metadata.json'</span>).domain

<span class="hljs-comment">// Charger les variables d'environnement avec `dotenv`</span>
<span class="hljs-built_in">require</span>(<span class="hljs-string">'dotenv'</span>).config()

<span class="hljs-comment">// Définir l'emplacement du cache et les paramètres d'appel de l'API</span>
<span class="hljs-keyword">const</span> CACHE_FILE_PATH = <span class="hljs-string">'_cache/webmentions.json'</span>
<span class="hljs-keyword">const</span> API = <span class="hljs-string">'https://webmention.io/api'</span>
<span class="hljs-keyword">const</span> TOKEN = process.env.WEBMENTION_IO_TOKEN

<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fetchWebmentions</span>(<span class="hljs-params">since, perPage = <span class="hljs-number">10000</span></span>) </span>{
  <span class="hljs-comment">// Avertir et s'arrêter là si le nom de domaine et le token d'API ne sont pas définis</span>
  <span class="hljs-keyword">if</span> (!domain || !TOKEN) {
    <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">'&gt;&gt;&gt; Impossible de récupérer les webmentions : domaine ou token manquant'</span>)
    <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>
  }

  <span class="hljs-keyword">let</span> url = <span class="hljs-string">`<span class="hljs-subst">${API}</span>/mentions.jf2?domain=<span class="hljs-subst">${domain}</span>&amp;token=<span class="hljs-subst">${TOKEN}</span>&amp;per-page=<span class="hljs-subst">${perPage}</span>`</span>
    <span class="hljs-keyword">if</span> (since) url += <span class="hljs-string">`&amp;since=<span class="hljs-subst">${since}</span>`</span> <span class="hljs-comment">// ne récupérer que les nouvelles webmentions</span>

  <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> fetch(url)
  <span class="hljs-keyword">if</span> (response.ok) {
    <span class="hljs-keyword">const</span> feed = <span class="hljs-keyword">await</span> response.json()
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`&gt;&gt;&gt; <span class="hljs-subst">${feed.children.length}</span> nouvelles webmentions récupérées depuis <span class="hljs-subst">${API}</span>`</span>)
    <span class="hljs-keyword">return</span> feed
  }

  <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>
}

<span class="hljs-comment">// Fusionner les nouvelles webmentions avec celles du cache, unique par id</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">mergeWebmentions</span>(<span class="hljs-params">a, b</span>) </span>{
  <span class="hljs-keyword">return</span> unionBy(a.children, b.children, <span class="hljs-string">'wm-id'</span>)
}

<span class="hljs-comment">// sauvegarder les webmentions combinnées dans le fichier de cache</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">writeToCache</span>(<span class="hljs-params">data</span>) </span>{
  <span class="hljs-keyword">const</span> dir = <span class="hljs-string">'_cache'</span>
  <span class="hljs-keyword">const</span> fileContent = <span class="hljs-built_in">JSON</span>.stringify(data, <span class="hljs-literal">null</span>, <span class="hljs-number">2</span>)
  <span class="hljs-comment">// créer le dossier de cache s'il n'existe pas déjà</span>
  <span class="hljs-keyword">if</span> (!fs.existsSync(dir)) {
    fs.mkdirSync(dir)
  }
  <span class="hljs-comment">// écrire les données dans le fichier de cache JSON</span>
  fs.writeFile(CACHE_FILE_PATH, fileContent, err =&gt; {
    <span class="hljs-keyword">if</span> (err) <span class="hljs-keyword">throw</span> err
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`&gt;&gt;&gt; webmentions mise en cache dans <span class="hljs-subst">${CACHE_FILE_PATH}</span>`</span>)
  })
}

<span class="hljs-comment">// Lire le contenu du cache à partir du fichier JSON</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">readFromCache</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">if</span> (fs.existsSync(CACHE_FILE_PATH)) {
    <span class="hljs-keyword">const</span> cacheFile = fs.readFileSync(CACHE_FILE_PATH)
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">JSON</span>.parse(cacheFile)
  }

  <span class="hljs-comment">// Pas de cache trouvé.</span>
  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">lastFetched</span>: <span class="hljs-literal">null</span>,
    <span class="hljs-attr">children</span>: []
  }
}

<span class="hljs-built_in">module</span>.exports = <span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
  <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'&gt;&gt;&gt; Lectures des webmentions depuis le cache…'</span>);

  <span class="hljs-keyword">const</span> cache = readFromCache()

  <span class="hljs-keyword">if</span> (cache.children.length) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`&gt;&gt;&gt; <span class="hljs-subst">${cache.children.length}</span> webmentions chargées depuis le cache`</span>)
  }

  <span class="hljs-comment">// Ne télécharger les nouvelles webmentions qu'en production</span>
  <span class="hljs-keyword">if</span> (process.env.NODE_ENV === <span class="hljs-string">'production'</span>) {
    <span class="hljs-built_in">console</span>.log(<span class="hljs-string">'&gt;&gt;&gt; Vérification de nouvelles webmentions...'</span>);
    <span class="hljs-keyword">const</span> feed = <span class="hljs-keyword">await</span> fetchWebmentions(cache.lastFetched)
    <span class="hljs-keyword">if</span> (feed) {
      <span class="hljs-keyword">const</span> webmentions = {
        <span class="hljs-attr">lastFetched</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().toISOString(),
        <span class="hljs-attr">children</span>: mergeWebmentions(cache, feed)
      }

      writeToCache(webmentions)
      <span class="hljs-keyword">return</span> webmentions
    }
  }

  <span class="hljs-keyword">return</span> cache
}</code></pre>
<h2 id="filtres-pour-la-generation">Filtres pour la génération</h2>
<p>Maintenant que nous avons rempli notre cache de webmentions, il nous faut pouvoir l'utiliser. Nous devons pour cela générer les fonctions, les <a href="https://www.11ty.dev/docs/filters/" target="_blank" rel="noopener noreferrer">filtres</a>, qu'Eleventy va utiliser pour générer nos fichiers.</p>
<p>D'abord, j'aime bien séparer les filtres de la configuration principale d'Eleventy pour ne pas trop la surcharger. Le fichier dédié aux filtres va définir chacun de nos filtres dans un objet. Les clés seront les noms de filtres et les valeurs seront les fonctions de filtres. Ajouter nos nouvelles fonctions de filtres dans le fichier <code>_11ty/filters.js</code> :</p>
<pre><code class="language-javascript hljs javascript"><span class="hljs-comment">// _11ty/filters.js</span>
<span class="hljs-keyword">const</span> { DateTime } = <span class="hljs-built_in">require</span>(<span class="hljs-string">"luxon"</span>); <span class="hljs-comment">// Déjà présent dans eleventy-base-blog</span>

<span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">getWebmentionsForUrl</span>: <span class="hljs-function">(<span class="hljs-params">webmentions, url</span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> webmentions.children.filter(<span class="hljs-function"><span class="hljs-params">entry</span> =&gt;</span> entry[<span class="hljs-string">'wm-target'</span>] === url)
  },
  <span class="hljs-attr">size</span>: <span class="hljs-function">(<span class="hljs-params">mentions</span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> !mentions ? <span class="hljs-number">0</span> : mentions.length
  },
  <span class="hljs-attr">webmentionsByType</span>: <span class="hljs-function">(<span class="hljs-params">mentions, mentionType</span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> mentions.filter(<span class="hljs-function"><span class="hljs-params">entry</span> =&gt;</span> !!entry[mentionType])
  },
  <span class="hljs-attr">readableDateFromISO</span>: <span class="hljs-function">(<span class="hljs-params">dateStr, formatStr = <span class="hljs-string">"dd LLL yyyy 'at' hh:mma"</span></span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> DateTime.fromISO(dateStr).toFormat(formatStr);
  }
}</code></pre>
<p>Maintenant pour pouvoir utiliser ces nouveaux filtres, dans notre fichier <code>.eleventy.js</code>, nous devons boucler sur les clefs de cet objet de filtres, pour ajouter chaque filtre à la configuration d'Eleventy :</p>
<pre><code class="language-javascript hljs javascript"><span class="hljs-comment">// .eleventy.js</span>
<span class="hljs-comment">// ...Autres imports</span>
<span class="hljs-keyword">const</span> filters = <span class="hljs-built_in">require</span>(<span class="hljs-string">'./_11ty/filters'</span>)

<span class="hljs-built_in">module</span>.exports = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">eleventyConfig</span>) </span>{
  <span class="hljs-comment">// Filters</span>
  <span class="hljs-built_in">Object</span>.keys(filters).forEach(<span class="hljs-function"><span class="hljs-params">filterName</span> =&gt;</span> {
    eleventyConfig.addFilter(filterName, filters[filterName])
  })

  <span class="hljs-comment">// Autres configs...</span></code></pre>
<aside class="note note-info"><p>Ici je n'utilise pas de filtre d’assainissement du HTML car j'ai remarqué que les données sont contenues dans un champ <code>text</code> qui est déjà nettoyé. C'est peut-être nouveau ou bien ce n'est pas valable pour toutes les webmentions. Je mettrais cet article à jour si je dois l'ajouter.</p></aside>
<h2 id="afficher-les-mentions">Afficher les mentions</h2>
<p>Nous sommes maintenant fin prêts à tout assembler et à afficher nos webmentions. Je les positionne à la fin de chaque article de blog, donc dans mon fichier <code>_includes/layouts/post.njk</code>, j'ajoute une nouvelle section pour les webmentions. Ici, nous déclarons une variable nommée <code>webmentionUrl</code> qui contient l’URL complète de la page, puis nous la passons dans le fichier partiel <code>webmentions.njk</code> :</p>
<pre><code class="language-html hljs xml"><span class="hljs-comment">&lt;!-- _includes/layouts/post.njk --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">section</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Webmentions<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
  {% set webmentionUrl %}{{ page.url | url | absoluteUrl(site.url) }}{% endset %}
  {% include 'webmentions.njk' %}
<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span></code></pre>
<p>Nous pouvons maintenant écrire notre fichier de modèle pour les webmentions. Dans cet exemple, j’affiche des liens, des retweets et des réponses. Je commence par définir toutes les variables dont j’aurais besoin dans quelques instants :</p>
<pre><code class="language-html hljs xml"><span class="hljs-comment">&lt;!-- _includes/webmentions.njk --&gt;</span>
  <span class="hljs-comment">&lt;!-- Filtrer les webmentions du cache pour n’inclure que celles relatives à l’URL de l’article en cours --&gt;</span>
  {% set mentions = webmentions | getWebmentionsForUrl(metadata.url + webmentionUrl) %}
  <span class="hljs-comment">&lt;!-- Définir les reposts comme des mentions de type `repost-of`  --&gt;</span>
  {% set reposts = mentions | webmentionsByType('repost-of') %}
  <span class="hljs-comment">&lt;!-- Calcul du total de reposts --&gt;</span>
  {% set repostsSize = reposts | size %}
  <span class="hljs-comment">&lt;!-- Définir les likes comme des mentions de type `like-of`  --&gt;</span>
  {% set likes = mentions | webmentionsByType('like-of') %}
  <span class="hljs-comment">&lt;!-- Calcul du total de likes --&gt;</span>
  {% set likesSize = likes | size %}
  <span class="hljs-comment">&lt;!-- Définir les réponses comme des mentions de type `in-reply-to`  --&gt;</span>
  {% set replies = mentions | webmentionsByType('in-reply-to')  %}
  <span class="hljs-comment">&lt;!-- Calcul du total de réponses --&gt;</span>
  {% set repliesSize = replies | size  %}</code></pre>
<p>Une fois nos variables définies, nous pouvons afficher ces données. Ici je vais seulement m'attarder sur la partie "réponses", libre à vous d'aller voir comment je gère les autres types de webmentions dans <a href="https://gist.github.com/siakaramalegos/b1f7ded21f9ecddaee91e3f6d88e2e48" target="_blank" rel="noopener noreferrer">ce gist</a>.</p>
<p>L'affichage des réponses est un peu plus complexe que de simplement afficher une photo et un lien. Je fais appel à un autre template ici pour afficher chaque webmention. Nous affichons le nombre total de réponses et affichons le mot "Réponse" au pluriel si nécessaire. Puis nous bouclons sur les webmentions de type réponse et les affichons à l'aide d'un nouveau fichier partiel Nunjucks :</p>
<pre><code class="language-html hljs xml"><span class="hljs-comment">&lt;!-- _includes/webmentions.njk --&gt;</span>
<span class="hljs-comment">&lt;!-- …définition des variables… --&gt;</span>
{% if repliesSize &gt; 0 %}
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"webmention-replies"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>{{ repliesSize }} {% if repliesSize == "1" %}Reply{% else %}Replies{% endif %}<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>

  {% for webmention in replies %}
    {% include 'webmention.njk' %}
  {% endfor %}
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
{% endif %}</code></pre>
<p>Nous pouvons afficher les réponses à l'aide de ce nouveau fichier partiel pour chaque réponse. Si l'auteur de la webmention a une photo de profil, nous l'affichons, sinon nous affichons un avatar. Même chose pour le nom, nous l'affichons s'il existe, sinon nous affichons "Anonyme". Notre filtre <code>readableDateFromISO</code> nous aide à afficher la date de publication dans un format plus sympathique pour les humains, enfin nous affichons le texte de la webmention :</p>
<pre><code class="language-html hljs xml"><span class="hljs-comment">&lt;!-- _includes/webmention.njk --&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">article</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"webmention"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"webmention-{{ webmention['wm-id'] }}"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"webmention__meta"</span>&gt;</span>
    {% if webmention.author %}
      {% if webmention.author.photo %}
      <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"{{ webmention.author.photo }}"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"{{ webmention.author.name }}"</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"48"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"48"</span> <span class="hljs-attr">loading</span>=<span class="hljs-string">"lazy"</span>&gt;</span>
      {% else %}
      <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"{{ '/img/avatar.svg' | url }}"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">""</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"48"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"48"</span>&gt;</span>
      {% endif %}
      <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"h-card u-url"</span> {% <span class="hljs-attr">if</span> <span class="hljs-attr">webmention.url</span> %}<span class="hljs-attr">href</span>=<span class="hljs-string">"{{ webmention.url }}"</span> {% <span class="hljs-attr">endif</span> %} <span class="hljs-attr">target</span>=<span class="hljs-string">"_blank"</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"noopener noreferrer"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">strong</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"p-name"</span>&gt;</span>{{ webmention.author.name }}<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
    {% else %}
      <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>Anonymous<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
    {% endif %}

    {% if webmention.published %}
      <span class="hljs-tag">&lt;<span class="hljs-name">time</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"postlist-date"</span> <span class="hljs-attr">datetime</span>=<span class="hljs-string">"{{ webmention.published }}"</span>&gt;</span>
        {{ webmention.published | readableDateFromISO }}
      <span class="hljs-tag">&lt;/<span class="hljs-name">time</span>&gt;</span>
    {% endif %}
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    {{ webmention.content.text }}
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span></code></pre>
<h2 id="sautons-courageusement-dans-l-inconnu">Sautons courageusement dans l’inconnu…</h2>
<p>Ça fonctionne ? Nous allons enfin pouvoir tester. Commencez par lancer la commande <code>npm run build</code> pour générer une liste initiale de webmentions qui sera sauvegardée dans le fichier <code>_cache/webmentions.json</code>. Puis lancer votre serveur de développement local pour vérifier que ça marche ! Bien entendu, vous devrez au moins avoir une webmention associée à un article pour voir quelque chose. 😁</p>
<p>Vous pouvez voir le résultat de ma propre implémentation sur mon <a href="https://sia.codes/posts/webmentions-eleventy-in-depth/#webmentions" target="_blank" rel="noopener noreferrer">site</a>. Bon courage ! Dites moi si vous trouvez des anomalies ou des erreurs dans cet article !</p>
<h2 id="poursuivez-en-ajoutant-des-microformats">Poursuivez en ajoutant des Microformats</h2>
<p>Keith Grant a un excellent article <a href="https://keithjgrant.com/posts/2019/02/adding-webmention-support-to-a-static-site/" target="_blank" rel="noopener noreferrer">Ajouter le support de Webmention à un site statique</a>. Lisez la section <a href="https://keithjgrant.com/posts/2019/02/adding-webmention-support-to-a-static-site/#enhancing-with-microformats" target="_blank" rel="noopener noreferrer">"Amélioration à l'aide des Microformats"</a> pour plus d'explication et d'exemple.</p>
<h2 id="ressources-additionnelles">Ressources additionnelles</h2>
<ul>
<li>La totalité du code source de mon site est sur <a href="https://github.com/siakaramalegos/sia.codes-eleventy" target="_blank" rel="noopener noreferrer">Github</a>. Il évoluera avec le temps, j'en suis sûre, regardez donc attentivement <a href="https://github.com/siakaramalegos/sia.codes-eleventy/commit/d7318565917b1342b38d6b3bff4e3e548276afca" target="_blank" rel="noopener noreferrer">ce commit</a> qui contient l'ensemble de mes changements pour l'ajout des webmentions.</li>
<li>Comment ajouter le support de <code>dotenv</code> sur Netlify est abordé dans <a href="https://stackoverflow.com/questions/48453493/set-environment-variable-for-build-in-netlify" target="_blank" rel="noopener noreferrer">cette réponse sur Stack Overflow</a>.</li>
<li>Comment définir un job <code>cron</code> via Github Actions pour régénérer périodiquement mon site sur Netlify (afin de récupérer et d'afficher les nouvelles webmentions) est détaillé dans <a href="https://www.voorhoede.nl/en/blog/scheduling-netlify-deploys-with-github-actions/" target="_blank" rel="noopener noreferrer">Programmer des déploiements Netlify avec les GitHub Actions</a>.</li>
</ul>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2019/12/22/partiels-fonction-hugo/</id>
    <title>Des fonctions dans nos partiels Hugo !</title>
    <published>2019-12-22T09:36:56+00:00</published>
    <updated>2019-12-22T15:21:43+00:00</updated>
    <link href="https://jamstatic.fr/2019/12/22/partiels-fonction-hugo/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<figure>
<picture title="Des fonctions dans nos partiels Hugo">
<source type="image/webp" srcset="/thumbnails/768x/regisphilibert.com/blog/2019/12/hugo-partial-series-part-2-functions-with-returning-partials/images/featured.1d4dcb0eee0d19d4194cce85a463aa8c.webp 768w, /thumbnails/1024x/regisphilibert.com/blog/2019/12/hugo-partial-series-part-2-functions-with-returning-partials/images/featured.1d4dcb0eee0d19d4194cce85a463aa8c.webp 1024w" width="1024" height="721" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/regisphilibert.com/blog/2019/12/hugo-partial-series-part-2-functions-with-returning-partials/images/featured.1d4dcb0eee0d19d4194cce85a463aa8c.avif 768w, /thumbnails/1024x/regisphilibert.com/blog/2019/12/hugo-partial-series-part-2-functions-with-returning-partials/images/featured.1d4dcb0eee0d19d4194cce85a463aa8c.avif 1024w" width="1024" height="721" sizes="100vw">
<img src="/regisphilibert.com/blog/2019/12/hugo-partial-series-part-2-functions-with-returning-partials/images/featured.1d4dcb0eee0d19d4194cce85a463aa8c.png" alt="Des fonctions dans nos partiels Hugo" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="721" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAD/klEQVR4nO1b7XLiMAxcm/RKAy99D3C/+6IthALW/QiyZdn5ItCZTrUzxpA4ouxmJTtJ3d9/70RECCHger3icrngcv7CqTvidDigO36i+/zE8fCB7vCB7nhA1x1wOh3x9XXC+XzB+XLF5RoQAiEQfiecg/cem80GLy+v2G7fsG1btLs92t0eu90e7X6Ptt3jbdfidfuG5uUPmqbBZrOB9x7OOfhabCIATCzxC4lXMXbg/a8DEYio5w6Ci9sGppHQj6EBskpB4ujUiOMS9UHjl1P8Q3jfr4b+/UwJMY+3DZGvkjBfROAmSCcOKMRAIcoTfuAPQ3/231oUQAmBnFOtolcR44EpMCLh8gtDJsp3/NyfAylEMkQ6odNJj8JVXoaJKUrmQiIEAOEmAjcSveWqErFmkHCJdJDYJvkrakgq3epgKUIghND3SXmTBZCZSaR6UQK4HGSkCeJ8FunWpzSVbNY7Q7wP/NnEiJB1gjeJ7MSfKU25oJlrdCGPBV16JdakXoQQkPUmhsKUQ0BRlDQp7t81KYIkn0RQWcx7zdgt0SEwUQYhz3XmUZSFzBAAfJaruFfq6XQXZw4ACM7EGIV2BbIir5tPB6Weh0eHiNVmrqcTzcDgjCIUSC0uJerNRwfU0lUMplOSAxy35//An4OBGY7UA0i1utKaouLLdBVrSAln7hgBKdI56yTXZDNfUYQ9i0Cij/NoDq4lccIhJsp8iNmqdgbXlIaUGEVBr8R1UHoYJiAnTMMOIfA6pOqQ3EqGO1Hhr+YQHthoMZJDTInVINGRpB2p/kuXAGi0GJlDTJTHoDb7HXMIDyZxcD7pNTwS0TGlHr1D4nI+XsOSXittZXgclB7wVYdkaw/KehNlGYboGtru9U6pVnmQqXEvaszVuPa63owFzPbXlpmGHiOUDAnDfeGQ6dhWTFajtja59U2t0ldHmxEeBlmr86mwcEhsegEDlEcZxjHjktJQrZ7nEFQEtcz1EGSXqaAcko8cCMDrFrFmMV3WIXeI3jIzQIjCLD/eIMHrOwLg4NNjK/PPdv1srymyFqmaVJ9+nxsjPUFhGMWCe0ZRkGX3/swVs3DHzbv7HWLPOCyAy7ox0oYFGWXa7t0+C3c7pL+vzjfWDY/CXYLIh07sQQeBIocvJ+f+GmIoME3/tDgmyDdhrk/WCWKz3wy163taiClhZgtSD2SK5FjPxaAgNQGsdj8QA2T6fIQTr3Kr4XnIGfblPp661WUwcdaDT3pXYXO0hsQDTIWFGFkwy5O+kn5s2vtdmHlSK0Fc3saC2ARrJeoWKR3CWcrJDaU4psdMOP1mqHr0qKQs1180rAa0OdcaRPay/3jKMVFDXPnR9FgMl8wxyd9/eRLULT8MnV8AAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/regisphilibert.com/blog/2019/12/hugo-partial-series-part-2-functions-with-returning-partials/images/featured.1d4dcb0eee0d19d4194cce85a463aa8c.png 768w, /thumbnails/1024x/regisphilibert.com/blog/2019/12/hugo-partial-series-part-2-functions-with-returning-partials/images/featured.1d4dcb0eee0d19d4194cce85a463aa8c.png 1024w" sizes="100vw">
</picture>
<figcaption>Des fonctions dans nos partiels Hugo</figcaption>
</figure>
<p>Comme nous l'avons déjà vu dans différents articles dont celui sur la <a href="/2019/12/03/mise-en-cache-fichiers-partiels-hugo/">mise en cache des fichiers partiels</a>, jusqu'ici le moteur de <em>templating</em> d'Hugo se concentrait principalement sur la génération de fichiers. Résultat : même si les fichiers partiels étaient très utiles pour afficher tout un tas de trucs, jusqu'à récemment, ils ne permettaient pas de retourner une valeur typée.</p>
<p>Tout a changé à partir de la version <code>0.55.0</code> d'Hugo qui a introduit l'instruction <code>return</code> dans l'API de la fonction <code>partial</code> ! Les partiels sont tout à coup devenus des fonctions réutilisables qui peuvent être appelées par des fichiers de modèles plus conventionnels.</p>
<p>Dans cet article, nous aborderons les bases de cette fonctionnalité avant de nous plonger dans des cas d'utilisation bien précis.</p>
<h3 id="avant-return"><strong>Avant <code>return</code></strong></h3>
<p>Avant l'arrivée de l'instruction <code>return</code>, quand les partiels ne savaient qu'imprimer du contenu, beaucoup de gens se reposaient malgré tout sur eux pour créer des bouts de code réutilisables qui avaient d'autres buts. Par exemple vous pouviez transformer l'URL relative d'une image pour la préfixer avec votre domaine S3:</p>
<pre><code class="language-go-html-template hljs go"># layouts/partial/FormatURL.html
{{- $S3Domain := site.Params.s3Domain -}}
{{- printf <span class="hljs-string">"%s/%s"</span> $S3Domain . | safeHTMLAttr -}}
# layout/_default/single.html
{{- with .Params.image -}}
&lt;img src=<span class="hljs-string">"{{ partial "</span>FormatURL.html<span class="hljs-string">" . }}"</span> /&gt;
{{ end }}</code></pre>
<p>Ici la notation <code>{{- -}}</code> nous assure que rien d'autre ne sera affiché aux côtés de la chaîne de l'URL : ni espace, ni retour à la ligne, etc.</p>
<p>On pourrait même aller plus loin et afficher des données au format JSON à l'appel du partiel :</p>
<pre><code class="language-go-html-template hljs go"># layouts/partial/GetSEOData.html
{{- with . -}}
  {{- $title := .Title -}}
  {{- $description := .Summary -}}
  {{- with .Params.seo.title -}}
    {{- $title = . -}}
  {{- end -}}
  {{- with .Params.seo.description -}}
    {{- $description = . -}}
  {{- end -}}
  {{- dict <span class="hljs-string">"title"</span> $title <span class="hljs-string">"description"</span> $description | jsonify -}}
{{- end -}}

# layouts/partial/head.html
{{ $seo := partial <span class="hljs-string">"GetSEOData.html"</span> . | transform.Unmarshal }}
{{ with $seo }}
  # seo tags...
{{ end }}</code></pre>
<p>Mais ces astuces se limitaient à des types de données basiques comme les chaînes de caractères, les nombres ou des tableaux (associatifs) de celles-ci.
Il n'y avait aucun moyen de facilement retourner un objet complexe comme une page ou un fichier, encore moins une collection de pages.</p>
<p>Puis vint <a href="https://gohugo.io/news/0.55.0-relnotes/" target="_blank" rel="noopener noreferrer">Hugo 0.55.0</a> , l'instruction <code>return</code> et les <em>partiels de fonction</em>!</p>
<h2 id="quelques-points-a-retenir"><strong>Quelques points à retenir</strong></h2>
<p>Avant de nous plonger dans le code, il y a certaines choses à faire et à ne pas faire que nous devons passer en revue.</p>
<h3 id="rџљ-pas-de-return-dans-les-clauses"><strong>🚫 Pas de <code>return</code> dans les clauses</strong></h3>
<pre><code class="language-go-html-template hljs go">{{ <span class="hljs-keyword">if</span> gt .Params.temperature <span class="hljs-number">70</span> }}
  {{ <span class="hljs-keyword">return</span> 😎 }}
{{ end }}</code></pre>
<p>Ici ☝️ la valeur <code>.Params.temperature</code> sera ignorée et c'est 😎 qui sera systématiquement retourné. Un partiel retournera simplement ce qui suit une instruction <code>return</code> et ce où qu'elle soit positionnée dans le code.</p>
<pre><code class="language-go-html-template hljs go">{{ with .Params.temperature }}
  {{ <span class="hljs-keyword">return</span> . }}
{{ end }}</code></pre>
<p>Même chose ici ☝️, <code>return</code> doit se trouver à la racine et ne peut pas être appelé dans une instruction imbriquée.</p>
<h3 id="rџљ-pas-de-multiples-instructions-return">🚫 Pas de multiples instructions <code>return</code></h3>
<pre><code class="language-go-html-template hljs go">{{ <span class="hljs-keyword">if</span> gt .Params.temperature <span class="hljs-number">70</span> }}
  {{ <span class="hljs-keyword">return</span> 😎 }}
{{ <span class="hljs-keyword">else</span> }}
  {{ <span class="hljs-keyword">return</span> ⛸️ }}
{{ end }}</code></pre>
<p>Cet exemple ne marchera pas, car les retours multiples ne sont pas supportés.</p>
<h3 id="rџ-Ќ-une-unique-variable-retournG-c-e">👍 Une unique variable retournée</h3>
<p>On tire donc des exemples précédents la règle générique : <strong>une variable retournée unique à la racine</strong>.</p>
<p>Le meilleur moyen de se conformer à cette règle est de porter notre attention sur l'unique <em>variable retournée</em>.</p>
<p>La <em>variable retournée</em> c'est votre base de départ, celle que vous allez manipuler et éventuellement retourner.</p>
<ol>
<li>🍽️ Affecter une valeur de départ,</li>
<li>🔪 la travailler,</li>
<li>💁‍♂️ et la retourner à la fin.</li>
</ol>
<pre><code class="language-go-html-template hljs go">{{ $emoji := ⛸️ }}
{{ <span class="hljs-keyword">if</span> gt .Params.temperature <span class="hljs-number">70</span> }}
  {{ $emoji = 😎}}
{{ <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> gt .Params.temperature <span class="hljs-number">100</span> }}
  {{ $emoji = 🥵}}
{{ end }}
{{ <span class="hljs-keyword">return</span> $emoji }}</code></pre>
<p>Quel que soit le nombre de lignes, d'espaces ou de retours à la ligne dans votre code, la seule valeur produite par votre partiel c'est cet emoji unique qui suit le mot magique <code>return</code>.</p>
<p>{{&lt; notice info &gt;}}
Tout comme pour <code>if</code>, <code>with</code>, <code>range</code> et leurs amis, ce qui suit <code>return</code> n'a pas besoin d'être entre parenthèses.</p>
<pre><code class="language-go-html-template hljs go">{{ <span class="hljs-keyword">return</span> gt .Params.temperature <span class="hljs-number">50</span> }}</code></pre>
<p>☝️ Cette notation est valide et retournera un booléen.
{{&lt; /notice &gt;}}</p>
<h3 id="YoYadShch-appeler-nos-partiels-de-fonction">🤙 Appeler nos partiels de fonction</h3>
<p>Comme pour n'importe quel autre partiel on écrit :</p>
<pre><code class="language-go-html-template hljs go">Emoji: {{ partial <span class="hljs-string">"emoji.html"</span> . }}</code></pre>
<p>Là où ça devient intéressant c'est qu'on peut stocker la valeur retournée !</p>
<pre><code class="language-go-html-template hljs go">{{ $emoji := partial <span class="hljs-string">"emoji.html"</span> . }}</code></pre>
<h3 id="rџ-Џ-quelques-conventions">📏 Quelques conventions</h3>
<p>Du moins celles que j'ai adoptées…</p>
<p>Pour bien distinguer mes partiels classiques de ceux qui retournent des valeurs de tous types, j'ai pris pour habitude de ranger ces derniers dans le dossier <code>layouts/partials/func/</code>. Cela a au moins le mérite de les isoler des partiels plus conventionnels, sans avoir non plus à avoir à taper beaucoup plus de caractères lors de leur appel.</p>
<pre><code class="language-go-html-template hljs go">{{ partial <span class="hljs-string">"func/emoji.html"</span> . }}</code></pre>
<p>Hugo part du principe que par défaut l'extension de votre fichier partiel est <code>.html</code>. Vu que j'utilise toujours l'extension <code>html</code> pour mes fichiers partiels, je peux omettre de l'écrire et ainsi gagner cinq précieux caractères :</p>
<pre><code class="language-go-html-template hljs go">{{ partial <span class="hljs-string">"func/emoji"</span> . }}</code></pre>
<p>Enfin, j'aime bien utiliser la casse <em>CamelCase</em> ainsi qu'un verbe autant que possible :</p>
<pre><code class="language-go-html-template hljs go">{{ partial <span class="hljs-string">"func/GetEmoji"</span> . }}</code></pre>
<p><strong>Et voilà mon code Hugo est tout beau :</strong></p>
<pre><code class="language-go-html-template hljs go">{{ with partial <span class="hljs-string">"func/GetEmoji"</span> . }}
  Emoji: {{ . }}
{{ end }}</code></pre>
<h2 id="coder-nos-partiels-de-fonction">Coder nos partiels de fonction</h2>
<p>Bien, la partie théorique était intéressante, maintenant il est temps de faire craquer quelques articulations et de nous mettre à taper au clavier ! Ensemble nous allons essayer de répondre à un besoin de base : <em>lister les termes des taxonomies de nos projets Hugo de manière efficace</em>.</p>
<p>Cette opération n'est pas forcément très intuitive. Une taxonomie et ses termes occupent une place à part entière dans un projet Hugo, mais vu depuis le contexte d'une page, ce n'est qu'une liste de chaînes de caractères dans votre Front Matter :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-meta">---</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">Une</span> <span class="hljs-string">nuit</span> <span class="hljs-string">au</span> <span class="hljs-string">Louvre</span>
<span class="hljs-attr">tags:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">Art</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">Paris</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">Musée</span>
<span class="hljs-meta">---</span>
</code></pre>
<p>Pour les lister sous forme de liens cliquables, vous devez construire vous même ces dits liens, en vous basant sur ces chaînes transformées en URLs ou en récupérant l'objet page à l'aide de <code>.GetPage</code> ou <code>.Site.Taxonomies</code>.</p>
<p>Ce serait bien si ce travail pénible pouvait être fait dans un <em>partiel de fonction</em> réutilisable et non dans les fichiers de modèles de nos contenus.</p>
<p>Quant à l'appel de ce type de partiel, il devrait être aussi concis que cela :</p>
<pre><code class="language-go-html-template hljs go">{{ <span class="hljs-keyword">range</span> .Params.tags }}
  {{ with partial <span class="hljs-string">"func/GetTagPage"</span> . }}
    &lt;a href=<span class="hljs-string">"{{ .RelPermalink }}"</span>&gt;{{ .Title }}&lt;/a&gt;
  {{ end }}
{{ end }}</code></pre>
<p><strong>🙌 ⌨️ C'est parti</strong></p>
<pre><code class="language-go-html-template hljs go">{{<span class="hljs-comment">/* 1. */</span>}}
{{ $tag := <span class="hljs-literal">false</span> }}
{{<span class="hljs-comment">/* 2. */</span>}}
{{ with index site.Taxonomies.tags (urlize .) }}
  {{<span class="hljs-comment">/* 3. */</span>}}
  {{ with .Page }}
    {{<span class="hljs-comment">/* 4. */</span>}
    {{ $tag = . }}
  {{ end }}
{{ end }}

{{<span class="hljs-comment">/* 5. */</span>}
{{ <span class="hljs-keyword">return</span> $tag }}</code></pre>
<ol>
<li>Nous commençons par initialiser notre <em>variable retournée</em> à sa valeur par défaut</li>
<li><code>site.Taxonomies.tags</code> retourne une collection de tous les tags du site avec leur objet <code>.Page</code>. Le <em>point</em> représente le contexte de notre partiel, ici le nom de notre tag, que nous transformons en URL pour correspondre à sa clef dans <code>site.Taxonomies.tags</code>.</li>
<li>Nous avons de fortes chances de tomber sur une <code>.Page</code>, mais <code>with</code> ajoute une vérification supplémentaire et nous permet en plus de changer de contexte.</li>
<li>Nous stockons le tag de la page dans notre <em>variable retournée</em>.</li>
<li>🎉</li>
</ol>
<p><strong>👍 Beau boulot !</strong></p>
<p>Et pour les autres taxonomies comme les <code>categories</code>? Hors de question de copier/coller tout cela dans un nouveau partiel pour remplacer <code>site.Taxonomies.tags</code> par <code>site.Taxonomies.categories</code>. 🙅‍♀️</p>
<p>Nous voulons pouvoir écrire ceci :</p>
<pre><code class="language-go-html-template hljs go">{{ <span class="hljs-keyword">range</span> .Params.categories }}
  {{ with partial <span class="hljs-string">"func/GetTermPage"</span> (dict <span class="hljs-string">"taxonomy"</span> <span class="hljs-string">"categories"</span> <span class="hljs-string">"term"</span> .) }}
    &lt;a href=<span class="hljs-string">"{{ .RelPermalink }}"</span>&gt;{{ .Title }}&lt;/a&gt;
  {{ end }}
{{ end }}</code></pre>
<h2 id="gestion-des-arguments">Gestion des arguments</h2>
<p>Jusqu'à maintenant nous n'avons passé qu'un seul argument à nos partiels de fonction, où le contexte ne contenait qu'un élément. Mais ici, il nous en faut deux : la taxonomie et son terme. Notre contexte devra donc être un tableau associatif qui contiendra les deux.</p>
<p>Nous mettons donc notre partiel à jour :</p>
<pre><code class="language-go-html-template hljs go">{{ $<span class="hljs-keyword">return</span> := <span class="hljs-literal">false</span> }}

{{<span class="hljs-comment">/* 1. */</span>}}
{{ $taxonomy := <span class="hljs-string">"tags"</span> }}
{{ with .taxonomy }}
  {{ $taxonomy = . }}
{{ end }}

{{<span class="hljs-comment">/* 2. */</span>}}
{{ with $term := .term }}
  {{ with index site.Taxonomies $taxonomy }}
    {{ with index . (urlize $term) }}
      {{ with .Page }}
        {{ $<span class="hljs-keyword">return</span> = . }}
      {{ end }}
    {{ end }}
  {{ end }}
{{ end }}

{{<span class="hljs-comment">/* 3. */</span>}}
{{ <span class="hljs-keyword">return</span> $<span class="hljs-keyword">return</span> }}</code></pre>
<ol>
<li>Maintenant que nous passons un argument <code>term</code>, nommer notre variable retournée <code>$term</code> pourrait prêter à confusion. Appelons la maintenant <code>$return</code> pour bien marquer que c'est <strong>la</strong> <em>variable retournée</em>.</li>
<li>Si aucun argument <code>.term</code> n'est présent, la <em>variable retournée</em> devrait rester vide. Avant d'aller plus loin, nous faisons appel à <code>with</code> pour nous assurer que <code>.term</code> est bien défini et nous stockons cette valeur initiale afin de pouvoir y accéder quelque que soit notre contexte. Ces quelques lignes sont d'ailleurs une très bonne illustration de changements de contexte !</li>
<li>🎉</li>
</ol>
<h2 id="mise-en-cache">Mise en cache !</h2>
<p>OK très bien, mais je veux une fonction qui liste les tags d'une page et qui me renvoie un tableau de tableaux associatifs qui contiennent chacun des données structurées comme <code>.URL</code> et <code>.Name</code>. De cette façon, si je veux passer de <code>.RelPermalink</code> à <code>.Permalink</code> dans le futur, je peux le faire dans ma <em>variable retournée</em> plutôt que dans chaque fichier de modèle où je souhaite afficher ces liens.</p>
<p>C'est l'occasion idéale de voir comment appeler un <em>partiel de fonction</em> depuis un <em>partiel de fonction</em> et mettre en cache sa valeur. :sweat_smile:</p>
<pre><code class="language-go-html-template hljs go">#layout/partials/<span class="hljs-function"><span class="hljs-keyword">func</span>/<span class="hljs-title">GetTags</span>.<span class="hljs-title">html</span></span>
{{<span class="hljs-comment">/* 1. */</span>}}
{{ $<span class="hljs-keyword">return</span> := slice }}
{{<span class="hljs-comment">/* 2. */</span>}}
{{ with .Params.tags }}
  {{ <span class="hljs-keyword">range</span> . }}
    {{<span class="hljs-comment">/* 3. */</span>}}
    {{ with partialCached <span class="hljs-string">"func/GetTerm"</span> (dict <span class="hljs-string">"taxonomy"</span> <span class="hljs-string">"tags"</span> <span class="hljs-string">"term"</span> .) <span class="hljs-string">"tags"</span> . }}
      {{<span class="hljs-comment">/* 4. */</span>}}
      {{ $tag := dict <span class="hljs-string">"URL"</span> .RelPermalink <span class="hljs-string">"Name"</span> .Title }}
      {{<span class="hljs-comment">/* 5. */</span>}}
      {{ $<span class="hljs-keyword">return</span> := $<span class="hljs-keyword">return</span> | <span class="hljs-built_in">append</span> $tag }}
    {{ end }}
  {{ end }}
{{ end }}

{{<span class="hljs-comment">/* 6. */</span>}}
{{ <span class="hljs-keyword">return</span> $<span class="hljs-keyword">return</span> }}</code></pre>
<ol>
<li>Nous voulons être sûrs de pouvoir parcourir la valeur de notre variable retournée à l'aide de la fonction <code>range</code>. Afin de nous assurer de retourner un tableau (<code>slice</code>), nous initialisons notre variable avec un tableau vide.</li>
<li>La fonction <code>range</code> ne bronchera pas avec un tableau vide, mais tout autre type de valeur entraînerait une erreur de génération. Il est donc toujours plus sage de tester à l'aide d'un <code>with</code>, sauf si vous êtes vraiment sûrs de retourner un tableau.</li>
<li>Nous appelons notre précédent partiel de fonction, mais cette fois nous le mettons en cache. Pour les variantes, nous utilisons les deux valeurs de ses arguments.</li>
<li>Nous sauvegardons le tout dans un tableau associatif à des fins de lisibilité. Puisque notre <code>$tag</code> est déclaré dans le contexte de notre boucle <code>range</code>, il ne pourra pas entrer en conflit avec un autre <code>$tag</code> comme par exemple le prochain tag de la liste.</li>
<li>Nous utilisons la fonction <a href="https://gohugo.io/functions/append/#readout" target="_blank" rel="noopener noreferrer"><code>append</code></a> pour ajouter notre tableau associatif <code>$tag</code> au tableau que nous retournons.</li>
<li>🎉</li>
</ol>
<p>Maintenant dans notre modèle nous pouvons écrire :</p>
<pre><code class="language-go-html-template hljs go"># layouts/_default/single.html
{{<span class="hljs-comment">/* 1. */</span>}}
{{ <span class="hljs-keyword">range</span> partial <span class="hljs-string">"func/GetTags"</span> $ }}
  {{<span class="hljs-comment">/* 2. */</span>}}
  &lt;a href=<span class="hljs-string">"{{ .URL }}"</span>&gt;{{ .Name }}&lt;/a&gt;
{{ end }}</code></pre>
<ol>
<li>Nous avons que la valeur retournée par notre partiel de fonction maison est à coup sûr un tableau, vide ou non. Nous pouvons donc utiliser <code>range</code> sans problème.</li>
<li>Nous pouvons maintenant utiliser les clefs personnalisées de nos tableaux associatifs.</li>
<li>C'est tout !</li>
</ol>
<h2 id="des-ameliorations-possibles">Des améliorations possibles ?</h2>
<p>Bien entendu ! Nous pourrions :</p>
<ul>
<li>Exclure certains tags du tableau retourné par la fonction <code>GetTags</code></li>
<li>Transformer <code>GetTags</code> en <code>GetTerms</code>, afin de pouvoir l'utiliser pour n'importe quelle taxonomie.</li>
<li>Trouver la bonne variante de notre <em>partiel de fonction</em> <code>GetTags</code> et utiliser <code>partialCached</code>.</li>
<li>Développer bien plus de partiels de fonction pour répondre à d'autres besoins !</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>Après avoir vu les bases, nous avons pu développer deux partiels de fonction qui nous aideront grandement dans la maintenance de l'affichage des taxonomies de notre site.</p>
<p>Et si nous avons besoin d’afficher seulement certains articles ou bien tous les articles mais en excluant certains tags ? Cela se passera dans la fonction <code>GetTags</code> et pas ailleurs ! Et si dans une prochaine version Hugo introduit un moyen plus efficace de gérer les termes d’une taxonomie ? Nous ajusterons notre fonction <code>GetTerm</code> !</p>
<p>Avec ses <em>partiels de fonction</em>, Hugo répond enfin à la séparation des problématiques de templating et de gestion des données, en permettant la réutilisabilité et le typage de données !</p>
<p>Est-ce que je vous ai déjà dit que c'était une de mes fonctionnalités préférées dont je vais abuser en 2020 ?</p>
<p>Si vous avez un retour d’expérience ou des questions à propos des partiels de fonction, ou que voulez simplement partager les partiels que vous avez développé suite à lecture de cet article, <a href="https://regisphilibert.com/blog/2019/12/hugo-partial-series-part-2-functions-with-returning-partials/" target="_blank" rel="noopener noreferrer">laissez un commentaire ou un bout de code</a> !</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2019/12/10/jamstack-pour-les-clients/</id>
    <title>Jamstack pour les clients</title>
    <published>2019-12-10T12:54:24+00:00</published>
    <updated>2019-12-14T14:24:18+00:00</updated>
    <link href="https://jamstatic.fr/2019/12/10/jamstack-pour-les-clients/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>Savoir choisir les bon outils pour un projet représente une part importante de ce qui fait le succès d'une agence de développement web. S'il est facile de s'enthousiasmer sur les dernières technologies en vogue, il est plus difficile d'expliquer à son client les bénéfices de la technologie que nous lui suggérons d'adopter.</p>
<p>De mon point de vue de commercial, bien qu'ils soient de bonne compagnie, les développeurs ont souvent du mal à expliquer les concepts techniques, les solutions et les bénéfices, en des termes simples. Principalement à cause du fait qu'ils parlent surtout un langage technique et qu'ils prennent leurs connaissances trop souvent pour acquises.</p>
<p>C'est génial quand vous faites partie d'une équipe de développement web. Vous utilisez leurs <strong>explications</strong> comme fil directeur et faites des recherches sur le sujet.</p>
<p>Mais ça ne marchera pas avec vos clients.</p>
<p>Donc, comment pouvez-vous mieux communiquer sur la Jamstack à vos clients. Déjà commencer par limiter le jargon métier et transformez les fonctionnalités techniques en des gains potentiels pour le business.</p>
<h2 id="les-benefices-de-la-jamstack">Les bénéfices de la Jamstack</h2>
<p>Je ne vais pas vous embêter avec les définitions et tout le tralala (on a déjà fait ça dans <a href="https://bejamas.io/blog/jamstack/" title="Jamstack : la pierre angulaire du développement web moderne" target="_blank" rel="noopener noreferrer">un autre article</a>, allez donc le consulter), mais je dirai ceci : bien que la Jamstack représente une nouvelle manière de développer des sites web et qui présente bien des avantages par rapport à la <em>stack</em> traditionnelle, ce mouvement rassemble une large communauté avec un grand nombre d'outils et de services qui vous seront du plus grand secours à cet égard.</p>
<p>Avec les possibilités des navigateurs actuels, les générateurs de site statique, les CMS headless, les APIs et les CDNs vous pouvez donc développer des sites et des applications qui ne sont pas liées à une technologie ou un framework spécifique. C'est en soi aisément le plus gros avantage que la Jamstack apporte à votre client.</p>
<p>Toutefois, vous échiner à expliquer pourquoi ces détails techniques comptent, ne vous aidera pas beaucoup dans vos discussions avec des prospects et des clients. Il se peut même qu'ils se sentent dépassés, perdus et frustrés.</p>
<p>Focalisez-vous plutôt sur comment cela pour être bénéfique pour leur activité.
En général, cela se résume à des revenus ou à une meilleure expérience pour eux, leur développeurs et leur audience.</p>
<h3 id="de-meilleures-performances">De meilleures performances</h3>
<blockquote>
<p>Plus de la moitié des gens abandonnent leur visite sur un site s'il met plus de trois secondes à se charger. Cela se traduit par des pertes de revenu directes, une baisse des conversions et une mauvaise expérience utilisateur. — <a href="https://www.thinkwithgoogle.com/marketing-resources/experience-design/mobile-page-speed-load-time/" target="_blank" rel="noopener noreferrer">ThinkWithGoogle</a></p>
</blockquote>
<p>La rapidité de votre site Web peut faire gagner ou perdre des visites engagées qui sont plus susceptibles d'être exploités à des fins commerciales, que vous recherchiez des ventes, des clics publicitaires et/ou une augmentation du trafic. De plus, aujourd'hui, les gens du monde entier s'attendent à ce que les marques offrent une expérience de site Web rapide et sans friction sur de multiples appareils.</p>
<p>C'est là que se trouve le premier grand argument de vente de la Jamstack : <strong>de meilleures performances</strong>. En proposant des pages statiques sur CDN, vous améliorez la vitesse de chargement des pages et les performances de votre site Web. La vitesse est devenue un facteur de classement extrêmement important. Pour votre client, cela signifie une meilleure optimisation pour les moteurs de recherche, ce qui peut conduire à un meilleur classement dans les résultats de recherche naturels et à plus de trafic.</p>
<p>D'autre part, plus de trafic signifie plus d'opportunités de transformer vos visiteurs en prospects. Étant donné que le taux de conversion des prospects est étroitement lié à la performance du site Web (tel que cité ci-dessus), le fait d'avoir une page Web livrée plus rapidement influence directement les revenus des clients.</p>
<h3 id="securite-accrue-et-mise-a-l-echelle-plus-facile">Sécurité accrue et mise à l'échelle plus facile</h3>
<p>Personne ne veut avoir un site Web sujet à des failles de sécurité majeures. En proposant des pages statiques, vous réduisez considérablement la surface des attaques malveillantes et des <em>exploits</em> potentiels. De plus, en cas d'augmentation inattendue du trafic, vous pouvez faire en sorte que le CDN sur lequel vos fichiers sont hébergés compense de manière transparente.</p>
<p>Pour votre client, cela signifie beaucoup moins de temps de maintenance de développement et moins de temps de récupération suite à un piratage potentiel. En d'autres termes, une réduction des coûts.</p>
<h3 id="hebergement">Hébergement</h3>
<p>Nous avons un potentiel de croissance du chiffre d'affaires grâce à l'amélioration de nos performances. Ensuite, nous réalisons des économies de coûts grâce à une réduction du temps de développement consacré à la maintenance, à la sécurité et aux problèmes de performance. Enfin, les coûts d'hébergement de vos clients diminueront considérablement car l'hébergement sur CDN pour les fichiers statiques est beaucoup moins cher que l'hébergement traditionnel.</p>
<h2 id="expliquer-la-jamstack-au-client">Expliquer la Jamstack au client</h2>
<p>Lorsqu'on traite avec des clients, le défi de communiquer clairement et efficacement les avantages, les buts et les bénéfices peut parfois s'avérer accablant. D'autant plus si votre client n'est pas un expert en technologie.</p>
<p>Alors, comment communiquez-vous tous les avantages avec votre client ?</p>
<h3 id="posez-des-questions-et-informez-vos-clients">Posez des questions et informez vos clients</h3>
<p>Commencez par poser beaucoup de questions à votre client afin de comprendre sa motivation, ses objectifs commerciaux, ses compétences techniques et marketing. Demandez-leur quels outils, applications ou technologies ils utilisent à la maison et au travail. Écoutez les retours d'information. C'est un peu comme construire la personnalité d'un acheteur pour un seul client.</p>
<p>Ensuite, mettez votre client à niveau pour qu'il puisse lui aussi comprendre de quoi vous parlez. Comment ? <strong>Aidez votre client à comprendre la Jamstack</strong> dans une langue qu'il comprendra beaucoup plus facilement (rappelez-vous que c'est pour cela que vous avez posé beaucoup de questions).</p>
<p>Faites des analogies entre les technologies que connaît déjà votre client et vos solutions. Par exemple, si vos clients connaissent WordPress, comparez les avantages et les inconvénients de WordPress et dites pourquoi les générateurs de site statique sont une excellente alternative et à quel point ces deux approches sont différentes ou similaires.</p>
<h3 id="faites-des-etudes-de-cas">Faîtes des études de cas</h3>
<p>Les études de cas sont l'un des moyens les plus efficaces de transformer des prospects hésitants en clients. Utilisez des études de cas de vos précedents travaux, non pas pour montrer, mais pour aider vos prospects et clients à réaliser qu'il existe une solution Jamstack élégante à leur problème. Plus vous pouvez aligner votre étude de cas sur les problèmes auxquels votre client est confronté, plus l'étude de cas aura d'impact.</p>
<p>Si vous n'en avez pas, utilisez Internet. De cette façon, vous familiariserez votre client avec la Jamstack, ses possibilités et ses inconvénients, et vous le rendrez plus ouvert à cette idée.</p>
<h3 id="donnez-leur-des-options-laissez-les-choisir">Donnez-leur des options, laissez-les choisir</h3>
<p>Si vous avez su tirer parti de vos discussions préalables pour éduquer le client et le familiariser avec la Jamstack, une fois que vous aurez commencé à travailler sur la solution technique, donnez à votre client une voix dans le choix de la décision technique. Faites en sorte qu'ils se sentent entendus et valorisés. Sollicitez continuellement leurs commentaires et impliquez-les dans le processus. Cela suscitera alors un plus grand intérêt et une plus grande responsabilité partagée de la part des clients.</p>
<h2 id="en-resume">En résumé…</h2>
<p>La plupart du temps, les clients veulent la lune avec leur budget. Cependant, ce que le client veut et ce dont il a besoin sont souvent deux choses complètement différentes. Jamstack est fondamentalement un ensemble d'outils proposé donc assurez-vous que c'est le meilleur ensemble d'outils pour votre client avant de le proposer.</p>
<p>C'est votre travail de vous assurer qu'ils obtiennent ce dont ils ont besoin et de comprendre la logique et la technologie sous-jacente.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2019/12/03/mise-en-cache-fichiers-partiels-hugo/</id>
    <title>Gestion du cache des fichiers partiels avec Hugo</title>
    <published>2019-12-03T17:10:24+00:00</published>
    <updated>2019-12-07T17:13:54+00:00</updated>
    <link href="https://jamstatic.fr/2019/12/03/mise-en-cache-fichiers-partiels-hugo/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<figure>
<picture title="Illustration des partiels d&#039;Hugo">
<source type="image/webp" srcset="/thumbnails/768x/regisphilibert.com/blog/2019/12/hugo-partial-series-part-1-caching-with-partialcached/images/featured.b0c47159546ab6284ce87f2a7d8f0b84.webp 768w, /thumbnails/1024x/regisphilibert.com/blog/2019/12/hugo-partial-series-part-1-caching-with-partialcached/images/featured.b0c47159546ab6284ce87f2a7d8f0b84.webp 1024w" width="1024" height="544" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/regisphilibert.com/blog/2019/12/hugo-partial-series-part-1-caching-with-partialcached/images/featured.b0c47159546ab6284ce87f2a7d8f0b84.avif 768w, /thumbnails/1024x/regisphilibert.com/blog/2019/12/hugo-partial-series-part-1-caching-with-partialcached/images/featured.b0c47159546ab6284ce87f2a7d8f0b84.avif 1024w" width="1024" height="544" sizes="100vw">
<img src="/regisphilibert.com/blog/2019/12/hugo-partial-series-part-1-caching-with-partialcached/images/featured.b0c47159546ab6284ce87f2a7d8f0b84.png" alt="Illustration des partiels d&#039;Hugo" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="544" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/regisphilibert.com/blog/2019/12/hugo-partial-series-part-1-caching-with-partialcached/images/featured.b0c47159546ab6284ce87f2a7d8f0b84.png 768w, /thumbnails/1024x/regisphilibert.com/blog/2019/12/hugo-partial-series-part-1-caching-with-partialcached/images/featured.b0c47159546ab6284ce87f2a7d8f0b84.png 1024w" sizes="100vw">
</picture>
<figcaption>Illustration des partiels d'Hugo</figcaption>
</figure>
<p>Les fichiers partiels sont parmi les modèles de fichiers les plus utilisés pour la maintenance de sites Hugo. C'est eux qui nous permettent d'isoler nos composants, inclusions, ou autres morceaux de code et même plus récemment nos fonctions.</p>
<p>Nous n'allons pas revenir dans cet article sur les bases des <a href="https://gohugo.io/templates/partials/" target="_blank" rel="noopener noreferrer">partiels</a> comme leur <em>contexte</em> que nous avons déjà <a href="/2018/02/08/hugo-le-point-sur-le-contexte/">détaillé auparavant</a>.</p>
<p>Nous allons voir comme tirer parti au mieux des fonctionnalités de ces fichiers, bien au-delà des inclusions habituelles. Nous verrons comment Hugo peut mettre en cache vos fichiers partiels pour réduire le temps de génération, comment les utiliser comme des fonctions, nous verrons enfin quelles sont les meilleures pratiques à adopter en termes d'organisation et de commentaires pour nous assurer de leur pérennité.</p>
<p>Entrons dans le vif du sujet sans plus attendre avec la solution dédiée à la mise en cache des fichiers partiels : <a href="https://gohugo.io/functions/partialcached/" target="_blank" rel="noopener noreferrer"><code>partialCached</code></a></p>
<h2 id="pourquoi-utiliser-partialcached">Pourquoi utiliser partialCached ?</h2>
<p>Comme vous le savez peut-être déjà, le but d'un fichier partiel est de permettre de refactoriser les parties de code utilisées souvent dans vos modèles.</p>
<p>Si comme moi vous avez horreur de <em>copier-coller</em> des bouts de code un peu partout dans vos projets, vous devez avoir beaucoup recours aux fichiers partiels !</p>
<p>Le rendu du code de votre fichier partiel peut être identique sur une majorité des pages et quelque peu différent pour certaines, ou bien il peut être complètement différent d'une page à l'autre. Ce deuxième scénario bénéficiera rarement des bienfaits d'une mise en cache, concentrons-nous donc ici sur le premier.</p>
<p>Songez à l'entête de page de votre site par exemple.</p>
<p>Votre entête de page affichera presque toujours le même balisage. Même logo, même lien vers la page d'accueil, même menu de navigation, même liens vers vos comptes sur les réseaux sociaux. Il sera affiché sur chacune des pages de votre site.</p>
<p>Si votre projet Hugo nécessite la création de milliers de fichiers HTML, alors, à chaque génération Hugo va devoir inspecter mille fois la configuration de votre menu, celle de vos profils sociaux pour au final générer le même balisage.</p>
<p>Avec <code>partialCached</code> vous pouvez dire à Hugo que ce bout de code ne bouge jamais et qu'il peut donc l'inspecter une seule fois, le mettre en cache et le réutiliser&#8239;:</p>
<pre><code class="language-go-html-template hljs go">{{ partialCached <span class="hljs-string">"header.html"</span> . }}</code></pre>
<p>C'est 999 fois où Hugo n'aura pas à interpréter le code de votre fichier partiel. En fonction de la complexité de votre menu de navigation, <strong>vous venez de potentiellement gagner pas mal de précieuses millisecondes</strong> ⏱️!</p>
<p>Mais notre entête de page est-il vraiment identique sur toutes les pages ?</p>
<p>Non, car il est fort probable que les liens du menu principal soient soulignés ou mise en forme pour indiquer aux visiteurs où ils se trouvent actuellement sur le site.</p>
<p>Le code de notre menu Hugo contient souvent quelque chose comme :</p>
<pre><code class="language-go-html-template hljs go">&lt;nav&gt;
{{ <span class="hljs-keyword">range</span> .Site.Menus.main }}
  &lt;a
    class=<span class="hljs-string">"{{ if eq $currentPage.Section .Page.Section }} active {{ end }}"</span>
    href=<span class="hljs-string">"{{ .URL }}"</span>
  &gt;
    {{ .Name }}
  &lt;/a&gt;
{{ end }}
&lt;/nav&gt;</code></pre>
<p>Le code ci-dessus parle quasiment de lui-même. Notre projet comporte un menu principal avec cinq entrées, chacune pointant vers une section du site. Pour rendre active l'entrée <strong>Blog</strong> du menu lorsqu'on se trouve sur une page de la section Blog, nous comparons la section de la page visitée (<code>$currentPage.Section</code>) avec celle de l'entrée de menu (<code>.Page.Section</code>).</p>
<p>Avec le <code>{{ partialCached "header.html" . }}</code> actuel, Hugo va maintenant évaluer une seule fois la condition de ce <code>if</code> et appliquer son résultat à toutes les pages suivantes générées, et ce quelle que soit leur section.</p>
<p>Heureusement il y a les variantes de partiel.</p>
<h2 id="les-variantes-de-partiel">Les variantes de partiel</h2>
<p>Nous savons que notre entête va seulement être modifié cinq fois, en fonction de la <code>.Section</code> de la page courante. Nous devons donc dire à Hugo de mettre en cache une différente variante du partiel en fonction de ce facteur.</p>
<p>Contrairement à la fonction <code>partial</code>, les arguments de <code>partialCached</code> ne se limitent pas au contexte.</p>
<p>Pour nos cas d'utilisation, il est clair que la variante est la <code>.Section</code> de la page courante, nous pouvons donc écrire ceci :</p>
<pre><code class="language-go-html-template hljs go">{{ partialCached <span class="hljs-string">"navigation.html"</span> . .Section }}</code></pre>
<p>🎉 C'est 995 fois où Hugo n'aura pas à interpréter le code de ce partiel.</p>
<p>Bien. Cela fait une variante en moins, mais quid si quelque chose d'autre doit changer et que ce n'est pas lié à la section ?</p>
<p>Par exemple <a href="https://regisphilibert.com/contact/" target="_blank" rel="noopener noreferrer">sur mon site</a>, les liens sociaux sont bien en vue sur la page de contact, du coup sur cette page ils ne sont pas affichés "en double" dans l'entête.</p>
<p>Le code ressemble à ça :</p>
<pre><code class="language-go-html-template hljs go">{{ <span class="hljs-keyword">if</span> ne .Layout <span class="hljs-string">"contact"</span> }}
  {{ <span class="hljs-keyword">range</span> site.Socials }}
    {{<span class="hljs-comment">/* vous voyez l'idée */</span>}}
  {{ end }}
{{ end }}</code></pre>
<p>Nous avons donc maintenant besoin de deux variantes, la variante <code>.Section</code> et la variante <code>Est-ce la page de contact ?</code>.</p>
<p>Heureusement pour nous, le nombre de variantes n'est pas limité, alors allons-y gaiement :</p>
<pre><code class="language-go-html-template hljs go">{{ partialCached <span class="hljs-string">"navigation.html"</span> . .Section <span class="hljs-string">"contact"</span> }}</code></pre>
<p>OK, c'était simplement à des fins de clarté et de lisibilité mais soyons réaliste, vous aurez vraisemblablement besoin de quelque chose de plus "dynamique" :</p>
<pre><code class="language-go-html-template hljs go">{{ $layout := cond (eq .Layout <span class="hljs-string">"contact"</span>) <span class="hljs-string">"contact"</span> <span class="hljs-string">"other"</span> }}
{{ partialCached <span class="hljs-string">"navigation.html"</span> . .Section $layout }}</code></pre>
<p>🎉 C'est maintenant 994 fois où Hugo n'aura pas à interpréter le code de ce partiel.</p>
<h2 id="haussons-le-niveau-d-un-cran-rџ-Ye">Haussons le niveau d'un cran 💪</h2>
<p>Plongeons-nous maintenant dans quelque chose d'un peu plus complexe.</p>
<p>Notre blog possède un emplacement pour afficher les auteurs d'un article. Il y a trois auteurs pour le site, cet emplacement pourra donc en lister un seul, ou alors une combinaison d'entre eux en fonction de la liste d'auteurs présente dans le front matter de l'article. On peut dire avec certitude que sur nos mille articles, beaucoup partageront la même liste d'auteurs.</p>
<p>Ici, la variante idéale serait donc de passer notre liste d'auteurs dans un ordre défini, et nous serions tentés de pouvoir écrire :</p>
<pre><code class="language-go-html-template hljs go">{{ partialCached <span class="hljs-string">"authors-box.html"</span> . .Params.authors }}</code></pre>
<p>Malheureusement à l'heure actuelle, les variantes passées en argument de la fonction <code>partialCached</code> doivent être <strong>des chaînes de caractères</strong> 🤷.</p>
<p>Pour respecter ce prérequis, nous devons transformer cette liste en chaîne de caractères avant de la passer en option, et la manière la plus sûre de le faire, comme souvent, c'est d'utiliser la fonction <a href="https://gohugo.io/functions/printf/#readout" target="_blank" rel="noopener noreferrer"><code>printf</code></a> avec le bon <a href="https://golang.org/pkg/fmt/#hdr-Printing" target="_blank" rel="noopener noreferrer">verbe</a>.</p>
<p>Personnellement j'aime bien <code>%x</code>, car il va générer la représentation d'une valeur en chaîne hexadécimale, quelque que soit le type de structure.</p>
<p>Admettons que nous ayons :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">authors:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">Bud</span> <span class="hljs-string">Parr</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">Frank</span> <span class="hljs-string">Taillandier</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">Régis</span> <span class="hljs-string">Philibert</span></code></pre>
<pre><code class="language-go-html-template hljs go">{{ $variant := printf <span class="hljs-string">"%x"</span> .Params.authors }}</code></pre>
<p>🖨️👇
<code>[42756420506172724672616e6b205461696c6c616e6469657252c3a9676973205068696c6962657274]</code></p>
<p>Nous avons maintenant une chaîne de caractères que nous pouvons passer comme variante du partiel :</p>
<pre><code class="language-go-html-template hljs go">{{ with .Params.authors }}
  {{ $authors := sort . }}
  {{ $variant := printf <span class="hljs-string">"%x"</span> $authors }}
  {{ partialCached <span class="hljs-string">"authors-box.html"</span> . $variant }}
{{ end }}</code></pre>
<p>Grâce à cela, nous sommes maintenant assurés qu'Hugo ne génèrera qu'une seule variante par combinaison d'auteurs, soit 7 au maximum.</p>
<aside class="note"><h3 id="pourquoi-ordonner-les-auteurs">Pourquoi ordonner les auteurs ?</h3>
<p>En les classant par ordre alphabétique, nous nous assurons de ne pas créer des variantes inutiles, et ce que quel que soit l'ordre dans lequel les auteurs ont été listés dans le front matter.</p></aside>
<aside class="note note-tip"><p>Cette solution pour utiliser les variantes marche pour les listes et les tableaux associatifs simples. Effectuez des tests si vous l'utiliser pour des structures de données imbriquées plus complexes.</p></aside>
<h2 id="et-les-langues-rџ-rџ-rџ-rџ">Et les langues ? 🇫🇷🇬🇧</h2>
<p>Dans un contexte multilingue, si nous repensons à notre partiel pour l'entête de page, il se pourrait que nous y trouvions aussi un sélecteur de langue et que nous soyons tentés d'ajouter une autre variante :</p>
<pre><code class="language-go-html-template hljs go">{{ partialCached <span class="hljs-string">"navigation.html"</span> . .Section .Lang }}</code></pre>
<p>Mais ce n'est pas la peine de le faire, car par défaut Hugo va générer autant de caches de partiels que de langues déclarées.
Dans notre cas de figure, Hugo va donc calculer le balisage de notre entête dix fois.</p>
<aside class="note note-🧮"><p>La règle d'or pour connaître le "calcul" du nombre de partiels est :<br>
<code>partiel x variantes x langues</code></p></aside>
<h2 id="amG-c-liorer-votre-temps-de-gG-c-nG-c-ration-vЏ-pyoЏ">Améliorer votre temps de génération ⏱️</h2>
<p>Pour les sites que vous avez développé vous-même, il est relativement facile d'aller inspecter votre dossier <code>partials</code> et d'identifier ceux qui pourraient être mis en cache. Mais pour les projets dont vous avez hérité ou que vous avez développé il y a moment, il existe deux options que vous pouvez passer à la ligne de commande: <code>hugo --templateMetrics --templateMetricsHints</code>.</p>
<p>La première option même utilisée seule est déjà très utile puisqu'elle vous affiche le détail des durées de génération. Cependant tout ne peut pas être mis en cache, seulement les fichiers partiels.</p>
<p>Ces options vous aideront à identifier les principaux goulots d'étranglement, cependant vous devriez tout le temps garder ces trois points en tête:</p>
<ol>
<li>Le niveau de complexité de votre fichier partiel et sa durée de génération ajoutée au temps total.</li>
<li>La comparaison entre le nombre de fois où un partiel sera traité et son potentiel de variantes.</li>
<li>L'adaptation de votre code de façon à ce qu'il puisse être mis en cache (identifier tôt les variantes vous dispensera de pas mal de refactorasition)</li>
</ol>
<h2 id="conclusion">Conclusion</h2>
<p>Lorsque vous créez ou maintenez un projet Hugo, vous devez toujours garder en tête que chaque ligne de code peut réduire potentiellement le temps de génération. Laissez Hugo faire le gros du travail seulement quelques fois et non systématiquement!</p>
<p>Allez donc jeter un œil à vos fichiers partiels, créez vos propres variantes, et économisez du temps et de l'argent en vous reposant autant que possible sur <code>partialCached</code> !</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2019/10/30/git-based-cms-vs-api-first-cms/</id>
    <title>Git-based, API-first headless CMS : lequel choisir ?</title>
    <published>2019-10-30T09:26:33+00:00</published>
    <link href="https://jamstatic.fr/2019/10/30/git-based-cms-vs-api-first-cms/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>L'agence <a href="https://bejamas.io" target="_blank" rel="noopener noreferrer">Bejamas</a> s'est spécialisée dans le développement de sites Jamstack. Après avoir testé différentes solutions, elle se penche dans cet article sur les différences entre les CMS basés sur Git et ceux basés sur des APIs. L'article a le mérite de donner un aperçu des avantages et des inconvénients des deux types de plates-formes.
Dans les faits la dichotomie n'est pas aussi binaire qu'on pourrait le penser. En effet, rien n'empêche un CMS qui communique avec l'<abbr title="Interface de Programmation Applicative">API</abbr> d'une plate-forme Git de vous permettre d'accéder à votre tour à ces contenus via une <abbr title="Interface de Programmation Applicative">API</abbr> de votre cru. Générer des fichiers JSON, les générateurs actuels comme <a href="/categories/hugo">Hugo</a> ou <a href="/categories/eleventy">Eleventy</a> font ça <a href="https://github.com/regisphilibert/juliette-hugo-component" title="Composant de thème Hugo pour exposer une API" target="_blank" rel="noopener noreferrer">très</a> <a href="https://forestry.io/blog/add-functionality-to-your-hugo-site-with-theme-components/" title="Autre composant de thème Hugo pour exposer une API" target="_blank" rel="noopener noreferrer">bien</a>. On peut considérer que les CMS basés sur des APIs offrent un confort d'utilisation en fournissant une API et sa documentation par défaut. Une iso fonctionnalité demandera naturellement un effort supplémentaire si vous devez développer vous-même votre API. À vous de peser le pour et le contre de l'approche <abbr title="Do It Yourself">DIY</abbr>, qui peut soit dit en passant tout à fait être réutilisable de projet en projet.
Il vous faut aussi prendre en compte dans l'équation <em>votre</em> workflow éditorial, la taille de <em>votre</em> site (et donc le temps nécessaire à la génération d'une nouvelle version) ainsi que <em>votre</em> fréquence de publication. Autant de paramètres qui doivent peser dans le choix d'un <a href="/categories/headless">headless</a> CMS. On rappellera que les CMS headless ne présentent aucun des inconvénients des CMS traditionnels, que ce soit en terme de workflow de développement, de performance ou de sécurité, et qu'ils offrent plus de flexibilité et une meilleure productivité, une fois l'investissement initial consenti.</p></aside>
<p>Alors que le monde est en train de migrer vers le cloud, les systèmes de gestion de contenu (CMSs), en tout cas les plus populaires, sont encore embourbés dans des technologies et des architectures qui ont plus de 20 ans. Ils n'en sont pas moins fonctionnel pour autant.</p>
<p>WordPress par exemple (qui date du début des années 2000) est utilisé à l'heure actuelle par <a href="https://w3techs.com/technologies/details/cm-wordpress/all/all" target="_blank" rel="noopener noreferrer">34% des sites web</a>. Dans le même temps, il y a une forte demande d'innovation, et le souhait d'une meilleure expérience numérique pousse beaucoup d'entreprises à accélérer leur rythme de développement et à se tourner vers les outils de développement d'interfaces client modernes, ceci afin de créer des sites plus légers, plus performants et plus sécurisés.</p>
<p>Mais il faut bien que le contenu de votre site web vive quelque part, n'est-ce pas ?</p>
<p>Bienvenue dans le monde des CMS headless. Plutôt que de <a href="https://bejamas.io/blog/headless-cms/" target="_blank" rel="noopener noreferrer">lister quelques uns des headless CMS</a> de cet écosystème — ce que nous avons déjà fait de manière opiniâtre — dans cet article, nous allons couvrir les différences, les défis, et les bénéfices de l'utilisation des CMS basés sur Git et de ceux accessibles par défaut via une API. <sup id="fnref1:git"><a href="#fn:git" class="footnote-ref">1</a></sup></p>
<h2 id="les-cms-bases-sur-git">Les CMS basés sur Git</h2>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/images/2019-10-30_git-based-cms-vs-api-first-cms/ec6f86c74af478ebc03092ae95b26d9e2c3b630e-2880x1030.a487867dd8f1f465461182ed588e32a7.webp 768w, /thumbnails/1024x/images/2019-10-30_git-based-cms-vs-api-first-cms/ec6f86c74af478ebc03092ae95b26d9e2c3b630e-2880x1030.a487867dd8f1f465461182ed588e32a7.webp 1024w" width="1024" height="366" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/2019-10-30_git-based-cms-vs-api-first-cms/ec6f86c74af478ebc03092ae95b26d9e2c3b630e-2880x1030.a487867dd8f1f465461182ed588e32a7.avif 768w, /thumbnails/1024x/images/2019-10-30_git-based-cms-vs-api-first-cms/ec6f86c74af478ebc03092ae95b26d9e2c3b630e-2880x1030.a487867dd8f1f465461182ed588e32a7.avif 1024w" width="1024" height="366" sizes="100vw">
<img src="/images/2019-10-30_git-based-cms-vs-api-first-cms/ec6f86c74af478ebc03092ae95b26d9e2c3b630e-2880x1030.a487867dd8f1f465461182ed588e32a7.png" alt="Les CMS basés sur Git" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="366" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAABVElEQVR4nO2ZyxKCMBAEJxT//8vxZBUqkAcQemX6rNbOdrImlZRzluEw3V2A+cRCYFgIDAuBYSEwLASGhcCwEBgWAsNCYFgIDAuBYSEwLATGfHcBraSUVt8Lcs5pdC1XkKK8h2yJWIMqpyZDkpSpAaQ2EUtomWpzoEdWr4z3d2lSpPJC+es/9SNC7wIr5LuZNauduCNamSOF6KmVOrq2mIjbmljTKLAj66lMhO185o6Inqd47B01PkbO+hGZevPMy+LWfmBEk86UUdPsqzNdukNGQBgzZ3IkD0JIDe9Vtxc2wumsVGOS1HwBG0Gp8GWde58l5GlZKD/H3girjNDkq8DeQ7aa3iKDIK51gWOFSOsNrQ1IkNEDWojU19ioMqRAL4bS9kmrdJeKRCghTwA/sp6GhcCwEBgWAsNCYFgIDAuBYSEwLASGhcCwEBgWAsNCYFgIjBeeL5hbbpwdmAAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/2019-10-30_git-based-cms-vs-api-first-cms/ec6f86c74af478ebc03092ae95b26d9e2c3b630e-2880x1030.a487867dd8f1f465461182ed588e32a7.png 768w, /thumbnails/1024x/images/2019-10-30_git-based-cms-vs-api-first-cms/ec6f86c74af478ebc03092ae95b26d9e2c3b630e-2880x1030.a487867dd8f1f465461182ed588e32a7.png 1024w" sizes="100vw">
</picture>
<p>Avec un CMS basé sur Git, les changements sont d'abord enregistrés dans votre dépôt Git, ce qui ensuite peut produire une nouvelle génération de votre site. Sans entrer trop dans les détails, sachez juste que vous allez travailler principalement avec des fichiers stockés dans votre dépôt Git. Voyons quels sont les avantages et les inconvénients de ce type de CMS.</p>
<h3 id="潰牵">Pour</h3>
<ul>
<li>Pas de verrou propriétaire sur vos contenus.</li>
<li>Gestion de version de <em>tous</em> les contenus par défaut.</li>
<li>Tous les contenus étant disponibles sous forme de fichiers texte, les développeurs peuvent continuer d'utiliser leurs outils habituels.</li>
<li>Facilite l'annulation de changements.</li>
<li>C'est l'approche la plus homogène pour la plupart des développeurs web, qui utilisent déjà un workflow basé sur Git.</li>
<li>Facile à configurer.</li>
</ul>
<h3 id="contre">Contre</h3>
<ul>
<li>Si vous avez plusieurs sites ou applications qui récupèrent des contenus depuis un même CMS, ce n'est pas forcément la meilleure solution.</li>
<li>Si votre site a une quantité très importante de contenus, vous préférerez peut-être regarder du côté d'une base de données.</li>
<li>Si vous prévoyez des mises à jours en continu, des articles publiés chaque minute par exemple, la génération et le déploiement continu n'est pas la meilleure option.</li>
<li>Moins d'options de modélisation de contenu et de formatage.</li>
</ul>
<h2 id="les-cms-api-first">Les CMS API-first</h2>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/images/2019-10-30_git-based-cms-vs-api-first-cms/d6ad8a9e53a659c61d205492dec1188d8085574c-2880x1482.8aee51214948565e1baf9df4e6e7d50a.webp 768w, /thumbnails/1024x/images/2019-10-30_git-based-cms-vs-api-first-cms/d6ad8a9e53a659c61d205492dec1188d8085574c-2880x1482.8aee51214948565e1baf9df4e6e7d50a.webp 1024w" width="1024" height="527" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/2019-10-30_git-based-cms-vs-api-first-cms/d6ad8a9e53a659c61d205492dec1188d8085574c-2880x1482.8aee51214948565e1baf9df4e6e7d50a.avif 768w, /thumbnails/1024x/images/2019-10-30_git-based-cms-vs-api-first-cms/d6ad8a9e53a659c61d205492dec1188d8085574c-2880x1482.8aee51214948565e1baf9df4e6e7d50a.avif 1024w" width="1024" height="527" sizes="100vw">
<img src="/images/2019-10-30_git-based-cms-vs-api-first-cms/d6ad8a9e53a659c61d205492dec1188d8085574c-2880x1482.8aee51214948565e1baf9df4e6e7d50a.png" alt="Les CMS API-first" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="527" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAB7ElEQVR4nO2b67KCMAyEE8f3f2Rzfmg5BUpbakMXu98Mo+OMELMk2wuqmQnB4TE6ALKGgoBBQcCAF0RVTVWnMTpFNnVVNRWRcIiIWPT6MtPkF2/Mc3QAOVTeJRyLYp/jJSIPVbtalFCtZqbx+17nhxckiBKECWLI51VVrWdCqmNzaqNNgnjcGdnrybpt/Vyfiqg29WCu8Z3hbbaxX2yP5fOLqyN1vZ55qKqQ3AW9W0ZoTyZ7DxmB903YxUO8RAm/PCRfZV81I/H4zdCmPsKsS3jHVOUhpSAQE3dX4GfqMTPM2L8WhNXRl2oPiRM/w506iiZTZ1X4cSsPmQEKAgYFAYOCgEFBwKAgYFRv4XLu8Y/nsH81Dymt2o6ef4zaHdzG4Hn+XctiJYxlEYRCYLC0rNGtgLxx36DaVh6Fz3P5sJetMU/VsDdO4pk7/Cj5LVXSGkNPWmI4m4MuW7iz8a0Y2e94P9ubCmoWgUuCpPLgburxM7BHQZQYLWpL6231ykNBak5Ym5RfrQiPlYNihRxd8OrREsKe/hUxcLXXCTPTlnVBCuLMNvEloaAfJf0VzvhMUZCRT77XxtHr/AiDj6QgMy1vbP/v4jGUPcPUHoK48An9L1xPENbGUkwrCCpTtyxEKAgYFAQMCgIGBQHjD6ZACnCc3Lo5AAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/2019-10-30_git-based-cms-vs-api-first-cms/d6ad8a9e53a659c61d205492dec1188d8085574c-2880x1482.8aee51214948565e1baf9df4e6e7d50a.png 768w, /thumbnails/1024x/images/2019-10-30_git-based-cms-vs-api-first-cms/d6ad8a9e53a659c61d205492dec1188d8085574c-2880x1482.8aee51214948565e1baf9df4e6e7d50a.png 1024w" sizes="100vw">
</picture>
<p>Avec les CMS dotés d'une API par défaut, vous avez accès à une API, généralement une API REST ou GraphQL, qui sert le contenu. Donc, vous récupérez vos données de la façon dont elles sont structurées mais c'est à vous de choisir le framework ou le langage que vous allez utiliser pour travailler avec ces données.</p>
<h3 id="潰牵-1">Pour</h3>
<ul>
<li>C'est la meilleure solution si vous maintenez plusieurs applications et sites web qui récupèrent le même contenu structuré.</li>
<li>Facile à utiliser avec de multiples terminaux client.</li>
<li>Beaucoup d'options disponibles pour vous permettre de personnaliser le CMS.</li>
<li>Vous avez des quantités très importantes de données à gérer.</li>
</ul>
<h3 id="contre-1">Contre</h3>
<ul>
<li>Pas de gestion de version dans Git, ni d'intégration dans le workflow de développement.</li>
<li>Va souvent de paire avec une limitation de stockage ou d'utilisation de l'API. Peut revenir cher si vous ne calculez pas bien votre coût.</li>
<li>Dépendance aux développeurs pour des gros changements.</li>
</ul>
<p>Maintenant que nous savons tout cela, regardons de plus près les bénéfices et les défauts de ces systèmes que vous devriez gardez en tête lors du choix du headless CMS pour votre prochain projet.</p>
<h2 id="les-benefices-des-cms-bases-sur-git">Les bénéfices des CMS basés sur Git</h2>
<h3 id="la-gestion-de-version-multi-objets">La gestion de version multi-objets</h3>
<p>Les plates-formes de CMS traditionnelles comme <strong>Drupal</strong>, <strong>Kirby</strong>, ou <strong>Wordpress</strong> proposent un gestion de version limitée, soit elle surveillent les graphes d'un unique objet soit elles maintiennent des structures de données bancales.</p>
<p>Cette approche ne pose pas de problèmes pour des besoins de gestion de contenu basiques — par exemple un blog ou un site web très simple, mais elle est loin d'offrir une expérience numérique multi-fichiers, qui est une nécessité pour la plupart des marques de nos jours.</p>
<p>Bien souvent, il existe une relation entre le contenu et le code (CSS ou JavaScript) et cela doit être pris en compte. Surveiller les modifications unitaires ne suffit pas.</p>
<p>La possibilité de restaurer la version précédente d'un fichier peut suffire pour un besoin éditorial basique mais pas pour la majorité des scénarios dans la vraie vie comme des audits d'accessibilité, un renouvellement d'identité de marque, et de futurs développements qui demandent un CMS plus sophistiqué.</p>
<p>C'est pourquoi une approche de gestion de version des différents types de fichiers, comme celle utilisée par les développeurs est nécessaire. De plus Git est aujourd'hui le système de gestion de version le plus populaire pour surveiller les changements dans le code source et on comprend aisément pourquoi l'expérience numérique actuelle nécessite à la fois des composants techniques et du contenu.</p>
<h3 id="depot-distribue">Dépôt distribué</h3>
<p>Contrairement aux CMS traditionnels, Git permet aux développeurs d'avoir leurs propres dépôts locaux et intermédiaires, tous issus d'un dépôt parent.</p>
<p>La gestion de version distribuée et le flux de développement sont simples et rapides.</p>
<p>Avec un système basé sur Git, les développeurs peuvent travailler en local tout en faisant parti du CMS. Ainsi ils peuvent continuer d'utiliser au quotidien leurs outils de prédilection, sans restriction aucune.</p>
<h3 id="ramifications">Ramifications</h3>
<p>Grâce aux branches de Git, vous pouvez créer un nombre illimité d'environnements, ce qui facilite en général pas mal le développement.</p>
<p>Les développeurs ainsi que les auteurs peuvent expérimenter et travailler en parallèle sur des fonctionnalités majeures et des améliorations du site.</p>
<p><em>NdT: <a href="https://www.youtube.com/embed/Y2ak5o0IqLw?start=103" target="_blank" rel="noopener noreferrer">la conférence de Shawn Erquhart</a> explique en détail pourquoi les CMS basés sur Git sont des outils plus puissants qu'il n'y paraît.</em></p>
<p><div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;">
<iframe src="https://www.youtube-nocookie.com/embed/Y2ak5o0IqLw" loading="lazy" width="640" height="360" frameborder="0" allow="accelerometer;autoplay;encrypted-media;gyroscope;picture-in-picture;fullscreen;web-share;" allowfullscreen="" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background-color:#d8d8d8;"></iframe>
</div></p>
<h2 id="les-benefices-des-cms-bases-sur-des-apis">Les bénéfices des CMS basés sur des APIs</h2>
<h3 id="atteindre-les-consommateurs-sur-des-terminaux-varies">Atteindre les consommateurs sur des terminaux variés</h3>
<p>Beaucoup de personnes se tournent aujourd'hui vers des appareils à commande vocale comme <a href="https://www.amazon.com/all-new-amazon-echo-speaker-with-wifi-alexa-dark-charcoal/dp/B06XCM9LJ4" target="_blank" rel="noopener noreferrer">Amazon Echo</a> et <a href="https://store.google.com/gb/product/google_home" target="_blank" rel="noopener noreferrer">Google Home</a> pour écouter les actualités, faire leurs achats en ligne, définir des rappels, rechercher sur le web, etc.</p>
<p>Les plate-formes traditionnelles de gestion de contenu, ne peuvent fournir ce type d'expérience utilisateur, contrairement aux CMS qui fournissent une API de contenu. Les marques doivent donc développer une <abbr title="Interface de Programmation Applicative">API</abbr> pour ces terminaux pour les faire communiquer avec leur système.</p>
<p>Vous pouvez alors fournir différentes expériences sur absolument n'importe quel périphérique sans restriction aucune.</p>
<h3 id="meilleure-integration-pour-le-marketing">Meilleure intégration pour le marketing</h3>
<p>Dans un environnement basé sur une API de contenu, les marques peuvent greffer leurs applications marketing préférées comme des outils d'automatisation, un CRM ou des services d'analyse. Elles peuvent aussi décider à tout moment de ne plus les intégrer. Cela donne la possibilité aux marques d'adapter rapidement leur stratégie digitale comme elles l'entendent.</p>
<h3 id="rediffusion-du-contenu-plus-rapide">Rediffusion du contenu plus rapide</h3>
<p>Avec les CMS basés sur des APIs, tous vos contenus sont stockés en un unique point central qui permet aux marques de publier leurs contenus sur différents canaux. Les créateurs de contenus écrivent donc une seule fois et peuvent les rediffuser sur chaque canal ou terminal. Dans le monde <a href="https://fr.wikipedia.org/wiki/Stratégie_omnicanale#Définition_de_l’omnicanal" target="_blank" rel="noopener noreferrer">omnicanal</a> d'aujourd'hui, c'est un gros avantage et un énorme gain de temps car il éviter à avoir à créer séparément des contenus pour chaque canal.</p>
<h2 id="les-defis-poses-par-les-cms-bases-sur-git">Les défis posés par les CMS basés sur Git</h2>
<h3 id="adopter-des-mesures-specifiques-pour-le-seo">Adopter des mesures spécifiques pour le SEO</h3>
<p>Faute de solution dédiée, l'amélioration du SEO avec Git dépendra de vous. Pour bénéficier d'une meilleure visibilité, vous devrez vous assurer de la compatibilité mobile de votre site, de la génération de sitemap, de l'insertion des balises meta et open graph.</p>
<h3 id="les-limitations-de-github-pages">Les limitations de GitHub Pages</h3>
<p>GitHub Pages est gratuit mais ne supporte par défaut que quelques plugins Jekyll, si vous utilisez un autre générateur ou que vous utilisez d'autres plugins, il est toujours possible de générer vos contenus localement, via GitHub Actions ou autre plate-forme d'intégration continue <sup id="fnref1:deploiement"><a href="#fn:deploiement" class="footnote-ref">2</a></sup>.</p>
<h2 id="les-defis-poses-par-les-cms-bases-sur-des-apis">Les défis posés par les CMS basés sur des APIs</h2>
<h3 id="une-grande-dependance-aux-developpeurs-web">Une grande dépendance aux développeurs web</h3>
<p>Créer plusieurs interfaces client personnalisées, représente un travail supplémentaire pour votre équipe. Cela demandera donc une communication constante entre les équipes marketing et celles de développements dès que des modifications devront être apportées. <sup id="fnref1:developpeurs"><a href="#fn:developpeurs" class="footnote-ref">3</a></sup></p>
<h3 id="pour-certains-la-previsualisation-de-contenu-est-problematique">(Pour certains) la prévisualisation de contenu est problématique</h3>
<p>Les CMS d'APIs de contenu proposent des interfaces d'administration qui permettent aux éditeurs d'entrer leur contenu. Toutefois, tous les CMS ne permettent pas une prévisualisation avant publication, même chose pour la gestion des brouillons qui devra être ajoutée manuellement. Ne pas pouvoir juger du rendu utilisateur final est souvent un inconvénient qui laisse peu de place à l'erreur.</p>
<h3 id="les-couts">Les coûts</h3>
<p>Vous devrez gardez en tête les coûts induits par l'utilisation des CMS qui proposent des APIs de contenu, même ceux qui sont open source. Cela inclut les coûts d'infrastructure (hébergement, CDNs, etc. pour les CMS à héberger soi-même) mais aussi les coûts de développement, de maintenance et de sécurité, puisque vous travaillerez avec une interface d'administration entièrement personnalisée.</p>
<h2 id="alors-quel-cms-headless-devez-vous-choisir">Alors quel CMS headless devez vous choisir ?</h2>
<p>Ce n'est pas une question à laquelle il est facile de répondre. Par exemple, nous nous sommes rendus compte que si vous n'avez pas besoin de créer des centaines d'articles ou de pages, et régénérer le site constamment, les CMS basés sur Git sont une bien meilleure option.</p>
<p>Bien entendu, cela dépend surtout de vos besoins. C'est à l'ensemble de votre équipe (marketing et développement) de bien comprendre la taille, le budget et le temps (oui c'est aussi un facteur de décision) consacré au projet, en plus de toutes les technologies à l'œuvre derrière.</p>
<h2 id="liste-de-plate-formes-cms-basees-sur-git">Liste de plate-formes CMS basées sur Git</h2>
<ul>
<li>Forestry - <a href="https://forestry.io" target="_blank" rel="noopener noreferrer">https://forestry.io</a></li>
<li>Netlify CMS - <a href="https://www.netlifycms.org" target="_blank" rel="noopener noreferrer">https://www.netlifycms.org</a></li>
<li>TinaCMS - <a href="https://tinacms.org" target="_blank" rel="noopener noreferrer">https://tinacms.org</a></li>
<li>Publii - <a href="https://getpublii.com" target="_blank" rel="noopener noreferrer">https://getpublii.com</a></li>
<li>Prose - <a href="http://prose.io" target="_blank" rel="noopener noreferrer">http://prose.io</a></li>
<li>Crafter CMS - <a href="https://craftercms.org" target="_blank" rel="noopener noreferrer">https://craftercms.org</a></li>
</ul>
<h2 id="liste-de-plates-formes-cms-basees-sur-des-apis">Liste de plates-formes CMS basées sur des APis</h2>
<ul>
<li>Sanity - <a href="https://sanity.io" target="_blank" rel="noopener noreferrer">https://sanity.io</a></li>
<li>Dato CMS - <a href="https://www.datocms.com/" target="_blank" rel="noopener noreferrer">https://www.datocms.com/</a></li>
<li>Strapi - <a href="https://strapi.io" target="_blank" rel="noopener noreferrer">https://strapi.io</a></li>
<li>Storyblok - <a href="https://www.storyblok.com/" target="_blank" rel="noopener noreferrer">https://www.storyblok.com/</a></li>
<li>Prismic - <a href="https://prismic.io" target="_blank" rel="noopener noreferrer">https://prismic.io</a></li>
<li>Contentful - <a href="https://www.contentful.com" target="_blank" rel="noopener noreferrer">https://www.contentful.com</a></li>
<li>Ghost - <a href="https://ghost.org" target="_blank" rel="noopener noreferrer">https://ghost.org</a></li>
<li>Cloud CMS - <a href="https://www.cloudcms.com" target="_blank" rel="noopener noreferrer">https://www.cloudcms.com</a></li>
<li>Directus - <a href="https://directus.io" target="_blank" rel="noopener noreferrer">https://directus.io</a></li>
<li>Rooftop - <a href="https://www.rooftopcms.com" target="_blank" rel="noopener noreferrer">https://www.rooftopcms.com</a></li>
</ul>
<div class="footnotes">
<hr>
<ol>
<li id="fn:git">
<p>NdT: Les CMS basés sur Git, utilisent eux aussi une API pour communiquer avec votre dépôt Git, et vous permettent également de générer <em>manuellement</em> une API à consommer depuis différents terminaux clients.&#160;<a href="#fnref1:git" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
<li id="fn:deploiement">
<p>Il existe d'autres solutions dédiées pour le déploiement de sites statiques comme <a href="https://netlify.com" target="_blank" rel="noopener noreferrer">Netlify</a> ou <a href="https://vercel.com" target="_blank" rel="noopener noreferrer">Vercel</a> pour n'en citer que deux.&#160;<a href="#fnref1:deploiement" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
<li id="fn:developpeurs">
<p>C'est aussi vrai pour les CMS basés sur Git, le moindre changement apporté dans la structure de données devra être reporté pour être affiché sur le site généré.&#160;<a href="#fnref1:developpeurs" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
</ol>
</div>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2019/09/28/navigation-multilingue-eleventy/</id>
    <title>Navigation multilingue avec Eleventy</title>
    <published>2019-09-28T00:00:00+00:00</published>
    <link href="https://jamstatic.fr/2019/09/28/navigation-multilingue-eleventy/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<h2 id="mise-en-place">Mise en place</h2>
<p>Développer un site multilingue avec un générateur de site statique est à la portée de tous dès lors que l'on dispose d'un langage de templating, de données structurées et d'un système permettant de contrôler les URLs, ce que proposent la plupart des générateurs de sites statiques.</p>
<p>Il existe beaucoup d'<a href="/categories/i18n">articles sur le sujet</a>, comme celui que nous avons publié sur <a href="/2019/09/07/site-multilingue-avec-eleventy/">la gestion d'un site multilingue avec Eleventy</a>. Hugo dispose d'<a href="https://gohugo.io/content-management/multilingual/" target="_blank" rel="noopener noreferrer">une documentation très claire</a> sur le sujet, de <a href="https://www.sylvaindurand.org/making-jekyll-multilingual/" target="_blank" rel="noopener noreferrer">nombreux</a> <a href="https://forestry.io/blog/creating-a-multilingual-blog-with-jekyll/" target="_blank" rel="noopener noreferrer">articles</a> en font de même pour Jekyll.</p>
<p>Notre but ici est de rediriger depuis une page écrite dans une langue donnée vers les traductions disponibles pour cette même page. Si aucune traduction n'existe pour une page, alors nous redirigerons vers la page d'accueil dans la langue demandée.</p>
<p>Ici, nous utilisons <a href="/categories/eleventy">Eleventy</a>, mais l'approche serait similaire avec d'autres générateurs de site statique.</p>
<p>Pour parvenir à nos fins, il nous faut :</p>
<ol>
<li>Pouvoir boucler sur les différents langages utilisés sur le site</li>
<li>Nous assurer que chaque contenu dispose d'une clé <code>locale</code> dont la valeur correspond au code de langue utilisé pour ce même contenu. Si vous avez besoin de vous rafraîchir la mémoire, relisez <a href="/2019/09/07/site-multilingue-avec-eleventy/">comment faire avec les fichiers de données de répertoire avec Eleventy</a>.</li>
<li>Définir une clé commune unique nommée <code>translationKey</code> pour relier les traductions d'un contenu entre elles.</li>
</ol>
<h2 id="les-langues-du-site">Les langues du site</h2>
<p>Nous allons commencer par créer un tableau des langues utilisées par notre site. J'ai pour habitude de définir un objet <code>languages</code> dans le fichier de données <code>./src/_data/site.js</code>.</p>
<pre><code class="language-js hljs javascript"><span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">title</span>: <span class="hljs-string">"Webstoemp"</span>,
  <span class="hljs-attr">description</span>:
    <span class="hljs-string">"Webstoemp is the portfolio and blog of Jérôme Coupé, a designer and front-end developer from Brussels, Belgium."</span>,
  <span class="hljs-attr">url</span>: <span class="hljs-string">"https://www.webstoemp.com"</span>,
  <span class="hljs-attr">baseUrl</span>: <span class="hljs-string">"/"</span>,
  <span class="hljs-attr">author</span>: <span class="hljs-string">"Jerôme Coupé"</span>,
  <span class="hljs-attr">authorTwitter</span>: <span class="hljs-string">"@jeromecoupe"</span>,
  <span class="hljs-attr">buildTime</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(),
  <span class="hljs-attr">languages</span>: [
    {
      <span class="hljs-attr">label</span>: <span class="hljs-string">"english"</span>,
      <span class="hljs-attr">code</span>: <span class="hljs-string">"en"</span>,
    },
    {
      <span class="hljs-attr">label</span>: <span class="hljs-string">"français"</span>,
      <span class="hljs-attr">code</span>: <span class="hljs-string">"fr"</span>,
    },
  ],
};</code></pre>
<p>À l'aide de ce tableau nous allons pouvoir parcourir les différentes langues de notre site et comparer la valeur du <code>code</code> de langue avec celui de la <code>locale</code> définie dans nos fichiers de contenu.</p>
<h2 id="ajout-des-cles-de-traduction">Ajout des clés de traduction</h2>
<p>Maintenant il nous faut créer une relation explicite entre les différentes traductions d'un même contenu. Avec un générateur de site statique qui stocke les données dans des fichiers, nous pouvons utiliser une même clé dans le front matter YAML de ces fichiers. Cette clé de traduction est une chaîne de caractère qui doit être unique pour chaque contenu.</p>
<p>Par exemple, pour connecter entre elles nos pages de contact dans différentes langues, nous pouvons utiliser :</p>
<p><code>./src/en/pages/about.njk</code></p>
<pre><code class="language-twig hljs twig"><span class="xml">---
permalink: "/</span><span class="hljs-template-variable">{{ locale }}</span><span class="xml">/about/index.html"
translationKey: "about-page"
---</span></code></pre>
<p><code>./src/fr/pages/about.njk</code></p>
<pre><code class="language-text">---
permalink: "/{{ locale }}/a-propos/index.html"
translationKey: "about-page"
---</code></pre>
<p>Nous pouvons appliquer le même principe à nos documents de collections, par exemple pour les articles de blog :</p>
<p><code>./src/en/bogposts/2019-09-12-my-awesome-blogpost.njk</code></p>
<pre><code class="language-text">---
title: "My awesome blogpost"
translationKey: "awesome-blogpost"
---</code></pre>
<p><code>./src/fr/bogposts/2019-09-12-mon-magnifique-article-de-blog.njk</code></p>
<pre><code class="language-text">---
title: "Mon magnifique article de blog"
translationKey: "awesome-blogpost"
---</code></pre>
<p>Nous disposons maintenant d'une relation explicite entre les traductions de nos contenus en différentes langues.</p>
<h2 id="coder-notre-selecteur-de-langue">Coder notre sélecteur de langue</h2>
<p>Voici le détail de ce que nous allons faire avec ce petit morceau de code:</p>
<ol>
<li>Boucler sur toutes les langues déclarées de notre site</li>
<li>Définir la page d'accueil comme valeur par défaut de <code>translatedUrl</code>, valeur qui sera remplacée lorsqu'une traduction de contenu est trouvée.</li>
<li>Dans cette boucle, parcourir tous les contenues du site. Eleventy nous fourni une méthode très pratique avec <a href="https://www.11ty.dev/docs/collections/#the-special-all-collection" target="_blank" rel="noopener noreferrer"><code>collections.all</code></a>.</li>
<li>Pour chaque contenu parcouru, vérifier si la clé <code>translationKey</code> correspond et si sa <code>locale</code> correspond au <code>code</code> de langue en cours. Si une correspondance est trouvée, alors définir <code>translatedUrl</code> avec l'URL de ce contenu.</li>
<li>Utiliser les valeurs de <code>translatedUrl</code> pour créer les liens de notre sélecteur de langue.</li>
</ol>
<pre><code class="language-twig hljs twig"><span class="hljs-comment">{# Boucler sur les langues du site #}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">for</span></span> lgg in site.languages %}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> loop.first %}</span><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"c-lggnav"</span>&gt;</span></span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span><span class="xml">

  </span><span class="hljs-comment">{# Définir translatedUrl à la page d'accueil de cette langue par défaut #}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">set</span></span> translatedUrl = "/" + lgg.code + "/" %}</span><span class="xml">

  </span><span class="hljs-comment">{# Définir la classe de la langue active #}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">set</span></span> activeClass = "is-active" if lgg.code == locale else "" %}</span><span class="xml">

  </span><span class="hljs-comment">{# Parcourir tous les contenus du site #}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">for</span></span> item in collections.all %}</span><span class="xml">

    </span><span class="hljs-comment">{# Pour chaque contenu, vérifier si
    - la valeur de sa translationKey correspond à celle du contenu en cours
    - sa locale correspond au code de langue parcourue #}</span><span class="xml">
    </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> item.data.translationKey == translationKey and item.data.locale == lgg.code %}</span><span class="xml">
      </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">set</span></span> translatedUrl = item.url %}</span><span class="xml">
    </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span><span class="xml">

  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> %}</span><span class="xml">

  <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"c-lggnav__item"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"c-lggnav__link  </span></span></span><span class="hljs-template-variable">{{ activeClass }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ translatedUrl }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ lgg.label }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>

  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> loop.last %}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span></span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> %}</span></code></pre>
<p>Et voilà, mission accomplie avec un effort minime. Ces boucles seront déclenchées pour chaque page de site. Comme Eleventy a de toute façon déjà créé <code>collections.all</code> et qu'il est assez performant en entrée-sortie, l'impact sur le temps de génération du site devrait être assez faible, même pour de gros sites.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2019/09/08/requeter-api-graphql-eleventy/</id>
    <title>Consommer l&#039;API GraphQL d&#039;un CMS headless avec Eleventy</title>
    <published>2019-09-08T21:00:48+00:00</published>
    <updated>2019-09-08T21:00:48+00:00</updated>
    <link href="https://jamstatic.fr/2019/09/08/requeter-api-graphql-eleventy/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<h2 id="les-differents-types-de-cms-headless">Les différents types de CMS headless</h2>
<p>Si vous avez besoin d'ajouter un <a href="/2017/12/15/cms-headless/">CMS headless</a> à votre site web, vous avez le choix entre deux approches : soit le contenu est versionné dans votre dépôt Git, soit il est accessible via une API tierce.</p>
<p>Dans les deux cas, les créateurs de contenu ont accès à une interface graphique, mais ce qui se passe en coulisses quand du contenu est crée, modifié ou effacé est totalement différent.</p>
<h3 id="les-cms-headless-bases-sur-git">Les CMS headless basés sur Git</h3>
<p>Les CMS basés sur Git comme <a href="https://www.netlifycms.org/" target="_blank" rel="noopener noreferrer">Netlify CMS</a> ou <a href="https://forestry.io" target="_blank" rel="noopener noreferrer">Forestry</a> vont sauvegarder votre contenu dans des fichiers texte et les sauvegarder dans votre dépôt Git. C'est l'approche que je préfère pour les raisons suivantes :</p>
<ul>
<li>code et contenu partagent le même workflow</li>
<li>le contenu est versionné par Git avec un historique clair</li>
<li>le contenu stocké sous forme de fichiers texte (markdown, YAML, JSON, etc.) est extrêmement portable</li>
</ul>
<h3 id="les-apis-de-cms-headless">Les APIs de CMS headless</h3>
<p>Les CMS basés sur des APIs comme <a href="https://www.contentful.com/" target="_blank" rel="noopener noreferrer">Contentful</a> ou <a href="https://www.datocms.com/" target="_blank" rel="noopener noreferrer">DatoCMS</a> vont sauvegarder vos contenus dans le Cloud et vont le rendre accessible via une API. GraphQL est en passe de devenir une façon populaire d'interroger et de tirer parti de ces APIs. Selon moi, cette approche présente de l'intérêt lorsque :</p>
<ul>
<li>le contenu est destiné à être publié sur différentes plateformes</li>
<li>vos modèles de données sont hautement relationnels</li>
</ul>
<h2 id="structure-du-projet">Structure du projet</h2>
<p><a href="https://www.11ty.dev/" target="_blank" rel="noopener noreferrer">Eleventy</a>(11ty), qui est en passe de devenir mon générateur de site statique de prédilection, est à même de pouvoir travailler avec les deux approches de façon assez élégante et avec un minimum d'efforts.</p>
<p>Qui aurait pensé faire une requète sur une API GraphQL et utiliser les données retournées pour générer des pages statiques serait aussi simple ?</p>
<p><a href="https://www.datocms.com/" target="_blank" rel="noopener noreferrer">DatoCMS</a> est un CMS headless que je recommande à mes clients. Son prix est raisonnable, il propose des options suffisantes et reste très flexible, il gère élégamment l'internationalisation, et propose une bonne expérience utilisateur et de développement.</p>
<p>Si cet article est écrit pour DatoCMS, cette méthodologie est appliquable à tout CMS headless offrant une API GraphQL.</p>
<p>Voici l'arborescence de fichiers classique avec laquelle nous allons travailler dans Eleventy :</p>
<pre><code class="language-text">+-- src
  +-- _data
    +-- blogposts.js
  +-- _includes
    +-- layouts
      +-- base.njk
  +-- blogposts
    +-- entry.njk
    +-- list.njk
+-- .eleventy.js
+-- .env
+-- .env.example
+-- package-lock.json
+-- package.json</code></pre>
<h2 id="configuration-de-datocms">Configuration de DatoCMS</h2>
<p>Après avoir crée notre compte, nous avons besoin d'un modèle de données et de quelques entrées dans DatoCMS. J'ai crée un modèle de données nommé <code>blogposts</code> avec une série de champs et quelques entrées.</p>
<p>Nous pouvons alors utiliser notre <a href="https://www.datocms.com/docs/content-delivery-api/authentication" target="_blank" rel="noopener noreferrer">token d'API</a> pour nous connecter à <a href="https://cda-explorer.datocms.com/" target="_blank" rel="noopener noreferrer">l'explorateur de l'API GraphQL</a> afin de visualiser les requêtes et les options disponibles, ainsi que le JSON qui nous est retourné.</p>
<p>Une fois encore, la plupart des CMS headless avec une API GraphQL offrent cette fonctionnalité d'une manière ou d'une autre.</p>
<h2 id="configuration-d-eleventy">Configuration d'Eleventy</h2>
<p>Nous allons devoir nous authentifier au serveur GraphQL de DatoCMS avec notre <a href="https://www.datocms.com/docs/content-delivery-api/authentication" target="_blank" rel="noopener noreferrer">token d'API</a>. Nous pouvons utiliser <a href="https://www.npmjs.com/package/dotenv" target="_blank" rel="noopener noreferrer"><code>dotenv</code></a> pour le stocker dans un fichier <code>.env</code> que nous ajouterons à notre fichier <code>.gitignore</code> pour éviter qu'il ne soit stocké dans notre dépôt Git. Après avoir installé le paquet, nous créons le fichier <code>.env</code> à la racine du projet et y ajoutons le token de l'API de DatoCMS :</p>
<pre><code class="language-text">DATOCMS_TOKEN="fak3t0k3n3c52750d04b3d92383b1d"</code></pre>
<p>Ensuite, il nous faut ajouter la ligne suivant au début de notre fichier de configuration <code>.eleventy.js</code> :</p>
<pre><code class="language-js hljs javascript"><span class="hljs-built_in">require</span>(<span class="hljs-string">"dotenv"</span>).config();</code></pre>
<p>Étant donné que ce fichier est traité très tôt par <a href="https://www.11ty.dev/" target="_blank" rel="noopener noreferrer">Eleventy</a>, notre token sera accessible dans tous nos templates via <code>process.env.DATOCMS_TOKEN</code>.</p>
<h2 id="utilisation-des-fichiers-de-donnees-javascript">Utilisation des fichiers de données JavaScript</h2>
<p>Au lieu d'aller piocher nos données à l'aide de collections et de fichiers Markdown avec du front matter en YAML, nous allons utiliser <a href="https://www.11ty.dev/docs/data-js/" target="_blank" rel="noopener noreferrer">les fichiers de données JavaScript d'Eleventy</a>. Nous utiliserons le fichier <code>src/_data/blogposts.js</code> pour nous connecter à <a href="https://www.datocms.com/docs/content-delivery-api/" target="_blank" rel="noopener noreferrer">la content delivery API</a> de DatoCMS lors de la génération du site, afin d'exporter un fichier JSON contenant tous les articles de blog avec les champs dont nous avons besoin. Le contenu de ce fichier sera accessible dans nos templates via l'objet <code>blogposts</code>.</p>
<p>Eleventy sera alors en mesure d'utiliser ce fichier JSON pour générer les pages de détail et d'index de notre blog.</p>
<p>Çi-dessous, le fichier complet nécessaire pour récupérer tous nos articles de blog, qui se base sur <a href="https://www.datocms.com/docs/content-delivery-api/first-request#vanilla-js-exampl" target="_blank" rel="noopener noreferrer">le code de la requête en Vanilla JS</a> donné en exemple dans la documentation de DatoCMS.</p>
<p>Afin de miniser les dépendances, j'ai privilégié l'utilisation de <code>node-fetch</code> à celle d'Appolo et consorts.</p>
<p>Par défaut l'API GraphQL de DatoCMS limite à 100 le nombre d'enregistrements retournés par requête (merci à <a href="https://twitter.com/danfascia" target="_blank" rel="noopener noreferrer">Dan Fascia</a> pour sa remarque). Si notre blog comporte plus de 100 entrées, il va donc nous falloir faire plusieurs requêtes et concaténer les résultats pour récupérer l'intégralité de nos articles de blog.</p>
<pre><code class="language-js hljs javascript"><span class="hljs-comment">// paquets requis</span>
<span class="hljs-keyword">const</span> fetch = <span class="hljs-built_in">require</span>(<span class="hljs-string">"node-fetch"</span>);

<span class="hljs-comment">// token de DatoCMS</span>
<span class="hljs-keyword">const</span> token = process.env.DATOCMS_TOKEN;

<span class="hljs-comment">// récupération des articles de blog</span>
<span class="hljs-comment">// voir https://www.datocms.com/docs/content-delivery-api/first-request#vanilla-js-example</span>
<span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getAllBlogposts</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-comment">// nombre maximal d'enregistrements retournés par requête</span>
  <span class="hljs-keyword">const</span> recordsPerQuery = <span class="hljs-number">100</span>;

  <span class="hljs-comment">// nombre d'enregistrements à ignorer (démarre à 0)</span>
  <span class="hljs-keyword">let</span> recordsToSkip = <span class="hljs-number">0</span>;

  <span class="hljs-comment">// Devons nous faire une nouvelle requête ?</span>
  <span class="hljs-keyword">let</span> makeNewQuery = <span class="hljs-literal">true</span>;

  <span class="hljs-comment">// Tableau pour stocker les articles de blog</span>
  <span class="hljs-keyword">let</span> blogposts = [];

  <span class="hljs-comment">// Effectuer des requpêtes jusqu'à ce que makeNewQuery passe à faux</span>
  <span class="hljs-keyword">while</span> (makeNewQuery) {
    <span class="hljs-keyword">try</span> {
      <span class="hljs-comment">// Initialisation du téléchargement</span>
      <span class="hljs-keyword">const</span> dato = <span class="hljs-keyword">await</span> fetch(<span class="hljs-string">"https://graphql.datocms.com/"</span>, {
        <span class="hljs-attr">method</span>: <span class="hljs-string">"POST"</span>,
        <span class="hljs-attr">headers</span>: {
          <span class="hljs-string">"Content-Type"</span>: <span class="hljs-string">"application/json"</span>,
          <span class="hljs-attr">Accept</span>: <span class="hljs-string">"application/json"</span>,
          <span class="hljs-attr">Authorization</span>: <span class="hljs-string">`Bearer <span class="hljs-subst">${token}</span>`</span>
        },
        <span class="hljs-attr">body</span>: <span class="hljs-built_in">JSON</span>.stringify({
          <span class="hljs-attr">query</span>: <span class="hljs-string">`{
            allBlogposts(
              first: <span class="hljs-subst">${recordsPerQuery}</span>,
              skip: <span class="hljs-subst">${recordsToSkip}</span>,
              orderBy: _createdAt_DESC,
              filter: {
                _status: {eq: published}
              }
            )
            {
              id
              title
              slug
              intro
              body(markdown: true)
              _createdAt
              image {
                url
                alt
              }
              relatedBlogs {
                id
              }
            }
          }`</span>
        })
      });

      <span class="hljs-comment">// Enregistrment de la réponse JSON lorsque la promesse est résolue</span>
      <span class="hljs-keyword">const</span> response = <span class="hljs-keyword">await</span> dato.json();

      <span class="hljs-comment">// Gestion des erreurs DatoCMS</span>
      <span class="hljs-keyword">if</span> (response.errors) {
        <span class="hljs-keyword">let</span> errors = response.errors;
        errors.map(<span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
          <span class="hljs-built_in">console</span>.log(error.message);
        });
        <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(<span class="hljs-string">"Aborting: DatoCMS errors"</span>);
      }

      <span class="hljs-comment">// mise à jour du tableau des articles avec les données retournées par la réponse en JSON</span>
      blogposts = blogposts.concat(response.data.allBlogposts);

      <span class="hljs-comment">// itération des valeurs pour la prochaine requête</span>
      recordsToSkip += recordsPerQuery;

      <span class="hljs-comment">// Vérification du nombre d'enregistrements retourné</span>
      <span class="hljs-comment">// S'il y en a moins de 100, on arrête de faire des requêtes</span>
      <span class="hljs-keyword">if</span> (response.data.allBlogposts.length &lt; recordsPerQuery) {
        makeNewQuery = <span class="hljs-literal">false</span>;
      }
    } <span class="hljs-keyword">catch</span> (error) {
      <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(error);
    }
  }

  <span class="hljs-comment">// mise en forme de l'objet blogposts</span>
  <span class="hljs-keyword">const</span> blogpostsFormatted = blogposts.map(<span class="hljs-function">(<span class="hljs-params">item</span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> {
      <span class="hljs-attr">id</span>: item.id,
      <span class="hljs-attr">date</span>: item._createdAt,
      <span class="hljs-attr">title</span>: item.title,
      <span class="hljs-attr">slug</span>: item.slug,
      <span class="hljs-attr">image</span>: item.image.url,
      <span class="hljs-attr">imageAlt</span>: item.image.alt,
      <span class="hljs-attr">summary</span>: item.intro,
      <span class="hljs-attr">body</span>: item.body,
      <span class="hljs-attr">relatedBlogs</span>: item.relatedBlogs
    };
  });

  <span class="hljs-comment">// retour de l'objet formatté</span>
  <span class="hljs-keyword">return</span> blogpostsFormatted;
}

<span class="hljs-comment">// export pour 11ty</span>
<span class="hljs-built_in">module</span>.exports = getAllBlogposts;</code></pre>
<p>Plutôt que d'utiliser directement les données de la réponse JSON, je la mets généralement en forme pour améliorer la maintenabilité future de mes templates. Si quelque chose change au niveau du CMS, je sais que j'aurais seulement à mettre à jour les fichiers de données, et non tous les templates qui les utilisent.</p>
<h3 id="les-images-et-les-vignettes">Les images et les vignettes</h3>
<p>Les fichiers et les images uploadés dans DatoCMS sont stockés sur <a href="https://www.imgix.com/" target="_blank" rel="noopener noreferrer">Imgix</a>, nous pouvons donc ajouter <a href="https://docs.imgix.com/apis/url" target="_blank" rel="noopener noreferrer">des paramètres à chaque URL d'images</a> pour les redimensionner, les retailler et les manipuler de diverses manières. Ces transformations se vont à la volée et sont ensuite mises en cache sur le CDN pour les utilisations ultérieures.</p>
<p>La majorité des CMS headless offrent des fonctionnalités similaires, soit en intégrant des services tiers comme <a href="https://cloudinary.com/" target="_blank" rel="noopener noreferrer">Cloudinary</a> ou <a href="https://uploadcare.com/" target="_blank" rel="noopener noreferrer">Uploadcare</a>, soit en proposant leur propre API pour les images.</p>
<h3 id="champs-relationnels">Champs relationnels</h3>
<p>L'API GraphQL de DatoCMS gère très bien les structures de données hautement imbriquées et vous permettra de récupérer les données voulues dans vos champs relationnels. Toutefois, j'adopte généralement une approche plus simple :</p>
<ul>
<li>Je crée un gros fichier JSON pour chaque type de données (articles, projets, évènements, etc.), chaque élément possède un identifiant unique.</li>
<li>Pour les champs relationnels, je récupère seulement l'identifiants des élements relatifs.</li>
<li>J'utilise des boucles imbriquées dans les templates pour récupérer les données à l'aide des identifiants.</li>
</ul>
<p>Comme les générateurs de site statique performants comme <a href="https://gohugo.io/" target="_blank" rel="noopener noreferrer">Hugo</a> ou <a href="https://www.11ty.dev" target="_blank" rel="noopener noreferrer">Eleventy</a> n'ont pas une empreinte mémoire importante lors du parcours de boucles dans les templates, je n'ai jamais fait face à des soucis de performance avec cette approche. C'est à la fois très flexible et vos requêtes s'en retrouvent simplifiées.</p>
<h2 id="generer-une-liste-paginee-des-articles-de-blog-avec-11ty">Générer une liste paginée des articles de blog avec 11ty</h2>
<p>Grâce à <a href="https://www.11ty.dev/docs/pagination/" target="_blank" rel="noopener noreferrer">la fonctionnalité pagination d'Eleventy</a>, nous pouvons parcourir notre fichier JSON (accessible via l'objet <code>blogposts</code>) et générer une liste paginée des articles de blog. Dans cet exemple, nous allons générer une liste de pages contenant chacune 12 éléments, grâce à la clé <code>size</code>.</p>
<p>Voici le code complet du fichier <code>src/blogposts/list.njk</code> :</p>
<pre><code class="language-twig hljs twig"><span class="xml">---
pagination:
  data: blogposts
  size: 12
permalink: blog</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> pagination.pageNumber &gt; 0 %}</span><span class="xml">/page</span><span class="hljs-template-variable">{{ pagination.pageNumber + 1}}</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span><span class="xml">/index.html
---

</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">extends</span></span> "layouts/base.njk" %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">set</span></span> htmlTitle = item.title %}</span><span class="xml">

</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">block</span></span> content %}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Blogposts<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>

  </span><span class="hljs-comment">{# boucler sur les éléments paginés #}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">for</span></span> item in pagination.items %}</span><span class="xml">
    </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> loop.first %}</span><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span></span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span><span class="xml">
      <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ item.image }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">?fit=crop<span class="hljs-symbol">&amp;amp;</span>w=200<span class="hljs-symbol">&amp;amp;</span>h=200"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ item.imageAlt }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/blog/</span></span></span><span class="hljs-template-variable">{{ item.slug }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ item.title }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">time</span> <span class="hljs-attr">datetime</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ item.<span class="hljs-name">date</span> | <span class="hljs-name">date</span><span class="hljs-params">('Y-M-DD')</span> }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ item.<span class="hljs-name">date</span>|<span class="hljs-keyword">date</span>("MMMM Do, Y") }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">time</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span></span><span class="hljs-template-variable">{{ item.summary }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> loop.last %}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span></span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> %}</span><span class="xml">

  </span><span class="hljs-comment">{# pagination #}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> pagination.hrefs | length &gt; 0 %}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
    </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> pagination.previousPageHref %}</span><span class="xml">
      <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ pagination.previousPageHref }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span>Previous page<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span><span class="xml">
    </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> pagination.nextPageHref %}</span><span class="xml">
      <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ pagination.nextPageHref }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span>Next page<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span><span class="xml">
  <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span><span class="xml">

</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endblock</span></span> %}</span></code></pre>
<h2 id="generer-les-pages-individuelles-pour-les-articles-dans-11ty">Générer les pages individuelles pour les articles dans 11ty</h2>
<p>Nous pouvons nous reposer sur la même fonctionnalité pour générer également toutes les pages individuelles. La seule astuce ici est de spécifier le nombre d'élements de chaque page comme égal à 1 et de définir les permaliens de façon dynamique. Voici le code complet pour <code>src/blogposts/entry.njk</code>:</p>
<pre><code class="language-twig hljs twig"><span class="xml">---
pagination:
  data: blogposts
  size: 1
  alias: blogpost
permalink: blog/</span><span class="hljs-template-variable">{{ blogpost.slug }}</span><span class="xml">/index.html
---
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">extends</span></span> "layouts/base.njk" %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">set</span></span> htmlTitle = blogpost.title %}</span><span class="xml">

</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">block</span></span> content %}</span><span class="xml">
  </span><span class="hljs-comment">{# blogpost #}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ blogpost.image }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">?fit=crop<span class="hljs-symbol">&amp;amp;</span>w=1024<span class="hljs-symbol">&amp;amp;</span>h=576"</span>
       <span class="hljs-attr">srcset</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ blogpost.image }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">?fit=crop<span class="hljs-symbol">&amp;amp;</span>w=600<span class="hljs-symbol">&amp;amp;</span>h=338 600w,
               </span></span></span><span class="hljs-template-variable">{{ blogpost.image }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">?fit=crop<span class="hljs-symbol">&amp;amp;</span>w=800<span class="hljs-symbol">&amp;amp;</span>h=450 800w,
               </span></span></span><span class="hljs-template-variable">{{ blogpost.image }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">?fit=crop<span class="hljs-symbol">&amp;amp;</span>w=1024<span class="hljs-symbol">&amp;amp;</span>h=576 1024w"</span>
       <span class="hljs-attr">sizes</span>=<span class="hljs-string">"100vw"</span>
       <span class="hljs-attr">class</span>=<span class="hljs-string">"u-fluidimg"</span>
       <span class="hljs-attr">alt</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ blogpost.imageAlt }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span></span><span class="hljs-template-variable">{{ blogpost.title }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">time</span> <span class="hljs-attr">datetime</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ blogpost.<span class="hljs-name">date</span> | <span class="hljs-name">date</span><span class="hljs-params">('Y-M-DD')</span> }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ blogpost.<span class="hljs-name">date</span>|<span class="hljs-keyword">date</span>("MMMM Do, Y") }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">time</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span></span><span class="hljs-template-variable">{{ blogpost.intro }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  </span><span class="hljs-template-variable">{{ blogpost.body | safe }}</span><span class="xml">

  </span><span class="hljs-comment">{# Articles relatifs #}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> blogpost.relatedBlogs|<span class="hljs-keyword">length</span> %}</span><span class="xml">
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Vous pourriez aussi aimer<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
    </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">for</span></span> item in blogpost.relatedBlogs %}</span><span class="xml">
      </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">for</span></span> post in blogposts %}</span><span class="xml">
        </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> post.id == item.id %}</span><span class="xml">
          <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/blog/</span></span></span><span class="hljs-template-variable">{{ post.slug }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ post.title }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
        </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span><span class="xml">
      </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> %}</span><span class="xml">
    </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> %}</span><span class="xml">
    <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endblock</span></span> %}</span></code></pre>
<h2 id="declencher-automatiquement-les-builds">Déclencher automatiquement les builds</h2>
<p>La plupart des CMS headless fournissent des webhooks qui vont envoyer une requête à une URL lorsque les données sont modifiées. Si vous hébergez votre site chez Netlify (vous devriez, c'est un super service), il suffit de quelques clics pour <a href="https://www.netlify.com/docs/webhooks/" target="_blank" rel="noopener noreferrer">créer un hook entrant</a> qui déclenchera la génération de votre site. à chaque reception d'une requête POST.</p>
<p>DatoCMS propose le déploiement en 1 clic grâce à son intégration avec Netlify. Il vous suffit de l'activer et voilà, votre blog sera généré à chaque fois que les données seront mises à jour.</p>
<p>Nous disposons maintenant d'un blog qui combine la puissance d'une base de données relationnelle avec la rapidité et la stabilité d'un site statique, hébergé sur CDN.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2019/09/07/site-multilingue-avec-eleventy/</id>
    <title>Un site multilingue avec Eleventy</title>
    <published>2019-09-07T12:27:07+00:00</published>
    <updated>2019-09-07T12:27:07+00:00</updated>
    <link href="https://jamstatic.fr/2019/09/07/site-multilingue-avec-eleventy/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Eleventy n'offre pas de fonctionnalités natives liées au multilinguisme et à la localisation, cela ne nous empêche aucunement de développer une bonne gestion du multilingue à l'aide de fichiers de données globales, de collections en utilisant Nunjucks comme langage de templating.</p></aside>
<p>Afin d'illustrer notre propos, nous allons développer un blog multilingue tout ce qu'il y a de plus classique.</p>
<p>Voici l'arborescence de fichiers avec laquelle nous allons travailler. C'est une architecture <a href="/categories/eleventy">Eleventy</a> standard, dont les principes et les techniques peuvent être appliqués à des projets plus importants.</p>
<pre><code class="language-text">+-- src
  +-- _data
    +-- site.js
    +-- footer.js
    +-- header.js
  +-- _includes
    +-- layouts
      +-- base.njk
    +-- partials
      +-- header.njk
      +-- footer.njk
  +-- en
    +-- pages
      +-- index.html
      +-- blog.html
      +-- contact.html
    +-- posts
      +-- yyyy-mm-dd-some-blogpost.md
      +-- posts.json
    +-- en.json
  +-- fr
    +-- pages
      +-- index.html
      +-- blog.html
      +-- contact.html
    +-- posts
      +-- yyyy-mm-dd-some-blogpost.md
      +-- posts.json
    +-- fr.json
+-- .eleventy.js</code></pre>
<h2 id="definition-des-locales">Définition des locales</h2>
<p>La première consiste à créer nos locales à l'aide <a href="https://www.11ty.dev/docs/data-template-dir/" target="_blank" rel="noopener noreferrer">des fichiers de données pour les répertoires</a>.</p>
<p>Pour cela il nous suffit d'ajouter les fichiers <code>en.json</code> and <code>fr.json</code> dans nos répertoires de langues. Dans chacun d'entre eux, nous définissons une clé <code>locale</code>. Elle va permettre d'accéder aux valeurs correspondantes dans tous les fichiers de layout présents dans les sous-répertoires d'un dossier de langue.</p>
<p>Par exemple, notre fichier <code>fr.json</code> contient :</p>
<pre><code class="language-js hljs javascript">{
  <span class="hljs-string">"locale"</span>: <span class="hljs-string">"fr"</span>
}</code></pre>
<p><code>{{ locale }}</code> va donc maintenant retourner "fr" ou "en" pour chacun de nos fichiers de layout, en fonction de sa position dans notre arborescence de fichiers.</p>
<h2 id="filtre-de-localisation-de-date">Filtre de localisation de date</h2>
<p>Nunjucks ne possède pas de filtre pour les dates. Nous pouvons en créer un à l'aide de <code>moment.js</code> et lui passer la valeur de notre <code>locale</code> pour qu'il localise les dates pour nous, ce qui est toujours une partie importante des projets multilingues.</p>
<p>Pour ce faire, nous allons insérer le code suivant dans notre fichier de configueration <code>eleventy.js</code> :</p>
<pre><code class="language-js hljs javascript"><span class="hljs-comment">// date filter (localized)</span>
eleventyConfig.addNunjucksFilter(<span class="hljs-string">"date"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">date, format, locale</span>) </span>{
  locale = locale ? locale : <span class="hljs-string">"en"</span>;
  moment.locale(locale);
  <span class="hljs-keyword">return</span> moment(date).format(format);
});</code></pre>
<p>À présent, nous pouvons utiliser ce filtre dans nos layouts et lui passer le paramètre <code>locale</code>. Notez bien que comme nous avons défini la <code>locale</code> par défaut comme <code>en</code>, nous pouvons omettre de la préciser pour les dates purement numériques. Un petit exemple :</p>
<pre><code class="language-twig hljs twig"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">time</span> <span class="hljs-attr">datetime</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ post.<span class="hljs-name">date</span> | <span class="hljs-name">date</span><span class="hljs-params">('Y-MM-DD')</span> }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ post.item|<span class="hljs-keyword">date</span>("DD MMMM Y", locale) }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">time</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span></code></pre>
<p>Maintenant que nos dates sont automatiquement localisées, passons aux collections.</p>
<h2 id="localisation-des-collections">Localisation des collections</h2>
<p>Nous allons pouvoir tirer parti de notre arborescence de fichiers pour créer des collections dans Eleventy. Le plus simple est encore de créer une collection par langue. Nous utilisons pour cela la fonction <a href="&lt;https://www.11ty.dev/docs/collections/#getfilteredbyglob(-glob-)&gt;"><code>getFilteredByGlob</code></a> dans notre fichier <code>eleventy.js</code>.</p>
<pre><code class="language-js hljs javascript"><span class="hljs-built_in">module</span>.exports = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">eleventyConfig</span>) </span>{
  eleventyConfig.addCollection(<span class="hljs-string">"posts_en"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">collection</span>) </span>{
    <span class="hljs-keyword">return</span> collection.getFilteredByGlob(<span class="hljs-string">"./src/en/posts/*.md"</span>);
  });
};

<span class="hljs-built_in">module</span>.exports = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">eleventyConfig</span>) </span>{
  eleventyConfig.addCollection(<span class="hljs-string">"posts_fr"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">collection</span>) </span>{
    <span class="hljs-keyword">return</span> collection.getFilteredByGlob(<span class="hljs-string">"./src/fr/posts/*.md"</span>);
  });
};</code></pre>
<p>Comme nos fichiers de contenu Markdown se trouvent dans des sous-répertoires de nos dossiers de langues, la variable <code>locale</code> est accessible. Nous pouvons par exemple l'utiliser pour créer les permaliens de tous nos articles.</p>
<p>Plutôt que d'ajouter une variable <code>permalink</code> dans le front matter de chaque fichier, nous pouvons créer un fichier de données <code>posts.json</code> dans chacun de nos dossiers <code>posts</code> avec le contenu suivant:</p>
<pre><code class="language-js hljs javascript">{
  <span class="hljs-attr">permalink</span>: <span class="hljs-string">"/{{ locale }}/blog/{{ page.fileslug }}/index.html"</span>;
}</code></pre>
<p>Nous avons ainsi localisé toutes les pages de détail de tous nos articles, il nous reste encore à boucler sur nos collections dans les différentes pages <code>blog.njk</code> de nos différentes langues.</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">for</span></span> post in collections.posts_en | reverse %}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> loop.first %}</span><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span></span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span><span class="xml">
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">article</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">time</span> <span class="hljs-attr">datetime</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ post.<span class="hljs-name">date</span> | <span class="hljs-name">date</span><span class="hljs-params">('Y-MM-DD')</span> }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ post.<span class="hljs-name">date</span> | <span class="hljs-name">date</span><span class="hljs-params">("DD MMMM[,] Y", locale)</span> }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">time</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ post.url }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ post.data.title }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>

    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>

  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> loop.last %}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span></span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> %}</span></code></pre>
<p>Notez que nous pourrions aussi aussi utiliser pour nos collections notre variable <code>locale</code>. Il faudrait pour cela utiliser à la place une notation entre crochets pour concaténer les chaînes de caractères.</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">set</span></span> posts = collections["posts_" + locale] %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">for</span></span> post in posts %}</span><span class="xml">
  </span><span class="hljs-comment">{# loop content #}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> %}</span></code></pre>
<h2 id="localisation-des-layouts-et-des-fichiers-partiels">Localisation des layouts et des fichiers partiels</h2>
<p>Alors que la duplication de nos pages et de nos articles est à priori logique, nous ne voulons pas faire de même pour nos layouts et nos fichiers partiels.</p>
<p>Fort heureusement, nous pouvons éviter cela en traduisant simplement certaines chaînes de caractères. Pour ce faire, nous devons créer <a href="https://www.11ty.dev/docs/data-global/" target="_blank" rel="noopener noreferrer">des fichiers de données génériques</a> qui contiendront nos traductions sous forme de paire clés/valeurs. Nous pourrons ensuite faire référence dynamiquement à ces clés dans nos fichiers partiels et de layouts à l'aide de notre chère variable <code>locale</code>.</p>
<h3 id="layouts">Layouts</h3>
<p>Commençons par un exemple dans un fichier de layout.</p>
<p>Le fichier <code>./src/_data/site.js</code> va rendre accessibles des variables via l'objet <code>site</code>.</p>
<pre><code class="language-js hljs javascript"><span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">buildTime</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(),
  <span class="hljs-attr">baseUrl</span>: <span class="hljs-string">"https://www.mysite.com"</span>,
  <span class="hljs-attr">name</span>: <span class="hljs-string">"mySite"</span>,
  <span class="hljs-attr">twitter</span>: <span class="hljs-string">"@handle"</span>,
  <span class="hljs-attr">en</span>: {
    <span class="hljs-attr">metaTitle</span>: <span class="hljs-string">"Title in english"</span>,
    <span class="hljs-attr">metaDescription</span>: <span class="hljs-string">"Description in english"</span>,
  },
  <span class="hljs-attr">fr</span>: {
    <span class="hljs-attr">metaTitle</span>: <span class="hljs-string">"Titre en français"</span>,
    <span class="hljs-attr">metaDescription</span>: <span class="hljs-string">"Description en français"</span>,
  },
};</code></pre>
<p>Nous pouvons utiliser ces variables dans notre fichier <code>./src/fr/pages/index.njk</code>. Dans notre exemple, nous allons d'abord assigner les valeurs à des variables Nunjucks plutôt que de les utiliser directement, car ces valeurs nous pourrions avoir besoin de les surcharger dans d'autres pages. La même logique peut être appliquée pour les modèles de page spécifiques aux articles.</p>
<pre><code class="language-twig hljs twig"><span class="xml">---
permalink: /</span><span class="hljs-template-variable">{{ locale }}</span><span class="xml">/index.html
---

</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">extends</span></span> "layouts/base.njk" %}</span><span class="xml">

</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">set</span></span> metaTitle = site[locale].metaTitle %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">set</span></span> metaDescription = site[locale].metaDescription %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">set</span></span> metaImage = site[locale].metaImage %}</span><span class="xml">

</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">block</span></span> content %}</span><span class="xml">
  </span><span class="hljs-comment">{# page content #}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endblock</span></span> %}</span></code></pre>
<p>Puisque ce layout étend le fichier <code>./src/_includes/layouts/base.njk</code>, les variables Nunjucks déclarées dans le gabarit enfant ainsi que les variables globales d'Eleventy sont également accessibles dans ce fichier.</p>
<pre><code class="language-twig hljs twig"><span class="xml"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ locale }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">http-equiv</span>=<span class="hljs-string">"X-UA-Compatible"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"ie=edge"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span></span><span class="hljs-template-variable">{{ metaTitle }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/css/main.min.css"</span>&gt;</span>

  <span class="hljs-comment">&lt;!-- open graph --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:type"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"article"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:title"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ metaTitle }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:image"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ metaImage }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:site_name"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ site.name }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">property</span>=<span class="hljs-string">"og:description"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ metaDescription }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span>

  <span class="hljs-comment">&lt;!-- twitter --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"twitter:card"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"summary"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"twitter:site"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ site.twitter }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"twitter:title"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ metaTitle }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"twitter:description"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ metaDescription }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"twitter:image"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ metaImage }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">include</span></span> "partials/siteheader.njk" %}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">block</span></span> content %}</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endblock</span></span> %}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">include</span></span> "partials/sitefooter.njk" %}</span><span class="xml">
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span></code></pre>
<h3 id="fichiers-partiels">Fichiers partiels</h3>
<p>La traduction de fichiers partiels comme <code>./src/_includes/partials/footer.njk</code> est basée sur le même principe.</p>
<p>Premièrement, nous créons un fichier <code>./data/footer.js</code> et nous utilisons nos locales comme clés.</p>
<pre><code class="language-js hljs javascript"><span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">mapUrl</span>: <span class="hljs-string">"https://goo.gl/maps/3YTkhCgfEgj1PRAd7"</span>,
  <span class="hljs-attr">fr</span>: {
    <span class="hljs-attr">addressTitle</span>: <span class="hljs-string">"Adresse"</span>,
    <span class="hljs-attr">addressStreet</span>: <span class="hljs-string">"Rue du marché"</span>,
    <span class="hljs-attr">addressNumber</span>: <span class="hljs-string">"42"</span>,
    <span class="hljs-attr">addressPostcode</span>: <span class="hljs-string">"1000"</span>,
    <span class="hljs-attr">addressCity</span>: <span class="hljs-string">"Bruxelles"</span>,
    <span class="hljs-attr">directionsLabel</span>: <span class="hljs-string">"Itinéraire"</span>,
  },
  <span class="hljs-attr">en</span>: {
    <span class="hljs-attr">addressTitle</span>: <span class="hljs-string">"Address"</span>,
    <span class="hljs-attr">addressStreet</span>: <span class="hljs-string">"Market street"</span>,
    <span class="hljs-attr">addressNumber</span>: <span class="hljs-string">"42"</span>,
    <span class="hljs-attr">addressPostcode</span>: <span class="hljs-string">"1000"</span>,
    <span class="hljs-attr">addressCity</span>: <span class="hljs-string">"Brussels"</span>,
    <span class="hljs-attr">directionsLabel</span>: <span class="hljs-string">"Directions"</span>,
  },
};</code></pre>
<p>Ensuite dans le fichier <code>./src/_includes/partials/footer.njk</code>, nous nous basons sur la valeur de notre variable <code>locale</code> pour accéder à ces clés à l'aide de la notation entre crochets.</p>
<pre><code class="language-twig hljs twig"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">footer</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span></span><span class="hljs-template-variable">{{ footer[locale].addressTitle }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
    </span><span class="hljs-template-variable">{{ footer[locale].addressStreet }}</span><span class="xml">, </span><span class="hljs-template-variable">{{ footer[locale].addressNumber }}</span><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">br</span>&gt;</span>
    </span><span class="hljs-template-variable">{{ footer[locale].addressPostcode }}</span><span class="xml">, </span><span class="hljs-template-variable">{{ footer[locale].addressCity }}</span><span class="xml">
  <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ footer.mapUrl }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ footer[locale].directionsLabel }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">footer</span>&gt;</span></span></code></pre>
<p>Et voilà, nous avons maintenant un pied de page traduit en plusieurs langues.</p>
<h2 id="flexible-par-nature">Flexible par nature</h2>
<p>Les générateurs de site statiques sont très flexibles quant aux structures de données que vous pouvez créer. Eleventy est l'un des générateurs les plus flexibles qu'il m'ait été donné d'utiliser, ce qui en fait un bon candidat pour créer une structure de données multilingue, adaptable à tout type de projet.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2019/09/07/de-jekyll-a-eleventy/</id>
    <title>De Jekyll à Eleventy</title>
    <published>2019-09-07T08:59:06+00:00</published>
    <updated>2019-09-07T10:24:06+00:00</updated>
    <link href="https://jamstatic.fr/2019/09/07/de-jekyll-a-eleventy/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Eleventy n'en finit pas de faire des émules, il séduit par sa simplicité et sa flexibilité, <a href="https://www.webstoemp.com" target="_blank" rel="noopener noreferrer">Jérôme Coupé</a> a sauté le pas à son tour et il est très satisfait de son choix.</p></aside>
<p>Jekyll est un générateur que je continue d'apprécier, d'utiliser et de suivre, néanmoins quand j'ai enfin eu le temps de mettre à jour <a href="https://www.webstoemp.com" target="_blank" rel="noopener noreferrer">mon site</a>, j'ai choisi de partir sur Eleventy.</p>
<h2 id="choisir-un-generateur-de-site">Choisir un générateur de site</h2>
<p>Les générateurs de site statiques gagnent toujours plus en popularité, grâce à l'omniprésence des APIs, aux processus de développement basés sur Git, à la puissance des frameworks JavaScript, aux CMS headless et aux couches de données unifiées fournies par GraphQL. Ils sont devenus un choix raisonnable pour tous types de sites web.</p>
<p>Mon site tournait précédemment sous Jekyll, que j'apprécie pour sa facilité d'utilisation et sa flexibilité. Toutefois, il était devenu plus lent que d'autres générateurs plus récents et me forçait à maintenir un environnement Ruby à jour. J'ai testé plusieurs outils, avant de finalement restreindre ma liste de choix à <a href="https://gohugo.io/" target="_blank" rel="noopener noreferrer">Hugo</a> et <a href="https://www.11ty.dev/" target="_blank" rel="noopener noreferrer">Eleventy</a>.</p>
<h3 id="hugo">Hugo</h3>
<p>Écrit en Go, Hugo est extrèmement rapide. il est également disponible sous forme de fichier binaire, ce qui ne vous force pas à maintenir un environnement Go. A titre d'expérience, j'ai développé la version 2 de mon site avec Hugo et l'exercice a été concluant.</p>
<p>Cependant, les inconvénients d'Hugo sont pour moi la <a href="https://gohugo.io/templates/introduction/" target="_blank" rel="noopener noreferrer">syntaxe de Go HTML template</a> qui demande pas mal de pratique pour être apprivoisée, et le fait qu'Hugo soit une solution "tout en un". Cela peut se révéler utile pour les gros projets, mais réduit les possibilités de l'étendre facilement.</p>
<h3 id="eleventy">Eleventy</h3>
<p>Ce qui nous amène à Eleventy. Eleventy est écrit en Node, vous avez donc accès à tout l'écosystème de NPM pour l'étendre, il est facile d'utilisation, et il est bien plus rapide que Jekyll (sans cependant atteindre les performances d'Hugo).</p>
<p>De plus Eleventy supporte <a href="https://www.11ty.dev/docs/languages/" target="_blank" rel="noopener noreferrer">plusieurs langagues de templating</a>.</p>
<h4 id="configuration">Configuration</h4>
<p>J'ai opté pour le moteur de templating <a href="https://mozilla.github.io/nunjucks/" target="_blank" rel="noopener noreferrer">Nunjucks</a> de Mozilla. Outre cela, je n'avais besoin que de fichiers Markdown et HTML.</p>
<p>Nunjucks n'offre pas de filtres <code>date</code> et <code>limit</code>, je les ai donc ajoutés dans mon fichier de configuration <code>eleventy.js</code>, en utilisant la bibliothèque <a href="https://momentjs.com/" target="_blank" rel="noopener noreferrer"><code>moment.js</code></a> pour le filtre de date.</p>
<pre><code class="language-js hljs javascript"><span class="hljs-keyword">const</span> moment = <span class="hljs-built_in">require</span>(<span class="hljs-string">"moment"</span>);

<span class="hljs-comment">// limit filter</span>
eleventyConfig.addNunjucksFilter(<span class="hljs-string">"limit"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">array, limit</span>) </span>{
  <span class="hljs-keyword">return</span> array.slice(<span class="hljs-number">0</span>, limit);
});

<span class="hljs-comment">// date filter</span>
eleventyConfig.addNunjucksFilter(<span class="hljs-string">"date"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">date, format</span>) </span>{
  <span class="hljs-keyword">return</span> moment(date).format(format);
});</code></pre>
<p>J'utilise <a href="https://gulpjs.com/" target="_blank" rel="noopener noreferrer">Gulp</a> comme outil de génération, j'ai donc dû dire à Eleventy d'ignorer mes assets. Pour ce faire, j'ai simplement ajouté la ligne suivante au fichier <code>.eleventyignore</code> situé à la racine de mon projet:</p>
<pre><code class="language-txt">src/assets/**/*</code></pre>
<p>L'étape suivante a été d'ajouter Eleventy à mon fichier <code>gulpfile.js</code> et d'utiliser la fonction <code>child_process</code> de Node. De cette manière, je peux facilement intégrer Eleventy à mes commandes <code>build</code> et <code>watch</code>.</p>
<pre><code class="language-js hljs javascript"><span class="hljs-comment">// packages</span>
<span class="hljs-keyword">const</span> cp = <span class="hljs-built_in">require</span>(<span class="hljs-string">"child_process"</span>);

<span class="hljs-comment">// Eleventy</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">build</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> cp.spawn(<span class="hljs-string">"npx"</span>, [<span class="hljs-string">"eleventy"</span>, <span class="hljs-string">"--quiet"</span>], { <span class="hljs-attr">stdio</span>: <span class="hljs-string">"inherit"</span> });
}</code></pre>
<h3 id="structure-de-donnees">Structure de données</h3>
<h4 id="collections">Collections</h4>
<p>Mon site est simple, je n'avais besoin que de deux collections (blogposts et projects) pour lesquelles j'ai créé deux dossiers contenant des fichiers Markdown avec du front matter YAML.</p>
<p>Eleventy propose une fonctionnalité intéressante qui vous permet d'utiliser des fichiers JSON nommés comme votre dossier de collection pour déclarer des valeurs front matter communes à tous les fichiers du répertoire.</p>
<p>Par exemple, j'ai utilisé un fichier <code>blogposts.json</code> dans mon dossier <code>blogposts</code> pour définir le layout et la structure des permaliens pour tous les articles de blog.</p>
<pre><code class="language-json hljs json">{
  <span class="hljs-attr">"layout"</span>: <span class="hljs-string">"layouts/blogpost.njk"</span>,
  <span class="hljs-attr">"permalink"</span>: <span class="hljs-string">"blog/{{ page.fileSlug }}/index.html"</span>
}</code></pre>
<p>Les projets quant à eux n'ont pas besoin de pages de détail, j'ai donc spécifié la valeur de <code>permalink</code> à <code>false</code> dans le fichier <code>projects.json</code> de mon dossier <code>projects</code> et je n'ai pas défini de <code>layout</code>.</p>
<pre><code class="language-json hljs json">{
  <span class="hljs-attr">"permalink"</span>: <span class="hljs-literal">false</span>
}</code></pre>
<p>Pour créer les deux collections et permettre à Eleventy de générer les fichiers HTML, j'ai utilisé la fonction <code>getFilteredByGlob( glob )</code> dans mon fichier <code>eleventy.js</code>:</p>
<pre><code class="language-js hljs javascript"><span class="hljs-keyword">const</span> moment = <span class="hljs-built_in">require</span>(<span class="hljs-string">"moment"</span>);

<span class="hljs-built_in">module</span>.exports = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">eleventyConfig</span>) </span>{
  <span class="hljs-comment">// blogpost collection</span>
  eleventyConfig.addCollection(<span class="hljs-string">"blogposts"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">collection</span>) </span>{
    <span class="hljs-keyword">return</span> collection.getFilteredByGlob(<span class="hljs-string">"./src/blogposts/*.md"</span>);
  });

  <span class="hljs-comment">// projects collection</span>
  eleventyConfig.addCollection(<span class="hljs-string">"projects"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">collection</span>) </span>{
    <span class="hljs-keyword">return</span> collection.getFilteredByGlob(<span class="hljs-string">"./src/projects/*.md"</span>);
  });

  <span class="hljs-comment">// limit filter</span>
  eleventyConfig.addNunjucksFilter(<span class="hljs-string">"limit"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">array, limit</span>) </span>{
    <span class="hljs-keyword">return</span> array.slice(<span class="hljs-number">0</span>, limit);
  });

  <span class="hljs-comment">// date filter</span>
  eleventyConfig.addNunjucksFilter(<span class="hljs-string">"date"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">date, format</span>) </span>{
    <span class="hljs-keyword">return</span> moment(date).format(format);
  });

  <span class="hljs-comment">// Base config</span>
  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">dir</span>: {
      <span class="hljs-attr">input</span>: <span class="hljs-string">"src"</span>,
      <span class="hljs-attr">output</span>: <span class="hljs-string">"dist"</span>,
    },
  };
};</code></pre>
<h4 id="les-fichiers-de-donnees">Les fichiers de données</h4>
<p>Eleventy vous laisse aisément travailler avec des fichiers de données aux format JSON ou JS situés par défaut dans le sous-dossier <code>_data</code> de votre répertoire de base (<code>src</code> dans mon cas).</p>
<p>J'ai donc utilisé un fichier <code>./src/_data/site.js</code> pour définir des variables globales du site, auxquelles je peux facilement accéder dans n'importe quel fichier en utilisant le nom du fichier de données et une des clés de l'objet correspondant.</p>
<pre><code class="language-js hljs javascript"><span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">title</span>: <span class="hljs-string">"Webstoemp"</span>,
  <span class="hljs-attr">description</span>:
    <span class="hljs-string">"Webstoemp is the portfolio and blog of Jérôme Coupé, a designer and front-end developer from Brussels, Belgium."</span>,
  <span class="hljs-attr">url</span>: <span class="hljs-string">"https://www.webstoemp.com"</span>,
  <span class="hljs-attr">baseUrl</span>: <span class="hljs-string">"/"</span>,
  <span class="hljs-attr">author</span>: <span class="hljs-string">"Jerôme Coupé"</span>,
  <span class="hljs-attr">authorTwitter</span>: <span class="hljs-string">"@jeromecoupe"</span>,
  <span class="hljs-attr">buildTime</span>: <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(),
};</code></pre>
<p>La valeur de <code>site.buildTime</code> peut maintenant être utilisée dans le pied de page du site :</p>
<pre><code class="language-twig hljs twig"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"c-sitefooter__copyright"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"u-margin-all-none"</span>&gt;</span><span class="hljs-symbol">&amp;copy;</span> Webstoemp </span><span class="hljs-template-variable">{{ site.buildTime | <span class="hljs-name">date</span><span class="hljs-params">("Y")</span> }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span></code></pre>
<h3 id="layouts-nunjucks">Layouts Nunjucks</h3>
<p>J'ai choisi Nunjucks notamment pour son mécanisme d'héritage de layouts. Je peux définir un layout de base et ensuite l'étendre avec d'autres layouts si besoin.</p>
<h4 id="blogposts">Blogposts</h4>
<p>Boucler sur la collection <code>blogposts</code> pour afficher les titres et les dates de publication n'est pas très compliqué:</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">for</span></span> post in collections.blogposts | reverse %}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> loop.first %}</span><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"c-list-ui"</span>&gt;</span></span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">article</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"c-blogteaser"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"c-blogteaser__date"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">time</span> <span class="hljs-attr">datetime</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ post.<span class="hljs-name">date</span> | <span class="hljs-name">date</span><span class="hljs-params">('Y-M-DD')</span> }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ post.<span class="hljs-name">date</span>|<span class="hljs-keyword">date</span>("MMMM Do, Y") }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">time</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"c-blogteaser__title"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ post.url }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ post.data.title }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> loop.last %}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span></span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name">else</span> %}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>No blogpost found<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> %}</span></code></pre>
<p>J'utilise un layout dédié pour afficher le détail de chaque article de blog. Le fichier <code>_includes/layouts/blogpost.nkj</code> appelle mon layout principal et ajoute l'image associée à l'article de blog, ainsi que le contenu Markdown au bloc <code>content</code> :</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">extends</span></span> "layouts/base.njk" %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">set</span></span> activeSection = "blog" %}</span><span class="xml">

</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">set</span></span> metaTitle = title %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">set</span></span> metaDescription = excerpt %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">set</span></span> metaImage = site.url ~ "/img/blogposts/_600x600/" ~ image %}</span><span class="xml">

</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">block</span></span> content %}</span><span class="xml">

  <span class="hljs-tag">&lt;<span class="hljs-name">article</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"c-blogpost"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"c-blogpost__media"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"l-container"</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">picture</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">source</span> <span class="hljs-attr">media</span>=<span class="hljs-string">"(min-width: 500px)"</span>
                  <span class="hljs-attr">srcset</span>=<span class="hljs-string">"/img/blogposts/_1024x576/</span></span></span><span class="hljs-template-variable">{{ image }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string"> 1024w,
                          /img/blogposts/</span></span></span><span class="hljs-template-variable">{{ image }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string"> 1500w"</span>
                  <span class="hljs-attr">sizes</span>=<span class="hljs-string">"(min-width: 1140px) 1140px,
                         100vw"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/img/blogposts/_600x600/</span></span></span><span class="hljs-template-variable">{{ image }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>
               <span class="hljs-attr">class</span>=<span class="hljs-string">"o-fluidimage"</span>
               <span class="hljs-attr">alt</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ imageAlt }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">picture</span>&gt;</span>

      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"c-blogpost__body  l-container l-container--narrow"</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">header</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"c-suptitle  c-suptitle--dark"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">time</span> <span class="hljs-attr">datetime</span>=<span class="hljs-string">"</span></span></span><span class="hljs-template-variable">{{ page.<span class="hljs-name">date</span> | <span class="hljs-name">date</span><span class="hljs-params">('Y-M-DD')</span> }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ page.<span class="hljs-name">date</span> | <span class="hljs-name">date</span><span class="hljs-params">("MMMM Do, YYYY")</span> }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">time</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h1</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"c-h1"</span>&gt;</span></span><span class="hljs-template-variable">{{ title }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"c-blogpost__intro"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span></span><span class="hljs-template-variable">{{ excerpt }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"c-wysiwyg"</span>&gt;</span>
        </span><span class="hljs-template-variable">{{ content | safe }}</span><span class="xml">
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>

</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endblock</span></span> %}</span></code></pre>
<h3 id="projects">Projects</h3>
<p>La même logique est appliquée pour afficher les projets, avec une petite nuance. Comme il n'y a pas de layout dédié pour la collection <code>projects</code>, il nous faut utiliser <a href="https://www.11ty.dev/docs/collections/#collection-item-data-structure" target="_blank" rel="noopener noreferrer"><code>templateContent</code></a> pour afficher le contenu des fichiers Markdown. Voici une version simplifiée du code :</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">for</span></span> project in collections.projects | reverse %}</span><span class="xml">
    </span><span class="hljs-template-variable">{{ project.templateContent | safe }}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> %}</span></code></pre>
<h2 id="satisfait-de-mon-choix">Satisfait de mon choix</h2>
<p>Au final, je suis très content du résultat et d'avoir opté pour Eleventy. Le <a href="https://github.com/jeromecoupe/webstoemp" target="_blank" rel="noopener noreferrer">code source de mon site</a> est publié sur GitHub, si vous avez envie d'aller y jeter un œil.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2019/06/22/cms-headless-en-3-jours/</id>
    <title>Intégrer un CMS en 3 jours</title>
    <published>2019-06-22T00:00:00+00:00</published>
    <link href="https://jamstatic.fr/2019/06/22/cms-headless-en-3-jours/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<figure>
<picture title="Image d&#039;illustration « netlify cms »">
<source type="image/webp" srcset="/thumbnails/768x/images/post/2019-06-22_cms-headless-en-3-jours/netlify-cms-blog-featured-image-02.1dfa91c27e5cc5fcf58106178d1fcbb3.webp 768w, /thumbnails/1024x/images/post/2019-06-22_cms-headless-en-3-jours/netlify-cms-blog-featured-image-02.1dfa91c27e5cc5fcf58106178d1fcbb3.webp 1024w" width="1024" height="377" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/post/2019-06-22_cms-headless-en-3-jours/netlify-cms-blog-featured-image-02.1dfa91c27e5cc5fcf58106178d1fcbb3.avif 768w, /thumbnails/1024x/images/post/2019-06-22_cms-headless-en-3-jours/netlify-cms-blog-featured-image-02.1dfa91c27e5cc5fcf58106178d1fcbb3.avif 1024w" width="1024" height="377" sizes="100vw">
<img src="/images/post/2019-06-22_cms-headless-en-3-jours/netlify-cms-blog-featured-image-02.1dfa91c27e5cc5fcf58106178d1fcbb3.png" alt="Image d&#039;illustration de l&#039;article" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="377" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAK20lEQVR4nO2b6XLeKhKG3+4GJM89zFTN/V/iRHB+9ELDJ29J7HgqpoposSyjfugVQv/5938HBp5tw382oI+N7f68tbxmv3f3s7++0XokAviPDea73bZvIF+sfQP5Yu0byBdr30C+WCvfEc/Xavwdg36t9m2yvlgrfvKtKF+jfWvIF2vfQL5Y+yNA6PVH/tpWXn/kY1qG8lf6r2dm5acBeUkryP7ZwYxnL/7P21blze3TNWQZy83Acrme8ErZ/itDenUGrud+68OB7OMiaN0fpEcienhwDGBgxJrLwCOgL6k9eYK90VESrY9+qskiyp1ATAokDcoXwRSI9wllgUN4IBVsPhOSA3DpxqxLg0mrezu3TweywGCAicDMICZwQLFhOYDR0fuE0scIGP5pw2HYjbgcBgv4WFALCALYgPD8HorBaScM0PBfpQd2HwaEtouAwQpDmMHCAYbSb/TRMTqjd4fSQaEpc8V5JCmPG8Hn5wgT4FgexPshZRCcYDADrJoPommOHIZ3+3U2JFmxPllDDIZoZxEIi2mJpkQDA6MPg+GdQL1jDKCb9IZ/KICRbJbf36G52XNQ+f7zzum5j4EBcAgMCIOYQWITzDTfgVDqDIXBILUWRGB8EpBFo81EMSuMIgKRAhE2504YSDCuC9dlH0aEbh80HMRup6FAMShp0riFcQvqLZqSYQgDIiDvhfXIySS7DMYAjwEemo0LCEIEJrYj/X4gt4FFUm8XrJoshVGKoJQCZtWQ3jsuh2EviKisqy/JGoAQPKXrPUKz6w2IgyD3OQ7lTlv89QkGFQGVYl3PuUiYYmY2t0LgMSBA9AKCGAwh/hggt41WLaHQEoIIo5TyAIT6hR/A4yznDnR92UjmirK5wozAhhGI6yRsogklorXUloDBv4M2GLWAagXVCq4FXIoeRaEIswlbNaJABV6IDAihEKssoJP1Y4G4U6N8yx0eh7Z4D2FggLmjdwbRiBAZndZo0p6GIfF/n39ivZeGefP8/i02BjazVAxE894URq2QWiAiOuGIDcAEUYhQDUYh0gCH1MH/FiBvyYHemCf9/kbmd7aBUFKn3Xfskwh4hMHVIBzWW4McDdIUSJGCIoLChMqMAqASoYJQiRUIGzADwvQrJuudEp5m2XOLrvmFOXBvfj1SDrIniJ6rIJm0+DsUXiNFYVseMrbr/Ek336UaygALWAqoVEhtBuSAnAfkOCBnQ2kNpVWUUlCLoDAHAAfSSCFVg1GSaft4k+X5HjAFa2HtdV1ROsk+RB37hX71mYvY74xnYEwBTyB7ErlHU/nezdDN5FoIG9pRILVCWlMIxwE5T8h5opwHqkGptaIWMcFT9GYa0phVc1ihCfOv+ZA3K0hKCMYgFSxduDqBrst+NJawd0SklcGMyN79xbtXWAGlgGAOYTm+DGIr8QhrBFWLmqWjoRwHynmiPD2hPp2o56FQjgO1VpQNSANCWxSIKBARi8h+BshPOgMNNXWWEwFIMPSeO/WB3gf68Fykx7WbsXjnTfCbz18rnzw4dprnHqITM4gFXIo661bVLBmMmnp7OlGeTtTjQKkVVdxkESoBFeZHSLWjsudi8usa8jNNa1IA3GeMgcEC4h5ZbQbXh2pLt/xj9MUbPIMj3d8mz53fyM8tWkGaebMnr0XNlII4UM8D7TxQn54MhmqJa0ipVZ06kXUFUgxI2YDwu4H8apjk9roblGGlkD4ea1l7UTFdb6+8PY/xPiP45dJh0NQKDcNlgVFqRTmOqQ3nE9qZYJwnyqHaIa2hWOhbiNaE0BJB8XBf9O+8H8hvau6Qe4cWDQmgTuFAI5rKkVUct3ftL6d0vEk0yBKMeMyTvVRncxhaRSgoDqM11EPNUnt6QvuX9vqkYFxr5Gjq9C3Z1boVon5F8IKk1sAGMwZpJ/4DQIBplsgkpMnf/JkeJ4S8HvJq27UgJafuGwAHQOG0HYbICqPWhtoa2nGgZSBPpwFRjSnngdI0D6FS1PcQT/gjayRjEKETaYkxZbuvA/nAjC7C1zHDY7+/FABxX17KY9SPpXSenLMfH8xS0gpRGMVhVIXRWkNrhwIxx52P1Zy4HBViJRSI6Kz3sacPGQPoQcarwbBy0B/cdbK35b/IPReOxgmtp17mjgLmCoADAi2mSSRpRZkw6h2MQ514O08cp/qSdhwoR0MxM6UwGJ0ZHYRrAH0MXMPzIrUIHpvQAK4lMf1DJuvlNutVlKc68vn0OUizPi8LBwimWKHkB9NkWlFK9ForWq1qphxIO9COpkAMTj0P1NbCTHERQASdCRcIFwDqAz96R4eC6ZGFUhQ6CebbhvqZz1tT32pD814Srt2M+R9Cn3bfa/HTIWcQHItdfANCNcKKfq4VAaKglqra0SqaaUgNLTE/0pomflYikeow1EFfIPzAwP/6wEBHHwBRhxfNcpKqHEjXdUiPHwNks+Nz1s/ZeyvUqAQjnefn6QaC5wxz9isQhmzLxWIxfxGxdRhZNKNWP1YVfJitCaceer+0imIld4g6aYdBfQDUw2xxH2ATuK8nZ6326It/q8lK5YbFlidB5hm8mhheBpiff7jHvJxzZNNpUchDV18u5glDFhirhpTiMPLRYJgZq1XDYKkFUgpIRCMmAD9sVZB6x7g6Oi70AXRZo0dQWqSL429aD7HJ+2C3p9nIZmTtAYgZbLNc38HzHTcQaHvnI4zkJzZ/IQ/aIaEhU1Nmb/natEKKLUKxAMzoAK4EA71jkK7+d8zIkLv5DlCMq/ikoZ+tZSUQEc2E4DktX8qMYpbreQwwsdy5znoHw1bGCK1IZumlPjdSrDACijv0aloSuYfe04ptDf+iMDSz9vEMTO0gB3JdGKTOfNiMZWJctlGDiCCsUEpoyc9qSALxGLWkYlkp00zwOkPFZtcefrLvQAkYCU6C4D9/WTseo6mIqjKcUiaYJdpar6PmJHNywExVHwB54tS7CUgduWo54wfbOo/eBTNF2V1I4bw/7DUYM2rJdvnxA/zcY/z5fNm0haPAtpi0XfAJzkswAgr7hgOHwXPCiEAKx7hUc0oCJMv3SfqbEe0RAkjsvbquCYSm4H9wjw0abtqFva7lW4HeCWSHscfv0TebnJ9bPjJVOufx3hzt/iYDuQMTxTuHvjj3PJkYWWun37kZQ/JlIF2lZ+heMepmsoiA61ItIBX4xR2XzLUcAmI/lkNhwvuATDPFMaMikdrj9hSpeC+lrlCKm7IpmOdm+gRED2brNS15NGH5KMsOSk5/Z/5tuoUBzJIODUu7u9eACEQdcnUI2WKbV6ttO4vuJqLYkyWWf70LSOyyY4aI2tnWNGY/Ds1oj9ZwHFv87kDcLtt+rJiZySQ8D4QeBDT9zHNg6OXzRevW0JwTAN5hZCDmzBFQXAOAiwjSCVfnZfmAdi0h21D3Vqce0ZTZ8Knmbq6aQVEw53EYoBYJlsbvHsmUMHlqSvbIaw2Tc/0pC+jO0dMDEErRWo7iKEG4y5Vu8h8XhrUBzGTPHTrHjl0Qugm8RyE1yiUGgROM92kIZo7gqj1rQRLZ7WM/ZmJlyZebu6khyQE/mKdkpnYY2dHfaosD2QWMGxCvd5jQ7ta9fMYPwP7npgKMXftpNwWFPGFbTSlgv01Dbop4WVB3jnpq0KwZSTj314DsMF6A8pBcZigZiH2Jl2vwfihuLpay3Ji72f1n6tw1AKIODKaHBZ2QaQYC4B9yQSB2tU/M5gAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/post/2019-06-22_cms-headless-en-3-jours/netlify-cms-blog-featured-image-02.1dfa91c27e5cc5fcf58106178d1fcbb3.png 768w, /thumbnails/1024x/images/post/2019-06-22_cms-headless-en-3-jours/netlify-cms-blog-featured-image-02.1dfa91c27e5cc5fcf58106178d1fcbb3.png 1024w" sizes="100vw">
</picture>
<figcaption>Image d'illustration « netlify cms »</figcaption>
</figure>
<p>Imaginons que vous soyez en train de créer la prochaine grande startup ou d'organiser un super évènement — la première question que tout le monde va vous poser est : « C'est quoi le site web ? ».</p>
<p>Une présence en ligne séduisante et fonctionnelle est tout simplement primordial en 2019, que ce soit pour les entreprises, les organisations à but non lucratif ou encore pour le recrutement de nouveaux employés — et il en va de même pour <a href="https://monetery.com/" target="_blank" rel="noopener noreferrer">Monetery</a>, l'événement technologique — inclusif — organisé chaque printemps par Dwolla. Nous avions besoin d’un site rapidement opérationnel et performant, nous avons donc d’abord opté pour une solution fiable et éprouvée que nous avions déjà utilisé : <a href="https://pages.github.com/" target="_blank" rel="noopener noreferrer">GitHub Pages</a>.</p>
<p>Cette solution a été rapidement opérationnelle lorsque nous avons lancé la page d’accueil de Monetery, mais il était évident que nous avions besoin d’une solution plus complète. En raison de notre processus de validation exigeant, la technique est rapidement devenue un obstacle.
Nous devions travailler à une meilleure solution afin de migrer nos contributeurs de contenu et effectuer les changements nécessaires rapidement.</p>
<p>Nous avons alors étudié les options qui s’offraient à nous :</p>
<ol>
<li>Mettre en place un outil de gestion de contenu (CMS) traditionnel tel que WordPress</li>
<li>Trouver un CMS headless à intégrer dans un générateur de site statique (SSG)</li>
</ol>
<p>Le nombre de solutions potentielles pour ces deux options est très vaste. Connaissant déjà bien les solutions traditionnelles, nous avons donc fouillé du côté de <a href="https://headlesscms.org" target="_blank" rel="noopener noreferrer">headlesscms.org</a> et de <a href="https://www.staticgen.com" target="_blank" rel="noopener noreferrer">staticgen.com</a> pour voir ce qui se passait ailleurs. Dwolla offre à son équipe d’ingénieurs du temps dédié au développement professionnel chaque semaine, ce qui nous a permis de tester les solutions potentielles.</p>
<p>L’une des solutions les plus intéressantes que nous avons testées vient de la société <a href="https://www.netlify.com" target="_blank" rel="noopener noreferrer">Netlify</a>, et de son projet <a href="https://www.netlifycms.org" target="_blank" rel="noopener noreferrer">Netlify CMS</a>.</p>
<p>Nous avons pensé que Netlify CMS pourrait être avantageux pour les raisons suivantes :</p>
<ul>
<li>Il est conçu pour être utilisé avec des générateurs de site statique, ce qui nous permet de conserver les avantages en terme de vitesse, de sécurité et d’évolutivité qui nous ont attirés vers les SSG</li>
<li>Il est SSG agnostique, et fonctionne donc avec notre site <a href="https://jekyllrb.com" target="_blank" rel="noopener noreferrer">Jekyll</a> existant mais ne nous empêcherait pas de changer d’avis (salut <a href="https://www.gatsbyjs.org" target="_blank" rel="noopener noreferrer">GatsbyJS</a> !)</li>
<li>Il n’y a pas de base de données car les modifications de contenu sont enregistrées via des commits Git — ce qui ravi les gens de la <a href="https://www.dwolla.com/security/" target="_blank" rel="noopener noreferrer">sécurité informatique</a> !</li>
<li>Il fournit une expérience d’édition simple et fonctionnelle</li>
<li>Il est open-source, il n’y a donc pas de dépendance à un fournisseur, et nous permet de reverser les fonctionnalités importantes à la communauté</li>
</ul>
<p>Suite à l’adhésion des parties prenantes, nous avons décidé de nous orienter vers cette solution. Nous allons parler des décisions que nous avons dû prendre et vous montrer comment intégrer Netlify CMS avec Jekyll sur votre propre site.</p>
<h2 id="devez-vous-passer-de-github-pages-a-netlify">Devez-vous passer de GitHub Pages à Netlify ?</h2>
<p>Ça a été le premier choix à faire. Changer d’hébergement nous a semblé augmenter le temps et la complexité du projet, aussi notre décision à donc été « non ». Utiliser Netlify CMS avec votre hébergeur actuel est un choix parfaitement valable.</p>
<p>Alors pourquoi avons-nous changé d’avis et opté pour Netlify ? La réponse tient dans deux fonctionnalités très convaincantes : <a href="https://www.netlify.com/docs/git-gateway/" target="_blank" rel="noopener noreferrer">Git Gateway</a> et le <a href="https://www.netlify.com/docs/continuous-deployment/#branches-deploys" target="_blank" rel="noopener noreferrer">déploiement de branches</a>.</p>
<p>Git Gateway fonctionne comme un intermédiaire entre le CMS et votre dépôt Git. Concrètement cela signifie que, par exemple, vos utilisateurs peuvent se connecter à l’administration du CMS en utilisant leur compte Google au lieu de leur demander d’avoir un compte GitHub. Ensuite Netlify effectue les commits via un compte GitHub autorisé à accéder au dépôt via OAuth. Bien que Git Gateway soit également un logiciel <a href="https://github.com/netlify/git-gateway" target="_blank" rel="noopener noreferrer">open-source</a>, il était clair que l’héberger nous-même allait ajouter une complexité considérable.</p>
<p>Le déploiement de branches vous permet d’avoir plusieurs versions de votre site en même temps. À titre de comparaison, GitHub Pages est très limité puisqu’il ne permet de déployer qu’une seule branche (généralement “master“ ou “gh-pages“). Au premier abord ça peut sembler sans intérêt, mais ça offre une possibilité très intéressante que nous allons détailler dans un instant.</p>
<h2 id="migrer-de-github-pages-a-netlify">Migrer de GitHub Pages à Netlify</h2>
<p>En général, publier votre site depuis Netlify est aussi simple que de créer un compte Netlify, connectez vous à votre fournisseur (GitHub, GitLab ou Bitbucket) et sélectionnez un dépôt. Dès que vous définissez la commande de <em>build</em>, Netlify peut commencer à déployer votre site. Les tâches telles que configurer le SSL sont expliquées dans la <a href="https://www.netlify.com/docs/" target="_blank" rel="noopener noreferrer">documentation Netlify</a>, nous ne les détaillerons donc pas ici.</p>
<p>Si vous utilisez les <em>gems</em> intégrées à Jekyll et le processus de <em>build</em> proposé par GitHub, vous aurez besoin de quelques outils complémentaires pour que ça fonctionne. Vous aurez besoin d’un <em>Gemfile</em> pour vos dépendances, et c’est aussi une bonne idée d’intégrer la commande de <em>build</em> au code source :</p>
<p><strong>Gemfile</strong></p>
<pre><code class="language-ruby hljs ruby">source <span class="hljs-string">"https://rubygems.org"</span>
gem <span class="hljs-string">'github-pages'</span></code></pre>
<p><strong>netlify.toml</strong></p>
<pre><code class="language-toml hljs ini"><span class="hljs-section">[build]</span>
<span class="hljs-attr">publish</span> = <span class="hljs-string">"_site/"</span>
<span class="hljs-attr">command</span> = <span class="hljs-string">"jekyll build"</span></code></pre>
<p>Une fois que tout vous semble bon et que le déploiement Netlify se déroule correctement, vous pouvez demander à gérer votre nom de domaine via Netlify et migrer vos DNS vers les serveurs de nom de Netlify. Une fois vos DNS complètement coupés, vous pouvez désactiver le site GitHub Pages en toute sécurité depuis votre dépôt.</p>
<h2 id="ajouter-netlify-cms-a-un-site-existant">Ajouter Netlify CMS à un site existant</h2>
<p>Netlify CMS se compose d’une <a href="https://fr.wikipedia.org/wiki/Application_web_monopage" target="_blank" rel="noopener noreferrer">application web monopage</a> (NDT : en anglais <em>single-page application</em> ou SPA) construite avec React qui réside dans un dossier admin de votre site. Pour Jekyll, il doit être placé à la racine du site. Il contiendra deux fichiers :</p>
<pre><code class="language-text">admin
├ index.html
└ config.yml</code></pre>
<p>La <a href="https://www.netlifycms.org/docs/add-to-your-site/" target="_blank" rel="noopener noreferrer">documentation de Netlify CMS</a> explique ça très bien :</p>
<blockquote>
<p>Le premier fichier, <code>admin/index.html</code>, est le point d’entrée à l’admin de Netlify CMS. Cela signifie que les utilisateurs y accèdent via <code>votresite.com/admin/</code>. Du côté du code, c’est une page HTML qui charge le fichier Javascript de Netlify CMS. Dans cet exemple, nous chargeons le fichier depuis un CDN public :</p>
</blockquote>
<p><strong>admin/index.html</strong></p>
<pre><code class="language-html hljs xml"><span class="hljs-meta">&lt;!doctype <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Content Manager<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://identity.netlify.com/v1/netlify-identity-widget.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-comment">&lt;!-- Include the script that builds the page and powers Netlify CMS --&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://unpkg.com/netlify-cms@^2.0.0/dist/netlify-cms.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></code></pre>
<blockquote>
<p>Le second fichier, <code>admin/config.yml</code>, est le coeur de l’installation de Netlify CMS, et il est un peu plus complexe. La section <a href="https://www.netlifycms.org/docs/add-to-your-site/#configuration" target="_blank" rel="noopener noreferrer">Configuration</a> rentre dans les détails.</p>
</blockquote>
<p>Pour commencer, voici à quoi peut ressembler le fichier de configuration :</p>
<p><strong>admin/config.yml</strong></p>
<pre><code class="language-yml hljs yaml"><span class="hljs-attr">backend:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">git-gateway</span>
  <span class="hljs-attr">branch:</span> <span class="hljs-string">master</span>
  <span class="hljs-attr">identity_url:</span> <span class="hljs-string">"https://yoursite.com/.netlify/identity"</span>
  <span class="hljs-attr">gateway_url:</span> <span class="hljs-string">"https://yoursite.com/.netlify/git"</span>
  <span class="hljs-attr">squash_merges:</span> <span class="hljs-literal">true</span>

<span class="hljs-attr">publish_mode:</span> <span class="hljs-string">editorial_workflow</span>
<span class="hljs-attr">media_folder:</span> <span class="hljs-string">"assets/img/uploads"</span>

<span class="hljs-attr">site_url:</span> <span class="hljs-string">https://yoursite.com</span>
<span class="hljs-attr">logo_url:</span> <span class="hljs-string">https://yoursite.com/assets/img/logo.svg</span>

<span class="hljs-attr">collections:</span></code></pre>
<p>La section <code>backend</code> couvre la configuration de base tel que le choix de la branche à modifier et la connexion Git Gateway dont nous avons parlé plus haut. La propriété <code>publish_mode</code> est paramétrée de manière à ce que notre flux de travail utilise le mode <a href="https://www.netlifycms.org/docs/add-to-your-site/#editorial-workflow" target="_blank" rel="noopener noreferrer"><em>editorial</em></a>. En bref cela signifie que nous avons la possibilité de sauvegarder les brouillons sous la forme de <em>Pull Requests</em> Git avant de décider de les publier. Combiné à la fonctionnalité de déploiement de branches de Netlify, cela va nous permettre d’avoir un aperçu immédiat du contenu non publié !</p>
<p><em>Remarque : depuis mai 2019, le flux de travail éditorial n’est pris en charge que lorsque vous utilisez GitHub.</em></p>
<p>Maintenant il ne nous reste plus qu’à déposer le widget Netlify Identify sur le site principal. C’est nécessaire car après s’être connecté l’utilisateur est redirigé vers la page d’accueil du site. Nous devons rediriger les utilisateurs vers l’administration du CMS, en ajoutant le script suivant avant la fermeture de la balise <em>body</em> :</p>
<pre><code class="language-javascript hljs javascript">&lt;script&gt;
  <span class="hljs-keyword">if</span> (<span class="hljs-built_in">window</span>.netlifyIdentity) {
    <span class="hljs-built_in">window</span>.netlifyIdentity.on(<span class="hljs-string">"init"</span>, user =&gt; {
      <span class="hljs-keyword">if</span> (!user) {
        <span class="hljs-built_in">window</span>.netlifyIdentity.on(<span class="hljs-string">"login"</span>, () =&gt; {
          <span class="hljs-built_in">document</span>.location.href = <span class="hljs-string">"/admin/"</span>;
        });
      }
    });
  }
&lt;<span class="hljs-regexp">/script&gt;</span></code></pre>
<p>Une fois ceci mis en place, avec l’authentification adéquate et la Git Gateway configurée sur netlify.com, vous devriez être en mesure de vous connecter à l’administration de Netlify CMS de votre site via l’URL <code>https://yourdomain.com/admin/</code>.</p>
<h3 id="qu-est-ce-que-les-collections">Qu’est-ce que les Collections ?</h3>
<p>Bien qu'à ce stade vous pouvez vous connecter, vous ne pouvez pas encore faire grand chose ! Aucune structure de données n’est configurée pour les champs du CMS dont vous aurez besoin pour éditer votre site. Vous avez peut-être remarqué le champ vide <code>collections</code> dans le fichier de configuration, et c’est là que la magie opère. Tous les champs des données que vous souhaitez enregistrer doivent appartenir à une collection.</p>
<p>Il existe deux <a href="https://www.netlifycms.org/docs/collection-types/" target="_blank" rel="noopener noreferrer">types de collections</a>, le dossier de collections et le fichier de collections. Pour comprendre la différence, voyons ce que Netlify CMS fait réellement lorsque vous modifiez un contenu : les données doivent être stockées quelque part et nous savons qu’il utilise Git comme <em>back-end</em>. Cela signifie que les données que vous enregistrez doivent se retrouver dans un un fichier de votre projet. Ainsi, lorsque nous configurons une collection, nous donnons à Netlify CMS l’information de structure et la convention de nommage des fichiers que nous voulons créer. C’est ensuite à votre générateur de site statique de déterminer comment interpréter ces fichiers et injecter les données dans des templates. Dans ce billet, nous allons expliquer comment ça fonctionne avec Jekyll.</p>
<p>Sachant cela, pouvez-vous deviner pourquoi il existe deux types de collections ? Dans le cas de données de configuration, nous pouvons dire au CMS de mettre ces champs dans un fichier spécifique de notre projet. Dans le cas de contenus répétés tels que des billets de blog ou des pages construites à partir de composants modulaires, nous souhaitons configurer Netlify CMS de manière à ce qu’il puisse générer un certain nombre de formats de fichier différents — il supporte YAML, JSON, Markdown avec un <a href="https://jekyllrb.com/docs/front-matter/" target="_blank" rel="noopener noreferrer">front matter</a>, et quelques autres.</p>
<h3 id="parametrage-d-un-fichier-de-collection-pour-les-donnees-de-configuration">Paramétrage d’un fichier de collection pour les données de configuration</h3>
<p>Un fichier de collection est l’endroit idéal pour définir les champs des données pour les éléments qui sont valables sur l’ensemble du site, tels que la navigation globale, le pied de page et les valeurs par défaut. Jetons un oeil à un fichier de collection issu d’un cas réel :</p>
<p><strong>admin/config.yml</strong></p>
<pre><code class="language-yml hljs yaml"><span class="hljs-attr">collections:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">label:</span> <span class="hljs-string">"Options transverses"</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">options</span>
    <span class="hljs-attr">editor:</span>
      <span class="hljs-attr">preview:</span> <span class="hljs-literal">false</span>
    <span class="hljs-attr">files:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">label:</span> <span class="hljs-string">"Menu de navigation"</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">nav</span>
        <span class="hljs-attr">file:</span> <span class="hljs-string">"_data/nav.yml"</span>
        <span class="hljs-attr">fields:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">label:</span> <span class="hljs-string">"Entrées de menu"</span>
            <span class="hljs-attr">label_singular:</span> <span class="hljs-string">"Entrée de menu"</span>
            <span class="hljs-attr">name:</span> <span class="hljs-string">topLevelItems</span>
            <span class="hljs-attr">widget:</span> <span class="hljs-string">list</span>
            <span class="hljs-attr">fields:</span>
              <span class="hljs-bullet">-</span> <span class="hljs-string">{label:</span> <span class="hljs-string">"Texte affiché"</span><span class="hljs-string">,</span> <span class="hljs-attr">name:</span> <span class="hljs-string">displayText,</span> <span class="hljs-attr">widget:</span> <span class="hljs-string">string}</span>
              <span class="hljs-bullet">-</span> <span class="hljs-string">{label:</span> <span class="hljs-string">URL,</span> <span class="hljs-attr">name:</span> <span class="hljs-string">url,</span> <span class="hljs-attr">widget:</span> <span class="hljs-string">string}</span>
              <span class="hljs-bullet">-</span> <span class="hljs-attr">label:</span> <span class="hljs-string">"Type d'objet"</span>
                <span class="hljs-attr">name:</span> <span class="hljs-string">itemType</span>
                <span class="hljs-attr">widget:</span> <span class="hljs-string">select</span>
                <span class="hljs-attr">options:</span> <span class="hljs-string">["Link",</span> <span class="hljs-string">"Button"</span><span class="hljs-string">]</span></code></pre>
<p>Cela définira une nouvelle collection qui apparaîtra à gauche de l’interface utilisateur de l’administration du CMS, et créera une page “Menu de navigation” au sein de cette collection. À l’intérieur se trouvent des champs qui définissent les entrées de navigation du site qui incluent chacune un nom, une URL, etc. Nous définissons le type de donnée et l’interface d’édition des champs à l'aide de <a href="https://www.netlifycms.org/docs/widgets/" target="_blank" rel="noopener noreferrer">widgets</a>. Lorsqu'une modification est apportée, elle sera enregistrée dans le fichier <code>_data/nav.yml</code> de votre projet.</p>
<figure>
<picture title="Gestion du menu de navigation depuis Netlify CMS">
<source type="image/webp" srcset="/thumbnails/768x/images/post/2019-06-22_cms-headless-en-3-jours/Screen-Shot-2019-05-29-at-4.14.23-PM.ac54512c35825a00d9d5809a7cf5ad1d.webp 768w, /thumbnails/1024x/images/post/2019-06-22_cms-headless-en-3-jours/Screen-Shot-2019-05-29-at-4.14.23-PM.ac54512c35825a00d9d5809a7cf5ad1d.webp 1024w" width="1024" height="650" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/post/2019-06-22_cms-headless-en-3-jours/Screen-Shot-2019-05-29-at-4.14.23-PM.ac54512c35825a00d9d5809a7cf5ad1d.avif 768w, /thumbnails/1024x/images/post/2019-06-22_cms-headless-en-3-jours/Screen-Shot-2019-05-29-at-4.14.23-PM.ac54512c35825a00d9d5809a7cf5ad1d.avif 1024w" width="1024" height="650" sizes="100vw">
<img src="/images/post/2019-06-22_cms-headless-en-3-jours/Screen-Shot-2019-05-29-at-4.14.23-PM.ac54512c35825a00d9d5809a7cf5ad1d.png" alt="Gestion du menu de navigation depuis Netlify CMS" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="650" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAEKElEQVR4nO1bbZKsIAxMtub+R90r5P2ASAhBQAkwW6+rZkUUF9N0wpf4+/tLAABEBLOORJUj9AMBAJF/CIgY8tWxKIdc2sqXT38Pfmatbq26WvhMqZkT6GIwJOSLEZH5okQAiATa6PysUGSkadxhDrESxxJSmjTmCyKIcsOyOkI2qXwu/96IAw1+GMcSwtAq0chVk+6TrivnjSYY1I+RowlhldRJwaASVCZCjLdaCuLnjRvVUxmM14QgYuE6ZkJyEeIDu62iJukg68OBFWYoxZ+Rx4TU/LgXtEiCejgXwVKPwUK4cimlz8ArlMGY4rJWkmN7L3mC9byCIxlvoLx/A4YJ0V3PnWiTA2CrJ17JemU2VqoD4KVCSmVYrXMN6p0xXTdQSknXVxvfQjchehC2Wx0tlJ0sq748ko53qFt2EDSp26uVsU8pNRi9YDPu71ZJk5BvU0YLBABIYowT87VKdhHzM/dxCHkPRZ+fBdm2iMwe8nJUFfLXlFEDDzZPwWSFMM5WxskoFPKNynhKvXzVU1Ry9ORiC2zDscUne1r+OELeKCNM+M2rVPP/if+bjtqi8tyaWjmHBImvUkiNiNqybVlSYsa6yHx8ZsWM2mh3BlpE9LusXCmedX6KoxXSR8TIHNT5SvnMnjp/G0/Y+yQy8vkmi4i2y9KwY8oJSnFRyCgpUgllq+8lZKiGV0qujZyglKmESCJ6SUGQLgnjTz4zt5I8H3dZCTz1rokP1/ZJZbpCnrqsnx/eDHcfG0pCnkG7uVwpX7Ri2AMmpYcc6aYCCVohVyo7n2W005Ti3su6I0UG7rRtNFzR9n7jnlr1K5VS7pRcBT9CxBrVLSnGGMImY746GNYGu11xxI0QwUc4x3Ai1xS1a9J7clc1UIuEXXHEh5Bo/NqlIk+MmEOarvNw3TbOW6OVe4D3KYPhE9ShXBplyG5xihn5bsRAjH8LPUkZDOegrl+uPfji3e21zw3M/zJoRP1Ny0nwiyEVG2mF/EeO5ZOLO0moKeKtUma6ORdCCNIuDvnzwomu5ymWKqT2Qc1qzFKKRwfAlRACih99UnzZ9MIzNqT9JWUwfAip2Kk2LSHnve6P4z2wVj1SHplpC55d420rhtmOeSQAQkAkoJtjLOlUD/t8NZwIISD+Bi2m2WWl2dU0ILsZ2FegSzxdLRwjZMWg0U8hVKalq8hcBPSblL9Dzz/crBnRnhdj95c/94x45NbtvdLqPS3/zYzcxRCGzB+jUtfjLGUwXIN6T6Ord0HDUU+9lyRZ5a1pfFtNpyiDsSSo68GhbYM+YmT6WcM9UxmM5b2s2vvft3h784S95t5dk94bl2JLt7fPS7wjZvz56xfGLKydOoF2uyzJ6iPmqRFPm3Heo5Drz3CpySjZ2E2Q0xdU66A7CqHzkMY6dlrfv7TKt/gHiKwz5ztkXsQAAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/post/2019-06-22_cms-headless-en-3-jours/Screen-Shot-2019-05-29-at-4.14.23-PM.ac54512c35825a00d9d5809a7cf5ad1d.png 768w, /thumbnails/1024x/images/post/2019-06-22_cms-headless-en-3-jours/Screen-Shot-2019-05-29-at-4.14.23-PM.ac54512c35825a00d9d5809a7cf5ad1d.png 1024w" sizes="100vw">
</picture>
<figcaption>Gestion du menu de navigation depuis Netlify CMS</figcaption>
</figure>
<p>Voici un exemple de ce à quoi peut resembler un fichier de données :</p>
<p><strong>_data/nav.yml</strong></p>
<pre><code class="language-yml hljs yaml"><span class="hljs-attr">topLevelItems:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">displayText:</span> <span class="hljs-string">'Une page'</span>
    <span class="hljs-attr">itemType:</span> <span class="hljs-string">Link</span>
    <span class="hljs-attr">url:</span> <span class="hljs-string">'/une-page/'</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">displayText:</span> <span class="hljs-string">'Lien externe'</span>
    <span class="hljs-attr">itemType:</span> <span class="hljs-string">Link</span>
    <span class="hljs-attr">url:</span> <span class="hljs-string">'https://google.com'</span></code></pre>
<h3 id="comment-utiliser-un-fichier-de-collection-dans-jekyll">Comment utiliser un fichier de collection dans Jekyll ?</h3>
<p>Voyons comment exploiter ces données dans un template Jekyll. Voici un template <em>Liquid</em> qui utilise nos données de navigation :</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
  {% for item in site.data.nav.topLevelItems %}
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
      {% if item.itemType == 'Link' %}
        <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ item.url }}"</span>&gt;</span>{{ item.displayText }}<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
      {% else %}
        ...
      {% endif %}
    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  {% endfor %}
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span></code></pre>
<p>Dans Jekyll, toutes les informations du dossier <code>_data</code> sont accessibles en utilisant la syntaxe <code>site.data.{file}.{field}</code>. Vous pouvez itérer et obtenir les champs que vous souhaitez.</p>
<h3 id="parametrer-un-dossier-de-collection-de-pages">Paramétrer un dossier de collection de pages</h3>
<p>Une dossier de collection est utilisé chaque fois que nous avons besoin de générer un certain nombre de fichiers selon un template, mais sans savoir combien. Par exemple, si vous créez un blog, c’est ce dont vous aurez besoin pour vos billets.
Dans cet exemple, nous allons utiliser une fonctionnalité intéressante de Jekyll afin de permettre aux contributeurs de créer les pages de notre site à la volée et selon la destination de leur choix.</p>
<p>Regardons la structure d’un dossier de collection provenant d’un fichier de configuration réel pour voir comment ça marche :</p>
<p><strong>admin/config.yml</strong></p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">collections:</span>
 <span class="hljs-bullet">-</span> <span class="hljs-attr">label:</span> <span class="hljs-string">"Pages"</span>
    <span class="hljs-attr">label_singular:</span> <span class="hljs-string">"Page"</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">pages</span>
    <span class="hljs-attr">folder:</span> <span class="hljs-string">"_pages"</span>
    <span class="hljs-attr">create:</span> <span class="hljs-literal">true</span>
    <span class="hljs-attr">slug:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{slug}}</span>"</span>
    <span class="hljs-attr">preview_path:</span> <span class="hljs-string">"<span class="hljs-template-variable">{{permalink}}</span>"</span>
    <span class="hljs-attr">editor:</span>
      <span class="hljs-attr">preview:</span> <span class="hljs-literal">false</span>
    <span class="hljs-attr">fields:</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">{label:</span> <span class="hljs-string">"Titre"</span><span class="hljs-string">,</span> <span class="hljs-attr">name:</span> <span class="hljs-string">title,</span> <span class="hljs-attr">widget:</span> <span class="hljs-string">string}</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">{label:</span> <span class="hljs-string">"Lien permanent"</span><span class="hljs-string">,</span> <span class="hljs-attr">name:</span> <span class="hljs-string">permalink,</span> <span class="hljs-attr">widget:</span> <span class="hljs-string">string}</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">label:</span> <span class="hljs-string">"Template"</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">"layout"</span>
        <span class="hljs-attr">widget:</span> <span class="hljs-string">"select"</span>
        <span class="hljs-attr">default:</span> <span class="hljs-string">"blocks"</span>
        <span class="hljs-attr">options:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">{</span> <span class="hljs-attr">label:</span> <span class="hljs-string">"Défaut"</span><span class="hljs-string">,</span> <span class="hljs-attr">value:</span> <span class="hljs-string">"blocks"</span> <span class="hljs-string">}</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">{</span> <span class="hljs-attr">label:</span> <span class="hljs-string">"Page d'accueil"</span><span class="hljs-string">,</span> <span class="hljs-attr">value:</span> <span class="hljs-string">"home"</span> <span class="hljs-string">}</span>
      <span class="hljs-bullet">-</span> <span class="hljs-string">{label:</span> <span class="hljs-string">"Meta description"</span><span class="hljs-string">,</span> <span class="hljs-attr">name:</span> <span class="hljs-string">metaDescription,</span> <span class="hljs-attr">widget:</span> <span class="hljs-string">text,</span> <span class="hljs-attr">required:</span> <span class="hljs-literal">false</span><span class="hljs-string">}</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">label:</span> <span class="hljs-string">"Partage sur les réseaux sociaux"</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">social</span>
        <span class="hljs-attr">widget:</span> <span class="hljs-string">object</span>
        <span class="hljs-attr">required:</span> <span class="hljs-literal">false</span>
        <span class="hljs-attr">fields:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">{label:</span> <span class="hljs-string">"Image OpenGraph"</span><span class="hljs-string">,</span> <span class="hljs-attr">name:</span> <span class="hljs-string">ogImage,</span> <span class="hljs-attr">widget:</span> <span class="hljs-string">image,</span> <span class="hljs-attr">required:</span> <span class="hljs-literal">false</span><span class="hljs-string">}</span>
          <span class="hljs-bullet">-</span> <span class="hljs-string">{label:</span> <span class="hljs-string">"Image Twitter"</span><span class="hljs-string">,</span> <span class="hljs-attr">name:</span> <span class="hljs-string">twitterImage,</span> <span class="hljs-attr">widget:</span> <span class="hljs-string">image,</span> <span class="hljs-attr">required:</span> <span class="hljs-literal">false</span><span class="hljs-string">}</span></code></pre>
<p>Ceci définit une nouvelle collection appelée “Pages” qui contiendra de nombreux fichiers stockés dans le dossier <code>/_pages/</code> de votre projet. Les fichiers seront nommés en fonction du modèle définit dans le champ <em>slug</em>, lequel est configuré pour prendre la valeur de la variable pas forcément très explicite <code>{{slug}}</code>. Ne vous inquiétez pas, dans ce cas, cela signifie simplement que nous utiliserons la valeur par défaut, à savoir le contenu du champ <code>Titre</code>. Vous pouvez configurer cela de différentes façons pour y inclure une date ou tout autre élément selon votre besoin, mais dans le cas de notre exemple c’est parfait.</p>
<figure>
<picture title="Liste des pages dans Netlify CMS">
<source type="image/webp" srcset="/thumbnails/768x/images/post/2019-06-22_cms-headless-en-3-jours/Screen-Shot-2019-05-29-at-4.17.02-PM.40c8ba1d996825e137db00e61aaa1b03.webp 768w, /thumbnails/1024x/images/post/2019-06-22_cms-headless-en-3-jours/Screen-Shot-2019-05-29-at-4.17.02-PM.40c8ba1d996825e137db00e61aaa1b03.webp 1024w" width="1024" height="441" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/post/2019-06-22_cms-headless-en-3-jours/Screen-Shot-2019-05-29-at-4.17.02-PM.40c8ba1d996825e137db00e61aaa1b03.avif 768w, /thumbnails/1024x/images/post/2019-06-22_cms-headless-en-3-jours/Screen-Shot-2019-05-29-at-4.17.02-PM.40c8ba1d996825e137db00e61aaa1b03.avif 1024w" width="1024" height="441" sizes="100vw">
<img src="/images/post/2019-06-22_cms-headless-en-3-jours/Screen-Shot-2019-05-29-at-4.17.02-PM.40c8ba1d996825e137db00e61aaa1b03.png" alt="Liste des pages dans Netlify CMS" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="441" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAETElEQVR4nOVa67riIAyc8fP9X3J3X8PsDwiEgLX2AC094wVFyiXDJEDln7//BF3A+E6EZ5miSgHaVGK2hO7IzhSoux9+kg8pcvpuLMxjEgjCUyAvwUteb9Lw0v6RxIPM6cOmD/BR2uq5y9YLwhq/JhNl6q7ViZLqsiV2TF+aSrhZ0reD8YS0+v9NJz/WnxpoK0XibJU4uyHRvNJWBhmuS6nprcTHWzD7CZMFMvLTGrgAkp3G/RSSZ7+4l8lzZRWWjGAiKY2YFPVhmrFlezZeoUqauocRMloZVXtGKdb4r5dTCrJ/9z3LZGw1hIaP85qoa1eVWKVYMiTm3Vgh3mWJI6VxcQyshMB7mJALNNmo6nhXolRH6rPJ6U7ILGVk90KISEoBDd6lIl4SVkcpL/V0a263B5EIrYzfrkFzSRqltMsurRBPSshjihNiH2KIkTo4U9feZS7y/LXz2JfJ/Wn2s2ijyKnKLkeIkpD3CkgKAdCIDzUxdp+gdbhWcmJXXO8Mzn0qe6cKi+UIAbJh1KaZpG14tTRqrr/t9Ld7jL1xdfq0DCFeGSFPY4Z3Ny1XE1Jd8eheZAvvnJRt50c8NLAMIUCtjGxoJawVB7Qki5Obh15u6mZz/zATCxydWGXUu/Js/GqTl5avea+thD4Q1/2FqJgJYWtx6mofxNzlCdlCHQaiW4qBHiSowZiRCAQyRAjQubm4D7HnoLMlc1lCWjFDp7QnohWgydJFZTYACEFK2P5VnHhiDEGm7lG4LCF74U9uEwwhJRe6TyEo0lDAeyJm4HqEJN9eK8OjOkY3xdLVDxbKgN3Vk+3VlhLis/0pyQClXI+Qjsg3mAABIZS0Esspzl5aFehPyNHBfaGM3X1QjxSXT9RTVQnk2JWbGKl82mSOVMotFZLcTfNIRFJsyDeixOxjgMMToQPGEXKGG/Bt+pgSY8OmuTW27LlVO0ApBwm5kNM1qAKx+5oP3O1xCorPYSkswzZ+n/AlIQd6OWNgG8poFwv/Isn3UvT+uf68TyGmuPt+fNA/dFnXUMpeZdhPlPrYUXTnvudW7iDMc1kjBnhAGdVfFMzqixrU+Xml1Wyng1I6BfVzjkmPKMMHeiliScpcTSGKk5TSSRk5o6zQHwR/3b0fKKXzsneOUoYow/52juAB9HRZEy4ZqYyfqqLZjwNKGbQxHDPFRirD1nfm2rG/yxpxyWLKqPr1hVIGn2X1UcpvUIZinMsaUc1iyvDYo5TntHlxyJv9HmUornn8fjNleGwpZZ5CUusHirgR3FEZiuEK+WrQN1eGR0spwxRC875RYE8l27iJMhTTYshwIyykDA+rlOExZMZsvIMyFNNXWd2NsrAyPEjgOevgf1wr91CG4rR9SF8jra0Mi2kKUUxpbUFlKE7fqfcy2rYy1qFmukIUQ1pdWBmK0xWiWH1p0QvPs/vYpfkbKEPxvNogfrtSTleIYkQ3WH24Pv4DzZ8251Md4ZEAAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/post/2019-06-22_cms-headless-en-3-jours/Screen-Shot-2019-05-29-at-4.17.02-PM.40c8ba1d996825e137db00e61aaa1b03.png 768w, /thumbnails/1024x/images/post/2019-06-22_cms-headless-en-3-jours/Screen-Shot-2019-05-29-at-4.17.02-PM.40c8ba1d996825e137db00e61aaa1b03.png 1024w" sizes="100vw">
</picture>
<figcaption>Liste des pages dans Netlify CMS</figcaption>
</figure>
<p>Veuillez noter les champs <code>permalink</code> et <code>preview_path</code>. Nous utiliserons le champ <em>permalink</em> pour définir le chemin d’accès à notre page dans Jekyll, et le champ de <em>preview</em> permet à Netlify CMS de savoir comment pointer vers la bonne URL de prévisualisation (déploiement de branches :+1:).</p>
<p>Voici un exemple de ce à quoi peut ressembler le fichier de contenu d’une page :</p>
<p><strong>_pages/home.md</strong></p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-meta">---</span>
<span class="hljs-attr">Title:</span> <span class="hljs-string">Accueil</span>
<span class="hljs-attr">permalink:</span> <span class="hljs-string">/</span>
<span class="hljs-attr">layout:</span> <span class="hljs-string">home</span>
<span class="hljs-attr">metaDescription:</span> <span class="hljs-string">Dites</span> <span class="hljs-string">nous</span> <span class="hljs-string">de</span> <span class="hljs-string">quoi</span> <span class="hljs-string">il</span> <span class="hljs-string">s'agit</span> <span class="hljs-string">!</span>
<span class="hljs-attr">social:</span> <span class="hljs-string">{}</span>
<span class="hljs-meta">---</span></code></pre>
<h3 id="comment-utiliser-un-dossier-de-collection-dans-jekyll">Comment utiliser un dossier de collection dans Jekyll ?</h3>
<p>Si vous lisiez avec attention, vous avez sans doute remarqué qu’une collection de fichiers génère des fichiers YAML, alors qu‘une collection de dossiers génère des fichiers Markdown avec un <em>front matter</em>. Vous pensez peut-être que c’est un peu étrange d’avoir un fichier Markdown sans contenu sous le <em>front matter</em> (séparé par trois tirets), mais soyez rassuré : c’est pour une bonne raison !</p>
<p>Nous travaillerons de concert avec la fonctionnalité de <a href="https://jekyllrb.com/docs/collections/" target="_blank" rel="noopener noreferrer">collections</a> propre à Jekyll afin de coupler nos fichiers Markdown avec un template, lire les données du <em>front matter</em> et ensuite générer notre page. Cela nous permettra de faire des choses plus travaillées plus tard, comme utiliser le <a href="https://www.netlifycms.org/docs/beta-features/#list-widget-variable-types" target="_blank" rel="noopener noreferrer">widget liste à types de variable</a> pour créer un composant de <em>page builder</em> !</p>
<p>Avant de commencer, nous avons besoin de compléter le fichier de configuration de Jekyll :</p>
<p><strong>_config.yml</strong></p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">collections:</span>
  <span class="hljs-attr">pages:</span>
    <span class="hljs-attr">output:</span> <span class="hljs-literal">true</span></code></pre>
<p>Ceci indique à Jekyll qu’il doit générer une nouvelle page pour chaque fichier Markdown présent dans le dossier <code>pages</code>.</p>
<p>Mais comment Jekyll fait-il pour savoir quel template utiliser ? Dans le cas présent c’est champ <code>layout</code> défini dans Netlify CMS qui s’occupe de ça. Jekyll fait correspondre la valeur du champ dans le <em>front matter</em> directement avec le nom du fichier de template présent dans le dossier <code>_layouts</code> du projet.</p>
<p>Regardons un exemple de template :</p>
<p><strong>_layouts/home.html</strong></p>
<pre><code class="language-html hljs xml">---
layout: default
---

<span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{{ page.title }}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"home"</span>&gt;</span>
  {{ content }}
<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span></code></pre>
<p>Toutes les données provenant du <em>front matter</em> qui nous intéresse sont accessibles en utilisant la syntaxe Jekyll <code>{collection}.{field}</code>. Nous pouvons utiliser les templates parents et autres comme on veut.</p>
<h3 id="realiser-un-page-builder-dans-jekyll">Réaliser un <em>page builder</em> dans Jekyll</h3>
<p>C’est un bon début, mais nous n’aurions pas besoin de tout ça dans notre dossier de collection si nous n’allions pas plus loin : créons un <em>page builder</em> flexible, basé sur des composants.</p>
<p>Pour commencer, nous devons définir nos composants dans le fichier de configuration de Netlify CMS :</p>
<p><strong>_admin/config.yml</strong></p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">collections:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-attr">label:</span> <span class="hljs-string">"Pages"</span>
      <span class="hljs-string">...</span>
      <span class="hljs-bullet">-</span> <span class="hljs-attr">label:</span> <span class="hljs-string">"Blocs de contenu"</span>
        <span class="hljs-attr">label_singular:</span> <span class="hljs-string">"Bloc de contenu"</span>
        <span class="hljs-attr">name:</span> <span class="hljs-string">blocks</span>
        <span class="hljs-attr">widget:</span> <span class="hljs-string">list</span>
        <span class="hljs-attr">types:</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">label:</span> <span class="hljs-string">"Mise en avant"</span>
            <span class="hljs-attr">name:</span> <span class="hljs-string">hero</span>
            <span class="hljs-attr">widget:</span> <span class="hljs-string">object</span>
            <span class="hljs-attr">fields:</span>
              <span class="hljs-bullet">-</span> <span class="hljs-string">{label:</span> <span class="hljs-string">"En-tête"</span><span class="hljs-string">,</span> <span class="hljs-attr">name:</span> <span class="hljs-string">heading,</span> <span class="hljs-attr">widget:</span> <span class="hljs-string">string}</span>
              <span class="hljs-bullet">-</span> <span class="hljs-string">{label:</span> <span class="hljs-string">"Contenu"</span><span class="hljs-string">,</span> <span class="hljs-attr">name:</span> <span class="hljs-string">content,</span> <span class="hljs-attr">widget:</span> <span class="hljs-string">markdown,</span> <span class="hljs-attr">buttons:</span> <span class="hljs-string">["bold",</span> <span class="hljs-string">"italic"</span><span class="hljs-string">,</span> <span class="hljs-string">"link"</span><span class="hljs-string">],</span> <span class="hljs-attr">required:</span> <span class="hljs-literal">false</span><span class="hljs-string">}</span>
          <span class="hljs-bullet">-</span> <span class="hljs-attr">label:</span> <span class="hljs-string">"Bloc de texte riche"</span>
            <span class="hljs-attr">name:</span> <span class="hljs-string">textBlock</span>
            <span class="hljs-attr">widget:</span> <span class="hljs-string">object</span>
            <span class="hljs-attr">fields:</span>
              <span class="hljs-bullet">-</span> <span class="hljs-string">{label:</span> <span class="hljs-string">"En-tête"</span><span class="hljs-string">,</span> <span class="hljs-attr">name:</span> <span class="hljs-string">heading,</span> <span class="hljs-attr">widget:</span> <span class="hljs-string">string,</span> <span class="hljs-attr">required:</span> <span class="hljs-literal">false</span><span class="hljs-string">}</span>
              <span class="hljs-bullet">-</span> <span class="hljs-string">{label:</span> <span class="hljs-string">"Contenu"</span><span class="hljs-string">,</span> <span class="hljs-attr">name:</span> <span class="hljs-string">content,</span> <span class="hljs-attr">widget:</span> <span class="hljs-string">markdown}</span>
          <span class="hljs-string">...</span></code></pre>
<p>Ici nous avons étendu notre collection de pages afin d’y inclure un widget de liste à type de variable qui contient différents types d’objets que l’éditeur de contenu pourra ajouter dynamiquement et réorganiser depuis l’administration du CMS.</p>
<figure>
<picture title="Edition de contenu depuis Netlify CMS">
<source type="image/webp" srcset="/thumbnails/768x/images/post/2019-06-22_cms-headless-en-3-jours/Screen-Shot-2019-05-29-at-4.19.06-PM.aa640c6a43dcf6204784fc69b96ce7af.webp 768w, /thumbnails/1024x/images/post/2019-06-22_cms-headless-en-3-jours/Screen-Shot-2019-05-29-at-4.19.06-PM.aa640c6a43dcf6204784fc69b96ce7af.webp 1024w" width="1024" height="612" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/post/2019-06-22_cms-headless-en-3-jours/Screen-Shot-2019-05-29-at-4.19.06-PM.aa640c6a43dcf6204784fc69b96ce7af.avif 768w, /thumbnails/1024x/images/post/2019-06-22_cms-headless-en-3-jours/Screen-Shot-2019-05-29-at-4.19.06-PM.aa640c6a43dcf6204784fc69b96ce7af.avif 1024w" width="1024" height="612" sizes="100vw">
<img src="/images/post/2019-06-22_cms-headless-en-3-jours/Screen-Shot-2019-05-29-at-4.19.06-PM.aa640c6a43dcf6204784fc69b96ce7af.png" alt="Edition de contenu depuis Netlify CMS" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="612" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAE7UlEQVR4nN1c7brlHAzN6jv3f6HnIl7zgxARqvVRc/KcvbW2BlmWEJ3Bz8+PIyJyztGs1Ll6SuTLvROnUgo6Ux7XIdP/nQv1hw/ptkk9RAARALou0HVd4YMi/Q8X4QJdQHhuPL3emqamdJfkRpRg6DLS4iODwRbu9Sw7/HncgEqFTltji2imuOJ6vYAAfAdI1hRVuR6xO0TWaU1XbhkzQAQ/vcW8CbNENyA9zADKaWOP2GCsGRhI0xSBjmKIboBzGpRP0OkQkG8bp73PpO/kO0SJHQy5q8RiCBC6OR0PEODIuXcpOQQMXG5JXUtYZeWfYItwTZEV/wJDiAhhAM6aOria9x1ObADBL32JyBHIGSoBv5ytAePB2ciQXuUFQygHI5vCnswQpDvrFVjN8nW02OHbCcC3Jdy7FpMFIFVg/gkfQlSAEYsEI9zqNPTXAZG/15y73LiGe/FbrRVQgFwmOLYt3koByFOlmiE+T/kTwRB0MISNL6+tKcsC5xaQ8OUoImM0INUJBuaqMeUwhnAjdOjBTxGaIdRkSLaOqTAjddhS5GL5bkAafeI0Y8olmWI/MyIRkBFm+OdJ3SMDxWcSoYFIHzNAVyXg45xkT/IfRAyEgKGFB9LUC2ZKBztk39/KFIYkyQ0iQSGiJkPi+r4CRNlJbYR8yuQ8vofIuJs1Exj+mZgqZ75C/owyQ0o+Gv2SikHhAi0fkkDgVINhr7KEBvE8dHZEwloXWGpr7cj2Ja3WvGDKZIYoCctciKHas8qqqmuusNrNyC+qJbKsOAwK1rbrG5FuhsyJ5o71pNxgc/hjoA5kSfFjbRA8quIBgksZgmIucPxnSowuFctmFzrlhH+AekpqSS2QlzWj17LlLytZIeWWIXPPOe57VV8FpaUag5RfK/0gglTi0l3Vxxg1chixZOcip75CackMlrQZu4O5Cgz4YQ6N8LUqzADEptigVW4+kypDvjkBLIVbEZeuxS8gy9jg5VzBDC03M0QsUSs3F8g1PqS5rGmELG4kCwRCZIDEMjuUdZo1vYb7likFQ05hRkvsCG1ueN6p++tezT0F1wK2dh8SRXdiDugRguBW8hhaOJR6bcBvmBIZsp0ZZ/jQ42QTQ1jSLgyTWKI0V2JiZYzsVNkMiJdpNilWsOgG5FRPuR2QeSMUknAxrw1I9jQRnQfMVkDQuHuuCCq8gey7e6pyzdvtsg0QHa0Y0oQUDbA3bS1A8rP14gxlRhMH5BMf8rbbybjI7i3d9ZB53xL8q+3YFkDmsEO8oGaOfKuCEpC0fWSm5JtHzZzdshwQGWlKOQ91COOntwX5t3zBaz9r1F5Y/AymLIr2JuEd9Ii2jA3hjNs63qUHdZ3KlOVHuCCKIfLHj2erqdaLD883fDZTksPnA7LdsvjEcIQhxmqK3wDRJYvpq1/49SALgC/8yAan/qJXnUDMkNLHfSvrnfpjS9pAPBn9oy9ufBnvWgjIW2aES2GVff+g9Ott4UJAXtlQTU2jr2g+PZ4+IRK8AJCXvcKX4/MAJILMB+Q9HuGi7sB5ZD999f/uEO4EZrBMBGSsV/Gw1XhFaJ3BDkIiyEfBxWdyt2t+6/RPYgbLUYAUzFhW04FIBDkKEC2P36jq9imvmrNFjgZkvuxAYmzf/ysBqTFlBzNGA5K/EpC6rERkTkTsVwOy8//wmhWq/wsuRA1JKrYlLQAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/post/2019-06-22_cms-headless-en-3-jours/Screen-Shot-2019-05-29-at-4.19.06-PM.aa640c6a43dcf6204784fc69b96ce7af.png 768w, /thumbnails/1024x/images/post/2019-06-22_cms-headless-en-3-jours/Screen-Shot-2019-05-29-at-4.19.06-PM.aa640c6a43dcf6204784fc69b96ce7af.png 1024w" sizes="100vw">
</picture>
<figcaption>Edition de contenu depuis Netlify CMS</figcaption>
</figure>
<p>Créons maintenant un nouveau template pour le rendu de nos widgets :</p>
<p><strong>_layouts/blocks.html</strong></p>
<pre><code class="language-html hljs xml">---
layout: default
---

{% for block in page.blocks %}
  {% include blocks/{{ block.type }}.html block=block %}
{% endfor %}</code></pre>
<p>Ici nous itérons sur chacun des composants de la page et incluons un autre fichier de template qui lui s’occupe du rendu. Voici à quoi pourrait ressembler un template de composant :</p>
<p><strong>_includes/blocks/hero.html</strong></p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">header</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"page-hero"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{{ block.heading }}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  {% if block.content and block.content != '' %}
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"max-width--330"</span>&gt;</span>
      {{ block.content | markdownify }}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  {% endif %}
<span class="hljs-tag">&lt;/<span class="hljs-name">header</span>&gt;</span></code></pre>
<p>Parce que nous avons transmis notre variable <code>block</code>, nous avons tout ce dont nous en avons besoin. Vous remarquez également que nous avons veillé à transformer notre Markdown en HTML avec <em>markdownify</em> car ce n’est pas fait automatiquement.</p>
<h2 id="notre-retour-d-experience-avec-netlify-netlify-cms">Notre retour d'expérience avec Netlify + Netlify CMS</h2>
<p>Grâce à ces techniques, nos ingénieurs ont pu intégrer Netlify CMS à notre site Jekyll existant pour <a href="https://monetery.com/" target="_blank" rel="noopener noreferrer">Monetery</a> et mettre en oeuvre un CMS opérationnel en l’espace de quelques jours (trois pour être exact).
Les contributeurs de contenu ont été en mesure de s’intégrer rapidement et de commencer à publier des modifications et de nouvelles pages peu de temps après le lancement. Pendant ce temps, nous avons également intégré un nouvel ingénieur qui a pu commencer à contribuer de manière significative dès son deuxième jour de travail !</p>
<p>Cela dit, <a href="https://www.dwolla.com/about/core-beliefs/" target="_blank" rel="noopener noreferrer">ce n’est jamais terminé</a>. Nous apprenons constamment de nos expériences et nous essayons de nous améliorer. Jetons un oeil critique sur les avantages et les inconvénients quant à l’utilisation de Netlify + Netlify CMS :</p>
<h3 id="潰牵">Pour</h3>
<ul>
<li>Héberger son site avec Netlify est un jeu d’enfant et nous n’avons rencontré aucun problème avec le site lui-même</li>
<li>Netlify CMS à été très facile à installer sur un projet Jekyll existant et il est intuitif pour les nouveaux ingénieurs</li>
<li>Il est facile et très pratique de créer une copie de l’ensemble de votre projet, y compris le contenu, et de l’exécuter localement à l’aide de Docker</li>
<li>L’interface de Netlify CMS est simple et facile à prendre en main pour les contributeurs de contenu</li>
<li>Le déploiement et l‘aperçu par branche est extraordinaire</li>
<li>L‘offre gratuite de Netlify vous donnent la liberté d’évaluer le service avant de vous engager</li>
<li>Il existe une <a href="https://gitter.im/netlify/NetlifyCMS" target="_blank" rel="noopener noreferrer">communauté</a> active et très utile pour Netlify CMS sur Gitter</li>
<li>Netlify CMS est open-source et les contributions sont bienvenues</li>
</ul>
<h3 id="contre">Contre</h3>
<ul>
<li>Nos contributeurs de contenu apprécient le flux de travail éditorial, mais n’aiment pas les nombreuses étapes nécessaires pour enregistrer et publier</li>
<li>L’enregistrement et la publication sont relativement lents, parfois jusqu’à plusieurs secondes</li>
<li>Nous rencontrons des erreurs occasionnelles — mais frustrantes — lors de l’utilisation de l’administration du CMS</li>
<li>Certains widgets ou fonctionnalités que vous pourriez rechercher, tels que l‘affichage conditionnel des champs de l’interface utilisateur de l’administration, n’ont pas encore été implémentés</li>
<li>L’interface utilisateur du CMS ne permet pas de sauvegarder le contenu sur votre ordinateur lors du développement en local, il sera toujours nécessaire de <em>commiter</em> sur votre dépôt Git, alors soyez prudent</li>
<li>Il est préférable d’utiliser Netlify comme hébergeur plutôt qu’un autre fournisseur si vous souhaitez utiliser des fonctionnalités telles que le déploiement de branches et un Git Gateway déjà hébergé — Cela peut ajouter des coûts supplémentaires à votre projet</li>
</ul>
<h2 id="communaute-et-contribution">Communauté et contribution</h2>
<p>Les échanges avec la communauté Netlify CMS ont été merveilleux, nous vous encourageons donc à essayer cette technologie. Dwolla croit également qu’il faut associer les mots et les actes, aussi nous sommes résolus à reverser à la communauté open-source. Nous sommes heureux d’annoncer que notre première <em>Pull Request</em> est déjà en ligne !</p>
<p>Découvrez le code sur GitHub : <a href="https://github.com/netlify/netlify-cms" target="_blank" rel="noopener noreferrer">https://github.com/netlify/netlify-cms</a></p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2019/03/01/gatsby-pour-les-applis-web/</id>
    <title>Gatsby pour les applis Web</title>
    <published>2019-03-01T10:00:00+00:00</published>
    <link href="https://jamstatic.fr/2019/03/01/gatsby-pour-les-applis-web/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>Gatsby est génial pour la génération des sites statiques. Vous le saviez probablement déjà ! Mais c'est en fait tout aussi bien pour les applis Web. Vous ne le saviez peut-être pas. Gatsby est fait pour construire des expériences qui profitent à la fois des bénéfices des sites dits statiques et des applis web. Vous n'avez pas à sacrifier les avantages des unes pour obtenir les bénéfices des autres.</p>
<p>Dans cet article, nous allons voir certains cas d'usage d'une appli Web complexe, comme le fait d'aller requêter des données tierces dynamiques ou de s'authentifier, et nous allons vous révéler pourquoi Gatsby est un très bon choix pour construire des applis web.</p>
<p>Nous allons prendre comme exemple une appli Web classique — Gmail — que nous avons reconstruit en Gatsby pour démontrer comment Gatbsy peut servir à construire des applications modernes et plaisantes.</p>
<p>Pour commencer, nous pouvons nous demander ce qu'est, au juste, une appli Web.</p>
<blockquote>
<p>Note :
Si vous ne l'avez pas déjà fait, nous vous suggérons de regarder la vidéo « <a href="https://www.gatsbyjs.com/build-web-apps-webinar" target="_blank" rel="noopener noreferrer">Beyond Static: Building Dynamic Apps with Gatsby</a> » de laquelle la plupart des idées de ce billet sont reprises.</p>
</blockquote>
<h2 id="qu-est-ce-qu-une-appli-web">Qu'est-ce qu'une appli Web ?</h2>
<p>J'ai par le passé <a href="https://www.gatsbyjs.org/blog/2018-10-15-beyond-static-intro/#what-is-an-app" target="_blank" rel="noopener noreferrer">tenté de définir</a> ce qu'est une appli Web traditionnelle, ce qui s'est avéré étonnamment difficile. Pour résumer, je pense qu'il y a plusieurs fonctionnalités clés qui tendent à créer une expérience semblable à celle d'une app :</p>
<ul>
<li>requêtage de données dynamiques;</li>
<li>authentification des utilisateurs, et le fait d'avoir des routes authentifiées côté client;</li>
<li>des interactions côté client portées par JavaScript.</li>
</ul>
<p>Bien entendu, une appli Web n'est pas une simple checklist pour laquelle il faudrait avoir coché chacun des points précédents afin d'obtenir une expérience semblable à celle d'une app. Je pense plutôt qu'il est plus simple d’<em>observer</em> un exemple d'une appli Web — qui aurait mis en place plusieurs de ces fonctionnalités — afin de pouvoir se créer un modèle mental du type d'applis Web que Gatsby peut construire.</p>
<p>Je pense plus particulièrement à deux exemples clé, qui sont pour moi les plus représentatifs de mon modèle mental d'un appli web… Gmail et Twitter.</p>
<h2 id="gmail">Gmail</h2>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/images/post/2019-03-01_gatsby-pour-les-applis-web/gmail.6c74777bf7613fa8abc087547e4f8a68.webp 768w, /thumbnails/1024x/images/post/2019-03-01_gatsby-pour-les-applis-web/gmail.6c74777bf7613fa8abc087547e4f8a68.webp 1024w" width="1024" height="768" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/post/2019-03-01_gatsby-pour-les-applis-web/gmail.6c74777bf7613fa8abc087547e4f8a68.avif 768w, /thumbnails/1024x/images/post/2019-03-01_gatsby-pour-les-applis-web/gmail.6c74777bf7613fa8abc087547e4f8a68.avif 1024w" width="1024" height="768" sizes="100vw">
<img src="/images/post/2019-03-01_gatsby-pour-les-applis-web/gmail.6c74777bf7613fa8abc087547e4f8a68.png" alt="capture d&#039;écran de Gmail" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="768" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAEW0lEQVR4nO1b25akIAxM5vj/v7j7st+RfeCWQICAoLbTNadbRUFMUQlhbPz39w/BYhDbEiAApi1o20ojFFoiv6eWuS0R+WIqzgEtf8QSiH5j2wKif3K/9eU/q/u149Gpude5+QVcjKDXxeWEtG48Vb/zBFQ7saAPZmBF5RM4lrV0Aar6uFUZmSsaAEHpsLcpxOH8yLEp5SZlALhQ+GSFILgAjjhvmF69ZiS5MWZYidGUEbCIEIyTJvKzBkJYapymUvqnLkGY6WGcQY7jHCGI7vZsGqv1gyZNZVeK4eJd8FNrTR04wco8IYjiw29O7NslD+t8rKaUu5UB4NRxRhkBc4REIn4AEd3oYB1BbqHoMG06ofiVlTUrmIv3oBUUYEwpE4Q4NxVJ+QnH7sbS7L6n1OnxBJ6ginH0bTBOCEJyU5jIiSsCgHGUuyLysUY6sh6GlTJwzf2oEzOnEGSqwNC+9FlikkXtTggMWvQzCJDAuMXseJgQbvTABAvonoWokljLlrhVc78nJeYngeo2RZkJlxVmVeWYL2JIzE1g3N+csPDTyeFkhAgQCsYIKRhouyGpFBLVc6OVXCiqmogpTwKKfRd3wzbkc+MuC+X6vWm5IFwyFNEH62iXPoixlDunFCGSEmwJQ4Tk8YMf6qTkQZ28Q2vZ6fOU0kxA2HwHBSkpp5YDfNBlpZs739dXRyKlYzZtHWQ2plzNUDHLZEeIgpTiPL8GRpbfM9vzf5vmyEkg9mlX0IusSrkeWBymGSfPzyURQRFhlSMqBHHNaq9GTE0ZHQrPKYVu4Ii7I3+sEZFVOKsQbZLrkJPhRm6mDOtLBiuVsp2ZzOxcHQoRIVuLQdwvyHLFbFWIpoy+jeaVUm97IzN57BDl6kFZleV1ACaFxOlA+nQglDHzCk5HKdWLGsVbkSSSF4rkL8WM7FysslMh4RzM2KijlE5R58RSlMNTiRHaJcrgdnlId8QrCunU4XHkFExNSHdmSFO3IIse8kS0Wb93R/eigSfkShkK5vUW1V1TasmnPTcB849wWzoMCgGzMgKWKUQ02Ea+onwZajF9chz0Y8jMO0fL36UdaO8uZaAkIqxkFB+QqslhV8hD8YieZUtKZzplV8jITRA3qKR3z2LnesRV23FlBBgVEr9s/QIA2kzKI5TBMTNwFRy20eyZ7/1Dyp8nAEAiAMQFMy1T1+A5FKFNChUc7h3ctQ8jFIIISLPvLn4Yomvif2P8HADpFZ7WaOaN5UrJj/m0FyHNkfj+aTxOGWtwIOYmW4OwgkkKMW+FmOrCnOc6UlNwWinVhJDFqd9AzBkc0qiblAK/hIQF+Vq2/O5DkqFhcYWxI+/y9hLFuhXMPe9RuJ3gWhZn59uC+8tw+jeGo7S9USUrlBEQFbJLGTV8VaJj869w349VygiIi4uzv7O+fBHx5fgq5GEoZlkWfJXhsNJVBXwV8jAMKeSUMpT7/JpV4AF8FTKJ1a4q4BZC3pgcrsJ/eJteoIZjIGgAAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/post/2019-03-01_gatsby-pour-les-applis-web/gmail.6c74777bf7613fa8abc087547e4f8a68.png 768w, /thumbnails/1024x/images/post/2019-03-01_gatsby-pour-les-applis-web/gmail.6c74777bf7613fa8abc087547e4f8a68.png 1024w" sizes="100vw">
</picture>
<p>Gmail peut-être vu, avec le recul, comme une “preuve de concept” qui a démontré deux choses très importantes :</p>
<ul>
<li>le JavaScript côté client peut offrir une expérience proche de celles des apps, et</li>
<li>une application JavaScript (qui s'exécute dans votre navigateur) soutient favorablement la comparaison avec une application native, que ce soit sur desktop ou mobile.</li>
</ul>
<p>Il ne faut pas sous-estimer l'impact de ces bénéfices. Gmail a <em>prouvé</em> qu'une expérience de type native est non seulement possible pour les utilisateurs grâce au JavaScript côté client, mais aussi que cette solution peut être préférable et plus pratique que la solution native. Nous reviendrons sur l'exemple de ce bon vieux Gmail en temps voulu.</p>
<h2 id="twitter-progressive-web-application">Twitter (Progressive Web Application)</h2>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/images/post/2019-03-01_gatsby-pour-les-applis-web/twitter.d3b4bc96da1e9d2a41f6b22400fc237c.webp 768w, /thumbnails/1024x/images/post/2019-03-01_gatsby-pour-les-applis-web/twitter.d3b4bc96da1e9d2a41f6b22400fc237c.webp 1024w" width="1024" height="768" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/post/2019-03-01_gatsby-pour-les-applis-web/twitter.d3b4bc96da1e9d2a41f6b22400fc237c.avif 768w, /thumbnails/1024x/images/post/2019-03-01_gatsby-pour-les-applis-web/twitter.d3b4bc96da1e9d2a41f6b22400fc237c.avif 1024w" width="1024" height="768" sizes="100vw">
<img src="/images/post/2019-03-01_gatsby-pour-les-applis-web/twitter.d3b4bc96da1e9d2a41f6b22400fc237c.png" alt="Capture d&#039;écran de Twitter Lite" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="768" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/post/2019-03-01_gatsby-pour-les-applis-web/twitter.d3b4bc96da1e9d2a41f6b22400fc237c.png 768w, /thumbnails/1024x/images/post/2019-03-01_gatsby-pour-les-applis-web/twitter.d3b4bc96da1e9d2a41f6b22400fc237c.png 1024w" sizes="100vw">
</picture>
<p>Twitter est un autre très bon exemple de mon modèle mental de ce qu'est (et ce que peut être !) une appli Web parce que :</p>
<ul>
<li>ça illustre une partie de la richesse d'une expérience Web moderne, et</li>
<li>ça repose sur des pratiques avancées et des optimisations poussées de performances pour proposer une expérience rapide et plaisante.</li>
</ul>
<p>En particulier, les fonctionnalités suivantes sont des composantes clés pour un nouveau genre d'applications :</p>
<ul>
<li>le cache agressif des données et une navigation rapide grâce aux Service Workers,</li>
<li>la mise en place du <a href="https://developers.google.com/web/fundamentals/performance/prpl-pattern/" target="_blank" rel="noopener noreferrer">pattern PRPL</a> (Push, Render, Pre-cache, Lazy-Loading),</li>
<li>l'illustration du pattern Coquille Applicative (<a href="https://developers.google.com/web/fundamentals/architecture/app-shell" target="_blank" rel="noopener noreferrer"><em>App Shell</em></a>) pour rendre encore plus rapide les visites subséquentes et afficher une page la plus complète possible, le plus rapidement possible.</li>
</ul>
<p>Ces concepts modernes, lorsqu'ils sont pris ensemble, sont un atout majeur dans l'approche qu'a suivi Twitter pour proposer une expérience proche d'une app. Les ingénieur·e·s de Twitter ont réussi à isoler le cœur de l'expérience Twitter et à le rendre disponible à travers une appli Web moderne ultra-rapide grâce à l'application de techniques d'ingénierie éprouvées; et certains utilisateurs la préfèrent même à l'expérience native. Pour en apprendre davantage sur ces techniques, vous pouvez parcourir cette excellente <a href="https://developers.google.com/web/showcase/2017/twitter" target="_blank" rel="noopener noreferrer">étude de cas</a> par Addy Osmani de Google.</p>
<p>Ces deux applis Web serviront de d'exemples à garder à l'esprit quand nous allons aborder l'utilisation de Gatsby pour les applis Web.</p>
<h2 id="gatsby-est-taille-pour-les-applis-web">Gatsby est taillé pour les applis Web</h2>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/images/post/2019-03-01_gatsby-pour-les-applis-web/what-if-i-told-you.b287071dcd27bf7cd291cb3bf29ea0a7.webp 768w, /thumbnails/1024x/images/post/2019-03-01_gatsby-pour-les-applis-web/what-if-i-told-you.b287071dcd27bf7cd291cb3bf29ea0a7.webp 1024w" width="1024" height="451" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/post/2019-03-01_gatsby-pour-les-applis-web/what-if-i-told-you.b287071dcd27bf7cd291cb3bf29ea0a7.avif 768w, /thumbnails/1024x/images/post/2019-03-01_gatsby-pour-les-applis-web/what-if-i-told-you.b287071dcd27bf7cd291cb3bf29ea0a7.avif 1024w" width="1024" height="451" sizes="100vw">
<img src="/images/post/2019-03-01_gatsby-pour-les-applis-web/what-if-i-told-you.b287071dcd27bf7cd291cb3bf29ea0a7.jpg" alt="what if I told you" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="451" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A8fooorM5B6VoWorPStC3YKKTKirstSOFWqwnG7rUF7c7QeazPtRz1pI3UTsLG7UY5q/czLJFwa4mC+ZWHNbNveGROtTIdiC7jy5NQIdpq9MN3NVGTBqbiaLUUnFThs1nIxBqzHJSJsWs0VHvFFAzn6KKK3OcenWrSHC1TBwasxtkUmXB2ZUvMnNUB1rTuVyDWa/DUkzoRKg5rVtHxismJuavwyYpMbNcMCKQqDVRJqspJmosZX1GPHimcqatEAio2SkO5H5hop3l0UXAysUU/FIRXQc7GU5ZNtIarzNtosEdyaWYEdazpXGaZJMc4zUJfNLlOqJOkmDVuKXOKzN1TxS4NOwM24jmrKMRVKzbfitMQ/LmpaMrirJUykGqhBU09HxWbiO5ZwKKi8yio5WTczKQ0UV0mTGGq0/SiigI7mZL96mUUVR1rYKelFFAM2tO7V0Kf6miipkYspSfepoooqBjqKKKRJ//2Q==);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/post/2019-03-01_gatsby-pour-les-applis-web/what-if-i-told-you.b287071dcd27bf7cd291cb3bf29ea0a7.jpg 768w, /thumbnails/1024x/images/post/2019-03-01_gatsby-pour-les-applis-web/what-if-i-told-you.b287071dcd27bf7cd291cb3bf29ea0a7.jpg 1024w" sizes="100vw">
</picture>
<p>Et si je vous disais… que construire un site avec Gatsby autorisait toutes ces fonctionnalités, traditionnellement réservées à des applis Web, parce qu'un “site statique” Gatsby est en fait une appli Web ?</p>
<p>Aucune application Gatsby n'est dans les faits purement statique. Tout ce qui peut être rendu à l'aide de HTML statique dès le chargement de la page l'est. Ensuite, le JavaScript côté client (via React !) prend le contrôle en tant que socle technique pour les fonctionnalités dynamiques des applis Web. Un petit tour d'horizon du système de build de Gatsby permet d’expliquer clairement le concept. Gatsby :</p>
<ol>
<li>injecte la donnée dans les pages (depuis <a href="https://www.gatsbyjs.org/docs/querying-with-graphql/" target="_blank" rel="noopener noreferrer">GraphQL</a> ou même <a href="https://www.gatsbyjs.org/docs/using-gatsby-without-graphql/" target="_blank" rel="noopener noreferrer">sans utiliser GraphQL</a>);</li>
<li>utilise la fonction d'API de rendu côté serveur <a href="https://reactjs.org/docs/react-dom-server.html#rendertostring" target="_blank" rel="noopener noreferrer"><code>ReactDOMServer.renderToString</code></a> pour transformer les composants React en fichiers HTML;</li>
<li>injecte le runtime et des additions (comme un routeur !) pour permettre de construire des features d’applis Web.</li>
</ol>
<ul>
<li>Gatsby offre une expérience similaire à <a href="https://facebook.github.io/create-react-app/" target="_blank" rel="noopener noreferrer">create-react-app</a> une fois que le runtime est fonctionnel</li>
</ul>
<p>Pour illustrer ce concept, commençons avec une exemple classique… Nous avons besoin de requêter des points de donnée à l'execution plutôt qu'au moment du build.</p>
<pre><code class="language-jsx hljs javascript"><span class="hljs-comment">// src/pages/messages.js</span>

<span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;

<span class="hljs-keyword">import</span> Layout <span class="hljs-keyword">from</span> <span class="hljs-string">"../components/layout"</span>;

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Messages</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">React</span>.<span class="hljs-title">Component</span> </span>{
  <span class="hljs-keyword">constructor</span>(props) {
    <span class="hljs-keyword">super</span>(props);

    <span class="hljs-keyword">this</span>.state = {
      <span class="hljs-attr">messages</span>: [],
    };
  }

  <span class="hljs-comment">// note: this is a simplified example without error handling, authentication, etc.</span>
  <span class="hljs-keyword">async</span> componentDidMount() {
    <span class="hljs-keyword">const</span> messages = <span class="hljs-keyword">await</span> fetch(
      <span class="hljs-string">`/api/some-url-to-get-messages`</span>
    ).then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> response.json());

    <span class="hljs-keyword">this</span>.setState({
      messages,
    });
  }

  render() {
    <span class="hljs-keyword">const</span> { messages } = <span class="hljs-keyword">this</span>.state;
    <span class="hljs-keyword">return</span> (
      <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">Layout</span>&gt;</span>
        {messages.length === 0 ? (
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Loading messages<span class="hljs-symbol">&amp;hellip;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        ) : (
          <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
            {messages.map((message) =&gt; (
              <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{message.id}</span>&gt;</span>{message.text}<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
            ))}
          <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
        )}
      <span class="hljs-tag">&lt;/<span class="hljs-name">Layout</span>&gt;</span></span>
    );
  }
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> Messages;</code></pre>
<p>Tout ce que nous avons fait ci-dessus est d'implémenter la méthode du cycle de vie React <a href="https://reactjs.org/docs/react-component.html#componentdidmount" target="_blank" rel="noopener noreferrer"><code>componentDidMount</code></a>, qui va déclencher une requête vers une API REST pour requêter des données (des messages !) depuis une API distante. Au runtime notre application requête donc des données dynamiquement. Ceci illustre l'efficacité du runtime Gatsby + React, ainsi que la manière dont une application Gatsby est en réalité proche d'un <code>create-react-app</code> déjà hydraté avec la donnée. <a href="https://reactjs.org/docs/state-and-lifecycle.html" target="_blank" rel="noopener noreferrer">Les méthodes du lifecycle React</a> sont pleinement supportées et permettent donc d'implémenter n'importe quel type d'interaction nécessaire, comme, dans notre exemple, une requête sur une API distante.</p>
<p>Considérons l'animation ci-dessous, qui représente l'expérience utilisateur. En réalité, nous avons généré statiquement les blocs non-dynamiques (le header, la sidebar…) et nous avons requêté dynamiquement le reste des données !</p>
<img src="/images/post/2019-03-01_gatsby-pour-les-applis-web/dynamic-data-fetching-1666692133691-23.818e30180e37b0d605b9de0b3c486836.gif" alt="illustration de la coquille applicative" loading="lazy" decoding="async" class="dark:brightness-90" width="480" height="339" style="">
<p>Cependant, notre exemple peut paraître simpliste — ou en tous cas assez éloigné d'une vraie appli Web. Que se passerait-il si l'API REST <a href="https://www.gatsbyjs.org/docs/authentication-tutorial/" target="_blank" rel="noopener noreferrer">nécessitait une authentification</a> ? Et si nous souhaitions des <a href="https://www.gatsbyjs.org/docs/building-apps-with-gatsby/#client-only-routes--user-authentication" target="_blank" rel="noopener noreferrer">routes gérées côté client</a>, par exemple pour avoir un lien direct vers un message spécifique ? Tout est possible ! Est-ce qu'on peut profiter de GraphQL lors du build <em>et</em> du runtime ? Bien sûr !</p>
<p>L'idée principale que je tiens à clarifier dans cet article est que les fonctionnalités <em>typiques</em> d'une appli Web sont non seulement possibles avec Gatsby, mais aussi et surtout simples et intuitives à implémenter grâce au runtime dynamique disponible dans chaque application Gatsby. Ce n'est <em>que</em> du React.</p>
<p>Gatsby sert à construire des applis Web dynamiques, de la même manière que Gatsby sert à construire des sites statiques. C'est maintenant acté. Gatsby peut être utilisé pour beaucoup de cas d'utilisations typiques d'une appli Web, que ce soit l'authentification, les routes côté client, le requêtage dynamique de données et bien plus.</p>
<h2 id="pourquoi-utiliser-gatsby-pour-une-appli-web">Pourquoi utiliser Gatsby pour une appli Web ?</h2>
<p><strong>Des optimisations de performance, par défaut</strong></p>
<p>Si l'on regarde les avantages que procure Gatsby, à minima :</p>
<ul>
<li>le rendu statique de composants React et des données associées en HTML statique;</li>
<li>l'optimisation des données, des images, etc. pour obtenir des sites ultra-rapides;</li>
<li>la présence de patterns liés à la performance et des bonnes pratiques comme le <a href="https://developers.google.com/web/fundamentals/performance/prpl-pattern/" target="_blank" rel="noopener noreferrer">PRPL</a>, la séparation de code par route, etc.</li>
</ul>
<p>Chacun de ces avantages mériteraient que l'on s'y attarde un peu plus, mais nous nous contenterons de dire que ce sont d' <em>excellentes</em> fonctionnalités que vous souhaitez avoir dans vos sites <em>statiques</em>, mais aussi dans vos applis web.</p>
<p>Ces optimisations de performances ne sont pas à activer — elles sont présentes par défaut. Quand de nouvelles techniques et de nouvelles optimisations apparaîtrons et gagneront en popularités, nous pourrons les y ajouter tout comme nous avons ajouté celles-ci. Ces optimisations peuvent alors être poussées à vos utilisateurs en mettant à jour votre version de Gatsby, un peu comme les améliorations d'outillage peuvent être obtenues en mettant à jour <a href="https://facebook.github.io/create-react-app/" target="_blank" rel="noopener noreferrer">create-react-app</a>.</p>
<p><strong>Les plugins et l'écosystème Gatsby</strong></p>
<p>Un des avantages principaux de Gatsby est son architecture modulaire. Vous avez besoin d'un plugin pour <a href="https://www.gatsbyjs.org/packages/gatsby-source-wordpress/" target="_blank" rel="noopener noreferrer">récupérer des données de Wordpress</a> ? Bien sûr, cela semble raisonnable. Vous avez besoin de <a href="https://www.gatsbyjs.org/packages/gatsby-transformer-yaml/" target="_blank" rel="noopener noreferrer">transformer des données YAML</a> en objets JavaScript plus simples à manipuler ? Allez, pourquoi pas ! Une envie de se <a href="https://www.gatsbyjs.org/packages/gatsby-source-graphql/" target="_blank" rel="noopener noreferrer">raccorder à une API GraphQL distante</a> et d'injecter ses données au moment du build ? Ah, je vois qu'on a des goûts de luxe. Vous souhaitez afficher des images optimisées, adaptées à toutes les tailles et qui affichent un effet de flou lors du chargement ? C'est parti !</p>
<p>Prenons le temps de regarder plus en détail ces fonctionnalités proposées par un de nos composants, <a href="https://www.gatsbyjs.org/packages/gatsby-image/" target="_blank" rel="noopener noreferrer"><code>gatsby-image</code></a>.</p>
<p><strong><code>gatsby-image</code></strong></p>
<p><a href="https://www.gatsbyjs.org/packages/gatsby-image/" target="_blank" rel="noopener noreferrer"><code>gatsby-image</code></a> est certainement un de mes composants préférés parmi ceux que Gatsby propose et maintient. Il offre un excellent rendu d'images, et amène des optimisations rendues possibles par des plugins comme <a href="https://www.gatsbyjs.org/packages/gatsby-plugin-sharp/" target="_blank" rel="noopener noreferrer"><code>gatsby-plugin-sharp</code></a>. Ces deux techniques, couplée avec l'API GraphQL disponible dans n'importe quelle application Gatsby, simplifie grandement l'expérience de développement lorsque l'on souhaite afficher des images optimisées. Parmi les fonctionnalités disponibles, citons :</p>
<ul>
<li>l'adaptabilité de la taille des images pour en afficher une optimisée à celle de votre interface — ce qui permet de reléguer la fameuse image de 5Mo en tête de page au fin fond des oubliettes;</li>
<li>la génération de multiples images, adaptées aux appareils mobiles, en utilisant <code>srcset</code> pour ne télécharger que celle dont votre utilisateur a besoin;</li>
<li>le chargement asynchrone des images avec une technique de flou dégressif — ou même de <a href="https://using-gatsby-image.gatsbyjs.org/traced-svg/" target="_blank" rel="noopener noreferrer">SVG tracé</a>.</li>
</ul>
<p><code>gatsby-image</code> est incroyable. Si vous n'avez pas encore pu tester ses fonctionnalités dans vos sites, je vous recommande chaudement de vous y mettre ! Vous pouvez vous inspirer de notre exemple, fraîchement redesigné, “<a href="https://using-gatsby-image.gatsbyjs.org/" target="_blank" rel="noopener noreferrer">Using Gatsby Image</a>” (en anglais) pour en apprendre plus et le voir dans un cas d'utilisation réelle. En somme, utilisez <code>gatsby-image</code> et vos utilisateurs vous remercieront.</p>
<p>Le potentiel de ces composants et de ces plugins est immense. De la même manière que les composants réutilisables ont été incroyablement importants dans le succès de l'écosystème React, les plugins ont une valeur immense pour les applications Gatsby. Pourquoi perdre du temps de développement à réimplémenter ces composants vous-même lorsque vous pouvez réutiliser et profiter du potentiel de l'écosystème Open Source ? Lorsque vous utilisez ces plugins et ces composants, vous pouvez passer davantage de temps à construire votre propre application Gatsby. Jetez un oeil à notre <a href="https://www.gatsbyjs.org/plugins" target="_blank" rel="noopener noreferrer">librairie de plugins</a> si ce n'est pas déjà fait !</p>
<p>Maintenant, la suite : nous allons comparer l'expérience utilisateur lorsque nous requêtons des données derrière une authentification entre une application Gatsby, et une application rendu côté serveur.</p>
<h2 id="la-coquille-applicative">La Coquille Applicative</h2>
<p>En ajoutant juste le plugin <a href="https://www.gatsbyjs.org/packages/gatsby-plugin-offline/" target="_blank" rel="noopener noreferrer"><code>gatsby-plugin-offline</code></a>, nous transformons notre appli Web en une Progressive Web App complète, qui fonctionne hors ligne, et qui crée une “coquille applicative” en enregistrant un Service Worker. Une coquille applicative est en réalité une collection de composants de votre application (par exemple, l'en tête, le pied de page, la navigation latérale, etc…) qui sont disponibles immédiatement depuis le Service Worker pendant que le contenu dynamique est récupéré en tâche de fond. Cela permet de créer des expériences utilisateur immersives, car l'application apparaît instantanément à l'écran et le contenu dynamique se remplit progressivement.</p>
<p>Si nous regardons de plus près cette approche, elle se décompose comme suit:</p>
<ul>
<li>on rend le plus de contenu possible instantanément (la coquille applicative);</li>
<li>on fait des requêtes asynchrones pour aller chercher des données supplémentaires, par exemple du contenu venant d'une API (plus particulièrement d'une API authentifiée).</li>
</ul>
<p>Comparons cette méthode avec celle du rendu côté serveur. Pour cet exemple, nous allons considérer que nous devons nous connecter à une API authentifiée. L'appel API que l'on déclenche sert à peupler le contenu de la page avant qu'elle soit envoyée (en HTML) à l'utilisateur. Nous devons donc attendre que l'appel API soit terminé avant de commencer à charger la page, plutôt que de proposer la coquille applicative et d'aller chercher les données dans un second temps, en tâche de fond.</p>
<p>Pour illustrer cet exemple, nous pouvons nous appuyer sur l'animation suivante. Sur la gauche, on peut voir une application qui utilise un Service Worker et une coquille applicative — une application Gatsby par exemple. Sur la droite, c'est une application rendue côté serveur, qui doit donc attendre que l'appel API soit terminé avant de pouvoir proposer la page complète, d'un coup.</p>
<img src="/images/post/2019-03-01_gatsby-pour-les-applis-web/app-shell-web-apps.36796aa34d81c4aa0b71ac77a6f3e870.gif" alt="comparaison coquille applicative vs rendu côté serveur" loading="lazy" decoding="async" class="dark:brightness-90" width="480" height="339" style="">
<p>Nous voyons donc clairement les avantages de cette approche. Lorsque nous chargeons une coquille applicative, nous donnons à nos utilisateurs l'impression que la page se charge plus rapidement, bien que si nous comparons les deux approches, elles affichent toutes deux <em>toute la donnée</em> au même moment. Nous avons donc le meilleur des deux mondes… On donne la perception que le site est très rapide, tout en sachant qu'il <em>est</em> très rapide grâce aux optimisations que l'on obtient avec Gatsby, par défaut.</p>
<p>Afin de revenir sur tous ces concepts, j'ai préparé une application de démonstration qui ressemble à notre vieil ami Gmail. Cette démonstration montre que les applis Web complexes sont non seulement <em>possibles</em> avec Gatsby, mais aussi que Gatsby est un excellent choix lorsque l'on doit en construire une.</p>
<h2 id="et-voici-gatsby-mail-qui-ne-sert-qu-a-des-fins-de-demonstration">Et voici… Gatsby Mail ! (Qui ne sert qu'à des fins de démonstration !)</h2>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/images/post/2019-03-01_gatsby-pour-les-applis-web/gatsby-mail.24b7eea294e201aa9918deda40d84220.webp 768w, /thumbnails/1024x/images/post/2019-03-01_gatsby-pour-les-applis-web/gatsby-mail.24b7eea294e201aa9918deda40d84220.webp 1024w" width="1024" height="768" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/post/2019-03-01_gatsby-pour-les-applis-web/gatsby-mail.24b7eea294e201aa9918deda40d84220.avif 768w, /thumbnails/1024x/images/post/2019-03-01_gatsby-pour-les-applis-web/gatsby-mail.24b7eea294e201aa9918deda40d84220.avif 1024w" width="1024" height="768" sizes="100vw">
<img src="/images/post/2019-03-01_gatsby-pour-les-applis-web/gatsby-mail.24b7eea294e201aa9918deda40d84220.png" alt="gatsby mail" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="768" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAHI0lEQVR4nN1c3ZazNgyckdP3f9DefL1frF5YsmWHECAEb6tziAIE/w0jWbJ3+feffxQAAFf7tJ7ULuP59cLwGb8Mv+KLGxu/faft5JR+7G7NRfJ9IGpN4TN+OQbCoRpV2wCflNsBGeU7AG2zQ1UrKHvq/xaAazINkFcD8V0Gsb3Aipdm7KhcwQyX6QwBehBUtTMvHwnrh50qFAQBKN/XcyczXKYD0jl9LVrLhVO42HCDZJl/UEFlAKdRY8DrVNur6TtfTCfTAQEMBFWoApqLroDs7WmdrJQBb9ogCHrA5bmog8y40sj+CkAKMwyMHMBx1uwpo5KAILRqkoUlMM0CEgM9jjJlixmfgjMVkMYMrWDkHM61hT+bPWXTFQxqA8jPDRyt1wJj8LnPuIIpcxnizDBQsh0dU3IAZUuCm4iBWgPCz+2aEEICUhB5F0ACPTNWurJ5vlfmAOJAAMVXKJCNHTnnxpQMaC6/f9vDyBJqNUmsQJgWA0UBSHlM/IOHLNdW907L41qXdFAGhlSztTgggSH6hinVL6CaolVmaGEGlYCycEMEoiggVfsVmrnBjJUubZ6/kykMqbOnOtVtwDSmKHTpQVn1Jx0z7CsRgAiACCEKqABijwkBhRoJWW44Uz4MHs+86vczpJofrZggl6P6jqzQJQemxOnwc4srAMCKD0EzU0KoEJIKO9xmEcXR+6zOmaI+O9vRpXfX9o7yxNQJWiCowYFnFCDikYdZ18CQBoSfM5wXMEQMkESoEkgCDyAzFVSFavEtqqHcrT7s7euBcZnnQxSdb3AWVJO1KBZnyTLMukIxHRgYUuHVkQNZCiiqAqTmJ5gLYMWltNRKreQFKntY8eralkybZbmODMmK4jcWRV4y8k/GEgDJwWxViQxxHcBwLZUdKE4EClIh1AKEFIb4C3IkJLk4Up/DkGKutWNHZchSgFgWbaAMUXyUHhCGgND8hwAqAs2sz7qzz6JgVjBbekW0n229av/Je+9kemBYWRIcegXlZ2CJ/86fjdIxpTdXIkQWILnfgGKhgszI5lskB5Z4+QfM1db1IzI1DmkxCJA9OHRH/tNAWUY/8i4eQT/DKv4DXVfJ4ldy1gKGlf0u3jkyezo57Z0k1tqYVIwO3UFZFi1MMd+yBxCgmSS6M0+tTosFkRciiyCnCIbalHh3F3Zf3yPTI3VgJVKPwPzkY4CYREBU2FIwRJl1JQ6JzAbKgaa/PD8r09Pv3QJVFxj61LdNgWvk/i63xQGQRLifpgC6sPNZOAjGUx/OPbYqcxniEgdY2wC1I0M1l0ywxSK7AbETihfNng1d+H98LK4evekMqcJB1+xSf5Tcxsb4xQjbgQHseXSHl9XXO1emMyTGEDFVLuaMxXwAPK0BhtT9eoExeqflrmJ5IiGdUpn0O2Q+Q0iIAUCbnqZkdj8RyAQhyCzJvi7ru1luAEQISYL0EDwSkeyI4NQE5GRkpjIkrluQRJIGhD4s7wS1wE6had8MqxT+PPVND2lHBCWVl4LSnpsl0xjimVmhpcQNDMmC9Cg0IMp6RQ6R+u73ZwCEQqQkEAfkLwdGKjsrQ+YCchVDDvYiLiDZW6q5vLUt+WdpjyX3icVXTWZQK+l3SYQ8pJqvlATp0XyMg3KmO1fJhQzZSP6MEp24EJJZqGBg+EuaSWTJUAviYpb4VblVDdleN1s0lkgydiSBiOmwwjhLLmDIycbbG1x3fiihYdGoZGIFOcHijiEgfFoUaba/7rlaAyVJ1X4wNbO2tj3oTrmAIZEZ+8D19HihgoKCsvnAl1X9NrVcy8GZb7BjzPYC7MwWJDBFpLDFWWP3fPfJLPmAIeeZASAk+pqN95CNIDIFpHbMeBV7PK8WWkUDQ7yuygaR8D0yYx4oHzBkZMYZp26P1c1qAkFJtpK5LKta5nVrulvXPwbdMyRod94idc2EFY25QfsJhlzYXBK2PNcNmu1cK+bMNl3zqZnOhuY4OOh6/4kpgTFuooLJaz7pfmgeR6vUT5mB8XG2gRArLwsgtptBt9Iaa4zYAGpgSwTot+RODjDkCy3m8F3j22pLfM/U6B5eG/hqqlZMVvvJfiBu/ZO2vVVdwoyXUkxX7bc5EhpDth6rtqam2su1J0CAJ2B+EzNcdjDkiy3mcML1y9tlsAOmrn/YvScgvLwzlpb8eubvLUO+y4xBbEDrLhzbJ7X1+6rXQHA9sKV79pfJBkNubLG9uOp+4UgzKihxoLmur4ovvuhTXjLkVmYAcEjalOvAY3v1F6az1urLZIUh93K5/LWsNvt/tilrvuEDf7Gvzsboy3adjG29nxlBPqnqpJP+pLqV/CaAz8CZuEDFugXoaWB+qcMdpYIytD++zkfl4WX1uaKbmTFZ9jAlbHrczYwzpux2hmwy4z8irwb6iv/qMH/XyS+StRfkLma43AbI/5oZpuu9D/r3L3zykbjCPnNcAAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/post/2019-03-01_gatsby-pour-les-applis-web/gatsby-mail.24b7eea294e201aa9918deda40d84220.png 768w, /thumbnails/1024x/images/post/2019-03-01_gatsby-pour-les-applis-web/gatsby-mail.24b7eea294e201aa9918deda40d84220.png 1024w" sizes="100vw">
</picture>
<p><a href="https://gatsby-mail.netlify.com/" target="_blank" rel="noopener noreferrer">Gatsby Mail</a> reprend certains des concepts et des thèmes que j'ai abordés. Plus particulièrement :</p>
<ol>
<li>Gmail, Twitter et les autres sont des exemples parlants d'applis Web complexes qui proposent des expériences riches.</li>
<li>Gatsby fournit des composants, des plugins, etc… qui permettent de développer ces expériences, servez-vous en !</li>
<li>Gatsby est un excellent choix lorsqu'il s'agit de construire de telles applis Web.</li>
</ol>
<p>De plus, Gatsby Mail fait la démonstration de fonctionnalités spécifiques aux applis Web, comme :</p>
<ul>
<li>du rendu statique couplé à des requêtes dynamiques grâce au <em>runtime</em> côté client;</li>
<li>de l'authentification et des routes gérées côté client;</li>
<li>des routes non-authentifiées, comme une page d'accueil (qui utilise <a href="https://reactjs.org/docs/context.html" target="_blank" rel="noopener noreferrer">l'API Context</a> de React);</li>
<li>l'utilisation de GraphQL au moment du build <em>et</em> <em>au runtime</em>, en s'appuyant sur une API GraphQL distante et sur <a href="https://github.com/apollographql/apollo-client/tree/master/packages/apollo-boost" target="_blank" rel="noopener noreferrer"><code>apollo-boost</code></a>;</li>
<li>le chargement d'une coquille applicative grâce à <code>gatsby-plugin-offline</code> (voyez par vous-même la démo en “3G rapide” ci-dessous !).</li>
</ul>
<p>et même des thèmes clairs/sombres, parce que pourquoi pas ! Vous pouvez voir ce que ce tout cela donne, et comment cela amène à une très bonne expérience utilisateur dans l'exemple ci-dessous, pour lequel j'ai simulé une connexion 3G rapide. La coquille applicative (l'en-tête, le pied de page, etc…) s'affiche <em>instantanément</em> pendant que l'on va chercher le contenu dynamique (depuis notre API distante GraphQL) en tâche de fond.</p>
<img src="/images/post/2019-03-01_gatsby-pour-les-applis-web/gatsby-mail-app-shell.cf8c7facfb9b75d8ac0880fa7397d047.gif" alt="animation de la coquille applicative sur Gatsby Mail" loading="lazy" decoding="async" class="dark:brightness-90" width="600" height="451" style="">
<p>Vous pouvez aller jeter un oeil au <a href="https://github.com/dschau/gatsby-mail" target="_blank" rel="noopener noreferrer">repository GitHub</a> pour en savoir plus sur comment elle a été développée, et vous approprier quelques unes de ces techniques pour la prochaine <strong>appli</strong> Web que vous développerez en Gatsby ;)</p>
<p>On a hâte de voir ce que vous allez construire.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2019/02/07/c-est-quoi-la-jamstack/</id>
    <title>C&#039;est quoi la Jamstack au juste ?</title>
    <published>2019-02-07T19:12:14+00:00</published>
    <updated>2019-02-28T08:17:42+00:00</updated>
    <link href="https://jamstatic.fr/2019/02/07/c-est-quoi-la-jamstack/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p><a href="https://twitter.com/peduarte" target="_blank" rel="noopener noreferrer">Pedro Duarte</a> a lancé <a href="https://jamstack.wtf" target="_blank" rel="noopener noreferrer">https://jamstack.wtf</a> un mini-site afin de donner une vue d'ensemble de la Jamstack. Nous vous proposons ici sa traduction en français afin de permettre à toujours plus de développeurs d'adopter cette façon de travailler.</p></aside>
<p>La Jamstack révolutionne notre manière de travailler en proposant une expérience de développement plus simple, de meilleures performances, des coûts bien moins élevés et une grande scalabilité.</p>
<p>Vous vous demandez peut-être ; oui OK, mais comment ? pourquoi ? c'est quoi au juste ?</p>
<p>C'est la raison d'être de cette page <a href="https://jamstack.wtf" target="_blank" rel="noopener noreferrer">https://jamstack.wtf</a>.</p>
<p>Le but de ce guide est de présenter de manière claire le concept de la Jamstack et d'inciter d'autres développeurs à adopter cette approche.</p>
<p>Le contenu ci-dessous est tiré du site ci-dessus 👆</p>
<p>Asseyez-vous, mettez-vous à l'aise et appréciez ✌️</p>
<hr>
<div id="toc"><ul>
<li><a href="#c-est-quoi-la-jamstack">C'est quoi la Jamstack ?</a><ul>
<li><a href="#signification">Signification</a></li>
<li><a href="#benefices">Bénéfices</a></li>
<li><a href="#bonnes-pratiques">Bonnes pratiques</a></li>
<li><a href="#chaine-de-publication">Chaîne de publication</a></li>
<li><a href="#historique">Historique</a></li>
</ul>
</li>
<li><a href="#bien-demarrer">Bien démarrer</a><ul>
<li><a href="#developpement">Développement</a></li>
<li><a href="#deploiement">Déploiement</a></li>
<li><a href="#parties-dynamiques">Parties dynamiques</a></li>
<li><a href="#cms">CMS</a></li>
<li><a href="#ressources">Ressources</a></li>
</ul>
</li>
<li><a href="#a-propos">À propos</a></li>
</ul></div>
<hr>
<h2 id="c-est-quoi-la-jamstack">C'est quoi la Jamstack ?</h2>
<h3 id="signification">Signification</h3>
<figure>
<img src="/images/post/2020-10-05_la-jamstack-n-est-rapide-que-si-vous-la-rendez-rapide/jamstack-horizontal.cb75428480e15291ef03268a1c4fa2ef.svg" alt="JAM c&#039;est pour JavaScript, APIs &amp; Markup." title="JAM c&#039;est pour JavaScript, APIs &amp;amp; Markup." loading="lazy" decoding="async" class="dark:brightness-90" width="492" height="139">
<figcaption>JAM c'est pour JavaScript, APIs &amp; Markup.</figcaption>
</figure>
<p><strong>JavaScript</strong><br>
Les fonctionnalités dynamiques sont gérées par JavaScript. Vous êtes libres d'utiliser la bibliothèque ou le framework que vous voulez.</p>
<p><strong>APIs</strong><br>
Les opérations côté serveur sont abstraites sous forme d'APIs réutilisables, accessibles en HTTPS à l'aide de JavaScript. Ces opérations peuvent être déléguées à des services tiers ou bien à vos propres fonctions.</p>
<p><strong>Markup</strong><br>
Les sites web sont servis sous forme de fichiers HTML statiques. Ces fichiers peuvent être générés à partir de fichiers source, comme du Markdown, à l'aide d'un générateur de site statique.</p>
<h3 id="benefices">Bénéfices</h3>
<p>Les principaux bénéfices apportés par la Jamstack sont :</p>
<p><strong>Une performance accrue</strong><br>
Servir du code généré et des assets à partir d'un CDN</p>
<p><strong>Une meilleure sécurité</strong><br>
Plus besoin de se soucier des vulnérabilités du serveur ou de la base de données</p>
<p><strong>Un coût bien moindre</strong><br>
L'hébergement de fichiers statiques est moins cher <a href="https://netlify.com" target="_blank" rel="noopener noreferrer">voire gratuit</a></p>
<p><strong>Une meilleure expérience de développement</strong><br>
Les développeurs front end peuvent se focaliser sur la partie client, sans être dépendants d'une architecture monolithique. Cela se traduit en général par un développement plus rapide et plus ciblé.</p>
<p><strong>Redimensionnement à la volée</strong><br>
Si votre site devient viral ou est soumis à un pic d'activité, le CDN compensera sans problèmes.</p>
<h3 id="bonnes-pratiques">Bonnes pratiques</h3>
<p>Les astuces suivantes vous aideront à tirer le meilleur parti de la stack.</p>
<p><strong>Réseau de distribution de contenu (CDN)</strong><br>
Puisque tous les fichiers et les assets sont générés en amont, ils peuvent être servis sur un CDN. Cela procure une meilleure performance et un redimensionnement à la volée.</p>
<p><a href="https://www.cloudflare.com/learning/cdn/what-is-a-cdn/" target="_blank" rel="noopener noreferrer">En savoir plus</a></p>
<p><strong>Déploiement atomique</strong><br>
Chaque déploiement est une photographie complète du site. Vous disposez ainsi d'une version consistante du site à l'échelle mondiale.</p>
<p><a href="https://buddy.works/blog/introducing-atomic-deployments#what-are-atomic-deployments" target="_blank" rel="noopener noreferrer">En savoir plus</a></p>
<p><strong>Invalidation du cache</strong><br>
Une fois votre site généré poussé en ligne, le CDN va invalider son cache. Cela signifie que la nouvelle version est instantanément disponible partout.</p>
<p><a href="https://www.netlify.com/blog/2015/09/11/instant-cache-invalidation/" target="_blank" rel="noopener noreferrer">En savoir plus</a></p>
<p><strong>Tout est versionné</strong><br>
Votre code vit dans un système de gestion de versions tel que Git. Les principaux avantages sont : l'historique des changements de chaque fichier et de chaque collaborateur ainsi que la traçabilité.</p>
<p><a href="https://www.atlassian.com/git/tutorials/what-is-version-control" target="_blank" rel="noopener noreferrer">En savoir plus</a></p>
<p><strong>Générations automatiques</strong><br>
Votre serveur est notifié lorsqu'une nouvelle génération est requise, typiquement à l'aide de webhooks. Le serveur génère le projet, met à jour les CDNs et le site est en ligne.</p>
<p><a href="https://www.agilealliance.org/glossary/automated-build" target="_blank" rel="noopener noreferrer">En savoir plus</a></p>
<h3 id="chaine-de-publication">Chaîne de publication</h3>
<p>Voici à quoi ressemblerait la chaîne de publication Jamstack idéale.</p>
<figure>
<img src="/images/post/2019-02-07_c-est-quoi-la-jamstack/chaine-publication.e492cc9d576f138d8ae1946826f895e4.svg" alt="Chaine de publication" title="Chaine de publication" loading="lazy" decoding="async" class="dark:brightness-90" width="326" height="1384">
<figcaption>Chaine de publication</figcaption>
</figure>
<h3 id="historique">Historique</h3>
<p><strong>2015</strong><br>
Les générateurs statiques sont de plus en plus en vogue, grâce à des générateurs populaires comme Jekyll.</p>
<p><strong>2016</strong><br>
Quelques développeurs pensent que les sites statiques n'ont pas à être forcément statiques, le terme "Jamstack" fait son apparition.</p>
<p><strong>2017</strong><br>
La révolution du web moderne commence à prioriser la performance, le redimensionnement à la volée et l'expérience de développement. Le terme Jamstack est adopté par un groupe de développeurs plus important et les premières entreprises commencent à annoncer des projets basés sur la Jamstack.</p>
<p><strong>2018</strong><br>
Des outils comme Netlify, Gatsby et Contentful contribuent à promouvoir le terme et la communauté grandit vite. C'est aussi l'année de la première conférence Jamstack.</p>
<p><a href="https://snipcart.com/blog/jamstack" target="_blank" rel="noopener noreferrer">Source : Snipcart</a></p>
<hr>
<h2 id="bien-demarrer">Bien démarrer</h2>
<p>C'est à vous de décider comment générer vos fichiers HTML. Les trois approches les plus communes sont :</p>
<h3 id="developpement">Développement</h3>
<p><strong>À la main</strong><br>
Une méthode simple et efficace d'écrire du HTML, c'est idéal pour les pages super simples.</p>
<p><strong>Générateurs de site statique</strong><br>
La plupart des sites Jamstack sont propulsés par un générateur de site statique.
Vous êtes libres de choisir votre GSS.</p>
<ul>
<li><a href="https://www.gatsbyjs.org/" target="_blank" rel="noopener noreferrer">Gatsby</a></li>
<li><a href="https://nextjs.org/" target="_blank" rel="noopener noreferrer">Next.js</a></li>
<li><a href="https://gohugo.io/" target="_blank" rel="noopener noreferrer">Hugo</a></li>
</ul>
<p><a href="https://www.staticgen.com/" target="_blank" rel="noopener noreferrer">Voir davantage de générateurs</a></p>
<p><strong>Framework frontend</strong><br>
La plupart des frameworks ne génèrent pas de fichiers HTML statiques par défaut, toutefois c'est possible si vous connaissez bien vos outils, cela demande plus d'expérience et de maintenance.</p>
<ul>
<li><a href="http://reactjs.org/" target="_blank" rel="noopener noreferrer">React</a></li>
<li><a href="https://vuejs.org/" target="_blank" rel="noopener noreferrer">Vue.js</a></li>
<li><a href="https://preactjs.com/" target="_blank" rel="noopener noreferrer">Preact</a></li>
</ul>
<h3 id="deploiement">Déploiement</h3>
<p>Vous devez héberger le résultat de la compilation de votre site. Il existe de fantastiques services qui font cela gratuitement et simplement.</p>
<ul>
<li><a href="https://netlify.com/" target="_blank" rel="noopener noreferrer">Netlify</a></li>
<li><a href="https://vercel.com/" target="_blank" rel="noopener noreferrer">Vercel</a></li>
<li><a href="https://pages.github.com/" target="_blank" rel="noopener noreferrer">Github Pages</a></li>
</ul>
<p><a href="https://www.thenewdynamic.org/tools/hosting-deployment/" target="_blank" rel="noopener noreferrer">Voir plus de services de déploiement</a></p>
<h3 id="parties-dynamiques">Parties dynamiques</h3>
<p>Les sites Jamstack n'ont pas à être entièrement statiques. Il existe des services formidables pour vous aider à insérer des parties dynamiques dans votre projet.</p>
<p><strong>Fonctions personnalisées</strong><br>
Vous pouvez également abstraire vos propres fonctions pour en faire des APIs réutilisables. Pour cela vous pouvez utiliser <a href="https://aws.amazon.com/lambda/features/" target="_blank" rel="noopener noreferrer">les fonctions AWS lambda</a> ou <a href="https://functions.netlify.com/examples/" target="_blank" rel="noopener noreferrer">les fonctions Netlify</a></p>
<p><strong>Commentaires</strong><br>
Beaucoup de sites Jamstack intègrent des sections pour les commentaires, principalement sur des blogs.</p>
<p><strong>Formulaires</strong><br>
Un excellent moyen d'interagir avec votre audience.</p>
<p><strong>E-Commerce</strong><br>
Mettre en place une boutique en ligne sur un site Jamstack n'a jamais été aussi simple.</p>
<p><strong>Recherche</strong><br>
Reposez-vous sur des services tiers pour intégrer des fonctionnalités de recherche.</p>
<p><a href="https://github.com/agarrharr/awesome-static-website-services#e-commerce" target="_blank" rel="noopener noreferrer">Voir plus de services pour les sites statiques</a></p>
<h3 id="cms">CMS</h3>
<p>Les sites Jamstack peuvent aussi être gérés via un système de gestion de contenu, plus précisément avec des CMS headless. Chaque changement effectué dans le CMS va entraîner une nouvelle génération du site, qui sera ensuite déployé sous forme de fichiers statiques.</p>
<ul>
<li><a href="http://contentful.com/" target="_blank" rel="noopener noreferrer">Contentful</a></li>
<li><a href="https://forestry.io/" target="_blank" rel="noopener noreferrer">Forestry</a></li>
<li><a href="https://docs.ghost.org/api/content/" target="_blank" rel="noopener noreferrer">Ghost</a></li>
<li><a href="https://developer.wordpress.org/rest-api/" target="_blank" rel="noopener noreferrer">Headless WordPress</a></li>
<li><a href="https://www.netlifycms.org/" target="_blank" rel="noopener noreferrer">Netlify CMS</a></li>
<li><a href="https://strapi.io/" target="_blank" rel="noopener noreferrer">Strapi</a></li>
</ul>
<p><a href="https://headlesscms.org/" target="_blank" rel="noopener noreferrer">Voir plus de services de gestion de contenu</a></p>
<h3 id="ressources">Ressources</h3>
<p>Voici une sélection de ressources sur la Jamstack qui comporte des matériaux d'apprentissage ainsi que des listes de services.</p>
<h4 id="services">Services</h4>
<ul>
<li><a href="https://github.com/agarrharr/awesome-static-website-services" target="_blank" rel="noopener noreferrer">Une liste de services pour les sites web statiques</a></li>
<li><a href="https://headlesscms.org/" target="_blank" rel="noopener noreferrer">Une liste de gestionnaires de contenu pour les sites Jamstack</a></li>
<li><a href="https://www.staticgen.com/" target="_blank" rel="noopener noreferrer">Une liste de générateurs de site statiques pour les sites Jamstack</a></li>
<li><a href="https://www.thenewdynamic.org/tool/" target="_blank" rel="noopener noreferrer">Un annuaire de sélection d'outils et de services</a></li>
</ul>
<h4 id="articles">Articles</h4>
<ul>
<li><a href="https://snipcart.com/blog/jamstack" target="_blank" rel="noopener noreferrer">Débuter avec la Jamstack? Tout ce que vous devez savoir pour bien démarrer</a></li>
<li><a href="https://www.quora.com/What-is-the-concept-behind-Jamstack" target="_blank" rel="noopener noreferrer">Quel est le concept derrière la Jamstack</a></li>
<li><a href="https://bejamas.io/blog/jamstack-modern-web-development/" target="_blank" rel="noopener noreferrer">Développement web moderne avec la Jamstack</a></li>
<li><a href="/2017/03/17/smashing-mag-va-dix-fois-plus-vite/">Smashing Magazine va dix fois plus vite</a></li>
<li><a href="https://blog.ghost.org/jamstack/" target="_blank" rel="noopener noreferrer">Ghost avec la Jamstack</a></li>
<li><a href="https://medium.com/netlify/jamstack-with-gatsby-netlify-and-netlify-cms-a300735e2c5d" target="_blank" rel="noopener noreferrer">Jamstack avec Gatsby, Netlify et Netlify CMS</a></li>
</ul>
<h4 id="videos">Vidéos</h4>
<ul>
<li><a href="https://www.youtube.com/watch?v=uWTMEDEPw8c" target="_blank" rel="noopener noreferrer">L'essor de la Jamstack, présentation de Mathias Biilmann</a></li>
<li><a href="https://vimeo.com/163522126" target="_blank" rel="noopener noreferrer">La nouvelle stack Front-end, présentation de Mathias Biilmann</a></li>
<li><a href="https://www.thenewdynamic.org/video/" target="_blank" rel="noopener noreferrer">Une sélection de vidéos par The New Dynamic</a></li>
<li><a href="https://www.youtube.com/watch?v=e5H7CI3yqPY" target="_blank" rel="noopener noreferrer">Comment freeCodeCamp sert des millions d'apprenants en utilisant la Jamstack</a></li>
</ul>
<h4 id="podcast">Podcast</h4>
<ul>
<li><a href="https://www.heavybit.com/library/podcasts/jamstack-radio/" target="_blank" rel="noopener noreferrer">Jamstack Radio</a></li>
</ul>
<hr>
<h2 id="a-propos">À propos</h2>
<p>Cette page a été mise en place par <a href="https://twitter.com/peduarte" target="_blank" rel="noopener noreferrer">@peduarte</a> et présentée au <a href="https://www.meetup.com/Jamstack-London/events/257961818/" target="_blank" rel="noopener noreferrer">meetup Jamstack de Londres</a> — (<a href="https://speakerdeck.com/peduarte/jamstack-cheatsheet" target="_blank" rel="noopener noreferrer">voir les slides</a>).</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2019/02/06/de-wordpress-a-hugo-un-nouvel-etat-d-esprit/</id>
    <title>De WordPress à Hugo : adopter un nouvel état d&#039;esprit</title>
    <published>2019-02-06T16:12:14+00:00</published>
    <updated>2019-05-03T14:12:14+00:00</updated>
    <link href="https://jamstatic.fr/2019/02/06/de-wordpress-a-hugo-un-nouvel-etat-d-esprit/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>Dans cet article, nous n'allons pas migrer un site de WordPress vers Hugo, nous allons voir comment passer des habitudes que vous avez prises avec WordPress à celles d'Hugo.</p>
<p>Nous allons soigneusement comparer les concepts d'Hugo et son vocabulaire avec ceux de WordPress, avec lesquels vous êtes déjà familier, afin que la courbe d'apprentissage soit un peu moins rude.</p>
<p>Nous allons partir de <code>the_post()</code>, <code>the_loop</code> et de la hiérarchie de modèle, pour mieux comprendre comment Hugo fonctionne !</p>
<h2 id="de-wordpress-a-hugo">De WordPress à Hugo</h2>
<p>Vu que de nos jours WordPress fait tourner une bonne partie des sites web, nous pouvons supposer que beaucoup d'entre vous connaissent, voire sont experts de ce CMS très populaire.
Moi aussi je faisais principalement du développement avec WordPress, avant de devenir complètement accro à Hugo.</p>
<p>Et j'ai mis du temps à me familiariser avec sa logique de fonctionnement. Quand j'ai découvert Hugo, je comparais son vocabulaire et ses concepts avec ceux de WordPress.</p>
<p>Je me suis rapidement aperçu que cette comparaison systématique était une mauvaise idée. Hugo possède son propre lexique et sa logique lui est propre et elle diffère beaucoup de celle de WordPress.</p>
<p>Mais j'ai réalisé qu'une étude parallèle plus attentive aurait pu m'aider à apprendre Hugo plus vite, et ainsi m'éviter pas mal d'erreurs coûteuses en chemin.</p>
<p>Donc si vous débutez avec Hugo, et que vous connaissez WordPress, ce qui va suivre ne pourra que vous être bénéfique.</p>
<h2 id="tout-est-page">Tout est page</h2>
<p>Cette affirmation catégorique est essentielle pour mieux appréhender le fonctionnement d'Hugo, surtout en ce qui concerne la logique dans les gabarits.</p>
<p>Pour Hugo, tout fichier compilé et ajouté à votre dossier cible public est une page. En ce sens, un article, une page, une liste d'articles, une liste de catégories ou de tags : tout ça ce sont des pages.</p>
<p>On peut le voir ainsi : tout ce qui possède une URL, c'est une page !</p>
<p>Si pour Hugo, tout est page, il faut néanmoins faire quelques distinctions bien nettes. Parmi elles, il y a les les <strong>Types</strong> et les <strong>Kinds</strong>.</p>
<h3 id="type">Type</h3>
<p>Si dans WordPress, toute entrée est un <strong>post</strong> avec un type distinct. Un article c'est un post de type <code>post</code>, une page c'est post de type <code>page</code> et une recette, c'est un post de type personnalisé <code>recipe</code> (ou ce qui vous chante).</p>
<p>Dans Hugo, chaque entrée ou fichier de contenu est une <strong>page</strong> habituelle d'un type différent. Et comme il n'existe pas de type pré-établi, tout type est votre propre type personnalisé. Pour créer une page d'un certain type :</p>
<ol>
<li>Vous ajoutez le <code>type</code> désiré dans le front matter</li>
<li>Ou plus généralement, vous laissez le premier niveau d'arborescence de contenu définir le type du fichier.</li>
</ol>
<p>Donc pour créer une page de type recette, vous pouvez soit écrire le front matter suivant :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">title:</span> <span class="hljs-string">De</span> <span class="hljs-string">délicieux</span> <span class="hljs-string">cupcakes</span>
<span class="hljs-attr">type:</span> <span class="hljs-string">recette</span>
<span class="hljs-meta">---</span></code></pre>
<p>Ou vous reposer sur la structure de votre arborescence et laisser faire la magie :</p>
<pre><code class="language-txt">content
  ├── post
  └── recette
      └── de-delicieux-cupcakes.md</code></pre>
<h3 id="kind">Kind</h3>
<p>Dans WordPress nous pouvons distinguer les layouts des templates. La page d'index qui affiche vos articles est construite d'après le fichier <code>archive.php</code>, cela s'appelle une archive. Et la page qui affiche le détail d'un article est elle construite à partir du fichier <code>single.php</code>, et s'appelle une <code>single</code>.</p>
<p>D'où les fonctions booléennes <code>is_single()</code>, <code>is_archive()</code>!</p>
<p>Dans Hugo, une fois encore, tout est page. Et donc pour déterminer ce que nous sommes supposés afficher, nous allons utiliser le mot <code>Kind</code>.</p>
<p>Voici différents exemples de valeurs pour <code>kind</code> :</p>
<ul>
<li>La page d'accueil de votre site web est la seule qui ait le <code>kind</code> <code>homepage</code></li>
<li>La page qui affiche vos recettes est une page avec le <code>kind</code> <code>section</code></li>
<li>La page qui affiche vos recettes catégorisées dans <em>chocolat</em> est une page avec le <code>kind</code> <code>taxonomy</code></li>
<li>La page qui regroupe toutes les catégories de vos recette (dont celles au chocolat) est une page avec le <code>kind</code> <code>taxonomyTerm</code></li>
<li>Enfin la page qui affiche une recette est la page la plus commune est de <code>kind</code> <code>page</code>.</li>
</ul>
<h2 id="modeles-et-hierarchie">Modèles et hiérarchie</h2>
<p>Maintenant que nous avons vu <em>Type</em> et <em>Kind</em>, plongeons nous dans la logique des gabarits de page d'Hugo.</p>
<p>Tout ce qui se trouve dans le dossier <code>layouts</code>, que ce soit celui de votre projet ou celui de votre thème est soumis à la hiérarchie de modèles, qui est un concept propre à Hugo, également désigné dans la documentation comme <a href="https://gohugo.io/templates/lookup-order/" target="_blank" rel="noopener noreferrer">l'ordre de consultation des modèles</a>.</p>
<p>En plus des conventions sur les noms de fichier, Hugo se base aussi sur l'arborescence des dossiers pour savoir quel modèle appliquer.</p>
<aside class="note note-info"><p>Comme évoqué précédemment, WordPress se base sur le fichier <code>archive.php</code> pour la mise en page de la liste d'articles de blog. Hugo se base lui sur un fichier <code>list.html</code> pour remplir cette fonction.</p></aside>
<p>De nombreux paramètres dont <code>Kind</code>, <code>Type</code>, le format en sortie, la langue, les termes de taxonomie, peuvent déterminer le modèle qu'il faudra utiliser pour une page donnée.</p>
<aside class="note note-info"><p>La meilleure approche pour comprendre la logique de l'organisation des modèles avec Hugo est encore de <a href="https://gohugo.io/templates/lookup-order/" target="_blank" rel="noopener noreferrer">lire la documentation officielle</a> à ce sujet.</p></aside>
<h3 id="les-modeles-de-page-personnalises">Les modèles de page personnalisés</h3>
<p>C'est un des <a href="https://developer.wordpress.org/themes/template-files-section/page-template-files/#creating-custom-page-templates-for-global-use" target="_blank" rel="noopener noreferrer">trucs</a> plus anciens de WordPress.</p>
<p>Si vous voulez qu'un éditeur puisse choisir la mise en page d'une page en particulier, vous devez créer un fichier de modèle de page, le déposer dans le dossier de votre thème et inclure cette saleté de gribouillage :</p>
<pre><code class="language-php hljs php"><span class="hljs-meta">&lt;?php</span> <span class="hljs-comment">/* Template Name: Custom 🤮 */</span> <span class="hljs-meta">?&gt;</span></code></pre>
<p>Avec Hugo, vous pouvez assigner une mise en page personnalisée à n'importe quel fichier de contenu à l'aide d'un simple paramètre front matter : <code>layout</code>.</p>
<p>Ensuite nommez votre fichier d'après la valeur définie pour <code>layout</code>, placez-le dans le dossier <code>layouts/_default/</code> et c'est bon !</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-meta">---</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">À</span> <span class="hljs-string">propos</span>
<span class="hljs-attr">layout:</span> <span class="hljs-string">about</span>
<span class="hljs-meta">---</span>
<span class="hljs-string">À</span> <span class="hljs-string">propos</span> <span class="hljs-string">de</span> <span class="hljs-string">moi</span> <span class="hljs-string">!</span></code></pre>
<pre><code class="language-txt">layouts
  └── _default
      └── about.html
</code></pre>
<h3 id="les-fichiers-includes">Les fichiers includes</h3>
<p>Une bonne pratique WordPress consiste à utiliser la fonction <code>get_template_parts</code> pour inclure un fichier de votre thème. Cela permet d'hériter des variables globales définies par WordPress (<code>$post</code>, <code>$wp_query</code>, etc.) mais c'est tout.</p>
<p>Dans Hugo, on parle de fichiers partiels. Ce sont des fichiers stockés dans <code>layouts/partials</code> qui seront chargés lors de l'appel à la fonction <code>partial</code>.</p>
<p>Le truc à savoir c'est que cette fonction prend comme paramètre un périmètre ou un contexte. Par défaut aucune information relative à votre page se sera transmise dans le fichier partiel.</p>
<p>On inclut un fichier partiel ainsi :</p>
<pre><code class="language-go-html-template hljs go">{{ partial <span class="hljs-string">"post-head"</span> . }}</code></pre>
<p>Le point ci-dessus .............☝️ correspond à la page courante.</p>
<p>Le contexte de la page courante comprend toutes les variables de page dont vous aurez besoin dans votre fichier partiel et dans tous vos modèles, nous allons y venir.</p>
<aside class="note note-info"><p>Comprendre le contexte dans Hugo, c'est la clé. Si ce n'est pas encore clair pour vous, 👉 lisez <a href="/2018/02/08/hugo-le-point-sur-le-contexte/">Hugo, le point sur le contexte</a></p></aside>
<h2 id="la-boucle-et-les-donnees">La boucle et les données</h2>
<h3 id="les-variables-de-page">Les variables de Page</h3>
<p>Dans WordPress, les données relatives à un article sont accessibles depuis les fichiers de gabarits de page via des fonctions comme <code>the_permalink()</code>, <code>the_title()</code>, <code>the_content()</code>, <code>the_date()</code> etc.</p>
<p>Hugo de son côté vous fourni un objet qui comprend des <a href="https://gohugo.io/variables/page/#readout" target="_blank" rel="noopener noreferrer">variables et des méthodes</a> appelé le contexte de page et stocké dans le fameux point (<code>.</code>) mentionné plus tôt.</p>
<p>Dans Hugo, les équivalents aux expressions WordPress citées un peu plus haut sont <code>.Permalink</code>, <code>.Title</code> , <code>.Content</code>, <code>.Date</code>.</p>
<p>Vous vous rappelez du fichier partiel de toute à l'heure ? Et bien une fois le contexte de page précisé, vous avez accès à toutes les variables de la page dans ce fichier :</p>
<pre><code class="language-go-html-template hljs go">{{<span class="hljs-comment">/* layouts/partials/post-head.html */</span>}}
&lt;div class=<span class="hljs-string">"post-head"</span>&gt;
  &lt;h1&gt;&lt;a href=<span class="hljs-string">"{{ .Permalink }}"</span>&gt;{{ .Title }}&lt;/a&gt;&lt;h1&gt;
  &lt;time&gt;{{ .Date }}&lt;/time&gt;
&lt;/div&gt;</code></pre>
<h3 id="boucler-avec-range">Boucler avec <code>range</code></h3>
<p>Pouvoir parcourir des articles pour construire des pages archives ou un widget <em>Derniers articles</em> est essentiel pour un moteur de template.</p>
<p>Selon le gabarit de page que vous utilisez, WordPress vous donnera toujours accès à une liste d'articles à parcourir, même s'il n'y en a qu'un seul à afficher pour une page <code>single</code>.</p>
<p>Donc que ce soit pour le fichier des archives des posts de blog, des archives de la catégorie <em>chocolat</em> des recettes, ces éléments sont dans la boucle <em>Loop</em>, paginés.</p>
<p>Avec Hugo, dans les gabarits de listes, les pages concernées sont stockées dans une <em>collection</em> et sont accessibles via l'objet <code>.Pages</code>.</p>
<p>Cela fait que pour le modèle de liste de la section recettes, <code>.Pages</code> retournera la collection des pages correspondantes : recettes.</p>
<p>Pour une liste de taxonomie, <code>.Pages</code> contiendra la liste des pages qui utilisent cette taxonomie ainsi que les informations sur la taxonomie en elle-même stockés dans <code>.Data</code>, telles que <code>.Data.Singular</code>, <code>.Data.Plural</code> et <a href="https://gohugo.io/variables/taxonomy/" target="_blank" rel="noopener noreferrer">bien plus</a>.</p>
<p>Une chose à retenir, c'est que contrairement à WordPress, pour une page <code>single</code>, <code>.Pages</code> sera vide (arf) car toutes les informations de la page sont déjà disponibles dans le contexte de page <code>.</code>.</p>
<h3 id="comparaison-des-boucles">Comparaison des boucles</h3>
<p>Voici notre boucle WordPress tant aimée :</p>
<pre><code class="language-php hljs php"><span class="hljs-comment">// theme/archive.php</span>
<span class="hljs-meta">&lt;?php</span> <span class="hljs-keyword">if</span> ( have_posts() ) : <span class="hljs-keyword">while</span> ( have_posts() ) : the_post(); <span class="hljs-meta">?&gt;</span>
  &lt;!-- post --&gt;
  &lt;h2&gt;
    &lt;a href=<span class="hljs-string">"&lt;?php the_permalink(); ?&gt;"</span>&gt;<span class="hljs-meta">&lt;?php</span> the_title() <span class="hljs-meta">?&gt;</span>&lt;/a&gt;
  &lt;/h2&gt;
  &lt;h6&gt;<span class="hljs-meta">&lt;?php</span> the_date(); <span class="hljs-meta">?&gt;</span>&lt;/h6&gt;
  &lt;p&gt;
    <span class="hljs-meta">&lt;?php</span> the_excerpt(); <span class="hljs-meta">?&gt;</span>
  &lt;/p&gt;
  &lt;hr&gt;
<span class="hljs-meta">&lt;?php</span> <span class="hljs-keyword">endwhile</span>; <span class="hljs-meta">?&gt;</span>
<span class="hljs-meta">&lt;?php</span> <span class="hljs-keyword">else</span>: <span class="hljs-meta">?&gt;</span>
&lt;!-- aucun post trouvé --&gt;
<span class="hljs-meta">&lt;?php</span> <span class="hljs-keyword">endif</span>; <span class="hljs-meta">?&gt;</span></code></pre>
<p>La même chose en beaucoup plus lisible et succinct avec Hugo, grâce à la transmission du contexte dans les fonctions :</p>
<pre><code class="language-go-html-template hljs go"><span class="hljs-comment">//layouts/_default/list.html</span>
{{ with .Pages }}
  {{ <span class="hljs-keyword">range</span> . }}
    &lt;h2&gt;
      &lt;a href=<span class="hljs-string">"{{ .Permalink }}"</span>&gt;{{ .Title }}&lt;/a&gt;
    &lt;/h2&gt;
    &lt;h6&gt;{{ .Date.Format <span class="hljs-string">"January, 02 2006"</span> }}&lt;/h6&gt;
    &lt;p&gt;
      {{ .Summary }}
    &lt;/p&gt;
    &lt;hr&gt;
  {{ end }}
{{ <span class="hljs-keyword">else</span> }}
&lt;!-- aucun élément trouvé --&gt;
{{ end }}</code></pre>
<h3 id="qu-en-est-il-des-autres-pages聽">Qu'en-est-il des autres pages ?</h3>
<p>Pour récupérer toutes les pages du site et ce depuis n'importe quel modèle de page, avec WordPress vous devrez écrire vous-même une requête.</p>
<p>Avec Hugo, nous avez simplement à appeler la collection <code>.Site.pages</code>. Comme tout est page, cette collection inclut aussi bien les pages normales, les pages de sections, les pages des taxonomies, la page d'accueil, etc. Pour ne sélectionner que les pages que WordPress appelle des <em>posts</em>, on utilisera <code>.Site.RegularPages</code>.</p>
<p>Voici un exemple de requête plus avancée avec WordPress, qui permet d'afficher un widget <code>Les dernières recettes</code> ordonnées en fonction d'un paramètre défini, et utilisable dans n'importe quel modèle de page :</p>
<pre><code class="language-php hljs php"><span class="hljs-meta">&lt;?php</span>
$recents = <span class="hljs-keyword">new</span> WP_Query(
  [
    <span class="hljs-string">'post_type'</span>=&gt;<span class="hljs-string">'recipe'</span>,
    <span class="hljs-string">'posts_per_page'</span>=&gt;<span class="hljs-number">5</span>,
    <span class="hljs-string">'orderby'</span>   =&gt; <span class="hljs-string">'meta_value_num'</span>,
    <span class="hljs-string">'meta_key'</span>  =&gt; <span class="hljs-string">'rating'</span>,
  ]
);
<span class="hljs-keyword">if</span> ( $recents-&gt;have_posts() ) : <span class="hljs-keyword">while</span> ( $recents-&gt;have_posts() ) : $recents-&gt;the_post(); <span class="hljs-meta">?&gt;</span>
  &lt;h2&gt;
    &lt;a href=<span class="hljs-string">"&lt;?php $recents-&gt;the_permalink(); ?&gt;"</span>&gt;<span class="hljs-meta">&lt;?php</span> $recents-&gt;the_title() <span class="hljs-meta">?&gt;</span>&lt;/a&gt;
  &lt;/h2&gt;
&lt;!-- post --&gt;
<span class="hljs-meta">&lt;?php</span> <span class="hljs-keyword">endwhile</span>; <span class="hljs-meta">?&gt;</span>
<span class="hljs-meta">&lt;?php</span> <span class="hljs-keyword">else</span>: <span class="hljs-meta">?&gt;</span>
&lt;!-- aucune entrée trouvée --&gt;
<span class="hljs-meta">&lt;?php</span> <span class="hljs-keyword">endif</span>; <span class="hljs-meta">?&gt;</span>
<span class="hljs-meta">?&gt;</span></code></pre>
<p>Et voici son élégante variante avec Hugo :</p>
<pre><code class="language-go-html-template hljs go">{{ $recents := (where .Site.RegularPages <span class="hljs-string">"Type"</span> <span class="hljs-string">"recipe"</span>).ByParam <span class="hljs-string">"rating"</span> }}
{{ <span class="hljs-keyword">range</span> first <span class="hljs-number">5</span> $recents }}
  &lt;h2&gt;
    &lt;a href=<span class="hljs-string">"{{ .Permalink }}"</span>&gt;{{ .Title }}&lt;/a&gt;
  &lt;/h2&gt;
{{ end }}</code></pre>
<aside class="note"><p>Pour apprendre comment filtrer et ordonner les collections de pages dans Hugo,
reportez-vous à la documentation de <a href="https://gohugo.io/templates/introduction/#example-1-using-context" target="_blank" rel="noopener noreferrer">range</a>, <a href="https://gohugo.io/functions/where/#readout" target="_blank" rel="noopener noreferrer">where</a> et comment <a href="https://gohugo.io/templates/lists/#order-content" target="_blank" rel="noopener noreferrer">ordonner le contenu</a>.</p></aside>
<h2 id="les-shortcodes">Les shortcodes</h2>
<p>Dans WordPress, les shortcodes sont "des fonctions qui retournent quelque chose en sortie", ajoutées à l'aide de plusieurs <code>add_shortcode</code> dans votre fichier <code>functions.php</code>.</p>
<p>Hugo supporte également les <a href="https://gohugo.io/templates/shortcode-templates/" target="_blank" rel="noopener noreferrer">shortcodes</a>, ils sont crées en ajoutant des modèles particuliers dans <code>layouts/shortcodes/</code>. Le contenu du fichier est récupéré avec <code>.Inner</code> et ses paramètres avec <code>.Get</code>. Contrairement à WordPress, ses derniers n'ont pas forcément besoin d'être nommés.</p>
<ul>
<li>Si le paramètre est nommé 👉 <code>.Get "title"</code>.</li>
<li>Sinon on utilise sa position 👉 <code>.Get 0</code>.</li>
</ul>
<h3 id="les-shortcodes-par-l-exemple">Les shortcodes par l'exemple</h3>
<p>Voici un exemple de shortcode WordPress tiré de la documentation[^1]. Il prendre en paramètre une classe, <code>caption</code> par défaut, et un contenu à insérer.</p>
<pre><code class="language-php hljs php"><span class="hljs-meta">&lt;?php</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">caption_shortcode</span><span class="hljs-params">( $atts, $content = null )</span> </span>{
  $a = shortcode_atts( <span class="hljs-keyword">array</span>(
    <span class="hljs-string">'class'</span> =&gt; <span class="hljs-string">'caption'</span>,
  ), $atts );

  <span class="hljs-keyword">return</span> <span class="hljs-string">'&lt;span class="'</span> . esc_attr($a[<span class="hljs-string">'class'</span>]) . <span class="hljs-string">'"&gt;'</span> . $content . <span class="hljs-string">'&lt;/span&gt;'</span>;
}
add_shortcode( <span class="hljs-string">'caption'</span>, <span class="hljs-string">'caption_shortcode'</span> );</code></pre>
<p>Le shortcode s'utilise ensuite ainsi :</p>
<pre><code class="language-html hljs xml">[caption class="headline"]My Caption[/caption]</code></pre>
<p>Voyons maintenant la réponse en une ligne d'Hugo :</p>
<pre><code class="language-go-html-template hljs go">{{<span class="hljs-comment">/* layouts/shortcodes/caption.html */</span>}}
&lt;span class=<span class="hljs-string">"{{ default "</span>caption<span class="hljs-string">" (.Get 0) }}"</span>&gt;{{ .Inner }}&lt;/span&gt;</code></pre>
<p>On écrira dans son fichier Markdown :</p>
<pre><code class="language-md hljs markdown">{{%/<span class="hljs-emphasis">* caption "headline" *</span>/%}}My Caption{{%/<span class="hljs-emphasis">* /caption *</span>/%}}</code></pre>
<p>Nous avons opté pour l'utilisation de la <em>position</em>… car il n'y a qu'un seul paramètre 🤷</p>
<aside class="note"><p><a href="https://jpescador.com/blog/leverage-shortcodes-in-hugo/" target="_blank" rel="noopener noreferrer">Tirer parti des shortcodes d'Hugo</a> (<a href="https://twitter.com/julio_pescador" target="_blank" rel="noopener noreferrer">Julio Pescador</a>)</p></aside>
<h2 id="parametres">Paramètres</h2>
<p>Il y a de nombreuses pages de paramètres dans le tableau de bord de WordPress. Vous vous retrouvez souvent à naviguer entre le mode écriture et lecture de manière à pouvoir définir vos permaliens, votre titre de site, votre pagination, vos commentaires, etc.</p>
<p>Tout comme pour l'édition de contenu, la <a href="https://gohugo.io/getting-started/configuration/" target="_blank" rel="noopener noreferrer">configuration</a> d'Hugo se fait dans des fichiers ! Et pour faire plaisir à tout le monde, vous pouvez choisir le format qui vous convient le mieux :</p>
<ul>
<li>YAML</li>
<li>TOML</li>
<li>JSON</li>
</ul>
<p>Si vous ne connaissez aucun d'entre eux (le dernier devrait vous dire quelques chose), vous pouvez vous pencher sur cet <a href="https://gohugo.io/getting-started/configuration/#example-configuration" target="_blank" rel="noopener noreferrer">exemple de configuration</a> que vous pouvez basculer entre les langues et vérifier leurs syntaxes.</p>
<p>Ces paramètres sont stockés dans un fichier config à la racine ou dans un <a href="https://gohugo.io/getting-started/configuration/#configuration-directory" target="_blank" rel="noopener noreferrer">répertoire</a> dédié, qui vous permet de les grouper et d'écraser les variables d'environnement de façon plus intelligente.</p>
<h2 id="formats-de-sortie">Formats de sortie</h2>
<p>Hein, c'est quoi ça ?</p>
<p>Ah oui c'est vrai, WordPress ne vous a pas présenté.</p>
<p>Imaginons que pour chaque page vous ayez un fichier HTML <code>cette-page/index.html</code> et c'est tout. Hugo vous permet aussi de faire en sorte que chaque page possède aussi une version au format JSON ainsi qu'au format <a href="https://www.ampproject.org/docs/" target="_blank" rel="noopener noreferrer">AMP</a>. Ces pages sont générées à côté de leur soeur au format HTML, respectivement <code>cette-page/index.json</code> et <code>cette-page/index.amp.html</code>.</p>
<p>Tout ce que vous avez à faire pour cela est de dire à Hugo d'ajouter les formats de sortie pour les <strong>Kinds</strong> desirés à l'aide du fichier de configuration évoqué juste avant, et d'ajouter les fichiers de templates correspondants.</p>
<p>Pour résumer :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># config.yaml</span>
<span class="hljs-attr">outputs:</span>
  <span class="hljs-attr">homepage:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">HTML</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">JSON</span>
  <span class="hljs-attr">page:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">HTML</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">AMP</span></code></pre>
<pre><code class="language-sh hljs bash">layouts
  ├── _default
  │   └── about.html
  ├── index.html
  ├── index.json
  └── recipes
      └── single.amp.html</code></pre>
<p>Et c'est tout ! Du moment que ces fichiers de gabarit contiennent le code qui va bien, votre page d'accueil sera disponible en HTML ainsi qu'en JSON, alors que vos recettes pourront être servies en HTML ou au format AMP !</p>
<p>Je vous conseille vivement d'aller éplicher la documentation sur <a href="https://gohugo.io/templates/output-formats#readout" target="_blank" rel="noopener noreferrer">les formats de sortie</a>, grâce à eux vous pourrez ajouter une API à votre site où un fichier <code>.ics</code> pour vos évènements, ou qui sait ce dont vous aurez besoin sur votre prochain projet !</p>
<aside class="note"><p><a href="https://forestry.io/blog/build-a-json-api-with-hugo/" target="_blank" rel="noopener noreferrer">Bâtir une API JSON avec les formats de sortie personnalisés d'Hugo</a></p></aside>
<h2 id="traitement-des-assets">Traitement des assets</h2>
<p>WordPress ne propose rien en ce sens, c'est à vous d'utiliser votre propre gestionnaire de tâches et ses paquets de traitement des assets.</p>
<p>Hugo propose lui sa propre suite d'outils de traitement des assets !</p>
<p>-- Heeein?
-- Oui! Ça s'appelle les <a href="https://gohugo.io/hugo-pipes/" target="_blank" rel="noopener noreferrer">tuyaux d'Hugo</a> et <strong>sans aucune dépendance node</strong> vous pouvez :</p>
<ul>
<li>Minifier 🗜️</li>
<li>Paquetter vos fichiers 📦</li>
<li>Compiler vos fichiers <a href="http://sass-lang.com/" target="_blank" rel="noopener noreferrer">Sass/Scss</a> 👓</li>
<li>Ajouter une empreinte et un contrôle d'intégrité 🔑</li>
</ul>
<p>Avec quelques dépendances vous pouvez :</p>
<ul>
<li>Exécuter <a href="https://postcss.org/" target="_blank" rel="noopener noreferrer">PostCSS</a> sur vos feuilles de style</li>
</ul>
<p>Le tout avec la même élégance à laquelle vous devez commencer à vous habituer :</p>
<pre><code class="language-go-html-template hljs go">{{ $style := resources.Get <span class="hljs-string">"main.scss"</span> | toCSS | minify | fingerprint }}
&lt;link href=<span class="hljs-string">"{{ $style.Permalink }}"</span> rel=<span class="hljs-string">"stylesheet"</span>&gt;</code></pre>
<p>Hugo s'assurera également que ces assets sont compilés et publiés uniquement si vous appelez leur <code>.Permalink</code> dans vos templates.</p>
<h3 id="traitement-des-images">Traitement des images</h3>
<p>Le traitement d'image par défaut de WordPress se fait uniquement lors de l'étape initiale de téléversement.</p>
<p>WordPress stocke ensuite les nouveaux formats de tailles d'images crées aux côtés du fichier d'origine. Lorsque vous appelez votre image, peu importe la fonction, vous devrez utiliser un paramètre pour récupérer la taille de la variante souhaitée.</p>
<p>Hugo quant à lui procède au traitement des images lorsque vous en avez besoin, ce qui signifie que vous obtiendrez cette variation pour cet entête d'article seulement si vous appelez les fonctions <code>.Fit</code> ou <code>.Resize</code> dans votre gabarit de page.</p>
<pre><code class="language-go-html-template hljs go">{{ $img := resources.Get <span class="hljs-string">"header.jpg"</span> | .Resize <span class="hljs-string">"600x"</span> }}
&lt;img src=<span class="hljs-string">"{{ $img.Permalink }}"</span> alt=<span class="hljs-string">""</span>&gt;</code></pre>
<p>Je sais! Moi aussi je ne me lasse pas de ces instructions sur deux lignes !</p>
<p>Vous pouvez lancer des traitements d'images, soit à l'aide des tuyaux d'Hugo ou des <a href="https://gohugo.io/content-management/page-resources/#readout" target="_blank" rel="noopener noreferrer">ressources de page</a>.</p>
<aside class="note"><ul>
<li><a href="https://regisphilibert.com/blog/2018/07/hugo-pipes-and-asset-processing-pipeline/" target="_blank" rel="noopener noreferrer">La révolution des tuyaux d'Hugo</a></li>
<li><a href="https://laurakalbag.com/processing-responsive-images-with-hugo/" target="_blank" rel="noopener noreferrer">Traitement des images responsive avec Hugo</a> | <a href="https://twitter.com/laurakalbag" target="_blank" rel="noopener noreferrer">Laura Kalbag</a></li>
<li><a href="https://blog.fullstackdigital.com/how-to-cache-bust-and-concatenate-js-and-sass-files-with-hugo-in-2018-9266fd3c411e" target="_blank" rel="noopener noreferrer">Cache-bust et concatenation de fichiers JS/SCSS avec Hugo</a> | <a href="https://twitter.com/BenBozzay" target="_blank" rel="noopener noreferrer">Ben Bozzay</a></li>
<li><a href="https://regisphilibert.com/blog/2018/01/hugo-page-resources-and-how-to-use-them/" target="_blank" rel="noopener noreferrer">Les ressources de page d'Hugo</a></li>
</ul></aside>
<h2 id="themes-et-plugins-vs-composants-de-theme">Thèmes et Plugins VS Composants de Thème</h2>
<p>WordPress fait un usage immodérée des thèmes et des plugins, pour qu'en échange vous puissiez modeler l'apparence de vos sites et ajouter des fonctionnalités en codant le moins possible.</p>
<p>Pour les thèmes, WordPress ne vous donne que deux niveaux de personnalisation.
Vous pouvez créer un thème parent, et y mettre toutes les choses génériques. Et ensuite créer un thème enfant, qui pourra, pour tous les homonymes de fichiers partagés avec son parent, prendre le pas sur ceux-ci.</p>
<p>Si vous avez besoin d'un niveau supplémentaire de personnalisation en plus de ces deux, comme un ensemble de shortcodes ou un format de sortie en AMP, il faudra avoir recous à des plugins.</p>
<p>Dans Hugo, on ne parle pas de plugins et des thèmes mais plutôt de composants.
Vous pouvez en ajouter autant que vous voulez.</p>
<p>Fichiers de gabarit, JavaScript, Scss, images, fichiers de données, chaînes <code>i18n</code> (que nous couvrons plus bas), il est possible de presque tout écraser grâce aux homonymes de fichiers et vous pouvez définir l'ordre de précédence.</p>
<p>Voyez les composants comme des thèmes enfant illimités !</p>
<p>Certains composants peuvent être des thèmes complets qui contiennent un nombre important de fichiers. D'autres peuvent se contenter d'être une simple variation de votre thème principal, et ajouter leur propre ensemble de gabarits personnalisés par exemple. D'autres peuvent simplement ajouter quelques définitions de shortcodes, un nouvel ensemble de variables Sass, ou un format de sortie supplémentaire.</p>
<h3 id="le-theme-par-l-exemple">Le thème par l'exemple</h3>
<p>Prenons pour exemple un projet fictif de clinique dentaire, où personne ne veut avoir à trop mettre les mains dans le code. Vous aurez besoin :</p>
<ul>
<li>Un thème principal adapté pour le secteur de la santé 👩‍⚕️</li>
<li>Une extension du thème principal adaptée pour le domaine dentaire 🦷</li>
<li>Une extension saisonnière du thème principal 🎄</li>
<li>Une solution pour gérer des menus de navigation riches</li>
<li>Un ensemble de shortcodes en relation avec le médical à l'intention de l'équipe de rédaction</li>
<li>Un fichier JSON pour chaque page.</li>
</ul>
<p>Avec WordPress il vous faudrait :</p>
<ul>
<li>
<p>Thèmes</p>
<ul>
<li>Un thème santé</li>
<li>Un thème dentaire enfant du thème santé</li>
</ul>
</li>
<li>
<p>Plugins</p>
<ul>
<li>Un plugin thème santé saisonnier</li>
<li>Un plugin Mega Menu</li>
<li>Un plugin Shordcodes médicaux</li>
<li>Un plugin API REST</li>
</ul>
</li>
</ul>
<p>Notez bien que si en plus de cela, vous souhaitez créer vos propres fichiers de gabarit pour prendre le pas sur les thèmes parent et enfant… et bien à ce que je sâche, ce n'est pas possible. 🤷‍♂️</p>
<p>Dans Hugo, vous n'avez qu'à ajouter ces différents répertoires dans votre dossier <code>themes</code> et y faire appel dans votre fichier de configuration principal.</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">theme:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">health-theme</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">health-theme-dental-extension</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">health-theme-season-extension</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">mega-menu-component</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">medical-shortcodes-component</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">json-api-component</span>

<span class="hljs-attr">output:</span>
  <span class="hljs-attr">page:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">HTML</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">JSON</span></code></pre>
<p>L'exemple ci-dessus déclare les composants de thèmes à utiliser ainsi que leur ordre de précédence. Et comme vu auparavant, nous nous assurons que le format de sortie JSON est ajouté à toutes les pages de type <code>page</code>.</p>
<p>C'est tout. Si vous devez écraser n'importe quel des composants des fichiers de gabarit, il vous suffit de placer un fichier homonyme dans le dossier <code>layouts</code> à la racine de votre projet (à condition que les chemins soient identiques bien entendu).</p>
<aside class="note"><p><a href="https://medium.com/@jeffmcmorris/tips-and-tricks-for-building-a-theme-in-hugo-4806bdd747d7" target="_blank" rel="noopener noreferrer">Trucs et astuces pour développer un thème pour Hugo</a> | <a href="https://medium.com/@jeffmcmorris" target="_blank" rel="noopener noreferrer">Jeff McMorris</a></p></aside>
<h2 id="on-en-parle-de-l-interface-du-cms-ou-pas">On en parle de l'interface du CMS ou pas ?</h2>
<p>Oui !</p>
<p>Comme vous le savez, l'interface graphique n'est pas du ressort d'Hugo. Mais il existe des tonnes de solutions, dont le formidable <a href="https://forestry.io" target="_blank" rel="noopener noreferrer">Forestry.io</a> qui vous permet de générer une belle interface de CMS personnalisable à partir du dépôt git de votre projet Hugo !</p>
<p>Croyez-moi, elles sont toutes bien plus rapides et mieux conçues que ce bon vieux tableau de bord.</p>
<h2 id="autres-fonctionnalites-notables">Autres fonctionnalités notables</h2>
<p>Avant de conclure, passons en revue sans ordre particulier les façons qu'ont WordPress et Hugo de gérer les fonctionnalités les plus communément demandées.</p>
<h3 id="multilinguisme">Multilinguisme</h3>
<p>Au revoir WPML! 🥳</p>
<p>Hugo gère nativement <a href="https://gohugo.io/content-management/multilingual/#readout" target="_blank" rel="noopener noreferrer">le multilingue</a>, y compris l'<code>i18n</code> et la traduction de chaînes de caractères.</p>
<p>Reportez-vous à l'article complet de Régis Philibert <a href="/2018/08/17/contenu-multilingue-avec-hugo/">sur la gestion multilingue dans Hugo</a>.</p>
<h3 id="menus">Menus</h3>
<p>Les menus Wordpress sont très puissants mais s'avèrent difficiles à maîtriser pour un développeur. La sortie est gérée à l'aide d'une fonction appelée <em>Walker</em> pas forcément évidente à lire et à comprendre lorsqu'il s'agit de s'aventurer dans des menus à plusieurs niveaux.</p>
<p>La solution proposée par Hugo pour <a href="https://gohugo.io/content-management/menus/#readout" target="_blank" rel="noopener noreferrer">les menus</a> vous laisse assigner n'importe quelle page à un menu ou à une url externe.</p>
<p>Concrètement, si vous avez deux menus sur votre site, vous assigner une page donnée à un menu de la sorte :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># /content/a-propos.md</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">À</span> <span class="hljs-string">propos</span>
<span class="hljs-attr">menu:</span>
  <span class="hljs-attr">main:</span>
    <span class="hljs-attr">name:</span> <span class="hljs-string">Qui</span> <span class="hljs-string">suis-je?</span>
    <span class="hljs-attr">weight:</span> <span class="hljs-number">2</span>
  <span class="hljs-attr">footer:</span>
    <span class="hljs-attr">weight:</span> <span class="hljs-number">1</span></code></pre>
<p>Si vous avez besoin d'ajouter une url externe au menu <code>main</code>, ça se fait au niveau de la configuration de votre site :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># /config.yaml</span>
<span class="hljs-attr">menus:</span>
  <span class="hljs-attr">main:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Blog</span>
      <span class="hljs-attr">url:</span> <span class="hljs-string">https://blog.tumblr.com</span>
      <span class="hljs-attr">weight:</span> <span class="hljs-number">3</span>
  <span class="hljs-attr">footer:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">Blog</span>
      <span class="hljs-attr">url:</span> <span class="hljs-string">https://blog.tumblr.com</span></code></pre>
<p>Dans l'exemple ci-dessus, votre lien de navigation vers votre page <em>À propos</em> apparaîtra en seconde position dans votre menu principal avec l'intitulé <em>Qui suis-je ?</em> Il apparaîtra également dans le menu de bas de page en première place en reprenant le titre de page: <em>À propos</em>.
En plus de cela, les deux menus incluent un lien externe <em>Blog</em> qui pointe vers votre ancien blog tumblr.</p>
<p>Contrairement à WordPress, il n'y a pas de notion d'emplacement de menu.
Vous appelez votre objet où vous voulez dans votre gabarit de page avec <code>.Site.Menus.main</code>, <code>.Site.Menus.footer</code> or <code>.Site.Menus.cequevousvoulez</code> et vous bouclez ensuite dessus avec <code>range</code>.</p>
<p>Encore une fois allez voir la <a href="https://gohugo.io/templates/menu-templates/#readout" target="_blank" rel="noopener noreferrer">doc</a> pour apprendre à ajouter des menus dans vos gabarits de page, c'est une grande avancée par rapport aux bons vieux <em>Walkers</em> (ici c'est plus 🏃‍♀️).</p>
<h3 id="champs-personnalises">Champs personnalisés</h3>
<p>Avec WordPress, à moins que vous aimiez passer des heures à réinventer la roue, vous allez tout le temps vous reposer sur le plugin <a href="https://www.advancedcustomfields.com/" target="_blank" rel="noopener noreferrer">ACF</a> pour récupérer et modifier les métadonnées des articles.</p>
<p>Hugo, comme Jekyll et d'autres générateurs basés sur Markdown, se repose sur <a href="https://gohugo.io/content-management/front-matter/#readout" target="_blank" rel="noopener noreferrer">Front Matter</a> pour la gestion de toutes les variables "perso".
Cela permet de stocker tous les paramètres non réservés de votre contexte de page dans l'objet <code>.Params</code>.</p>
<p>Donc dans votre gabarit, plutôt que :</p>
<pre><code class="language-php hljs php"><span class="hljs-meta">&lt;?php</span> <span class="hljs-keyword">if</span> ($subfield = get_field(<span class="hljs-string">'subfield'</span>)){ <span class="hljs-keyword">echo</span> $subfield; } <span class="hljs-meta">?&gt;</span></code></pre>
<p>vous écrivez :</p>
<pre><code class="language-go-html-template hljs go">{{ with .Params.subtitle }}{{ . }}{{ end }}</code></pre>
<p>.................................................... ☝️ Vous allez l'aimer ce point !</p>
<h3 id="les-options-generiques-du-site">Les options génériques du site</h3>
<p>Qu'en est-il de ces options génériques non rattachées à une page en particulier ?</p>
<p>Et bien une fois de plus, si vous avez pratiqué WordPress après 2013, il y a des chances que vous ayez fait appel à ACF pour gérer ça, parce qu'ajouter des champs optionnels vous même à WordPress c'est vraiment une galère !</p>
<p>Hugo vous propose deux manière de faire. Vous pouvez ajouter des variables personnalisés comme <code>tagline</code> à l'objet <code>Params</code> dans le fichier de configuration principal de votre site et les récupérer avec <code>.Site.Params.tagline</code> par exemple.</p>
<p>Si vous avez des ensembles de données plus complexes, vous pouvez ajouter des fichiers <code>yaml|toml|json</code> dans votre dossier <code>data/</code>. Tout ce qui s'y trouve sera agrégé dans un objet bien pratique <code>.Site.Data</code> accessible dans vos gabarits.</p>
<p>Donc si vous voulez que vos contributeurs puissent gérer les liens vers les réseaux sociaux ainsi que des options génériques, vous pouvez ajouter deux fichiers dans le répertoire <code>data</code>.</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># data/socials.yaml</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">title:</span> <span class="hljs-string">Facebook</span>
  <span class="hljs-attr">icon:</span> <span class="hljs-string">fb</span>
  <span class="hljs-attr">url:</span> <span class="hljs-string">https://facebook.com/hugo_rocks</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">title:</span> <span class="hljs-string">Twitter</span>
  <span class="hljs-attr">icon:</span> <span class="hljs-string">tw</span>
  <span class="hljs-attr">url:</span> <span class="hljs-string">https://twitter.com/hugoRocks</span></code></pre>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># data/options.yaml</span>
<span class="hljs-attr">socials:</span> <span class="hljs-literal">true</span>
<span class="hljs-attr">tagline:</span> <span class="hljs-string">Hugo</span> <span class="hljs-string">rocks!</span></code></pre>
<p>Et dans votre fichier partiel…</p>
<pre><code class="language-go-html-template hljs go">{{<span class="hljs-comment">/* layouts/partials/socials.html */</span>}}
{{ <span class="hljs-keyword">if</span> .Site.Data.options.social }}
&lt;ul class=<span class="hljs-string">"socials"</span>&gt;
  {{ <span class="hljs-keyword">range</span> .Site.Data.socials }}
    &lt;li&gt;
      &lt;a href=<span class="hljs-string">"{{ .url }}"</span>&gt;&lt;i class=<span class="hljs-string">"icon icon-{{ .icon }}"</span>&gt;&lt;/i&gt; {{ .title }}&lt;/a&gt;
    &lt;/li&gt;
  {{ end }}
&lt;/ul&gt;
{{ end }}</code></pre>
<aside class="note"><p><a href="https://novelist.xyz/tech/hugo-data-files/" target="_blank" rel="noopener noreferrer">Utilisation des fichiers de données dans Hugo par l'exemple</a> | <a href="https://twitter.com/peterychuang" target="_blank" rel="noopener noreferrer">Peter Y. Chuang</a></p></aside>
<h3 id="commentaires">Commentaires</h3>
<p>Je doute que beaucoup d'entre vous utilisent encore les commentaires natifs de WordPress en 2019… mais il y a des chances pour que vous ayez envie de proposer des discussions à propos de vos articles.</p>
<p>Comme tous les générateurs Hugo se contente de produire des fichiers statiques, il vous faudra donc vous tourner vers un service tiers pour la gestion de vos commentaires.</p>
<p>Heureusement il existe un support natif de <a href="https://disqus.com/" target="_blank" rel="noopener noreferrer">Disqus</a> <a href="https://gohugo.io/content-management/comments/#add-disqus" target="_blank" rel="noopener noreferrer">prêt à l'emploi</a>.</p>
<p>Et si vous n'êtes pas fans de Disqus, il existe bien d'autres solutions, qui ne demandent souvent qu'une simple balise script et le balisage correspondant.</p>
<aside class="note"><ul>
<li><a href="http://donw.io/post/github-comments/" target="_blank" rel="noopener noreferrer">Remplacer Disqus avec les commentaires Github</a> | <a href="https://twitter.com/Donzanoid" target="_blank" rel="noopener noreferrer">Don Williamson</a></li>
<li><a href="https://networkhobo.com/2017/12/30/hugo-staticman-nested-replies-and-e-mail-notifications/" target="_blank" rel="noopener noreferrer">Hugo + Staticman : réponses imbriquées et notifications par email</a> | <a href="https://twitter.com/dancwilliams" target="_blank" rel="noopener noreferrer">Dan C Williams</a></li>
</ul></aside>
<h3 id="formulaires">Formulaires</h3>
<p>Là aussi, il faut passer par un service tiers, le plus souvent gratuit, par exemple :</p>
<ul>
<li>Formkeep.io</li>
<li>Netlify's forms</li>
<li>TypeForm</li>
</ul>
<h3 id="contenu-relatif">Contenu relatif</h3>
<p>Générer des suggestions du genre "Vous aimerez aussi" dans WordPress repose entièrement sur des plugins externes ou votre propre requête d'articles personnalisée.</p>
<p>Hugo sait faire ça tout seul comme un grand, et il le fait très bien, à l'aide de son propre système entièrement personnalisable de <a href="https://gohugo.io/content-management/related/#readout" target="_blank" rel="noopener noreferrer">relation de contenus</a></p>
<h3 id="recherche">Recherche</h3>
<p>Comme pour tout ce qui est dynamique, rien de natif dans un générateur de site statique. Ce qui n'est pas pire que la recherche WordPress par défaut si vous voulez mon opinion. Maintenant il existe des douzaines de services qui vont proposer une expérience de recherche digne de ce nom pour vous, parmi eux :</p>
<ul>
<li><a href="https://github.com/olivernn/lunr.js" target="_blank" rel="noopener noreferrer">Lunr.js</a> 🆓</li>
<li><a href="https://www.algolia.com/" target="_blank" rel="noopener noreferrer">Algolia</a> et leur incroyable widget <a href="https://community.algolia.com/instantsearch.js/" target="_blank" rel="noopener noreferrer">InstantSearch.js</a> (🆓 pour les sites de petite et moyenne tailles)</li>
</ul>
<aside class="note"><ul>
<li><a href="http://blevesearch.com/news/Site-Search/" target="_blank" rel="noopener noreferrer">Recherche sur Bleve avec Hugo</a></li>
<li><a href="https://gist.github.com/eddiewebb/735feb48f50f0ddd65ae5606a1cb41ae" target="_blank" rel="noopener noreferrer">Recherche côté client pour Hugo avec Fuse.js</a> (<a href="https://twitter.com/edwardawebb/" target="_blank" rel="noopener noreferrer">Eddie Webb</a>)</li>
</ul></aside>
<h2 id="conclusion">Conclusion</h2>
<p>Cet article n'est pas gravé dans le marbre, beaucoup de choses vont continuer d'évoluer dans Hugo, et il se peut que certaines comparaisons ne fassent plus sens.</p>
<p>Nous espérons simplement qu'en étudiant sur les concepts bien arrêtés depuis longtemps et rarement remis en question de WordPress, nous avons contribué à aider quelques utilisateurs de WordPress à mieux comprendre l'état d'esprit et la logique d'Hugo, et qui sait à les convaincre de franchir le pas vers la Jamstack en 2019 ! 🏃</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2019/02/05/implementer-les-categories-dans-eleventy/</id>
    <title>Implémenter les catégories dans Eleventy</title>
    <published>2019-02-05T08:15:06+00:00</published>
    <updated>2019-02-06T08:10:26+00:00</updated>
    <link href="https://jamstatic.fr/2019/02/05/implementer-les-categories-dans-eleventy/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>Je veux disposer de catégories dans Eleventy afin de pouvoir dispatcher
mes articles dans divers domaines génériques <sup id="fnref1:1"><a href="#fn:1" class="footnote-ref">1</a></sup>. L'idée c'est que tous les
articles sur les livres aillent dans la catégorie "Culture", les articles
techniques dans la catégorie "Tech", etc.</p>
<ul>
<li>Un article n'a pas besoin de préciser une catégorie.</li>
<li>Un article ne peut appartenir qu'à une seule catégorie</li>
<li>Par convention, les noms de catégories commencent par une majuscule.</li>
</ul>
<p>Les tags, eux, peuvent figurer dans n'importe quelle catégorie. Un livre
étrange et un article technique fantaisiste appartiennent à des
catégories différentes, mais peuvent très bien être tous les deux étiquettés "bizarre".</p>
<h2 id="comment-ca-marche-a-l-usage">Comment ça marche à l'usage ?</h2>
<p>Regardons comment nous allons utiliser les catégories :</p>
<ul>
<li>Comment préciser la catégorie d'un article,</li>
<li>Comment accéder à la catégorie d'un article dans un modèle</li>
<li>Comment manipuler tous les articles d'une même catégorie</li>
</ul>
<h3 id="preciser-la-categorie">Préciser la catégorie</h3>
<p>On utilise la propriété <code>category</code> ainsi pour préciser la catégorie à laquelle
appartient l'article :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-meta">---</span>
<span class="hljs-attr">date:</span> <span class="hljs-number">10</span><span class="hljs-string">/30/2018</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">Loomings</span>
<span class="hljs-attr">category:</span> <span class="hljs-string">Tech</span>
<span class="hljs-attr">tags:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">tools</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">git</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">eleventy</span>
<span class="hljs-meta">---</span>
</code></pre>
<h3 id="appeler-la-categorie-dans-un-modele">Appeler la catégorie dans un modèle</h3>
<p>Dans un modèle, on fait référence à la propriété catégorie comme d'habitude :</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/categories/{{category}}"</span>&gt;</span>{{ category }}<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span></code></pre>
<h3 id="manipuler-les-articles-d-une-meme-categorie">Manipuler les articles d'une même catégorie</h3>
<p>On peut manipuler les articles d'une même catégories en créant une collection <code>categories</code> <sup id="fnref1:2"><a href="#fn:2" class="footnote-ref">2</a></sup>.
Pour lister tous les articles rangés dans la catégorie <code>Tech</code>, on pourrait procéder de la sorte :</p>
<pre><code class="language-twig hljs twig"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
  </span><span class="hljs-template-tag">{%- <span class="hljs-name"><span class="hljs-keyword">for</span></span> article in collections.categories["Tech"] -%}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span></span><span class="hljs-template-variable">{{ article.data.title }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  </span><span class="hljs-template-tag">{%- <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> -%}</span><span class="xml">
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span></span></code></pre>
<p>Tout comme l'objet <code>collections</code> possède une propriété pour chaque tag, l'objet
<code>collections.categories</code> possède une propriété pour chaque catégorie.
Chaque propriété fait référence à un tableau d'articles. Ça donne quelque chose comme ça :</p>
<pre><code class="language-json hljs json">collections: {
  all: [ items ],
  categories: {
    Culture: [ items ],
    Life: [ items ],
    Thinking: [ items ]
  }
}</code></pre>
<h2 id="implementation">Implémentation</h2>
<p>Nous voulons :</p>
<ul>
<li>une liste de catégories</li>
<li>un objet qui contient une propriété pour chaque catégorie, chaque propriété est une liste d'articles pour cette catégorie</li>
</ul>
<h3 id="creer-une-liste-de-categories">Créer une liste de catégories</h3>
<p>Pour générer une liste de catégories nous itérons sur tous les fichiers générés.
Cette fonction crée une collection <code>categoryList</code> qui contient les noms de toutes les catégories.</p>
<pre><code class="language-js hljs javascript">getCatList = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">collection</span>) </span>{
  <span class="hljs-keyword">let</span> catSet = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>();

  collection
    .getAllSorted()
    .forEach(
      <span class="hljs-function">(<span class="hljs-params">item</span>) =&gt;</span>
        <span class="hljs-keyword">typeof</span> item.data.category === <span class="hljs-string">"string"</span> &amp;&amp; catSet.add(item.data.category)
    );

  <span class="hljs-keyword">return</span> [...catSet];
};

eleventyConfig.addCollection(<span class="hljs-string">"categoryList"</span>, getCatList);</code></pre>
<h3 id="creer-une-liste-d-articles-pour-chaque-categorie">Créer une liste d'articles pour chaque catégorie</h3>
<p>Pour générer les listes d'articles de chaque catégorie, nous voulons créer un objet qui possède une propriété pour chaque catégorie, et chaque propriété contient une liste d'articles de cette catégorie. Pour le dire plus simplement, nous voulons finir avec un objet qui ressemble à ça :</p>
<pre><code class="language-json hljs json">categories {
  Culture: [article_1, article_4],
  Tech: [article_3],
  Life: [article_1, article_3]
}</code></pre>
<p>Nous pouvons utiliser la fonction <code>makeCategories()</code> comme callback de <code>addCollection()</code> pour créer cet objet. Nous itérons sur chaque élément qui possède une propriété <code>category</code> dans son front matter et nous l'ajoutons à la liste de cette catégorie <sup id="fnref1:explication"><a href="#fn:explication" class="footnote-ref">3</a></sup> :</p>
<pre><code class="language-js hljs javascript">makeCategories = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">collection</span>) </span>{
  <span class="hljs-keyword">let</span> categories = {};

  <span class="hljs-comment">// Every rendered page</span>

  collection.getAllSorted().forEach(<span class="hljs-function">(<span class="hljs-params">item</span>) =&gt;</span> {
    <span class="hljs-keyword">let</span> category = item.data.category;

    <span class="hljs-comment">// Ignore the ones without a category</span>

    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> category !== <span class="hljs-string">"string"</span>) <span class="hljs-keyword">return</span>;

    <span class="hljs-keyword">if</span> (<span class="hljs-built_in">Array</span>.isArray(categories[category]))
      <span class="hljs-comment">//  category array exists? Just push</span>
      categories[category].push(item);
    <span class="hljs-comment">//  Otherwise create it and</span>
    <span class="hljs-comment">//  make `item` the first, uh, item.</span>
    <span class="hljs-keyword">else</span> categories[category] = [item];
  });

  <span class="hljs-keyword">return</span> categories;
};</code></pre>
<p>Puisque nous souhaitons appeler notre collection de catégories <code>catgories</code>, nous la créons comme cela :</p>
<pre><code class="language-js hljs javascript">addCollection(<span class="hljs-string">"categories"</span>, makeCategories);</code></pre>
<p>Nous avons maintenant un moyen de créer une collection, qui contient elle-même ses propres collections. Cela me permet de ranger mes articles dans des@ endroits distincts.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn:1">
<p>Ça c'est ce que je dis <em>maintenant</em>. Ma première motivation était de comprendre <a href="/2019/01/29/les-collections-dans-eleventy/">comment marchent les collections</a>.]&#160;<a href="#fnref1:1" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
<li id="fn:2">
<p>Vous l'appelez comme vous voulez. Il se trouve que j'aime bien "categories".&#160;<a href="#fnref1:2" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
<li id="fn:explication">
<p>Ce <code>if (Array.isArray(categories[category]))</code> est vraiment stupide. N'existe-t-il pas un moyen d'ajouter un élément dans un tableau et de le créer au passage s'il n'existe pas ?&#160;<a href="#fnref1:explication" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
</ol>
</div>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2019/01/29/les-collections-dans-eleventy/</id>
    <title>Les collections dans Eleventy</title>
    <published>2019-01-29T07:54:22+00:00</published>
    <link href="https://jamstatic.fr/2019/01/29/les-collections-dans-eleventy/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Le générateur de site statique open source <a href="/categories/eleventy">Eleventy</a> est à la différence d'autres générateurs — comme Jekyll ou Hugo — beaucoup moins opiniâtre. Là où ces deux générateurs vont imposer <em>la</em> manière dont vous pouvez créer des collections de documents (appelées sections de contenu dans Hugo), Eleventy lui vous laisse le choix.</p></aside>
<p>Dans Eleventy les <code>collections</code> permettent de grouper des articles selon divers
critères. Une collection pourrait désigner une série d'articles. Un autre
collection pourrait regrouper les articles à propos de livres. Une troisième
collection pourrait rassembler tous les contenus d'un même répertoire.</p>
<p>Eleventy vous permet de créer des collections de deux manières :</p>
<ul>
<li><a href="#les-collections-à-base-de-tags">implicitement</a>, à l'aide de tags dans le front matter</li>
<li><a href="#les-collections-sur-mesure">explicitement</a>, avec la fonction <code>addCollection()</code></li>
</ul>
<h2 id="les-collections-a-base-de-tags">Les collections à base de tags</h2>
<p>Toutes les pages qui partagent un même tag appartiennent à la même collection.
Un modèle avec le front matter suivant va générer des pages dans les collections
<code>books</code> et <code>reviews</code>.</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-meta">---</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">Finding</span> <span class="hljs-string">Oz</span>
<span class="hljs-attr">category:</span> <span class="hljs-string">Culture</span>
<span class="hljs-attr">tags:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">books</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">reviews</span>
<span class="hljs-meta">---</span>
<span class="hljs-string">.</span> <span class="hljs-string">.</span> <span class="hljs-string">.</span></code></pre>
<p>Dans un modèle, on accède aux collections par leur nom, en tant que propriété de
l'object global <code>collections</code>.</p>
<pre><code class="language-twig hljs twig"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
  Le titre de cette page est :
  </span><span class="hljs-template-variable">{{ collections.books[0].data.title }}</span><span class="xml">
<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span></code></pre>
<p>On utilise généralement les collections dans des boucles afin d'itérer sur
chaque élément de la collection.</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">for</span></span> post in collections.books %}</span><span class="xml">
  </span><span class="hljs-template-variable">{{ post.data.title }}</span><span class="xml">
  </span><span class="hljs-template-variable">{{ post.url }}</span><span class="xml">
  </span><span class="hljs-template-variable">{{ post.data.category }}</span><span class="xml">
  </span><span class="hljs-template-variable">{{ post.data.tags }}</span><span class="xml">
  </span><span class="hljs-template-variable">{{ post.<span class="hljs-name">date</span> }}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> %}</span></code></pre>
<p>L'objet <code>collections</code> lui, ressemble à ça :</p>
<pre><code class="language-json hljs json">{
  <span class="hljs-attr">"all"</span>: [...],
  <span class="hljs-attr">"nav"</span>: [...],
  <span class="hljs-attr">"books"</span>: [
    {
      <span class="hljs-attr">"inputPath"</span>: <span class="hljs-string">"./src/articles/finding-oz.md"</span>,
      <span class="hljs-attr">"outputPath"</span>: <span class="hljs-string">"_site/articles/finding-oz/index.html"</span>,
      <span class="hljs-attr">"fileSlug"</span>: <span class="hljs-string">"finding-oz"</span>,
      <span class="hljs-attr">"data"</span>: {...},
      <span class="hljs-attr">"date"</span>: <span class="hljs-string">"2009-08-07T13:52:12.000Z"</span>,
      <span class="hljs-attr">"url"</span>: <span class="hljs-string">"/articles/finding-oz/"</span>,
      <span class="hljs-attr">"templateContent"</span>: <span class="hljs-string">"&lt;p&gt;As with most books ... much about The Wizard of Oz&lt;/li&gt;\n&lt;/ul&gt;\n"</span>,
      <span class="hljs-attr">"template"</span>: {...}
    },
    ...
  ],
  <span class="hljs-attr">"programming"</span>: [...],
}</code></pre>
<p>Chaque propriété est un tableau d'<a href="https://www.11ty.dev/docs/collections/#collection-item-data-structure" target="_blank" rel="noopener noreferrer">objets d'éléments de
collection</a>
(également appelés <a href="https://www.11ty.dev/docs/collections/#return-values" target="_blank" rel="noopener noreferrer">objets
modèle</a> dans la
documentation).</p>
<p>La collection spéciale <code>all</code> représente un tableau de tous les objets page
générés par Eleventy.</p>
<table>
<thead>
<tr>
<th style="text-align: left;">Propriété</th>
<th style="text-align: left;">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;"><code>inputPath</code></td>
<td style="text-align: left;">Chemin vers ce fichier incluant le répertoire source. <hr><code class="phony">./src/articles/finding-oz.md</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>outputPath</code></td>
<td style="text-align: left;">Chemin du fichier généré. <hr><code class="phony">articles/finding-oz/index.html</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>fileSlug</code></td>
<td style="text-align: left;">Version courte en fonction du nom et de l'emplacement du fichier. <a href="https://www.11ty.dev/docs/data/#fileslug" target="_blank" rel="noopener noreferrer">En fonction des règles</a>. <hr><code class="phony">finding-oz</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>data</code></td>
<td style="text-align: left;">Données du front matter de la page rendue. Les variables globales disponibles pour chaque page.</td>
</tr>
<tr>
<td style="text-align: left;"><code>date</code></td>
<td style="text-align: left;">La date du fichier au format UTC. <a href="https://www.11ty.dev/docs/dates/" target="_blank" rel="noopener noreferrer">Voir les règles</a>. <hr><code class="phony">2019-01-27T13:52:12.000Z</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>url</code></td>
<td style="text-align: left;">Chemin vers le contenu. N'inclus pas le protocole et le nom d'hôte. <hr><code class="phony">/articles/finding-oz/</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>templateContent</code></td>
<td style="text-align: left;">Le contenu généré de la page, n'inclut pas les balises enveloppantes de mise en page.<hr><code class="phony">&lt;p&gt;Comme la plupart des livres ... à propos du Magicien d'Oz&lt;/li&gt;\n&lt;/ul&gt;\n</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>template</code></td>
<td style="text-align: left;">Toutes sortes de données analysées par le modèle. Des choses comme la configuration d'Eleventy, la configuration du moteur de rendu pour le markdown, et beaucoup de choses sur lesquelles nous ne devrions probablement pas nous baser.</td>
</tr>
</tbody>
</table>
<p><strong>Implémentation : Comment un tag devient une collection</strong></p>
<p><a href="https://github.com/11ty/eleventy/blob/7cac4ac0b6b99dd79d07ab94d1a443c276fe73db/src/TemplateMap.js#L146-L161" target="_blank" rel="noopener noreferrer"><code>getTaggedCollectionsData()</code></a> est la fonction qui transforme des tags en collections.</p>
<pre><code class="language-javascript hljs javascript"><span class="hljs-keyword">async</span> getTaggedCollectionsData() {
<span class="hljs-keyword">let</span> collections = {};
collections.all = <span class="hljs-keyword">this</span>.createTemplateMapCopy(
<span class="hljs-keyword">this</span>.collection.getAllSorted()
);
debug(<span class="hljs-string">`Collection: collections.all size: <span class="hljs-subst">${collections.all.length}</span>`</span>);

<span class="hljs-keyword">let</span> tags = <span class="hljs-keyword">this</span>.getAllTags();
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> tag <span class="hljs-keyword">of</span> tags) {
collections[tag] = <span class="hljs-keyword">this</span>.createTemplateMapCopy(
<span class="hljs-keyword">this</span>.collection.getFilteredByTag(tag)
);
debug(<span class="hljs-string">`Collection: collections.<span class="hljs-subst">${tag}</span> size: <span class="hljs-subst">${collections[tag].length}</span>`</span>);
}
<span class="hljs-keyword">return</span> collections;
}</code></pre>
<p><code>getTaggedCollectionsData()</code> est appelée dans <code>TemplateMap.cache()</code> qui est
l'endroit ou Eleventy génère les collections.</p>
<h2 id="les-collections-sur-mesure">Les collections sur mesure</h2>
<p>Outre les collections créées à partir de tags, vous pouvez utiliser la fonction
<code>addCollection()</code> dans votre fichier de configuration <code>.eleventy.js</code> pour créer
vos propres collections.</p>
<p>Par exemple, voici comment créer une collection nommée <code>articles</code> constituée de pages
générées à partir de modèles présents dans le dossier <code>src/articles/</code> :</p>
<pre><code class="language-js hljs javascript">eleventyConfig.addCollection(<span class="hljs-string">"articles"</span>, (collection) =&gt;
  collection
    .getAllSorted()
    .filter(
      <span class="hljs-function">(<span class="hljs-params">item</span>) =&gt;</span>
        item.url &amp;&amp;
        !item.inputPath.includes(<span class="hljs-string">"index.njk"</span>) &amp;&amp;
        item.inputPath.startsWith(<span class="hljs-string">"./src/articles/"</span>)
    )
);</code></pre>
<p>La fonction <code>addCollection()</code> prend deux paramètres<sup id="fnref1:addcollection"><a href="#fn:addcollection" class="footnote-ref">1</a></sup> :</p>
<ul>
<li>le nom de la collection (une chaîne de caractères)</li>
<li>une fonction qui prend une <code>collection</code> en paramètre.</li>
</ul>
<p>Vous pourriez penser que le paramètre collection est un tableau d'objets de
modèle comme l'objet <code>collections</code> basé sur les tags. Ce paramètre est en fait
une instance d'une <a href="https://github.com/11ty/eleventy/blob/master/src/TemplateCollection.js" target="_blank" rel="noopener noreferrer"><code>TemplateCollection</code></a>, qui dérive de
<a href="https://github.com/11ty/eleventy/blob/master/src/Util/Sortable.js" target="_blank" rel="noopener noreferrer"><code>Sortable</code></a>, et ressemble à ceci :</p>
<pre><code class="language-json hljs json">{
  <span class="hljs-attr">"items"</span>: [
    { ... },
    . . .
    { ... }
  ],
  <span class="hljs-attr">"sortFunctionStringMap"</span>: { ... },
  <span class="hljs-attr">"sortAscending"</span>: <span class="hljs-literal">true</span>,
  <span class="hljs-attr">"sortNumeric"</span>: <span class="hljs-literal">false</span>
}</code></pre>
<p>La propriété <code>items</code> est un tableau de tous les objets de modèle. C'est la même
chose que <code>collections.all</code>. Vous ne voulez pas accéder aux éléments directement
en écrivant : <code>collection.item[n]</code>.
Utilisez plutôt les <a href="https://www.11ty.dev/docs/collections/#collection-api-methods" target="_blank" rel="noopener noreferrer">méthodes suivantes</a> pour accéder aux éléments.</p>
<table>
<thead>
<tr>
<th style="text-align: left;">Méthode</th>
<th style="text-align: left;">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;"><code>getAll()</code></td>
<td style="text-align: left;">Récupérer tous les éléments dans un ordre spécifique.</td>
</tr>
<tr>
<td style="text-align: left;"><code>getAllSorted()</code></td>
<td style="text-align: left;">Récupérer tous les éléments dans l'ordre.</td>
</tr>
<tr>
<td style="text-align: left;"><code>getFilteredByTag(tagName)</code></td>
<td style="text-align: left;">Récupérer tous les éléments qui possèdent un tag spécifique.</td>
</tr>
<tr>
<td style="text-align: left;"><code>getFilteredByGlob(glob)</code></td>
<td style="text-align: left;">Récupérer tous les éléments dont l' <code>inputPath</code> correspond à un ou plusieurs patterns globaux.</td>
</tr>
</tbody>
</table>
<p>Les éléments sont <em>presque</em> <a href="#elements-collection">les mêmes</a> que ceux des
collections basées sur des tags, à la différence près que dans les collections
basées sur des tags, les éléments ont une propriété <code>templateContent</code>. Dans les
collections créées avec la fonction <code>addCollection()</code>, les éléments ont une
propriété <code>_pages</code>. Je ne saurais dire pourquoi.</p>
<p>Vous pouvez utiliser <code>addCollection()</code> pour créer des collections de pages.
Depuis Eleventy 0.5.3, vous pouvez l'utiliser pour créer des collections ou des
objets de votre choix.</p>
<p>Par exemple, voici comment vous créeriez une collection constituée d'un tableau
de toutes les catégories :</p>
<pre><code class="language-js hljs javascript"><span class="hljs-built_in">module</span>.exports = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">collection</span>) </span>{
  <span class="hljs-keyword">let</span> catSet = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Set</span>();

  collection
    .getAllSorted()
    .forEach(
      <span class="hljs-function">(<span class="hljs-params">item</span>) =&gt;</span>
        <span class="hljs-keyword">typeof</span> item.data.category === <span class="hljs-string">"string"</span> &amp;&amp; catSet.add(item.data.category)
    );

  <span class="hljs-keyword">return</span> [...catSet];
};</code></pre>
<p><strong>Implémentation : Comment sont construites les collections sur mesure</strong></p>
<p><a href="https://github.com/11ty/eleventy/blob/7cac4ac0b6b99dd79d07ab94d1a443c276fe73db/src/TemplateMap.js#L167-L191" target="_blank" rel="noopener noreferrer"><code>getUserConfigCollectionsData()</code></a> est la fonction qui appelle ce qui est retourné par la fonction <code>addCollection()</code>.</p>
<pre><code class="language-javascript hljs javascript"><span class="hljs-keyword">async</span> getUserConfigCollectionsData() {
<span class="hljs-keyword">let</span> collections = {};
<span class="hljs-keyword">let</span> configCollections =
<span class="hljs-keyword">this</span>.configCollections || eleventyConfig.getCollections();
<span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> name <span class="hljs-keyword">in</span> configCollections) {
<span class="hljs-keyword">let</span> ret = configCollections[name](<span class="hljs-keyword">this</span>.collection);
<span class="hljs-comment">// work with arrays and strings returned from UserConfig.addCollection</span>
<span class="hljs-keyword">if</span> (
<span class="hljs-built_in">Array</span>.isArray(ret) &amp;&amp;
ret.length &amp;&amp;
ret[<span class="hljs-number">0</span>].inputPath &amp;&amp;
ret[<span class="hljs-number">0</span>].outputPath
) {
collections[name] = <span class="hljs-keyword">this</span>.createTemplateMapCopy(ret);
} <span class="hljs-keyword">else</span> {
collections[name] = ret;
}
debug(
<span class="hljs-string">`Collection: collections.<span class="hljs-subst">${name}</span> size: <span class="hljs-subst">${collections[name].length}</span>`</span>
);
}
<span class="hljs-keyword">return</span> collections;
}</code></pre>
<p><code>getUserConfigCollectionsData()</code> est appelé dans <code>TemplateMap.cache()</code> qui est
l'endroit où Eleventy construit les collections.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn:addcollection">
<p><code>addCollection()</code> ne fait rien d'autre qu'associer la fonction
qui construit la collection au nom de la collection.
La fonction qui construit la collection est elle-même appelée plus tard dans
<a href="https://github.com/11ty/eleventy/blob/7cac4ac0b6b99dd79d07ab94d1a443c276fe73db/src/TemplateMap.js#L167-L191" target="_blank" rel="noopener noreferrer"><code>getUserConfigCollectionsData()</code></a>.&#160;<a href="#fnref1:addcollection" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
</ol>
</div>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2019/01/28/meetup-jamstack-paris-1/</id>
    <title>Meetup Jamstack Paris #1</title>
    <published>2019-01-28T17:45:24+00:00</published>
    <link href="https://jamstatic.fr/2019/01/28/meetup-jamstack-paris-1/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Au programme du premier meetup Jamstack Paris, un retour d'expérience sur la migration d'un site Angular vers Gatsby, et un exemple de développement en live d'un plugin Gatsby. <a href="https://www.youtube.com/channel/UC66eQOycjMnaqzpbRUhEK2w" target="_blank" rel="noopener noreferrer">Les vidéos sont en ligne</a>.</p></aside>
<p>Dans la première présentation, <a href="https://twitter.com/dot_louis" target="_blank" rel="noopener noreferrer">Louis Lafont</a>, développeur front-end chez <a href="https://monbanquet.fr/" target="_blank" rel="noopener noreferrer">MonBanquet</a>, présente la stack qu'il a choisi pour la refonte du site, à savoir <a href="https://gatsbyjs.org" target="_blank" rel="noopener noreferrer">Gatsby</a> pour la partie front, <a href="https://www.contentful.com/" target="_blank" rel="noopener noreferrer">Contentful</a> pour la gestion de contenu et <a href="https://www.netlify.com/" target="_blank" rel="noopener noreferrer">Netlify</a> pour héberger le tout.</p>
<p>Il aura fallu près de deux mois à Louis pour mener à bien cette tâche et apprécier la <em>developer experience</em> offerte par Gatsby. Au final grâce notamment au plugin <a href="https://using-gatsby-image.gatsbyjs.org/" target="_blank" rel="noopener noreferrer">gatsby-image</a>, la PWA générée est bien entendu beaucoup plus rapide, le <em>Time to Interactive</em> est passé sous les deux secondes. Autre gain appréciable, la prise en main rapide de Contentful par l'équipe marketing chargée de la mise à jour des contenus. Tout le monde a gagné en confort d'utilisation, en autonomie et en productivité.</p>
<p>Et le tout pour la modique somme de <strong>zéro euro</strong> 😲, puisque Gatsby et ses plugins sont sous license open source, et que pour le moment le volume de données consommé ne génère aucun frais d'abonnement aux différentes plate-formes Saas. Le pricing est une bénédiction pour les startups.</p>
<p>Tous le monde est ravi, la prochaine étape sera de s'attaquer à l'authentification utilisateur et d'ajouter un gestion de panier 💳 e-commerce.</p>
<p><div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;">
<iframe src="https://www.youtube-nocookie.com/embed/xLQ4to7Ubn0" loading="lazy" width="640" height="360" frameborder="0" allow="accelerometer;autoplay;encrypted-media;gyroscope;picture-in-picture;fullscreen;web-share;" allowfullscreen="" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background-color:#d8d8d8;"></iframe>
</div></p>
<hr>
<p>Pour la deuxième présentation, <a href="https://twitter.com/Phacks" target="_blank" rel="noopener noreferrer">Nicolas Goutay</a>, évangéliste performance web chez Theodo, se propose ni plus ni moins de développer un plugin Gatsby en direct. Nicolas avait déjà testé Gatsby sur <a href="https://github.com/phacks/theatres-parisiens" target="_blank" rel="noopener noreferrer">un projet qui liste les théâtres parisiens</a>.</p>
<p>Grand amateur de disques vinyles, Nicolas comme tout bon nerd a enregistré sa collection dans <a href="https://www.discogs.com/" target="_blank" rel="noopener noreferrer">Discogs</a>, qui propose une API pour récupérer les releases. Problème, le nombre d'appel est très limité, et <a href="https://phacks.github.io/showcase-for-discogs/" target="_blank" rel="noopener noreferrer">son premier mockup</a>, ne peut même pas afficher la totalité de sa collection, voire rien du tout si le nombre d'appel à la minute est déjà atteint. Pas glop.</p>
<p>L'application récupére les références des <em>releases</em> sur Discogs pour pouvoir ensuite générer une tracklist de chaque album via l'API de YouTube. Pour contourner la limitation de Discogs, Nicolas a donc eu l'idée d'utiliser Gatsby et de générer son application au build, quitte a espacer les appels à l'API pour pouvoir tout récupérer. Pour cela il a donc développé <a href="https://www.gatsbyjs.org/docs/create-source-plugin/" target="_blank" rel="noopener noreferrer">un plugin Gatsby source</a>, qui va lui permettre de créer facilement des noeuds, qui pourront être ensuite requêter dans GraphQL. La démo permet d'apprécier encore une fois la <em>developer experience</em> offerte par Gatsby, avec notamment une gestion automatique de la documentation de tous les attributs des noeuds.</p>
<p><div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;">
<iframe src="https://www.youtube-nocookie.com/embed/7pbFDBXiuAA" loading="lazy" width="640" height="360" frameborder="0" allow="accelerometer;autoplay;encrypted-media;gyroscope;picture-in-picture;fullscreen;web-share;" allowfullscreen="" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background-color:#d8d8d8;"></iframe>
</div></p>
<hr>
<p>👏 Un grand bravo aux organisateurs, le <a href="https://www.meetup.com/fr-FR/Jamstack-paris/events/257983707/" target="_blank" rel="noopener noreferrer">prochain meetup aura lieu le 27 février</a>.
Au programme encore du Gatsby, cette fois avec du Algolia et du WordPress dedans.</p>
<p>On espère voir toujours plus de retours d'expérience, pas forcément que sur Gatsby, même si les frameworks front-end sont devenus en quelques années le nouveau standard de facto. En tout cas ça fait rudement plaisir de voir que la communauté francophone se fédère, nul doute que ce genre d'évènement contribuera à inciter à l'adoption d'architectures décentralisées.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2018/12/12/passer-de-jekyll-a-eleventy/</id>
    <title>Passer de Jekyll à Eleventy</title>
    <published>2018-12-12T00:00:00+00:00</published>
    <link href="https://jamstatic.fr/2018/12/12/passer-de-jekyll-a-eleventy/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p><a href="/categories/jekyll">Jekyll</a> est à ce jour le générateur de site statique le plus utilisé, c'est aussi un des plus anciens, et il fait face aujourd'hui à beaucoup de concurrents. Un des projets récents qui se rapproche le plus de Jekyll est <a href="/categories/eleventy">Eleventy</a>, développé par le très sympathique <a href="https://twitter.com/zachleat" target="_blank" rel="noopener noreferrer">Zach Leat</a>. Eleventy, c'est Jekyll repensé pour tirer parti de JavaScript et de l'écosystème npm. C'est un outil qui reste très simple d'approche et qui supporte Liquid comme langage de gabarit.<br>
Autant d'arguments qui ont vite fait de convaincre <a href="https://paulrobertlloyd.com/" target="_blank" rel="noopener noreferrer">Paul Robert Lloyd</a> de tenter de migrer son site vers Eleventy. Qui sait, la lecture de cet article vous incitera peut-être à faire de même ?</p></aside>
<p>Ne pas compliquer les choses s'avère parfois payant. Bien que beaucoup de sites que nous utilisons tous les jours aient besoin de bases de données relationnelles pour gérer leurs contenus, et de pages dynamiques pour répondre aux contributions de leurs utilisateurs, beaucoup de sites plus simples peuvent se contenter de servir du HTML pré-compilé, c'est généralement une solution beaucoup moins onéreuse et bien plus sécurisée.</p>
<p>La <a href="https://www.jamstack.org" target="_blank" rel="noopener noreferrer">Jamstack</a> (JavaScript, APIs réutilisables et Markup préparé à l'avance) est un terme marketing populaire qui désigne cette nouvelle manière d'architecturer des sites web, et en un sens c'est un retour aux débuts du web, avant que les développeurs ne commencent à bricoler avec des scripts CGI ou PHP. En réalité mon site web a toujours servi du HTML pré-compilé : d'abord à l'aide de <a href="https://movabletype.org" target="_blank" rel="noopener noreferrer">Movable Type</a>, et puis récemment avec celle de <a href="https://jekyllrb.com" target="_blank" rel="noopener noreferrer">Jekyll</a>, à propos duquel <a href="https://24ways.org/2013/get-started-with-github-pages/" target="_blank" rel="noopener noreferrer">Anna écrivait en 2013</a>.</p>
<p>En combinant trois langages faciles d'approche — Markdown pour le contenu, YAML pour les données et Liquid pour les modèles de page — Jekyll a rencontré un large public et a influencé le design de <a href="https://www.staticgen.com" target="_blank" rel="noopener noreferrer">beaucoup de générateurs de sites statiques</a> qui ont suivi. Jekyll n'en est pas parfait pour autant. Outres des temps de compilation qui peuvent être importants, il est développé en Ruby. Bien que Ruby soit un langage de programmation très élégant, c'est un nouvel écosystème à appréhender et à savoir gérer, en plus ce celui que l'on utilise déjà côté front : JavaScript. Quand j'utilisais Jekyll, je me disais souvent "La même chose, mais en Node". Heureusement pour moi, un des elfes de Noël a exaucé mon vœu <a href="https://blog.codinghorror.com/the-principle-of-least-power/" target="_blank" rel="noopener noreferrer">Atwoodien</a> et a déposé un tel générateur de site statique au pied de mon sapin.</p>
<h2 id="presentation-d-eleventy">Présentation d'Eleventy</h2>
<p><a href="https://www.11ty.dev" target="_blank" rel="noopener noreferrer">Eleventy</a> est une alternative beaucoup plus flexible que Jekyll. Outre le fait qu'il soit écrit en Node, il est beaucoup moins strict quant à la manière d'organiser ses fichiers, et supporte d'autres langages de gabarits comme EJS, Pug, Handlebars et Nunjucks, en plus de Liquid. Le top c'est que les temps de compilations sont <em>bien</em> meilleurs (et les <a href="https://github.com/11ty/eleventy/issues/56" target="_blank" rel="noopener noreferrer">optimisations futures</a> promettent des gains supplémentaires).</p>
<p>Vu que le contenu est stocké avec la même combinaison familière de front matter YAML et de Markdown, passer de Jekyll à Eleventy semble plutôt raisonnable au premier abord. Et pourtant, j'ai découvert à mes dépens qu'il y avait quelques pièges. Si vous envisagez une migration, voici quelques petits trucs et astuces pour vous aider dans votre parcours<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref">1</a></sup>.</p>
<aside class="note note-info"><p>Tout au long de cet article, nous allons prendre comme exemple le site du <a href="https://www.markdownguide.org" target="_blank" rel="noopener noreferrer">Guide Markdown</a> de Matt Cone. Si vous voulez tester les modifications, commencez par cloner le <a href="https://github.com/mattcone/markdown-guide" target="_blank" rel="noopener noreferrer">dépôt git</a> et placez-vous dans le dossier du projet :</p>
<pre><code class="language-sh hljs bash">git <span class="hljs-built_in">clone</span> https://github.com/mattcone/markdown-guide.git
<span class="hljs-built_in">cd</span> markdown-guide</code></pre></aside>
<h2 id="avant-de-commencer">Avant de commencer</h2>
<p>Si vous avez déjà utilisé des outils comme Grunt, Gulp ou webpack, vous connaissez déjà un peu l'écosystème de Node.js, mais si vous avez uniquement utilisé Jekyll pour compiler vos CSS et générer votre HTML, il est maintenant temps pour vous d'<a href="https://nodejs.org" target="_blank" rel="noopener noreferrer">installer Node.js</a> et de configurer votre projet afin de pouvoir utiliser son gestionnaire de paquet, npm :</p>
<ol>
<li><strong>Installer Node.js :</strong></li>
</ol>
<ul>
<li>Mac : Si ce n'est pas déjà fait, je vous conseille d'<a href="https://brew.sh" target="_blank" rel="noopener noreferrer">installer Homebrew</a>, a gestionnaire de paquets pour Mac. Ensuite dans un terminal tapez <code>brew install node</code>.</li>
<li>Windows : <a href="https://nodejs.org/en/download/" target="_blank" rel="noopener noreferrer">Téléchargez l'installateur pour Windows</a> depuis le site web de Node.js et suivez les instructions.</li>
</ul>
<ol start="2">
<li><strong>Initialiser NPM :</strong> Assurez-vous d'être dans le répertoire du projet et tapez <code>npm init</code>. Cette commande va vous poser quelques questions avant de créer un fichier appelé <code>package.json</code>. Comme le <code>Gemfile</code> de RubyGems, il contient la liste des dépendances tierces de votre projet.</li>
</ol>
<p>Si vous gérez les versions de votre site avec Git, assurez-vous également d'ajouter le répertoire <code>node_modules</code> à votre fichier <code>.gitignore</code>. Contrairement à RubyGems, npm stocke par défaut ses dépendances dans le répertoire de votre projet. Ce répertoire peut vite devenir assez important, et comme il contient des fichiers binaires compilés spécifiquement pour votre ordinateur, il ne devrait pas être versionné. Eleventy prend ce fichier en compte, ce qui veut dire que tout ce que vous voulez que Git ignore, Eleventy l'ignorera aussi.</p>
<h2 id="installer-eleventy">Installer Eleventy</h2>
<p>Maintenant que Node.js est installé et que votre projet est prêt à utiliser npm, nous pouvons installer Eleventy en tant que dépendance :</p>
<pre><code class="language-sh hljs bash">npm install --save-dev @11ty/eleventy</code></pre>
<p>Si vous ouvrez votre <code>package.json</code> vous devriez y voir figurer les lignes suivantes :</p>
<pre><code class="language-sh hljs bash">…
<span class="hljs-string">"devDependencies"</span>: {
  <span class="hljs-string">"@11ty/eleventy"</span>: <span class="hljs-string">"^0.6.0"</span>
}
…</code></pre>
<p>Nous pouvons maintenant lancer Eleventy en ligne de commande à l'aide de l'utilitaire <code>npx</code> de NPM. Par exemple pour convertir le fichier <code>README.md</code> en HTML, nous taperons la commande suivante :</p>
<pre><code class="language-sh hljs bash">npx eleventy --input=README.md --formats=md</code></pre>
<p>Cette commande va générer un fichier HTML dans <code>_site/README/index.html</code>.<br>
Eleventy utilise par défaut le même répertoire de destination que Jekyll (<code>_site</code>), comme nous le verrons à de nombreuses reprises pendant cette transition.</p>
<h2 id="la-configuration">La configuration</h2>
<p>Alors que Jekyll utilise la syntaxe YAML pour son fichier de configuration, Eleventy lui se repose sur JavaScript. Cela permet de programmer des options et ouvre donc des possibilités assez puissantes comme nous le verrons par la suite.</p>
<p>Commençons par créer notre fichier de configuration (<code>.eleventy.js</code>), et reportons les paramètres pertinents du <code>_config.yml</code> dans leurs options équivalentes :</p>
<pre><code class="language-js hljs javascript"><span class="hljs-built_in">module</span>.exports = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">eleventyConfig</span>) </span>{
  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">dir</span>: {
      <span class="hljs-attr">input</span>: <span class="hljs-string">"./"</span>,      <span class="hljs-comment">// Équivalent au paramètre source de Jekyll</span>
      <span class="hljs-attr">output</span>: <span class="hljs-string">"./_site"</span> <span class="hljs-comment">// Équivalent au paramètre destination de Jekyll</span>
    }
  };
};</code></pre>
<p>Quelques choses bonnes à savoir :</p>
<ul>
<li>Alors que Jekyll vous permet de lister les fichiers et dossiers à exclure de la génération avec le paramètre <code>exclude</code>, <a href="https://www.11ty.dev/docs/ignores/" target="_blank" rel="noopener noreferrer">Eleventy recherche ses mêmes valeurs</a> dans un fichier nommé  <code>.eleventyignore</code> (en plus du <code>.gitignore</code>).</li>
<li>Par défaut, Eleventy utilise <a href="https://github.com/markdown-it/markdown-it" target="_blank" rel="noopener noreferrer">markdown-it</a> pour parcourir le Markdown. Si vous utilisez des fonctionnalités avancées (comme les abréviations, les listes de définition et les notes de bas de page), vous devrez <a href="https://www.11ty.dev/docs/languages/markdown/" target="_blank" rel="noopener noreferrer">déclarer votre propre instance de cette bibliothèque Markdown (ou d'une autre) à Eleventy</a>et la configurer avec les options et les plugins de votre choix.</li>
</ul>
<h2 id="les-gabarits-de-mise-en-forme">Les gabarits de mise en forme</h2>
<p>Eleventy manque encore de flexibilité quant à la localisation des <code>layouts</code>, qui doivent pour le moment se trouver dans le répertoire <code>_includes</code> (<a href="https://github.com/11ty/eleventy/issues/137" target="_blank" rel="noopener noreferrer">Surveiller la résolution du problème sur GitHub</a>).</p>
<p>Nous allons donc devoir déplacer nos fichiers du répertoire <code>_layouts</code> vers <code>_includes\layouts</code>, puis mettre à jour les références pour y incorporer le sous-dossier <code>layouts</code>. Nous pourrions mettre à jour la propriété <code>layout:</code> dans le front matter de chacun de nos fichiers de contenu, mais nous allons opter pour la <a href="https://www.11ty.dev/docs/layouts/#layout-aliasing" target="_blank" rel="noopener noreferrer">création d'alias</a> dans la configuration d'Eleventy :</p>
<pre><code class="language-js hljs javascript"><span class="hljs-built_in">module</span>.exports = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">eleventyConfig</span>) </span>{
    <span class="hljs-comment">// les alias sont relatifs au répertoire _includes</span>
    eleventyConfig.addLayoutAlias(<span class="hljs-string">'about'</span>, <span class="hljs-string">'layouts/about.html'</span>);
    eleventyConfig.addLayoutAlias(<span class="hljs-string">'book'</span>, <span class="hljs-string">'layouts/book.html'</span>);
    eleventyConfig.addLayoutAlias(<span class="hljs-string">'default'</span>, <span class="hljs-string">'layouts/default.html'</span>);

    <span class="hljs-keyword">return</span> {
      <span class="hljs-attr">dir</span>: {
        <span class="hljs-attr">input</span>: <span class="hljs-string">"./"</span>,
        <span class="hljs-attr">output</span>: <span class="hljs-string">"./_site"</span>
      }
    };
  }</code></pre>
<h3 id="determiner-le-langage-a-utiliser-pour-les-gabarits">Déterminer le langage à utiliser pour les gabarits</h3>
<p>Par défaut Eleventy va transformer les fichiers Markdown (<code>.md</code>) avec Liquid, mais nous avons aussi besoin de dire à Eleventy comment procéder au traitement des autres fichiers qui utilisent des gabarits Liquid. Il existe pour cela <a href="https://www.11ty.dev/docs/languages/#overriding-the-template-language" target="_blank" rel="noopener noreferrer">plusieurs manières de faire</a>, la plus simple étant de modifier les extensions des fichiers. Ici, quelques fichiers se trouvent dans notre dossier <code>api</code> que nous voulons traiter avec Liquid et exporter au format JSON. Pour cela nous ajoutons le suffixe <code>.liquid</code> à notre fichier (en conséquence <code>basic-syntax.json</code> devient <code>basic-syntax.json.liquid</code>), Eleventy saura alors quoi faire.</p>
<h2 id="les-variables">Les variables</h2>
<p>De l'extérieur, Jekyll et Eleventy se ressemblent pas mal, mais chaque outil modélise son contenu et ses données d'une manière légèrement différente, il va donc nous falloir mettre à jour quelques variables dans nos modèles.</p>
<h3 id="les-variables-de-site">Les variables de site</h3>
<p>En plus des directives de compilation, Jekyll vous permet de stocker des variables globales dans son fichier de configuration et d'y accéder dans les modèles via l'espace de nom <code>site.*</code>. Par exemple dans notre Guide Markdown nous avons les valeurs suivantes :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">title:</span> <span class="hljs-string">"Markdown Guide"</span>
<span class="hljs-attr">url:</span> <span class="hljs-string">https://www.markdownguide.org</span>
<span class="hljs-attr">baseurl:</span> <span class="hljs-string">""</span>
<span class="hljs-attr">repo:</span> <span class="hljs-string">http://github.com/mattcone/markdown-guide</span>
<span class="hljs-attr">comments:</span> <span class="hljs-literal">false</span>
<span class="hljs-attr">author:</span>
  <span class="hljs-attr">name:</span> <span class="hljs-string">"Matt Cone"</span>
<span class="hljs-attr">og_locale:</span> <span class="hljs-string">"en_US"</span></code></pre>
<p>Le fichier de configuration d'Eleventy utilise JavaScript et n'est pas fait pour stocker de telles valeurs. Toutefois comme avec Jekyll, nous pouvons utiliser <a href="https://www.11ty.dev/docs/data-global/" target="_blank" rel="noopener noreferrer">des fichiers de données pour stocker des variables globales</a>. Si nous ajoutons nos données relatives au site dans un fichier JSON situé dans le dossier <code>_data</code> et que nous le nommons <code>site.json</code>, nous pouvons continuer à utiliser l'espace de nom <code>site.*</code> et laisser nos variables telles quelles.</p>
<pre><code class="language-json hljs json">{
    <span class="hljs-attr">"title"</span>: <span class="hljs-string">"Markdown Guide"</span>,
    <span class="hljs-attr">"url"</span>: <span class="hljs-string">"https://www.markdownguide.org"</span>,
    <span class="hljs-attr">"baseurl"</span>: <span class="hljs-string">""</span>,
    <span class="hljs-attr">"repo"</span>: <span class="hljs-string">"http://github.com/mattcone/markdown-guide"</span>,
    <span class="hljs-attr">"comments"</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">"author"</span>: {
      <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Matt Cone"</span>
    },
    <span class="hljs-attr">"og_locale"</span>: <span class="hljs-string">"en_US"</span>
  }</code></pre>
<h3 id="les-variables-de-page">Les variables de page</h3>
<p>Le tableau ci-dessous établit la correspondance entre les variables de page.
Retenez qu'on peut accéder directement aux propriétés front matter, alors que les valeurs des méta-données dérivées (comme les URLs, les dates, etc.) sont préfixées avec l'espace de nom <code>pages.*</code> :</p>
<table>
<thead>
<tr>
<th style="text-align: left;">Jekyll</th>
<th style="text-align: left;">Eleventy</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;"><code>page.url</code></td>
<td style="text-align: left;"><code>page.url</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>page.date</code></td>
<td style="text-align: left;"><code>page.date</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>page.path</code></td>
<td style="text-align: left;"><code>page.inputPath</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>page.id</code></td>
<td style="text-align: left;"><code>page.outputPath</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>page.name</code></td>
<td style="text-align: left;"><code>page.fileSlug</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>page.content</code></td>
<td style="text-align: left;"><code>content</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>page.title</code></td>
<td style="text-align: left;"><code>title</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>page.foobar</code></td>
<td style="text-align: left;"><code>foobar</code></td>
</tr>
</tbody>
</table>
<p>Lorsque d'une itération sur des pages, les valeurs front matter sont accessibles via l'objet <code>data</code> et le contenu via <code>templateContent</code> :</p>
<table>
<thead>
<tr>
<th style="text-align: left;">Jekyll</th>
<th style="text-align: left;">Eleventy</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;"><code>item.url</code></td>
<td style="text-align: left;"><code>item.url</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>item.date</code></td>
<td style="text-align: left;"><code>item.date</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>item.path</code></td>
<td style="text-align: left;"><code>item.inputPath</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>item.id</code></td>
<td style="text-align: left;"><code>item.outputPath</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>item.name</code></td>
<td style="text-align: left;"><code>item.fileSlug</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>item.content</code></td>
<td style="text-align: left;"><code>item.templateContent</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>item.title</code></td>
<td style="text-align: left;"><code>item.data.title</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>item.foobar</code></td>
<td style="text-align: left;"><code>item.data.foobar</code></td>
</tr>
</tbody>
</table>
<p>Espérons que ces différences entre les pages et les variables d'item disparaissent dans une future version (<a href="https://github.com/11ty/eleventy/issues/338" target="_blank" rel="noopener noreferrer">suivre le problème sur GitHub</a>), afin de faciliter la compréhension de la manière dont Eleventy structure ses données.</p>
<h3 id="les-variables-de-pagination">Les variables de pagination</h3>
<p>Alors qu'avec Jekyll, la pagination est limitée à lister des articles sur une page, Eleventy vous permet de <a href="https://www.11ty.dev/docs/pagination/" target="_blank" rel="noopener noreferrer">paginer n'importe quelles données ou documents de collections</a>. Vu cette disparité, les changements sont plus importants, mais ce tableau liste la correspondance des variables équivalentes :</p>
<table>
<thead>
<tr>
<th style="text-align: left;">Jekyll</th>
<th style="text-align: left;">Eleventy</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;"><code>pagination.page</code></td>
<td style="text-align: left;"><code>pagination.pageNumber</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>pagination.per_page</code></td>
<td style="text-align: left;"><code>pagination.size</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>pagination.posts</code></td>
<td style="text-align: left;"><code>pagination.items</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>pagination.previous_page_path</code></td>
<td style="text-align: left;"><code>pagination.previousPageHref</code></td>
</tr>
<tr>
<td style="text-align: left;"><code>pagination.previous_page_path</code></td>
<td style="text-align: left;"><code>pagination.nextPageHref</code></td>
</tr>
</tbody>
</table>
<h2 id="les-filtres">Les filtres</h2>
<p>Jekyll propose <a href="https://jekyllrb.com/docs/liquid/filters/" target="_blank" rel="noopener noreferrer">quelques filtres supplémentaires</a>, en plus de ceux fournis par défaut par Liquid.
Il y en a un certain nombre — cet article ne peut pas tous les couvrir — mais vous pouvez les répliquer avec <a href="https://www.11ty.dev/docs/filters/" target="_blank" rel="noopener noreferrer">l'option de configuration</a> <code>addFilter</code> d'Eleventy. Convertissons les deux filtres utilisés par notre Guide Markdown : <code>jsonify</code> et <code>where</code>.</p>
<p>Le filtre <code>jsonify</code> sert à exporter un objet ou une chaîne de caractères dans un format JSON valide. Comme JavaScript propose <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify" target="_blank" rel="noopener noreferrer">une méthode JSON native</a> pour cela, nous pouvons l'utiliser dans notre filtre. La méthode <code>addFilter</code> prend deux paramètres en entrée : en premier le nom du filtre, en deuxième la fonction dans laquelle nous voulons passer le contenu pour le transformer :</p>
<pre><code class="language-js hljs javascript"><span class="hljs-comment">// {{ variable | jsonify }}</span>
  eleventyConfig.addFilter(<span class="hljs-string">'jsonify'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">variable</span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">JSON</span>.stringify(variable);
  });</code></pre>
<p>Le filtre <code>where</code> de Jekyll est un peu plus complexe au sens où il prend deux arguments additionnels, la clef sur laquelle on veut effectuer la recherche et la valeur recherchée :</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-variable">{{ site.members | where: "graduation_year","2014" }}</span></code></pre>
<p>Pour reproduire ce comportement, nous pouvons passer trois arguments au lieu d'un à la fonction passée à <code>addFilter</code>: le tableau (<code>array</code>) que nous voulons examiner, la clef sur laquelle on veut effectuer la recherche et la valeur recherchée :</p>
<pre><code class="language-js hljs javascript"><span class="hljs-comment">// {{ array | where: key,value }}</span>
  eleventyConfig.addFilter(<span class="hljs-string">'where'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">array, key, value</span>) </span>{
    <span class="hljs-keyword">return</span> array.filter(<span class="hljs-function"><span class="hljs-params">item</span> =&gt;</span> {
      <span class="hljs-keyword">const</span> keys = key.split(<span class="hljs-string">'.'</span>);
      <span class="hljs-keyword">const</span> reducedKey = keys.reduce(<span class="hljs-function">(<span class="hljs-params">object, key</span>) =&gt;</span> {
        <span class="hljs-keyword">return</span> object[key];
      }, item);

      <span class="hljs-keyword">return</span> (reducedKey === value ? item : <span class="hljs-literal">false</span>);
    });
  });</code></pre>
<p>Il se passe pas mal de trucs dans ce filtre, que je vais tenter d'expliquer.<br>
Nous examinons chaque <code>item</code> dans notre <code>array</code>, et nous <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/reduce" target="_blank" rel="noopener noreferrer">réduisons</a> la <code>key</code> (passée comme une chaine à l'aide de la notation avec le point) de manière à pouvoir être analysée correctement (comme une référence d'objet) avant de comparer sa valeur avec celle de <code>value</code>. Si elle correspond, l'<code>item</code> reste dans le tableau retourné, sinon il est supprimé. Pfiou !</p>
<h2 id="les-includes">Les includes</h2>
<p>Comme pour les filtres, <a href="https://jekyllrb.com/docs/liquid/tags/" target="_blank" rel="noopener noreferrer">Jekyll fournit un jeu de tags</a> qui ne fait pas partie intégrante du cœur de Liquid. Parmi eux, l'un des plus utiles est le tag <code>include</code>. La bibliothèque utilisée par Eleventy, <a href="https://github.com/harttle/liquidjs" target="_blank" rel="noopener noreferrer">LiquidJS</a> fournit aussi un tag <code>include</code>, mais sa syntaxe diffère légèrement de <a href="https://help.shopify.com/en/themes/liquid/tags/theme-tags#include" target="_blank" rel="noopener noreferrer">celle définie par Shopify</a>. Si vous ne passez pas de variables en paramètre de vos includes, vous ne devriez pas à avoir à faire de modification pour que ça marche.<br>
Dans le cas contraire, alors qu'avec Jekyll vous écrivez :</p>
<pre><code class="language-twig hljs twig"><span class="xml"><span class="hljs-comment">&lt;!-- page.html --&gt;</span>
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">include</span></span> <span class="hljs-name">include</span>.html value="key" %}</span><span class="xml">

<span class="hljs-comment">&lt;!-- include.html --&gt;</span>
</span><span class="hljs-template-variable">{{ <span class="hljs-name">include</span>.value }}</span></code></pre>
<p>dans Eleventy, vous allez écrire :</p>
<pre><code class="language-twig hljs twig"><span class="xml"><span class="hljs-comment">&lt;!-- page.html --&gt;</span>
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">include</span></span> "<span class="hljs-name">include</span>.html", value: "key" %}</span><span class="xml">

<span class="hljs-comment">&lt;!-- include.html --&gt;</span>
</span><span class="hljs-template-variable">{{ value }}</span></code></pre>
<p>L'inconvénient de la syntaxe Shopify c'est que les assignations de variables ne sont plus limitées au périmètre de l'<code>include</code> et peuvent donc être exposées ailleurs ; gardez cela bien en tête lors de la conversion de vos gabarits, car vous aurez peut-être à faire des ajustements supplémentaires.</p>
<h3 id="parametrer-liquid">Paramétrer Liquid</h3>
<p>Vous aurez peut-être remarqué dans l'exemple ci-dessus que LiquidJS s'attend à ce que le nom des fichiers d'inclusion soient entre guillemets (sinon ils seront traités comme des variables). Nous pourrions mettre à jour nos gabarits pour ajouter des guillemets autour des noms de fichier (c'est l'approche conseillée), mais nous pouvons aussi désactiver ce comportement en mettant l'option`<code>dynamicPartials</code>de LiquidJS à<code>false\</code>.</p>
<p>En outre, Eleventy ne supporte pas le tag <code>include_relative</code>, nous ne pouvons donc pas inclure des fichiers relativement à l'emplacement du fichier courant. Toutefois, LiquidJS nous laisse définir plusieurs chemins dans lesquels rechercher les fichiers à inclure via l'option <code>root</code>.</p>
<p>Heureusement pour nous, Eleventy nous laisse <a href="https://www.11ty.dev/docs/languages/liquid/" target="_blank" rel="noopener noreferrer">passer des options à LiquidJS</a> :</p>
<pre><code class="language-js hljs javascript">eleventyConfig.setLiquidOptions({
    <span class="hljs-attr">dynamicPartials</span>: <span class="hljs-literal">false</span>,
    <span class="hljs-attr">root</span>: [
      <span class="hljs-string">'_includes'</span>,
      <span class="hljs-string">'.'</span>
    ]
  });</code></pre>
<h2 id="les-collections">Les collections</h2>
<p><a href="https://jekyllrb.com/docs/collections/" target="_blank" rel="noopener noreferrer">Dans Jekyll les collections</a> permettent aux auteurs de créer les collections de documents de leur choix, en plus des pages et des articles. Eleventy propose <a href="https://www.11ty.dev/docs/collections/" target="_blank" rel="noopener noreferrer">une fonctionnalité similaire</a>, mais qui permet de faire beaucoup plus de choses.</p>
<h3 id="les-collections-dans-jekyll">Les collections dans Jekyll</h3>
<p>Dans Jekyll, pour créer des collections, vous devez ajouter leurs noms dans le fichier <code>_config.yml</code> et créer les dossiers correspondants dans votre projet. Notre guide Markdown possède deux collections :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">collections:</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">basic-syntax</span>
    <span class="hljs-bullet">-</span> <span class="hljs-string">extended-syntax</span></code></pre>
<p>Elles correspondent aux dossiers <code>_basic-syntax</code> et <code>_extended-syntax</code>, nous pouvons itérer sur leurs contenus de la sorte :</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">for</span></span> syntax in site.extended-syntax %}</span><span class="xml">
  </span><span class="hljs-template-variable">{{ syntax.title }}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> %}</span></code></pre>
<h3 id="les-collections-dans-eleventy">Les collections dans Eleventy</h3>
<p>Il existe deux manières de configurer des collections dans Eleventy.
Tout d'abord, en utilisant simplement la propriété <code>tag</code> dans le front matter des fichiers de contenu :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-meta">---</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">Barré</span>
<span class="hljs-attr">syntax-id:</span> <span class="hljs-string">barre</span>
<span class="hljs-attr">syntax-summary:</span> <span class="hljs-string">"~~La terre est plate~~"</span>
<span class="hljs-attr">tag:</span> <span class="hljs-string">extended-syntax</span>
<span class="hljs-meta">---</span></code></pre>
<p>Nous pouvons ensuite itérer sur les contenus étiquetés de la sorte :</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">for</span></span> syntax in collections.extended-syntax %}</span><span class="xml">
  </span><span class="hljs-template-variable">{{ syntax.data.title }}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> %}</span></code></pre>
<p>Eleventy permet aussi de déclarer des collections à l'aide de la fonction <code>addCollection</code>. Par exemple, plutôt que d'utiliser des tags, nous pouvons rechercher des fichiers à l'aide d'un motif global (une manière de spécifier un ensemble de fichiers à rechercher à l'aide de caractères joker) :</p>
<pre><code class="language-js hljs javascript">eleventyConfig.addCollection(<span class="hljs-string">'basic-syntax'</span>, collection =&gt; {
  <span class="hljs-keyword">return</span> collection.getFilteredByGlob(<span class="hljs-string">'_basic-syntax/*.md'</span>);
});

eleventyConfig.addCollection(<span class="hljs-string">'extended-syntax'</span>, collection =&gt; {
  <span class="hljs-keyword">return</span> collection.getFilteredByGlob(<span class="hljs-string">'_extended-syntax/*.md'</span>);
});</code></pre>
<p>Nous pouvons faire encore mieux. Par exemple, imaginons que nous voulions trier une collection par la propriété <code>display_order</code> du front matter de nos documents. Nous pourrions prendre les résultats retournés par la fonction <code>collection.getFilteredByGlob</code> et les trier à l'aide de <a href="https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/sort" target="_blank" rel="noopener noreferrer">la fonction <code>sort</code> de JavaScript</a> :</p>
<pre><code class="language-js hljs javascript">eleventyConfig.addCollection(<span class="hljs-string">'example'</span>, collection =&gt; {
  <span class="hljs-keyword">return</span> collection.getFilteredByGlob(<span class="hljs-string">'_examples/*.md'</span>).sort(<span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> {
    <span class="hljs-keyword">return</span> a.data.display_order - b.data.display_order;
  });
});</code></pre>
<p>Avec un peu de chance, cet exemple vous a fait comprendre <a href="https://www.11ty.dev/docs/collections/#collection-api-methods" target="_blank" rel="noopener noreferrer">ce qu'il est possible de faire avec cette approche</a>.</p>
<h2 id="utiliser-les-donnees-de-repertoire-pour-definir-les-parametres-par-defaut">Utiliser les données de répertoire pour définir les paramètres par défaut</h2>
<p>Par défaut, Eleventy ne va pas toucher à la structure de vos fichiers de contenus quand il va générer le site. Dans le cas présent, cela signifie que
<code>/_basic-syntax/lists.md</code> sera généré sous <code>/_basic-syntax/lists/index.html</code>.
Comme dans Jekyll, nous pouvons <a href="https://www.11ty.dev/docs/permalinks/" target="_blank" rel="noopener noreferrer">définir où les fichiers seront générés</a> à l'aide de la propriété <code>permalink</code>. Par exemple si nous voulons que cette page devienne accessible sous <code>/basic-syntax/lists.html</code> nous pouvons ajouter :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-meta">---</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">Lists</span>
<span class="hljs-attr">syntax-id:</span> <span class="hljs-string">lists</span>
<span class="hljs-attr">api:</span> <span class="hljs-string">"no"</span>
<span class="hljs-attr">permalink:</span> <span class="hljs-string">/basic-syntax/lists.html</span>
<span class="hljs-meta">---</span></code></pre>
<p>Là encore, ce n'est pas quelque chose que vous voulez gérer au niveau de chaque fichier, et une fois de plus Eleventy propose des fonctionnalités qui peuvent vous aider : <a href="https://www.11ty.dev/docs/data-template-dir/" target="_blank" rel="noopener noreferrer">les données de dossier</a> et les <a href="https://www.11ty.dev/docs/permalinks/#use-data-variables-in-permalink" target="_blank" rel="noopener noreferrer">variables pour les permaliens</a>.</p>
<p>Par exemple, pour parvenir au même résultat que précédemment pour tous les contenus stockés dans le dossier <code>_basic-syntax</code>, nous pouvons y créer un fichier JSON du même nom, <code>_basic-syntax/_basic-syntax.json</code> et y définir nos valeurs par défaut. Pour les permaliens, nous avons le droit d'utiliser une variable Liquid pour construire le chemin désiré :</p>
<pre><code class="language-json hljs json">{
  <span class="hljs-attr">"layout"</span>: <span class="hljs-string">"syntax"</span>,
  <span class="hljs-attr">"tag"</span>: <span class="hljs-string">"basic-syntax"</span>,
  <span class="hljs-attr">"permalink"</span>: <span class="hljs-string">"basic-syntax/{{ title | slug }}.html"</span>
}</code></pre>
<p>Maintenant, le guide Markdown ne publie pas les exemples de syntaxe sous forme d'URLs individuelles et permanentes, il se contente d'utiliser les fichiers de contenu pour stocker les données. Modifions donc un peu tout ça. Affranchissons-nous des règles imposées par Jekyll sur l'emplacement et le nom des dossiers de collections, et déplaçons tout dans un dossier nommé <code>_content</code> :</p>
<pre><code class="language-txt">markdown-guide
└── _content
    ├── basic-syntax
    ├── extended-syntax
    ├── getting-started
    └── _content.json</code></pre>
<p>Ajoutons également un fichier de données (<code>_content.json</code>) dans ce dossier. Comme les règles définies au niveau du dossier sont appliquées de manière récursive, cela signifie que tous les fichiers contenus dans cette arborescence ne seront plus publiés :</p>
<pre><code class="language-json hljs json">{
  <span class="hljs-attr">"permalink"</span>: <span class="hljs-literal">false</span>
}</code></pre>
<h2 id="les-fichiers-statiques">Les fichiers statiques</h2>
<p>Eleventy ne va transformer que les fichiers dont il connaît les gabarits. Maintenant nous pouvons aussi avoir des fichiers statiques qui n'ont pas besoin d'être convertis, mais que nous devons copier dans le dossier de destination. Pour cela, nous pouvons utiliser <a href="https://www.11ty.dev/docs/copy/" target="_blank" rel="noopener noreferrer">la copie de fichier "passe-plat"</a>. Dans notre fichier de configuration, nous indiquons à Eleventy quels dossiers/fichiers copier via l'option <code>addPassthroughCopy</code>.
Puis nous activons cette fonctionnalité dans ce qui est retourné, en mettant <code>passthroughFileCopy</code> à <code>true</code> :</p>
<pre><code class="language-js hljs javascript"><span class="hljs-built_in">module</span>.exports = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">eleventyConfig</span>) </span>{
  …

  <span class="hljs-comment">// Copy the `assets` directory to the compiled site folder</span>
  eleventyConfig.addPassthroughCopy(<span class="hljs-string">'assets'</span>);

  <span class="hljs-keyword">return</span> {
    <span class="hljs-attr">dir</span>: {
      <span class="hljs-attr">input</span>: <span class="hljs-string">"./"</span>,
      <span class="hljs-attr">output</span>: <span class="hljs-string">"./_site"</span>
    },
    <span class="hljs-attr">passthroughFileCopy</span>: <span class="hljs-literal">true</span>
  };
}</code></pre>
<h2 id="considerations-finales">Considérations finales</h2>
<h3 id="gestion-des-assets">Gestion des assets</h3>
<p>Contrairement à Jekyll, Eleventy ne propose aucun support de compilation et d'assemblage des scripts — ce ne sont pas les options qui manquent dans l'écosystème npm dans ce domaine. Si vous utilisiez Jekyll pour compiler vos fichiers Sass en CSS, ou CoffeeScript en JavaScript, vous devrez rechercher comment faire ça, car malheureusement ce n'est pas le but du présent article.</p>
<h3 id="publication-sur-github-pages">Publication sur GitHub Pages</h3>
<p>Un des gros avantages de Jekyll, c'est son <a href="https://jekyllrb.com/docs/github-pages/" target="_blank" rel="noopener noreferrer">intégration dans GitHub Pages</a>. Pour publier un site généré avec Eleventy — ou tout autre site non généré par Jekyll — sur GitHub Pages peut s'avérer compliqué, et implique généralement de devoir <a href="https://github.com/tschaub/gh-pages" target="_blank" rel="noopener noreferrer">copier le site généré dans la branche <code>gh-pages</code></a> ou d'<a href="https://blog.revathskumar.com/2014/07/publish-github-pages-using-git-submodules.html" target="_blank" rel="noopener noreferrer">inclure cette branche comme un sous-module Git</a>. Vous pouvez aussi utiliser un service d'intégration continue comme <a href="https://travis-ci.com" target="_blank" rel="noopener noreferrer">Travis</a> ou <a href="https://circleci.com" target="_blank" rel="noopener noreferrer">CircleCI</a> et pousser le site généré sur votre serveur web. De quoi vous faire tourner la tête !</p>
<p>C'est peut-être pour cette raison que des services spécialisés dans l'hébergement de fichiers statiques ont émergé comme <a href="https://www.netlify.com" target="_blank" rel="noopener noreferrer">Netlify</a> ou <a href="ttps://firebase.google.com/products/hosting/">Google Firebase</a>.
Rappelez-vous cependant que vous pouvez publier un site statique où vous voulez !</p>
<h2 id="montez-d-un-cran">Montez d'un cran</h2>
<p>Si vous songiez à passer à Eleventy, j'espère que ce bref aperçu vous aura été utile. Il sert également à rappeler qu'il n'est pas toujours prudent de prendre le train en marche.</p>
<p>Essayer de nouveaux outils et des technologies émergentes est toujours gratifiant, cela demande pas mal de travail et de compromis. Eleventy est très intéressant, mais il n'a qu'un an, et donc peu de thèmes ou de plugins sont disponibles. De plus, il n'est maintenu que par une seule personne. Alors que Jekyll est un projet mature, qui possède une grande communauté, ainsi que de nombreux contributeurs et mainteneurs.</p>
<p>J'ai passé mon site sous Eleventy car la lenteur et la rigidité de Jekyll m'empêchaient de faire ce que je voulais. Mais j'ai également investi du temps dans cette migration. Après avoir lu ce guide, et en fonction des spécificités de votre projet, vous déciderez peut-être de garder Jekyll, surtout si c'est pour parvenir au même résultat. Et ce n'est pas un problème !</p>
<p>Mais <a href="https://www.11ty.dev/docs/#sites-using-eleventy" target="_blank" rel="noopener noreferrer">ceux-là vont jusqu'à 11</a>.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn:1">
<p>L'information présentée ici est valable pour les versions 0.6.0 d'Eleventy et 3.8.5 de Jekyll.&#160;<a href="#fnref1:1" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
</ol>
</div>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2018/12/07/oubliez-docker-le-futur-c-est-la-jamstack/</id>
    <title>Oubliez Docker, le futur c&#039;est la Jamstack</title>
    <published>2018-12-07T00:21:13+00:00</published>
    <updated>2018-01-26T00:09:11+00:00</updated>
    <link href="https://jamstatic.fr/2018/12/07/oubliez-docker-le-futur-c-est-la-jamstack/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>La popularité croissante des architectures décentralisées s'explique par l'évolution de l'offre de services disponibles. Que ce soit pour héberger du code source, gérer ses contenus, fournir une authentification, une gestion des paiements, etc. faire appel à des services distants fait de plus en plus sens pour les entreprises dont l'informatique n'est pas le coeur de métier mais simplement un moyen de fournir un service à leurs clients.</p></aside>
<p>À l'heure où les entreprises se débattent pour devenir plus agiles et rester
pertinentes, elles peuvent compter sur les dernières évolutions des
technologies. Oubliez Docker, Jamstack marque la prochaine évolution du
développement web moderne.</p>
<p>Jamstack c'est pour JavaScript, APis et Markup. C'est la merveilleuse union de
technologies modernes, du logiciel <em>as a service</em> et des langages au coeur des
fondations du web. Cette stack procure :</p>
<ul>
<li>une réduction des coûts,</li>
<li>une mise sur le marché plus rapide,</li>
<li>une sécurité accrue,</li>
<li>plus de fiabilité, de disponibilité, et une meilleure adaptation à la montée en charge,</li>
<li>une réduction de la dépendance à des technologies propriétaires.</li>
</ul>
<p>Les technologies comme Docker vont continuer à jouer un rôle primordial dans le
futur du numérique, mais elles devraient devenir de plus en plus transparentes
pour la majorité des entreprises et seront utilisées sans que vous le sachiez.
Le fait de devoir gérer et maintenir soi-même sa propre infrastructure
appartiendra bientôt au passé.</p>
<p>Si votre coeur de métier n'est pas de fournir des services dans le Cloud, cet
article vous explique pourquoi la Jamstack devrait devenir votre première
préoccupation si vous tenez à fournir un avantage stratégique et une agilité
accrue à la majorité de vos activités en ligne.</p>
<h3 id="la-jamstack">La Jamstack ?</h3>
<p>La Jamstack c'est en partie du JavaScript et du code généré qui peuvent être
hébergés en statique n'importe où. Elle est parfaitement complémentaire avec les
technologies serverless et peut faire appel à des fonctions serverless exécutées
dans le Cloud. Les serveurs d'applications traditionnelles sont en passe d'être
totalement superflus, en leur lieu et place une bonne partie de la complexité
est résolue lors de l'étape de génération à l'aide de l'outillage fourni par
entre autres par JavaScript.</p>
<p>Des générateurs de site statiques comme Next.js ou Gatsby sont souvent utilisés
pour combler le besoin de faire du rendu côté serveur. Ces outils s'intègrent
parfaitement avec des services du Cloud pendant l'étape de génération et
produisent en sortie une application complète composée de pages pré-rendues. Par
exemple, ces outils peuvent récupérer des contenus stockés dans un CMS headless
et générer l'ensemble de votre site sous forme de pages statiques en quelques
secondes. Un instantané immuable de toutes ces pages peut alors être déployé en
production et distribué partout dans le monde.</p>
<p>Il est possible de déclencher des modifications à partir de n'importe quel
service en aval grâce à des webhooks et de générer une nouvelle version en
quelques secondes après avoir récupéré le contenu mis à jour.</p>
<p>Bien que les applications Jamstack soient hébergées de manière statique, cela ne
veut pas dire qu'elles n'en sont pas moins dynamiques ou interactives que des
pages dont le rendu est effectué côté serveur. Ces applications peuvent être
conçues pour interagir avec des APIs web côté client et peuvent fournir du
contenu dynamique et de l'interactivité comme n'importe quelle application
classique. On peut toujours authentifier des utilisateurs et enrichir les pages
progressivement en fonctionnalités.</p>
<p>Des fonctions exécutées depuis le Cloud peuvent fournir des capacités de
services additionnelles au besoin, et peuvent être elles aussi être écrites en
JavaScript.</p>
<figure>
<picture title="Exemple d&#039;architecture Jamstack.">
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/v1548402220/jamstatic/exemple-architecture-jamstack.8025184a65a2ed60d88a57c97382eea1.webp 768w, /res.cloudinary.com/jamstatic/image/upload/v1548402220/jamstatic/exemple-architecture-jamstack.8025184a65a2ed60d88a57c97382eea1.webp 999w" width="999" height="752" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/v1548402220/jamstatic/exemple-architecture-jamstack.8025184a65a2ed60d88a57c97382eea1.avif 768w, /res.cloudinary.com/jamstatic/image/upload/v1548402220/jamstatic/exemple-architecture-jamstack.8025184a65a2ed60d88a57c97382eea1.avif 999w" width="999" height="752" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/v1548402220/jamstatic/exemple-architecture-jamstack.8025184a65a2ed60d88a57c97382eea1.png" alt="Exemple d&#039;architecture Jamstack" loading="lazy" decoding="async" class="dark:brightness-90" width="999" height="752" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/v1548402220/jamstatic/exemple-architecture-jamstack.8025184a65a2ed60d88a57c97382eea1.png 768w, /res.cloudinary.com/jamstatic/image/upload/v1548402220/jamstatic/exemple-architecture-jamstack.8025184a65a2ed60d88a57c97382eea1.png 999w" sizes="100vw">
</picture>
<figcaption>Exemple d'architecture Jamstack.</figcaption>
</figure>
<h3 id="pourquoi-la-jamstack">Pourquoi la Jamstack ?</h3>
<p>Un application Jamstack réduit les dépendances nécessaires lors de l'exécution
pour des performances, une fiabilité, une capacité de montée en charge et une
sécurité maximales. En effet comme elles n'ont pas besoin de serveurs
d'application dédiés pour servir les pages, cela réduit considérablement la
surface d'attaque possible. Le contenu statique est extrêmement facile à mettre
en cache et à distribuer via un fournisseur d'accès au Cloud. Vous ne dépendez
pas d'un fournisseur en particulier.</p>
<p>Les entreprises peuvent développer des solutions à l'aide de la Jamstack pour se
lancer plus rapidement sur le marché et ce à moindre coût. C'est dû au fait
qu'il y a besoin de beaucoup moins de ressources pour gérer et maintenir
l'application en production et en développement. Une petite équipe agile
multi-disciplinaire aura seulement besoin d'être en capacité de livrer du HTML,
du CSS et du JavaScript. Pas besoin d'avoir beaucoup d'autres compétences quand
il s'agit de publier pour le web.</p>
<p>Dans les applications traditionnelles, les mises à jour de dépendances se font
généralement sur le serveur. Cela augment le risque d'interruption. Avec la
Jamstack comme tout se fait en aval, il y a beaucoup moins de chances que
quelque chose puisse impacter les utilisateurs finaux — et encore moins si les
générations sont testées automatiquement avant la mise en production.</p>
<p>La JAMStack sur repose sur les services dans le Cloud pour l'extension de
fonctionnalités. Elle suit la tendance du développement JavaScript full-stack
facilité par des outils comme React et les offres de "functions as a service" du
Cloud. Fini les serveurs d'applications et les systèmes monolithiques avec des
bases de données SQL. Plus besoin d'une infrastructure disponible en permanence
pour effectuer du rendu côté serveur à l'aide de langages compilés comme Java ou
.NET.</p>
<p>Une architecture Jamstack n'est jamais aussi performante que lorsqu'elle utilise
les services d'un hébergeur spécialisé comme Netlify. Netlify est une
plate-forme complète de Cloud pour les architectures Jamstack, qui se connecte
directement à votre dépôt de code. Chaque sauvegarde permet d'à la fois
versionner et déployer votre application et ses fonctions de support dans le
Cloud sur votre environnement de production en quelques secondes.</p>
<p>Netlify déploie vos applications sur son réseau global de CDN pour une
performance optimale et propose également d'autres fonctionnalités à forte
valeur ajoutée, que vous auriez dû autrement développé ou configurer vous-mêmes.</p>
<h3 id="verrou-logiciel">Verrou logiciel</h3>
<p>Beaucoup d'entreprises utilisent des logiciels tiers qu'elles hébergent
elles-mêmes, ces logiciels demandent d'avoir des équipes importantes chargées de
leur développement, de leur déploiement et de leur maintenance, avant même que
vous n'ayez songé à développeur votre application. Parfois les deux sont
indissociables et cela augmente considérablement les compétences minimales
requises dont vous auriez normalement besoin pour développer votre application.</p>
<p>Les systèmes de gestion de contenu (CMS) en sont un parfait exemple. Quand ces
fonctionnalités ne sont pas au coeur de votre activité, aujourd'hui cela n'a
plus aucun sens d'investir du temps et des ressource spécialisées pour leur
maintenance, leur disponibilité et leur support. Ce fut pendant longtemps la
seule option disponible et un sacrifice à consentir pour essayer de tirer parti
de la valeur ajoutée que ces outils pouvaient vous procurer en retour.</p>
<p>Depuis les offres de services ont évolué et les solutions adaptatives du Cloud
entièrement gérées et maintenues pour une fraction du prix ont changé la donne.
Prismic et Contentful sont des exemples de solutions de CMS découplés qui
offrent des APIs web hautement interopérables. Plus besoin des compétences d'un
expert maison, ces services s'intègrent facilement à une architecture Jamstack,
quelle que soit la technologie choisie.</p>
<p>Sur le même principe, presque n'importe quelle fonctionnalité peut être lancée
ou générée par des services offerts par le Cloud. Cela inclut des serices
d'intégration et de déploiement continus qui peuvent être déclenchés à la
demande. Dans la plupart des cas, il n'y a plus besoin d'héberger et de gérer
vos propres serveurs qu'ils soient chez vous ou dans le Cloud.</p>
<h3 id="commodites-logicielles">Commodités logicielles</h3>
<p>Étant donnée que les offres Saas (<em>Software as a service</em>) transforment les
produits en services dans le Cloud, les entreprises doivent repenser leur
stratégie pour rester agiles.</p>
<p>Il est critique pour une entreprise de comprendre l'offre actuelle et de faire
appels à ces services distants lorsque c'est possible de façon à réduire ses
coûts et éviter de fournir un effort inutile en reproduisant ces infrastructures
disponibles partout en interne à un coût plus important.</p>
<p>Les commodités, c'est quand des produits sont disponibles à grande échelle, en importante quantité et qu'ils sont hautement standardisés. Les commodités répondent à un but très précis et évoluent au fil du temps à travers les répliques et les affinements d'un produit.</p>
<p>Ici le terme commodité fait référence à <a href="https://medium.com/wardleymaps" target="_blank" rel="noopener noreferrer">la technique de cartographie de Wardley</a> et décrit la façon dont les produits évoluent : de leur genèse à des solutions sur mesure, puis à des produits et enfin à des commodités. Parmi les composants de la topologie d'un business qui favorisent l'évolution des process, et en fonction de leur valeur pour le client, vous pouvez établir une stratégie et décider sur quoi votre entreprise doit porter ses efforts afin de vous focaliser uniquement sur votre canal de vente.</p>
<p>Avec la Jamstack, on tire parti des services du Cloud comme suit :</p>
<ul>
<li>hébergement de fichiers statiques plutôt que des serveurs d'application,</li>
<li>intégration et déploiement dans le Cloud plutôt que chez soi en fonction de ses capacités,</li>
<li>partenaires Saas plutôt que des produits tiers hébergés en interne,</li>
<li>fonctions déclenchées à la demande dans le Cloud plutôt que sur des serveurs qui tournent en permanence,</li>
<li>des développeurs JavaScript full-stack plutôt que des compétences et des langages variés.</li>
</ul>
<h3 id="en-resume">En résumé</h3>
<p>Les technologies comme Docker sont au coeur des infrastructures modernes. Elles
simplifient le provisionnement de ressources de manière très consistante et très
fiable.</p>
<p>Toutefois, à moins que vous soyez un expert qui fournit des services dans le
Cloud, se focaliser sur l'infrastructure ne fait plus sens.</p>
<p>Une grande partie de la complexité de l'infrastructure de développement d'un
produit interne provient de dépendances lourdes comme celles à des systèmes de
gestion de contenu. Pendant longtemps il a fallu héberger des logiciels tiers,
les configurer pour qu'ils soient tout le temps disponibles, et posséder des
compétences variées pour développer et maintenir l'ensemble. Cela pousse les
entreprises à s'engouffrer dans de faux problèmes en se focalisant sur
l'infrastructure et le déploiement, plutôt que de se défaire de ces dépendances
qui peuvent s'avérer douloureuses à gérer.</p>
<p>La Jamstack propose un modèle d'architecture efficace pour remplacer l'approche
traditionnelle du développement web et permet à une petit équipe agile d'adopter
un bon rythme. Plus besoin de se préoccuper de l'infrastructure et des serveurs.
Elle encourage également l'intégration d'autres offres SaaS qui fournissent des
APIs interopérables et s'affranchit des systèmes</p>
<p>La Jamstack rend les mises à jour, le support et le redimensionnement de ses
applications trivial. C'est l'assurance d'une agilité accrue et d'un lancement
sur le marché plus rapide, avec de meilleures performances. Le monde de
l'entreprise bouge lentement et ceux qui pourront s'adapter en tireront un
avantage compétitif.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2018/11/28/optimisation-compilation-jekyll/</id>
    <title>Optimisation du temps de compilation de Jekyll</title>
    <published>2018-11-28T00:00:00+00:00</published>
    <link href="https://jamstatic.fr/2018/11/28/optimisation-compilation-jekyll/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Il y a trois ans, fatigué par WordPress et de sa galaxie de plugins douteux, j'ai décidé de migrer vers un générateur de site statique. Après quelques essais avec diverses solutions, j'ai opté pour Jekyll, dont la communauté me semblait plus mature.
Trois ans plus tard, je commence à comprendre les forces et les faiblesses de la solution, mais je reste loin d'en maîtriser tous les mystères. Je l'ai bidouillée pour publier du contenu multilingue, j'ai développé mes propres plugins, j'ai intégré des éléments d'architecture piochés chez des amis… Disons que je suis désormais assez à l'aise.
Par contre, à force de manipulations, mon Jekyll ressemblait moins au célèbre docteur qu'au monstre de Frankenstein&nbsp;: un assemblage de portions de code grossièrement liées entre elles par des liens fragiles, se déplaçant lentement en gémissant… En un mot comme en cent&nbsp;: mon <em lang="en">build</em> était lent.</p></aside>
<h2 id="tl-dr">TL;DR</h2>
<ul>
<li>Constatant que ma compilation Jekyll était des plus lentes, j'ai contacté la communauté Jamstack francophone pour des conseils.</li>
<li>Plusieurs choses ont émergé, chaque conseil permettant d'optimiser le temps de compilation.</li>
<li>Le plus gros gain provient des évolutions de Jekyll lui-même, sur lequel l'équipe est en train de faire un énorme travail.</li>
</ul>
<hr>
<p>Pour générer <a href="https://borisschapira.com" target="_blank" rel="noopener noreferrer">mon site</a> (<a href="https://github.com/borisschapira/borisschapira.com" title="Dépôt GitHub borisschapira.com" target="_blank" rel="noopener noreferrer">source</a>), j'ai développé un ensemble de commandes Rake<sup id="fnref1:rake"><a href="#fn:rake" class="footnote-ref">1</a></sup> qui se chargent de nettoyer l'espace de travail, de déterminer l'environnement de destination et de configurer la compilation en conséquence, de vérifier l'intégrité des en-têtes front matter de mes articles, de générer le site avec Jekyll et, enfin, de contrôler ce qui est produit en utilisant <a href="https://rubygems.org/gems/html-proofer/" target="_blank" rel="noopener noreferrer">la gemme <code>html-proofer</code></a>. Je n'exécute pas tout cela sur mon ordinateur&nbsp;: à la place, je délègue cette tâche à Netlify, branché sur mon dépôt GitHub pour compiler et déployer mes branches.</p>
<p>Toutes ces tâches prenaient individuellement du temps, mais la partie dédiée à la génération avec Jekyll restait la plus consommatrice de CPU, prenant plusieurs minutes. Résultat&nbsp;: pendant des mois, mes compilations Netlify ont duré plus de 10 minutes. Chaque compilation était plus lente que la précédente.</p>
<p>Il y a quelques jours, Netlify a complètement arrêté le déploiement du site web car mes compilations prenaient tellement de temps qu'elles dépassaient la limite imposée par le système d'intégration continue de Netlify<sup id="fnref1:netlimit"><a href="#fn:netlimit" class="footnote-ref">2</a></sup>.</p>
<p>Il était temps d'agir.</p>
<hr>
<p>Un proverbe africain dit&nbsp;:</p>
<blockquote>
<p>Seul, on va plus vite. Ensemble, on va plus loin.</p>
</blockquote>
<p>Je ne pense pas que ce soit tout à fait vrai. Je crois que lorsque nous nous entourons des bonnes personnes, nous pouvons aller <em>ailleurs</em>, vers des possibilités que nous n'avions même pas envisagées.</p>
<hr>
<p>Quand j'ai pensé que j'atteignais la limite de mes compétences, j'ai demandé l'aide à la communauté que je connais le mieux&nbsp;: les membres de Jamstatic. Et parmi eux, un membre de la <span lang="en">Core Team</span> de Jekyll, <a href="https://github.com/DirtyF" target="_blank" rel="noopener noreferrer">Frank</a>. Il m'a beaucoup aidé (et continue de le faire) en me montrant des possibilités que je n'avais pas envisagées.</p>
<h2 id="inclusions-nbsp-a-consommer-avec-moderation">Inclusions&nbsp;: à consommer avec modération</h2>
<p>L'expérience m'a appris qu'il est souvent plus difficile de maintenir un projet que de le réaliser en premier lieu. Pour augmenter ses chances de succès, mieux vaut trouver des techniques d'organisation du code qui soient adaptées à sa compréhension sur le long terme. À mon sens, diviser le code en portions significatives est l'une des astuces de maintenance les plus efficaces.</p>
<p>Dans Jekyll, cela se fait avec des inclusions, via la balise <code>{% include %}</code>. Mais attention, une décomposition de code trop ambitieuse aura un coût sur votre temps de compilation, que vous pourrez visualiser avec le <a href="https://jekyllrb.com/docs/configuration/options/#build-command-options" target="_blank" rel="noopener noreferrer">profileur Liquid [EN]</a>.</p>
<p>D'après <a href="https://github.com/pathawks/" target="_blank" rel="noopener noreferrer">Pat Hawks</a>, membre de la <span lang="en">Core Team</span> Jekyll&nbsp;:</p>
<blockquote>
<p>Chaque fois que vous utilisez une balise <code>{% include %}</code>, Jekyll doit ouvrir le fichier concerné, lire son contenu en mémoire, puis analyser le gabarit avec Liquid.
Cela se produit à chaque <code>{% include %}</code>, et pas une seule fois par fichier&nbsp;! Donc l'utilisation d'une même inclusion sur 100 pages provoquera le chargement et l'analyse de cette inclusion 100 fois. Le problème s'aggrave très rapidement si vous commencez à faire des inclusions dans vos inclusions…</p>
</blockquote>
<p>Une façon de surmonter ce coût supplémentaire est de mettre en cache les blocs compilés pendant l'interprétation de votre <code>{% include %}</code>. Il y a un plugin pour cela&nbsp;: le plugin <a href="https://github.com/benbalter/jekyll-include-cache" target="_blank" rel="noopener noreferrer">jekyll-include-cache</a> de Ben Balter. Mais attention à deux choses très importantes&nbsp;:</p>
<ol>
<li>assurez-vous de passer toutes les données nécessaires à votre <code>{% include %}</code> en paramètres, car elles seront utilisées comme clés pour le cache&nbsp;;</li>
<li>si vous le pouvez, n'utilisez des inclusions que pour générer des portions de code réutilisables. Si les paramètres de l'inclusion la rendent si spécifique qu'elle n'est pas réutilisable ailleurs, alors le cache relatif à cette inclusion aura été construit pour rien et n'apportera aucun gain de performance.</li>
</ol>
<p>Ces deux contraintes sont si fortes qu'elles m'ont obligé à réintégrer plus de la moitié de mes inclusions dans mes gabarits (<em>_layouts</em>). Je ne suis pas entièrement satisfait de cette situation (car, en conséquence, je trouve que les capacités de maintenance sont dégradées) mais je dois avouer que j'ai gagné près de <strong>10&nbsp;%</strong> de temps de compilation<sup id="fnref1:parole"><a href="#fn:parole" class="footnote-ref">3</a></sup> en sacrifiant ce petit confort.</p>
<p>Et avec des commentaires Liquid (<code>{% comment %}This is a comment{% endcomment %}</code>), je peux toujours organiser efficacement mon code, même réintégré dans un seul fichier.</p>
<h2 id="faites-confiance-aux-gemmes-compilees-en-c">Faites confiance aux gemmes compilées en C</h2>
<p>Par défaut, Jekyll est basé sur un ensemble de gemmes écrites en Ruby. Récemment, de nouvelles gemmes sont apparues, partiellement écrites en C, ce qui améliore leur performance d'exécution. L'équipe Jekyll a eu la gentillesse d'ajouter des tests conditionnels dans le générateur pour utiliser ces gemmes si elles sont référencées dans votre Gemfile. Vous n'avez donc qu'à ajouter les gemmes à votre Gemfile pour tirer parti de ces améliorations.</p>
<p>Il en existe certainement d'autres, mais en voici au moins deux&nbsp;:</p>
<ul>
<li><a href="https://github.com/Shopify/liquid-c" target="_blank" rel="noopener noreferrer">la gemme <code>liquid-c</code></a>, pour optimiser la compilation Liquid&nbsp;;</li>
<li><a href="https://github.com/sass/sassc-ruby" target="_blank" rel="noopener noreferrer">la gemme <code>sassc</code></a>, si vous avez besoin de Jekyll pour compiler des fichiers Sass plus efficacement.</li>
</ul>
<p>Je n'ai pas besoin de Jekyll pour mes fichiers Sass mais en utilisant <code>liquid-c</code>, j'ai économisé <strong>9&nbsp;%</strong> du temps de compilation.</p>
<h2 id="si-vous-pouvez-l-eviter-n-utilisez-pas-lsi">Si vous pouvez l'éviter, n'utilisez pas LSI</h2>
<p>Jekyll est livré avec une option très pratique appelée <em lang="en">Latent Semantic Indexing</em> (LSI) dont le rôle est d'analyser tout le contenu avant de générer le site, afin d'alimenter, pour chaque article, une collection qualitative d'articles connexes (au lieu des dix articles les plus récents). LSI fait un très bon travail mais si vous avez des centaines d'articles comme moi, il fonctionnera lentement, très lentement<sup id="fnref1:gsl"><a href="#fn:gsl" class="footnote-ref">4</a></sup>.</p>
<p>Après une très rapide non-analyse des données analytiques dont je ne dispose pas<sup id="fnref1:analytics"><a href="#fn:analytics" class="footnote-ref">5</a></sup>, j'ai décidé de me séparer de la proposition d'articles associés et d'économiser <strong>17&nbsp;%</strong> du temps de compilation.</p>
<h2 id="markdown-nbsp-choisissez-la-bonne-variante">Markdown&nbsp;: choisissez la bonne variante</h2>
<p>Markdown est un langage à balisage léger vraiment sympa mais il lui manque une chose très importante&nbsp;: une standardisation. Il existe des douzaines de parseurs Markdown, chacun avec ses caractéristiques spécifiques. Depuis quelque temps, une initiative de standardisation émerge autour de <a href="https://commonmark.org/" target="_blank" rel="noopener noreferrer">CommonMark</a>, et les projets l'implémentant fleurissent.</p>
<p>Par défaut, Jekyll utilise <a href="https://kramdown.gettalong.org/" target="_blank" rel="noopener noreferrer">kramdown</a>, un surensemble de Markdown développé en Ruby qui fait du très bon boulot. Pour le remplacer par CommonMark, j'ai voulu utiliser <a href="https://github.com/gjtorikian/commonmarker" target="_blank" rel="noopener noreferrer">commonmarker</a> qui est, encore une fois, un wrapper Ruby pour une implémentation en C. Pour en tirer parti dans Jekyll, vous pouvez utiliser <a href="https://github.com/jekyll/jekyll-commonmark" target="_blank" rel="noopener noreferrer">le plugin jekyll-commonmark de Pat Hawks</a>.</p>
<p>Attention, la transition n'est pas sans adaptations. kramdown et CommonMark sont assez différents&nbsp;: en passant de l'un à l'autre, j'ai dû sacrifier quelques sucres syntaxiques.</p>
<p>Par exemple, CommonMark ne supporte pas les attributs de bloc tels que <code>{ :.myclass}</code> pour décorer un paragraphe de contenu. Vous aurez besoin d'utiliser de bonnes vieilles balises HTML. N'oubliez pas d'activer l'option <code>UNSAFE</code> dans votre configuration Jekyll (<code>_config.yml</code>) si vous ne voulez pas générer beaucoup de commentaires du type <code>&lt;!-- raw HTML omitted --&gt;</code>&nbsp;:</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">markdown:</span> <span class="hljs-string">CommonMark</span>
<span class="hljs-attr">commonmark:</span>
  <span class="hljs-attr">options:</span> <span class="hljs-string">["SMART",</span> <span class="hljs-string">"FOOTNOTES"</span><span class="hljs-string">,</span> <span class="hljs-string">"UNSAFE"</span><span class="hljs-string">,</span> <span class="hljs-string">"HARDBREAKS"</span><span class="hljs-string">]</span>
  <span class="hljs-attr">extensions:</span> <span class="hljs-string">["strikethrough",</span> <span class="hljs-string">"autolink"</span><span class="hljs-string">,</span> <span class="hljs-string">"table"</span><span class="hljs-string">]</span></code></pre>
<p>Vous remarquerez peut-être aussi que CommonMark est moins tolérant que kramdown, qui corrige à la volée de nombreuses approximations de contribution. Passer à cet analyseur m'a aidé à détecter des problèmes dans mes articles que je n'avais jamais remarqué auparavant. Si vous avez un contenu conséquent, attendez-vous à devoir corriger quelques coquilles.</p>
<p>Un petit prix à payer pour gagner encore <strong>9&nbsp;%</strong> de temps de compilation.</p>
<h2 id="faites-confiance-a-la-em-lang-en-team-em-pour-aller-dans-la-bonne-direction">Faites confiance à la <em lang="en">Team</em> pour aller dans la bonne direction</h2>
<p>Enfin, l'une des dernières améliorations apportées a été le passage à la version <code>master</code> de Jekyll. Depuis la version 3.8.5 (ma version précédente), de nombreuses améliorations ont été apportées, et le gain de performance est vraiment considérable&nbsp;: <strong>93&nbsp;%</strong>&nbsp;!</p>
<p>Je n'arrivais tellement pas à y croire que j'ai temporairement versionné mon dossier <code>_site</code> et vérifié qu'il n'y avait rien de cassé en changeant de version. Et dans mon cas&nbsp;: rien à redire, tout est parfait.</p>
<p>Si vous ne deviez retenir, ou tester, qu'une seule optimisation, c'est celle-ci. Faites confiance à la <span lang="en">Core Team</span>, la performance est une de leurs priorités.</p>
<h2 id="qu-en-est-il-de-mon-chantier-multilingue-nbsp">Qu'en est-il de mon chantier multilingue&nbsp;?</h2>
<p>Avec toutes ces optimisations, ma compilation Jekyll est passée de plus de 15 minutes à environ une minute. C'est encore beaucoup, et je sais pourquoi&nbsp;: ma gestion "fait maison" de l'internationalisation, et plus particulièrement de la traduction de mes articles, est sous-optimale.</p>
<p>Elle est basée sur une clé front matter <code>i18n-key</code> qui me permet de faire se correspondre mes articles et pages d'une langue à l'autre et sur un plugin qui, pour chaque contenu, scanne tous les autres contenus pour trouver ceux qui sont des traductions du contenu courant. Cette stratégie O(n²), bien que facile à mettre en œuvre, n'est pas efficace du tout et pénalise mes performances de compilation.</p>
<p><a href="https://github.com/ashmaroli" target="_blank" rel="noopener noreferrer">Ashwin Maroli</a>, l'un des membres de la <em lang="en">Jekyll Plugin Core Team</em>, travaille sur un plugin qui utilise une convention d'organisation des fichiers pour trouver les traductions, ce qui devrait considérablement améliorer les choses&nbsp;: <a href="https://github.com/ashmaroli/jekyll-locale" target="_blank" rel="noopener noreferrer">jekyll-locale</a>. J'ai essayé d'implémenter le plugin sur mon blog mais j'ai rencontré quelques impondérables lors de cette première tentative. J'y reviendrai plus tard, une fois que j'aurais simplifié mon organisation des contenus. J'aurais également besoin que certains autres plugins soit modifiés pour être compatibles, comme <a href="https://github.com/sverrirs/jekyll-paginate-v2" target="_blank" rel="noopener noreferrer">jekyll-paginate-v2 de Sverrir Sigmundarson</a>, qui me sert pour la pagination.</p>
<p>Je ne manquerai pas d'en parler quand j'attaquerai à nouveau ce chantier.</p>
<hr>
<h2 id="protocole-experimental">Protocole expérimental</h2>
<p>Comme les optimisations ci-dessus nécessitent également de modifier le contenu, je n'ai pas effectué de benchmark en parallèle de l'implémentation itérative des optimisations. J'ai attendu d'avoir complètement terminé, et donc d'avoir le contenu final, puis j'ai repris ma configuration d'avant optimisation et j'ai analysé les gains étape par étape.</p>
<p>Voici mon protocole de test&nbsp;: Je suis parti d'une installation sans aucune de ces optimisations, puis j'ai écrit un script implémentant les optimisations une par une et compilant le site (sauf la suppression des inclusions, car… j'étais trop paresseux pour scripter la modification des gabarits, je l'avoue). J'ai ensuite programmé l'exécution du script 10 fois et je me suis couché pendant que mon ordinateur passait près de 16 heures à passer ces optimisations au banc d'essai.</p>
<p>Voici les données brutes, si certains veulent jouer avec&nbsp;:</p>
<table>
<thead>
<tr>
<th>Run</th>
<th>Step</th>
<th>Done in… (s)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Test 1</td>
<td>1 - Before</td>
<td>1337,872</td>
</tr>
<tr>
<td>Test 1</td>
<td>2 - Switch to liquid-c</td>
<td>1509,997</td>
</tr>
<tr>
<td>Test 1</td>
<td>3 - Remove LSI</td>
<td>1264,981</td>
</tr>
<tr>
<td>Test 1</td>
<td>4 - Switch to CommonMark</td>
<td>1282,607</td>
</tr>
<tr>
<td>Test 1</td>
<td>5 - Switch to master</td>
<td>64,949</td>
</tr>
<tr>
<td>Test 2</td>
<td>1 - Before</td>
<td>1510,356</td>
</tr>
<tr>
<td>Test 2</td>
<td>2 - Switch to liquid-c</td>
<td>1457,161</td>
</tr>
<tr>
<td>Test 2</td>
<td>3 - Remove LSI</td>
<td>1239,278</td>
</tr>
<tr>
<td>Test 2</td>
<td>4 - Switch to CommonMark</td>
<td>1058,934</td>
</tr>
<tr>
<td>Test 2</td>
<td>5 - Switch to master</td>
<td>72,335</td>
</tr>
<tr>
<td>Test 3</td>
<td>1 - Before</td>
<td>2148,253</td>
</tr>
<tr>
<td>Test 3</td>
<td>2 - Switch to liquid-c</td>
<td>1465,446</td>
</tr>
<tr>
<td>Test 3</td>
<td>3 - Remove LSI</td>
<td>1253,785</td>
</tr>
<tr>
<td>Test 3</td>
<td>4 - Switch to CommonMark</td>
<td>886,152</td>
</tr>
<tr>
<td>Test 3</td>
<td>5 - Switch to master</td>
<td>92,384</td>
</tr>
<tr>
<td>Test 4</td>
<td>1 - Before</td>
<td>1621,322</td>
</tr>
<tr>
<td>Test 4</td>
<td>2 - Switch to liquid-c</td>
<td>1506,737</td>
</tr>
<tr>
<td>Test 4</td>
<td>3 - Remove LSI</td>
<td>1225,414</td>
</tr>
<tr>
<td>Test 4</td>
<td>4 - Switch to CommonMark</td>
<td>1057,497</td>
</tr>
<tr>
<td>Test 4</td>
<td>5 - Switch to master</td>
<td>87,943</td>
</tr>
<tr>
<td>Test 5</td>
<td>1 - Before</td>
<td>1589,323</td>
</tr>
<tr>
<td>Test 5</td>
<td>2 - Switch to liquid-c</td>
<td>1607,33</td>
</tr>
<tr>
<td>Test 5</td>
<td>3 - Remove LSI</td>
<td>1261,815</td>
</tr>
<tr>
<td>Test 5</td>
<td>4 - Switch to CommonMark</td>
<td>914,124</td>
</tr>
<tr>
<td>Test 5</td>
<td>5 - Switch to master</td>
<td>77,526</td>
</tr>
<tr>
<td>Test 6</td>
<td>1 - Before</td>
<td>1643,596</td>
</tr>
<tr>
<td>Test 6</td>
<td>2 - Switch to liquid-c</td>
<td>1481,98</td>
</tr>
<tr>
<td>Test 6</td>
<td>3 - Remove LSI</td>
<td>1237,843</td>
</tr>
<tr>
<td>Test 6</td>
<td>4 - Switch to CommonMark</td>
<td>1336,47</td>
</tr>
<tr>
<td>Test 6</td>
<td>5 - Switch to master</td>
<td>69,099</td>
</tr>
<tr>
<td>Test 7</td>
<td>1 - Before</td>
<td>1456,432</td>
</tr>
<tr>
<td>Test 7</td>
<td>2 - Switch to liquid-c</td>
<td>1487,558</td>
</tr>
<tr>
<td>Test 7</td>
<td>3 - Remove LSI</td>
<td>1268,737</td>
</tr>
<tr>
<td>Test 7</td>
<td>4 - Switch to CommonMark</td>
<td>1106,233</td>
</tr>
<tr>
<td>Test 7</td>
<td>5 - Switch to master</td>
<td>69,562</td>
</tr>
<tr>
<td>Test 8</td>
<td>1 - Before</td>
<td>2943,453</td>
</tr>
<tr>
<td>Test 8</td>
<td>2 - Switch to liquid-c</td>
<td>1492,383</td>
</tr>
<tr>
<td>Test 8</td>
<td>3 - Remove LSI</td>
<td>1229,34</td>
</tr>
<tr>
<td>Test 8</td>
<td>4 - Switch to CommonMark</td>
<td>1321,156</td>
</tr>
<tr>
<td>Test 8</td>
<td>5 - Switch to master</td>
<td>70,332</td>
</tr>
<tr>
<td>Test 9</td>
<td>1 - Before</td>
<td>1587,532</td>
</tr>
<tr>
<td>Test 9</td>
<td>2 - Switch to liquid-c</td>
<td>1586,698</td>
</tr>
<tr>
<td>Test 9</td>
<td>3 - Remove LSI</td>
<td>1264,424</td>
</tr>
<tr>
<td>Test 9</td>
<td>4 - Switch to CommonMark</td>
<td>950,634</td>
</tr>
<tr>
<td>Test 9</td>
<td>5 - Switch to master</td>
<td>69,907</td>
</tr>
<tr>
<td>Test 10</td>
<td>1 - Before</td>
<td>1643,22</td>
</tr>
<tr>
<td>Test 10</td>
<td>2 - Switch to liquid-c</td>
<td>1581,063</td>
</tr>
<tr>
<td>Test 10</td>
<td>3 - Remove LSI</td>
<td>1229,119</td>
</tr>
<tr>
<td>Test 10</td>
<td>4 - Switch to CommonMark</td>
<td>1332,547</td>
</tr>
<tr>
<td>Test 10</td>
<td>5 - Switch to master</td>
<td>69,022</td>
</tr>
</tbody>
</table>
<div class="footnotes">
<hr>
<ol>
<li id="fn:rake">
<p>Rake est un orchestrateur similaire à make, mais en Ruby (<a href="https://rubygems.org/gems/rake/" target="_blank" rel="noopener noreferrer">en savoir plus</a>).&#160;<a href="#fnref1:rake" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
<li id="fn:netlimit">
<p>15 minutes, comme confirmé par Chris McCraw dans son article "<a href="https://www.netlify.com/blog/2016/10/18/how-our-build-bots-build-sites/" target="_blank" rel="noopener noreferrer">How Our Build Bots Build Sites</a>".&#160;<a href="#fnref1:netlimit" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
<li id="fn:parole">
<p>Vous allez devoir me croire sur parole, parce que mon protocole d'expérimentation ne contenait pas de test avec et sans inclusions. Il s'agit donc d'une estimation personnelle.&#160;<a href="#fnref1:parole" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
<li id="fn:gsl">
<p>Il semblerait que <a href="https://rubygems.org/gems/gsl" target="_blank" rel="noopener noreferrer">la gemme gsl</a> permette d'améliorer cela, mais je ne l'ai pas testé. Vos retours m'intéressent.&#160;<a href="#fnref1:gsl" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
<li id="fn:analytics">
<p>Parce que je n'ai pas besoin de ça pour connaître les personnes qui me lisent, car elles interagissent souvent avec moi dans les commentaires ou sur Twitter.&#160;<a href="#fnref1:analytics" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
</ol>
</div>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2018/09/07/microbloguer-avec-jekyll/</id>
    <title>Microbloguer avec Jekyll</title>
    <published>2018-09-07T14:26:04+00:00</published>
    <link href="https://jamstatic.fr/2018/09/07/microbloguer-avec-jekyll/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Garder la main sur ses contenus est devenu une préoccupation pour beaucoup, on a pu le voir récemment avec l'annonce de Medium qui a décidé d'arrêter le support des noms de domaine personnalisés. Versionner ses contenus dans un format texte est la raison première de l'existence des générateurs comme Jekyll.
Et si pour être un peu plus indépendant des plate-formes des réseaux sociaux qui se nourrissent de nos données, on commençait par publier sur son site, quitte à republier automatiquement ensuite sur Medium ou Twitter ? Fiona Voss a tenté l'expérience et ça marche très bien ! 🎉</p></aside>
<p>Un microblog est un blog sur lequel figurent des articles courts, le plus souvent sans titre. Jusqu'à récemment je pensais que Twitter ou Tumblr étaient les seules plate-formes sur lesquelles les gens pouvaient microbloguer. Il s'avère que les membres enthousiastes de la vibrante communauté de l'<a href="https://indieweb.org/" target="_blank" rel="noopener noreferrer">IndieWeb</a> microbloguent sur leurs sites web. À mon tour, j'ai décidé de faire de même et de configurer mon site pour pouvoir microbloguer.</p>
<p>Je peux créer deux types de posts, des notes et des articles, <a href="http://fionavoss.blog/" target="_blank" rel="noopener noreferrer">ma page d'accueil</a> quant à elle affiche indifféremment les deux types de contenu. Je peux aisément poster sur mon blog Jekyll depuis n'importe où avec mon téléphone, et mes notes sont automatiquement republiées sur Twitter.</p>
<p>Pourquoi procéder ainsi ? Microbloguer sur mon site me donne plus de liberté que l'utilisation de Twitter. Je n'ai pas à respecter la limite du nombre de caractères de Twitter, et je peux baliser mes posts comme j'en ai envie. J'ai la maîtrise sur le design de mon site, je peux choisir ou pas d'afficher des publicités.</p>
<p>Puisque je poste aussi sur Twitter, je reste connecté aux personnes présentes sur cette plate-forme, mais si un jour je décide de quitter Twitter, je peux supprimer mon compte sans perdre mes notes ou me demander comme les exporter dans un format exploitable. De plus les gens peuvent me suivre sans avoir de compte Twitter, via mon flux RSS ou le site micro.blog.</p>
<p>Voici comment j'ai configuré mon site pour faire ça:</p>
<h2 id="configuration-de-jekyll">Configuration de Jekyll</h2>
<p>J'ai commencé par utiliser le thème <a href="https://github.com/poole/lanyon" target="_blank" rel="noopener noreferrer">Lanyon</a> pour Jekyll, puis je suis passée à Minima le thème par défaut, parce que la navigation me plaisait davantage. Minima est un thème basé sur une gem, donc pour personnaliser l'apparence de mon site, j'ai dû recopier les fichiers de gabarits de page stockés dans la gem <a href="https://jekyllrb.com/docs/themes/#overriding-theme-defaults" target="_blank" rel="noopener noreferrer">comme indiqué dans la documentation</a>.</p>
<p>Voici à quoi ressemble le fichier Markdown d'une note. Ça ressemble à un post Jekyll standard, sauf que la chaîne de caractères pour le titre est vide. Si je supprime cette ligne, Jekyll utiliserait le slug, ici "343", comme titre. Ce ne serait pas très joli.</p>
<pre><code class="language-md hljs markdown">---
layout: post
date: "2018-03-25T00:05:43.780Z"
title: ""
<span class="hljs-section">slug: "343"
---</span>

Je poste sur mon site Jekyll depuis mon téléphone à l'aide de l'application iOS Micro.blog. Maintenant je peux représenter le web indépendant à la RailsConf !</code></pre>
<p>J'utilise les catégories afin de pouvoir différencier les articles des notes. Jekyll affecte automatiquement la catégorie en fonction du chemin du fichier, dans mon cas ce sera soit <code>articles/_posts</code> soit <code>notes/_posts</code>. On peut créer des pages pour les notes et les articles en se basant sur <a href="https://jekyllrb.com/docs/posts/#displaying-post-categories-or-tags" target="_blank" rel="noopener noreferrer">cet exemple de la documentation de Jekyll</a>.</p>
<p>Ma page d'accueil se contente d'afficher tous les posts, elle se fiche des catégories. Pour éviter d'avoir des niveaux de titre vides et créer des sauts de ligne inutiles pour les notes qui n'ont pas de titre, dans mon gabarit je vérifie d'abord si le titre est vide. Pas super élégant, mais ça fait le job.</p>
<pre><code class="language-go-html-template hljs go">{%- <span class="hljs-keyword">if</span> post.title != <span class="hljs-string">""</span> -%}
  &lt;h3&gt;
    &lt;a class=<span class="hljs-string">"post-link"</span> href=<span class="hljs-string">"{{ post.url | relative_url }}"</span>&gt;
      {{ post.title | escape }}
    &lt;/a&gt;
  &lt;/h3&gt;
{%- endif -%}</code></pre>
<p>Si le post est un article, j'affiche un extrait (le premier paragraphe par défaut) avec un lien pour lire la suite. Si c'est une note, j'affiche l'intégralité de son contenu.</p>
<pre><code class="language-go-html-template hljs go">{%- <span class="hljs-keyword">if</span> post.categories contains <span class="hljs-string">'articles'</span> -%}
  {{ post.excerpt }}
  {% <span class="hljs-keyword">if</span> post.content contains site.excerpt_separator %}
    &lt;a href=<span class="hljs-string">"{{ post.url | prepend: site.baseurl }}"</span>&gt;Read more&lt;/a&gt;
  {% endif %}
{% <span class="hljs-keyword">else</span> %}
  {{ post.content }}
{%- endif -%}</code></pre>
<p>La ligne <code>if post.content contains site.excerpt_separator</code> s'assure que nous n'affichons le lien Lire la suite que s'il y a du contenu supplémentaire. En pratique ce n'est pas nécessaire puisque tous mes articles comportent plus d'un paragraphe; En théorie je pourrais écrire un long post sans titre ou un court post avec un titre et il serait quand même affiché que ce soit un article ou une note, puisque la logique d'affichage ne prend pas en compte la catégorie à laquelle il appartient.</p>
<h2 id="posse-avec-micro-blog">POSSE avec Micro.blog</h2>
<p><a href="https://indieweb.org/POSSE" target="_blank" rel="noopener noreferrer">POSSE</a> veut dire <em>Publish (on your) Own Site, Syndicate Elsewhere</em> (Publiez sur votre propre site et syndiquez ailleurs), le but est d'avoir le meilleur des deux mondes : garder la main sur vos contenus en publiant d'abord sur votre site, tout en gardant le contact avec les autres sur les réseaux sociaux. C'était la partie la plus facile à configurer pour moi, car je me suis reposée sur le service <a href="https://micro.blog/" target="_blank" rel="noopener noreferrer">Micro.blog</a> qui ressemble à Twitter mais en plus petit et en plus sympa.</p>
<p>Avec Micro.blog vous pouvez créer un compte et syndiquer les posts du flux RSS de votre blog vers celui de votre instance Micro.blog. Les posts courts apparaîtront en entier, comme des tweets. Les posts plus longs afficheront l'extrait ou le titre ainsi qu'un lien vers l'article complet sur votre blog. Les autres utilisateurs de micro.blog peuvent vous suivre et répondre à vos posts dans l'application Micro.blog.<a href="https://micro.blog/fiona" target="_blank" rel="noopener noreferrer">Voilà à quoi ressemble mon flux.</a></p>
<p>J'aime beaucoup la communauté Micro.blog. On sent plus d'humanité et moins de promotion que sur Twitter. La plate-forme n'affiche aucune publicité et le flux est strictement chronologique. Comme la communauté est restreinte, c'est plus facile de suivre les gens, et je pense que plus de personnes voient mes notes que sur Twitter. Bien qu'il y ait moins de gens, j'ai plus de discussions sur Micro.blog.</p>
<p>Micro.blog me permet aussi de rester en contact avec les gens qui sont sur Twitter, puisque mon flux est automatiquement publié là-bas. Ce service là me coûte deux dollars par mois.</p>
<p>Micro.blog peut aussi héberger votre blog pour 5 dollars par mois. Cela peut être une bonne solution, si vous ne voulez pas avoir à configurer et à maintenir votre site, ou si vous avez déjà un blog mais que vous souhaitez garder votre microblog à part.</p>
<h2 id="publication">Publication</h2>
<p>Micro.blog propose des applications pour macOS et iOS, qui peuvent poster sur votre blog s'il supporte Micropub. Micropub est la spécification d'une norme pour publier de manière standard sur un site qui peut fonctionner avec une variété d'applications clientes. Si vous bloguez sur un site dynamique, vous auriez à configurer un point d'accès pour recevoir les requêtes de type POST dans un format spécifique. Si vous utilisez WordPress, qui reste un choix populaire dans la communauté IndieWeb, il y a des plugins pour cela.</p>
<p>Comme j'utilise Jekyll, un générateur de site statique, je pensais que Micropub serait compliqué à configurer, mais heureusement il y a un super outil qui fait exactement ce qu'il faut : <a href="https://github.com/voxpelli/webpage-micropub-to-github" target="_blank" rel="noopener noreferrer">webpage-micropub-to-github</a>.
C'est une application qui crée un point d'accès Micropub pour les sites Jekyll hébergés sur GitHub Pages.</p>
<p>J'ai dû d'abord ajouter quelques lignes de code dans la balise <code>&lt;head&gt;</code> pour l'authentification :</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"authorization_endpoint"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://indieauth.com/auth"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"token_endpoint"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://tokens.indieauth.com/token"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://twitter.com/fionajvoss"</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"me"</span> /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://github.com/FionaVoss"</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"me"</span> /&gt;</span></code></pre>
<p>Les deux premières lignes indiquent à Micropub quel service d'authentification
utiliser, les deux ligne suivantes lient mes profils sociaux à mon site web de
manière à ce que je puisse m'authentifier avec <a href="https://indieauth.com/" target="_blank" rel="noopener noreferrer">IndieAuth.com</a>.</p>
<p>L'un ou l'autre des profils aurait suffit, je n'ai pas vraiment besoin des deux liens. J'ai aussi dû ajouter l'URL de mon site à mes biographies Twitter et GitHub.</p>
<p><code>webpage-micropub-to-github</code> est auto-hébergé, donc j'ai du ensuite déployé ma propre instance de cette application. Tout ce que j'ai eu à faire a été de cliquer sur le bouton "Deployer sur Heroku" dans <a href="https://github.com/voxpelli/webpage-micropub-to-github/blob/master/README.md" target="_blank" rel="noopener noreferrer">README</a> et de configurer quelques variables d'environnement, dont mon pseudonyme GitHub, ma clef d'API et le nom de mon dépôt. J'ai forké le dépôt même si ce n'était pas nécessaire puisque je n'ai fait aucune modification de code dans l'application.</p>
<p>Petite précision : pour configurer la variable optionnelle <code>MICROPUB_LAYOUT_NAME</code> j'ai du entré la valeur <code>"posts"</code>, entre guillemets; sans quoi ça faisait planter l'application. Même chose pour <code>MICROPUB_FILENAME_STYLE</code>, que j'ai défini à <code>"notes/_posts/:year-:month-:day-:slug"</code> pour que mes posts aillent dans la catégorie Notes.</p>
<p>Une fois déployée, j'ai dû lier l'application dans ma balise <code>&lt;head&gt;</code> pour indiquer à micropub où poster:</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">link</span>
  <span class="hljs-attr">rel</span>=<span class="hljs-string">"micropub"</span>
  <span class="hljs-attr">href</span>=<span class="hljs-string">"https://jekyll-micropub-to-github.herokuapp.com/micropub/main"</span>
/&gt;</span></code></pre>
<p>Enfin, j'ai dû ajouter l'URL de mon site web dans mes préférences Micro.blog et suivre le processus d'authentification d'IndieAuth, qui consiste en tout en pour tout à m'authentifier avec mon compte Twitter.</p>
<p>Quand je poste, Micro.blog envoie une requête à <code>webpage-micropub-to-github</code>. Puis <code>webpage-micropub-to-github</code> formate correctement le post en Markdown pour Jekyll et ajoute le fichier à mon dépôt via l'API de GitHub. Et comme GitHub Pages régénère mon site à chaque nouveau commit sur la branche <code>master</code>, le post apparaît instantanément.</p>
<p>Passer par Micropub me permet non seulement d'utiliser les applications pour Micro.blog pour poster, mais également tout un tas d'autres. J'ai testé également <a href="https://quill.p3k.io/" target="_blank" rel="noopener noreferrer">Quill</a>. Puisque c'est une application web, c'est pratique pour poster depuis un ordinateur public sans avoir à installer quoi que ce soit. Pour poster depuis mon téléphone, je passe par Micro.blog.</p>
<p>Même si la configuration demande un peu d'effort, poster depuis mon site est aussi simple et rapide que de passer par Twitter, c'est un élément clef qui montre que microbloguer avec Jekyll est tout à fait faisable.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2018/08/26/recherche-plate-forme-de-deploiement-continu-parfaite/</id>
    <title>À la recherche de la plate-forme de déploiement continu parfaite</title>
    <published>2018-08-26T16:26:33+00:00</published>
    <link href="https://jamstatic.fr/2018/08/26/recherche-plate-forme-de-deploiement-continu-parfaite/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>L'automatisation est une des composantes qui permet de bien travailler en versionnant son projet et en configurant une publication automatique. Cette bonne pratique issue du développement permet de s'assurer que tout le monde peut contribuer et que les changements seront bien publiés. DJ Walker a pris le temps de passer en revue différents services pour vous, c'est parti pour la visite guidée.</p></aside>
<p>Nous vous avons déjà parlé des <a href="https://forestry.io/blog/automate-deploy-w-circle-ci/" target="_blank" rel="noopener noreferrer">avantages du déploiement automatique,</a> et plus particulièrement de ceux des sites statiques. L'intégration continue et le déploiement continu sont la stratégie qu'on retrouve le plus souvent quand il s'agit de gérer la publication logicielle. Il existe une multitude d'options pour la mise en place de pipelines CI/CD, avec leurs forces et leurs faiblesses. Quelle est celle qui est faite pour vous ? Dans cet article, nous nous penchons sur cinq services différents  avec lesquels vous pouvez développer, tester et déployer votre code.</p>
<h2 id="la-configuration-est-cle">La configuration est clé</h2>
<p>Lors de l'évaluation de l'utilité d'un outil de CI/CD, je veux d'abord connaître la réponse à deux questions :</p>
<p><strong>Quel niveau de contrôle ai-je sur l'environnement qui va générer une nouvelle version de mon application ?</strong> Pouvoir configurer l'environnement dans lequel vous allez lancer vos étapes de build est essentiel. J'ai tendance à préférer lancer les étapes de build dans un conteneur Docker avec une image définie par mes soins. Les conteneurs sont devenus le moyen idéal pour faire tourner du code dans un environnement reproductible et isolé.</p>
<p><strong>Comment se configurent les étapes de build ?</strong>
Vous pourriez simplement lancer vos tâches à l'aide d'un gros script shell ou d'un outil robuste comme <code>make</code>. Ces scripts peuvent toutefois devenir rapidement complexes et difficiles à déboguer. Idéalement, l'enchaînement des tâches est configuré à l'aide d'un langage de configuration qui offre des abstractions plus commodes pour éviter d'avoir à écrire de multiples scripts réutilisables. Bien qu'une syntaxe de configuration intuitive et facile à comprendre soit utile, quel que soit l'outil utilisé vous devrez en apprendre les rudiments, attendez-vous donc à lire leur documentation pour comprendre comment les utiliser.</p>
<p>Peu importe comment fonctionne la configuration, l'important est d'opter pour des outils qui vous permettent de stocker et versionner votre configuration de build dans votre dépôt. Stocker votre configuration présente plusieurs avantages. Lancer le build d'un commit précédent se fera avec la configuration correspondante à cette même période. Vous pouvez travailler votre configuration sur une branche à part, et tirer parti de la portabilité qui découle de l'utilisation de la gestion de version.</p>
<h2 id="circleci">CircleCI</h2>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-f_auto-q_auto-w_960/v1603617610/jamstatic/circleci_hero.becc91a4ea20845f715f9bac4cbb1884.webp 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-f_auto-q_auto-w_960/v1603617610/jamstatic/circleci_hero.becc91a4ea20845f715f9bac4cbb1884.webp 960w" width="960" height="489" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-f_auto-q_auto-w_960/v1603617610/jamstatic/circleci_hero.becc91a4ea20845f715f9bac4cbb1884.avif 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-f_auto-q_auto-w_960/v1603617610/jamstatic/circleci_hero.becc91a4ea20845f715f9bac4cbb1884.avif 960w" width="960" height="489" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/c_scale-f_auto-q_auto-w_960/v1603617610/jamstatic/circleci_hero.becc91a4ea20845f715f9bac4cbb1884.png" alt="" loading="lazy" decoding="async" class="dark:brightness-90" width="960" height="489" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAFg0lEQVR4nO2ba5LbIBCEewbdIfe/p5nODxgYkLSSN9ldUlFXsZJtCSM+5oUT+fXrF/FoGelPD+DRqAfIYtp+egCu1fym/ND3PhaymB4gi+kBspgeIItpmaDu+u7g/lPB+0yPhSymB8hieoAspgfIn0jw14PQckF9Fc3JhbQ/2EOQcMMfZiUPkDuKIASAyAiF9Q8xtk/oAXIlKfMPEYjW4wSk8CBoFYoxfPCe/nsgZ3PmLkpEABVIbA6kQmGFQC1QKCxQHNAb+u+BnKpZRW1JoaqQJBDVZjUkSzPCjAAMgHUOb0J5gJypWoeoQtPYRBWiMgCxbBAzmAgsCwADadWK7kN5C4jI53M84tyvrvZbSIfRgaQtQVMqxwmIGWFqyDkDyACkhBEChBWj4b2n3I4uG6ZdpL8Wud3xvk9pnXsQXFUlfghUBapaQKQNaUsNjKiWZyFgZrCcPfoDyNVyUC4QjqnxB9rOCxsp/dcLxDu8aSU8esX+wDwYIE/Ox1F9g2omJVLihrqVpA3blpC2DZoUEIFVd/USBetqo6EEeAXI6q7Ebi3CjeEJJZwJZDaV8JoHfd/3Rfzgs4uPvl4ezEVaUFdNSJqQUsKWNmzbBk2pAcmSy7hrPBEjxBSiBjEB3XLkOpbsLOTUXQWR84Xcn+6+ex9DjixiBUdWfUKwkhpLVJFSAeNApE6GEVAjJBtUFKYKiFZ3xerur/3Wxl3RKYgY2EfY+2r599z5RyZxfMVKIIp8NSNU5A6nAPJWFizLZf6+ar2vW9k7jnbzINtCRJ0aRizRVfXTCVSf0qvJvRMrflI82LiKC4cnr/s0eALTE5kO5sJC2mo/vJwj3HoB2x1+Yzw/7uymLS0jX3qeLZFAJqFW6g1KTHsNZgaawchyL6MHCWAu3NYQQ/YwpFlvoy+oqRw6jDk8zLufA8h/QOwTSqJkUmYlm9IMCKBkA5KzwV4vWC5QHEyfg/MabNZB2isF4sFW81A/OKE5CEy2zNm+l5c7breMsvpzNkAMlFJjaLbBQnLOrbm1kFY2HNtEeP/nGoEMm2YRTAgWjMu9VEZDCIlHw97JRkCLyms5kmWlm5WWXxWUtoBd9rEMuVqQ5QzLuVkKaHU/696Db4MlxP1+/y1RKhi/KOa8fj6YZ2juuqw2hGtWVXXDAwwRIGcArO5LhxhdLILIliuUaiG5gvQgdKcw7BNfm8ZjSP+apoAwgGCHYbUPC31beL04FNYJpBFWVxNJqBpMe2lA1C13WgPDCqRswdtt6wAcSLQO9SYjkLlqj9bRrMQ3d9CPhrLfFh52V9csp/osZm39AISQoEnbeveF6bHC3RfdRVmwjJvPOgJxEEMDhny6jrdbSjg6FHMYHGH4vavHkea2UCEYDLUqF+nrkzU9Dr+JNAD2HgjXNrooAKme+7H+YrbLxjidx2ahzcVIdGFLQ2E9GAQCWq/gu5H7nt5kCdF1v6m9hURLSdLf2+3yxuCOYyAZfYAW+vYAvzIUn+i2gAi2HfCwudrKgIO4+gntf6BqcBhiCQ62Y8Jszm4rXj9bRfyOVWG42upH91Hxw9lL/AVtrbPuFGuTYHb123ZQ5tHcHNXqIKJ2UPCl49/qL46jzxMArzoIz7oGK5mWdxy0BzQjkGubC8Qvfqgv0zeMuQOJG1ZEcVkWsi3f0z8c2RTMHIjHkYweV/51KF+sraWlij5ZyrEmcRi+qytXFoKQ+uJjKI8GbXihV9FzCqwcYbSgHINJzC5CDPKJjyAyHiAXGl1WzIaGCn4O6kSsVP2t4egTH48PjEuNQI5S0/ga8Xx2WdP5UW3yxI5Lbbvsap74OdU9S30P4vyp9Tw61VgYHrmT09rjQs/kf0rX/5T0mdhv1fNf2hbTA2QxPUAW029wpajkU9YbNgAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-f_auto-q_auto-w_960/v1603617610/jamstatic/circleci_hero.becc91a4ea20845f715f9bac4cbb1884.png 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-f_auto-q_auto-w_960/v1603617610/jamstatic/circleci_hero.becc91a4ea20845f715f9bac4cbb1884.png 960w" sizes="100vw">
</picture>
<p>CircleCI est un service d'hébergement de CI/CD qui se connecte à votre dépôt et exécute vos étapes de build à chaque nouveau commit. CircleCI peut exécuter vos tâches dans une image Docker, une machine virtuelle Linux, ou une VM MacOS pour vos projets iOS.</p>
<p>La configuration se fait à l'aide d'un fichier <code>.circleci/config.yml</code> dans votre dépôt. Ce fichier indique l'image Docker à utiliser pour votre environnement de build, ainsi que les commandes à exécuter afin de générer, tester et déployer votre application.</p>
<p>CircleCI est le service utilisé par Forestry pour lancer ses tests et déployer son code. Il s'intègre sans problème avec GitHub et Bitbucket.</p>
<h3 id="on-aime">On aime</h3>
<p>Le fait que ce soit à la fois hautement configurable et simple à intégrer avec les projets GitHub.</p>
<h3 id="on-est-pas-super-fan">On est pas super fan</h3>
<p>On ne peut pas connecter de projets GitLab à CircleCI pour le moment.</p>
<aside class="note note-info"><p>Lire comment <a href="https://forestry.io/blog/automate-deploy-w-circle-ci/" target="_blank" rel="noopener noreferrer">déployer un site statique avec CircleCI</a>.</p></aside>
<h2 id="travisci">TravisCI</h2>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-f_auto-q_auto-w_862/jamstatic/travis_pipeline.d33d612c32616045339fc1f55c3b5ada.webp 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-f_auto-q_auto-w_862/jamstatic/travis_pipeline.d33d612c32616045339fc1f55c3b5ada.webp 862w" width="862" height="310" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-f_auto-q_auto-w_862/jamstatic/travis_pipeline.d33d612c32616045339fc1f55c3b5ada.avif 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-f_auto-q_auto-w_862/jamstatic/travis_pipeline.d33d612c32616045339fc1f55c3b5ada.avif 862w" width="862" height="310" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/c_scale-f_auto-q_auto-w_862/jamstatic/travis_pipeline.d33d612c32616045339fc1f55c3b5ada.png" alt="" loading="lazy" decoding="async" class="dark:brightness-90" width="862" height="310" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A9opppaQ0CuJSGikNDC4hOKM0hphOKBkmaM1FupwoAkFOpopaBXFpwpmacKYXH5optFAAaaacaaRSENzSGlIpKYkITUMjYqYioJFqJ7aFjA+TU6GqypzVlF4qIX6gSZpaaBThWxDCnCkxS4oAWiiikMWkNKaSgGNNJin4pCKYrDDTGGakIoxSsWRbKetOxRiiwBilAoFOxTJaEpaMUtArBiilFFBQlFFFAgoPSiigaG0UUUDCiiigAp9FFAmFFFFAgooooGf/2Q==);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-f_auto-q_auto-w_862/jamstatic/travis_pipeline.d33d612c32616045339fc1f55c3b5ada.png 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-f_auto-q_auto-w_862/jamstatic/travis_pipeline.d33d612c32616045339fc1f55c3b5ada.png 862w" sizes="100vw">
</picture>
<p>TravisCI est une solution de CI/CD hébergée qui s'intègre avec vos projets GitHub. Les builds TravisCI se configurent dans un fichier <code>.travis.yml</code> présent dans votre dépôt.</p>
<p>Les scripts de build s'exécutent dans un environnement Ubuntu qui peut être configuré à l'aide de commandes shell pendant la phase d'installation de votre build. TravisCI fournit également des abstractions pour installer <a href="https://docs.travis-ci.com/user/languages/" target="_blank" rel="noopener noreferrer">différents langages de programmation</a> dans votre environnement de build.</p>
<h3 id="on-aime-1">On aime</h3>
<p>TravisCI promet d'être gratuit à vie pour les projets open source. TravisCI travaille de pair avec Github et peut lancer automatiquement les tests d'intégration <a href="https://docs.travis-ci.com/user/pull-requests/" target="_blank" rel="noopener noreferrer">lorsqu'une pull request est ouverte</a>.</p>
<h3 id="on-est-pas-super-fan-1">On est pas super fan</h3>
<p>Avec TravisCI, vos builds doivent tourner dans un environnement Ubuntu. Vous pouvez installer Docker dans cet environnement pour récupérer des images, mais c'est la solution la plus verbeuse de toutes. De plus, vous ne pouvez utiliser TravisCI que si vos projets sont hébergés sur GitHub.</p>
<aside class="note note-info"><p><a href="https://docs.travis-ci.com/user/getting-started/" target="_blank" rel="noopener noreferrer">Guide de démarrage TravisCI</a></p></aside>
<h2 id="drone">Drone</h2>
<p>Drone est un serveur de CI/CD écrit en Go. Pour le moment vous devez héberger Drone sur votre serveur, mais une option d'hébergement dédiée est dans les tuyaux. Drone possède un système de plugin qui permet ainsi d'ajouter de nouvelles fonctionnalités.</p>
<p>Configurer un build pour Drone se fait à l'aide d'un fichier <code>.drone.yml</code>. On notera que la syntaxe de configuration de Drone est dérivée de la configuration de <code>docker-compose</code>. Si vous connaissez déjà <code>docker-compose</code>, vous serez en terrain connu avec le langage de configuration de Drone.</p>
<h3 id="on-aime-2">On aime</h3>
<p>Drone propose des <a href="http://docs.drone.io/matrix-builds/" target="_blank" rel="noopener noreferrer">matrices de builds</a> pour permettre de tester simplement votre code dans de multiples configurations, par exemple différentes versions de vos dépendances.</p>
<h3 id="on-est-pas-super-fan-2">On est pas super fan</h3>
<p>Drone est un arrivant relativement récent, et sa documentation aurait besoin d'un peu d'amour.</p>
<aside class="note note-info"><p><a href="http://docs.drone.io/getting-started/" target="_blank" rel="noopener noreferrer">Bien démarrer avec Drone CI</a></p></aside>
<h2 id="gitlab-ci">GitLab CI</h2>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_2.0-f_auto-q_auto-w_868/jamstatic/ci-cd-test-deploy-illustration_2x.0613618ae80f9bf63f844f8336d7d6b1.webp 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_2.0-f_auto-q_auto-w_868/jamstatic/ci-cd-test-deploy-illustration_2x.0613618ae80f9bf63f844f8336d7d6b1.webp 1024w" width="1024" height="477" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_2.0-f_auto-q_auto-w_868/jamstatic/ci-cd-test-deploy-illustration_2x.0613618ae80f9bf63f844f8336d7d6b1.avif 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_2.0-f_auto-q_auto-w_868/jamstatic/ci-cd-test-deploy-illustration_2x.0613618ae80f9bf63f844f8336d7d6b1.avif 1024w" width="1024" height="477" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_2.0-f_auto-q_auto-w_868/jamstatic/ci-cd-test-deploy-illustration_2x.0613618ae80f9bf63f844f8336d7d6b1.png" alt="" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="477" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_2.0-f_auto-q_auto-w_868/jamstatic/ci-cd-test-deploy-illustration_2x.0613618ae80f9bf63f844f8336d7d6b1.png 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_2.0-f_auto-q_auto-w_868/jamstatic/ci-cd-test-deploy-illustration_2x.0613618ae80f9bf63f844f8336d7d6b1.png 1024w" sizes="100vw">
</picture>
<p>Si vous utilisez GitLab pour héberger votre code, vous avez déjà accès aux outils d'intégration continue de GitLab. Tout ce que vous avez à faire est d'ajouter un pipeline de CI à votre projet avec un fichier <code>.gitlab-ci.yml</code>. J'aime le fait de ne pas à avoir à parcourir une interface graphique pour configurer l'intégration continue d'un projet — si vous avez plein de projets et que vous souhaitez gérer leur intégration continue par lots, cette option vous donne des possibilités d'automatisation.</p>
<h3 id="on-aime-3">On aime</h3>
<p>GitLab CI est compatible avec toutes les versions de GitLab : vous pouvez l'utiliser sur gitlab.com ou sur votre instance GitLab hébergée. Le composant d'intégration continue de GitLab est écrit en Go, il est donc facile à exécuter sur les systèmes d'exploitation majeurs, y compris Windows et MacOS. Vous pouvez même <a href="https://gitlab.com/gitlab-org/gitlab-runner/issues/312" target="_blank" rel="noopener noreferrer">lancer vos tests d'intégration en local sur votre machine</a> !.</p>
<h3 id="on-est-pas-super-fan-3">On est pas super fan</h3>
<p>Forcément pour utiliser GitLab CI, vous devez héberger votre code source avec GitLab, le fait de pouvoir héberger vous-même votre suite logicielle devrait vous rassurer si vous avez peur d'être trop dépendant d'un service tiers.</p>
<aside class="note note-info"><p><a href="https://docs.gitlab.com/ee/ci/quick_start/" target="_blank" rel="noopener noreferrer">GitLab CI : guide de démarrage rapide</a></p></aside>
<h2 id="jenkins">Jenkins</h2>
<img src="/images/jenkins-logo.3e83aa52a384b4cc1de13db61db99231.svg" alt="Jenkins logo" loading="lazy" decoding="async" class="dark:brightness-90">
<p>Jenkins est un serveur d'intégration et de déploiement continu que vous installez et lancer sur votre propre serveur. Le projet Jenkins <a href="https://www.cloudbees.com/jenkins/about" target="_blank" rel="noopener noreferrer">a débuté en 2004</a> et aujourd'hui c'est une solution adoptée par les entreprises qui souhaitent posséder leur propre infrastructure de CI.</p>
<p>Jenkins dispose d'une foule de plugins pour l'ajout de fonctionnalités à votre serveur de CI. Les builds sont configurés dans un fichier <code>Jenkinsfile</code>, à partir du moment où vous avez <a href="https://www.jenkins.io/doc/book/pipeline/getting-started/" target="_blank" rel="noopener noreferrer">installé le plugin Pipeline</a> recommandé. Comme CircleCI, vous pouvez configurer votre environnement de build à l'aide d'une image Docker, même s'il existe également <a href="https://www.jenkins.io/doc/book/pipeline/syntax/#agent" target="_blank" rel="noopener noreferrer">beaucoup d'autres options</a>.</p>
<p>Jenkins est écrit en Java et est compatible avec les principaux systèmes d'exploitation. Les builds peuvent tourner sous environnement Linux, BSD, MacOS ou Windows.</p>
<h3 id="on-aime-4">On aime</h3>
<p>Jenkins est totalement libre d'utilisation et open source. Jenkins supporte les plugins et dispose d'une <a href="https://plugins.jenkins.io/" target="_blank" rel="noopener noreferrer">bibliothèque très fournie</a> de par sa relative longévité dans le domaine de l'intégration continue.</p>
<p>Le fait de pouvoir faire tourner ses builds sur n'importe quel système d'exploitation, y compris Windows ou Mac OS, car Jenkins est écrit en Java.</p>
<h3 id="on-est-pas-super-fan-4">On est pas super fan</h3>
<p>Pas grand-chose à redire à ce niveau : Jenkins peut faire à peu près tout ce que vous voulez ! Toutefois, il se peut que les petites équipes n'aient peut-être pas envie de devoir se coltiner la maintenance et l'hébergement de leur propre serveur d'intégration continue.</p>
<aside class="note note-info"><p><a href="https://www.jenkins.io/doc/pipeline/tour/getting-started/" target="_blank" rel="noopener noreferrer">Jenkins : Guide de démarrage</a></p></aside>
<h2 id="choisir-l-outil-qui-vous-convient-le-mieux">Choisir l'outil qui vous convient le mieux</h2>
<p>La variété d'options pour la mise en place de l'intégration et du déploiement continu a rendu l'automatisation plus accessible que jamais aux développeurs. Les projets open source qui possèdent des prérequis assez simples peuvent tirer parti de l'offre gratuite de <strong>TravisCI</strong>. Les utilisateurs de GitLab devraient se pencher sur l'utilisation de <strong>GitLab CI</strong>. Drone est une bonne solution pour ceux qui recherchent une solution simple à héberger soi-même, surtout s'ils apprécient la syntaxe de <code>docker-compose</code>. <strong>CircleCI</strong> est un bon choix pour ceux qui veulent de la souplesse mais qui ne souhaitent pas héberger leur serveur. <strong>Jenkins</strong> demandera quelques heures et de l'huile de coude, mais c'est un logiciel capable de faire beaucoup de choses.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2018/08/17/contenu-multilingue-avec-hugo/</id>
    <title>Contenu multilingue avec Hugo</title>
    <published>2018-08-17T09:36:38+00:00</published>
    <link href="https://jamstatic.fr/2018/08/17/contenu-multilingue-avec-hugo/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>Hugo gère parfaitement le multilingue par défaut et permet ainsi de facilement traduire les contenus et les chaînes de caractères pour la localisation. Tout est pensé pour que la gestion de langues supplémentaires soit aussi simple que possible pour les développeurs et les contributeurs, ils peuvent ainsi se focaliser sur l'essentiel.</p>
<p>Voyons ensemble comment configurer un projet Hugo multilingue et traduire votre contenu.</p>
<h2 id="configurer-les-langues">Configurer les langues</h2>
<p>La première chose à faire sur un projet multilingue est d'indiquer à Hugo les langues à prendre en compte. Dans notre exemple nous en aurons trois :</p>
<ol>
<li>Anglais 🇬🇧</li>
<li>Français 🇫🇷</li>
<li>Espagnol 🇪🇸</li>
</ol>
<p>Nous ajoutons donc les paramètres suivants dans notre fichier de configuration :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># config.yaml</span>
<span class="hljs-attr">languages:</span>
  <span class="hljs-attr">en:</span>
    <span class="hljs-attr">languageName:</span> <span class="hljs-string">English</span>
    <span class="hljs-attr">weight:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">fr:</span>
    <span class="hljs-attr">languageName:</span> <span class="hljs-string">Français</span>
    <span class="hljs-attr">weight:</span> <span class="hljs-number">2</span>
  <span class="hljs-attr">es:</span>
    <span class="hljs-attr">languageName:</span> <span class="hljs-string">Español</span>
    <span class="hljs-attr">weight:</span> <span class="hljs-number">3</span></code></pre>
<p>Ces langues sont dorénavant accessibles via <code>.Site.Languages</code>, triées par le poids indiqué, la valeur la plus petite sera la plus importante.</p>
<p>Il est possible de personnaliser des paramètres globaux déjà définis dans <code>.Site.Params</code> ou <code>.Param</code>. Aucun souci à se faire donc pour le paramètre à appeler.</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># config.yaml</span>
<span class="hljs-attr">params:</span>
  <span class="hljs-attr">description:</span> <span class="hljs-string">Everything</span> <span class="hljs-string">you</span> <span class="hljs-string">need</span> <span class="hljs-string">to</span> <span class="hljs-string">know</span> <span class="hljs-string">about</span> <span class="hljs-string">the</span> <span class="hljs-string">three</span> <span class="hljs-string">languages.</span>
  <span class="hljs-attr">twitter_handle:</span> <span class="hljs-string">3Languages</span>

<span class="hljs-attr">languages:</span>
  <span class="hljs-attr">en:</span>
    <span class="hljs-attr">languageName:</span> <span class="hljs-string">English</span>
    <span class="hljs-attr">weight:</span> <span class="hljs-number">1</span>
  <span class="hljs-attr">fr:</span>
    <span class="hljs-attr">languageName:</span> <span class="hljs-string">Français</span>
    <span class="hljs-attr">weight:</span> <span class="hljs-number">2</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">Tous</span> <span class="hljs-string">ce</span> <span class="hljs-string">que</span> <span class="hljs-string">vous</span> <span class="hljs-string">avez</span> <span class="hljs-string">toujours</span> <span class="hljs-string">voulu</span> <span class="hljs-string">savoir</span> <span class="hljs-string">sur</span> <span class="hljs-string">les</span> <span class="hljs-string">trois</span> <span class="hljs-string">langues.</span>
    <span class="hljs-attr">twitter_handle:</span> <span class="hljs-string">3Languages_france</span>
  <span class="hljs-attr">es:</span>
    <span class="hljs-attr">languageName:</span> <span class="hljs-string">Español</span>
    <span class="hljs-attr">weight:</span> <span class="hljs-number">3</span>
    <span class="hljs-attr">description:</span> <span class="hljs-string">Todo</span> <span class="hljs-string">lo</span> <span class="hljs-string">que</span> <span class="hljs-string">necesitas</span> <span class="hljs-string">saber</span> <span class="hljs-string">sobre</span> <span class="hljs-string">los</span> <span class="hljs-string">tres</span> <span class="hljs-string">idiomas.</span>
    <span class="hljs-attr">twitter_handle:</span> <span class="hljs-string">3Languages_espana</span>
</code></pre>
<pre><code class="language-go-html-template hljs go">&lt;meta name=<span class="hljs-string">"description"</span> content=<span class="hljs-string">"{{ .Param "</span>description<span class="hljs-string">" }}"</span>&gt;
&lt;meta name=<span class="hljs-string">"twitter:site"</span> content=<span class="hljs-string">"{{ .Param "</span>twitter_handle<span class="hljs-string">" }}"</span>&gt;</code></pre>
<h2 id="traduire-nos-pages">Traduire nos pages</h2>
<p>Hugo propose deux manière de faire pour traduire vos contenus.</p>
<p>La première consiste à inclure le code de la langue dans le nom du fichier de votre contenu, par exemple : <code>/content/about.fr.md</code>.</p>
<p>La deuxième suppose de créer un fichier dans un dossier dédié à une langue, par exemple : <code>/content/french/about.md</code>.</p>
<p>Nous allons voir en détail comment s'assurer de deux choses, quelle que soit la méthode utilisée :</p>
<ol>
<li>Chaque page a une langue de définie.</li>
<li>Chaque page est reliée à ses traductions.</li>
</ol>
<h3 id="gG-c-rer-les-traductions-avec-les-noms-de-fichiers-rџ">Gérer les traductions avec les noms de fichiers 📄</h3>
<p>Voici à quoi ressemble notre page <code>about</code> et ses traductions :</p>
<pre><code class="language-sh hljs bash">content
├── about.md
├── about.es.md
└── about.fr.md</code></pre>
<p>Hugo assigne ici le français au fichier <code>about.fr.md</code> et la version espagnole au fichier <code>about.es.md</code>. C'est très intuitif.</p>
<p>Quid du fichier <code>about.md</code> ? Comme aucune langue n'est précisée, il se verra assigné celle par défaut.</p>
<p>Si vous n'avez pas défini la valeur de <code>DefaultContentLanguage</code> dans votre fichier de configuration, la langue par défaut est l'anglais.</p>
<p>Si vous souhaitez modifier ce comportement et assigner le français par défaut aux fichiers sans nomenclature de code de langue, il vous faut ajouter cette ligne à votre fichier de configuration :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># config.yaml</span>
<span class="hljs-attr">DefaultContentLanguage:</span> <span class="hljs-string">fr</span></code></pre>
<h3 id="gG-c-rer-les-traductions-par-dossier-rџ-Ѓ">Gérer les traductions par dossier 📁</h3>
<p>Il est également possible d'affecter un dossier à chaque langue pour y déposer vos contenus traduits. Pour ce faire, vous devez spécifier le paramètre <code>contentDir</code> dans la configuration des langues :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">languages:</span>
  <span class="hljs-attr">en:</span>
    <span class="hljs-attr">languageName:</span> <span class="hljs-string">English</span>
    <span class="hljs-attr">weight:</span> <span class="hljs-number">1</span>
    <span class="hljs-attr">contentDir:</span> <span class="hljs-string">content/english</span>
  <span class="hljs-attr">fr:</span>
    <span class="hljs-attr">languageName:</span> <span class="hljs-string">Français</span>
    <span class="hljs-attr">weight:</span> <span class="hljs-number">2</span>
    <span class="hljs-attr">contentDir:</span> <span class="hljs-string">content/french</span>
  <span class="hljs-attr">es:</span>
    <span class="hljs-attr">languageName:</span> <span class="hljs-string">Español</span>
    <span class="hljs-attr">weight:</span> <span class="hljs-number">3</span>
    <span class="hljs-attr">contentDir:</span> <span class="hljs-string">content/spanish</span></code></pre>
<p>Vous pouvez spécifier un chemin relatif à votre projet ou un chemin absolu. L'utilisation d'un chemin absolu signifie que vos dossiers de traduction ne se trouvent pas forcément dans votre projet, mais ailleurs sur votre ordinateur.</p>
<p>En reprenant l'exemple précédent, notre arborescence de contenus ressemble maintenant à quelque chose comme :</p>
<pre><code class="language-sh hljs bash">content
├── english
│   └── about.md
├── french
│   └── about.md
└── spanish
    └── about.md</code></pre>
<p>Hugo peut désormais assigner une langue à chacune des pages en fonction du dossier dans lequel elles se trouvent.</p>
<h2 id="crG-c-er-des-liens-vers-les-traductions-rџ">Créer des liens vers les traductions 🔗</h2>
<p>La création de lien vers les traductions est fondamentale.</p>
<p>En règle générale, nous allons vouloir indiquer à nos visiteurs les traductions disponibles de la page en cours, que ce soit via un menu ou des métadonnées pour le SEO.</p>
<p>Nous avons vu qu'Hugo sait assigner une langue à une page, mais qu'en est-il de la possibilité de lier des traductions entre elles ?</p>
<p>Dans les deux cas, Hugo va se baser sur le nom de fichier et sa localisation par rapport au dossier <code>content</code>. En fonction du système utilisé, on peut utiliser les nomenclatures suivantes :</p>
<table>
<thead>
<tr>
<th style="text-align: left;">Par nom de fichier</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;"><code>content/about.md</code></td>
<td><code>content/about.fr.md</code></td>
<td>✅</td>
</tr>
<tr>
<td style="text-align: left;"><code>content/about.fr.md</code></td>
<td><code>content/about.es.md</code></td>
<td>✅</td>
</tr>
<tr>
<td style="text-align: left;"><code>content/about/index.md</code></td>
<td><code>content/about/index.fr.md</code></td>
<td>✅</td>
</tr>
<tr>
<td style="text-align: left;"><code>content/about.md</code></td>
<td><code>content/a-propos.fr.md</code></td>
<td>🚫</td>
</tr>
<tr>
<td style="text-align: left;"><code>content/company/about.md</code></td>
<td><code>content/about.fr.md</code></td>
<td>🚫</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th style="text-align: left;">Par dossier</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;"><code>content/english/about.md</code></td>
<td><code>content/french/about.md</code></td>
<td>✅</td>
</tr>
<tr>
<td style="text-align: left;"><code>content/english/about/index.md</code></td>
<td><code>content/french/about/index.md</code></td>
<td>✅</td>
</tr>
<tr>
<td style="text-align: left;"><code>content/english/about.md</code></td>
<td><code>content/french/a-propos.md</code></td>
<td>🚫</td>
</tr>
<tr>
<td style="text-align: left;"><code>content/english/company/about.md</code></td>
<td><code>content/english/about.md</code></td>
<td>🚫</td>
</tr>
</tbody>
</table>
<p>Notez bien qu'on peut forcer la liaison si elle ne correspond pas à celle par défaut. Il suffit pour cela d'ajouter le paramètre <code>translationKey</code> dans le Front Matter aux pages qui partagent le même contenu.</p>
<pre><code class="language-markdown hljs markdown"><span class="hljs-section"># Dans les trois pages : about.md, a-propos.fr.md, acerda.es.md</span>
---
<span class="hljs-section">translationKey: about
---</span></code></pre>
<p>Grâce à cette clé de traduction, en l'absence de nomenclature commune, Hugo se fera un plaisir de relier ces pages entre elles.</p>
<h3 id="ajouter-des-liens-vers-les-traductions-dans-les-modeles-de-page">Ajouter des liens vers les traductions dans les modèles de page</h3>
<p>Maintenant que nos contenus dans différentes langues sont reliés entre eux, comment en tirer parti dans les gabarits de page ?</p>
<p>Hugo stocke les traductions liées dans deux variables de page :</p>
<ul>
<li><code>.Translations</code> pour les autres traductions liées à un contenu,</li>
<li><code>.AllTranslations</code> pour toutes les traductions liées y compris celle en cours.</li>
</ul>
<p>Les traductions sont ici également triées en fonction du paramètre <code>Weight</code> défini dans le fichier configuration.</p>
<p>Pour indiquer aux moteurs de recherche qu'il existe des traductions de contenu, il nous suffit d'ajouter le code suivant dans la balise  <code>&lt;head&gt;</code> :</p>
<pre><code class="language-go-html-template hljs go">{{ <span class="hljs-keyword">if</span> .IsTranslated }}
  {{ <span class="hljs-keyword">range</span> .Translations }}
  &lt;link rel=<span class="hljs-string">"alternate"</span> hreflang=<span class="hljs-string">"{{ .Language.Lang }}"</span> href=<span class="hljs-string">"{{ .Permalink }}"</span> title=<span class="hljs-string">"{{ .Language.LanguageName }}"</span>&gt;
  {{ end }}
{{ end }}</code></pre>
<p>Si nous préférons lister toutes les langues, y compris celle de la page en cours il nous suffit de boucler plutôt sur <code>.AllTranslations</code>.</p>
<p>On peut utiliser la même logique pour ajouter un sélecteur de langue qui ne s'affiche que si une ou plusieurs traductions sont disponibles :</p>
<pre><code class="language-go-html-template hljs go">{{ <span class="hljs-keyword">if</span> .IsTranslated }}
  &lt;nav class=<span class="hljs-string">"LangNav"</span>&gt;
  {{ <span class="hljs-keyword">range</span> .Translations }}
    &lt;a href=<span class="hljs-string">"{{ .Permalink }}"</span>&gt;{{ .Language.LanguageName }}&lt;/a&gt;
  {{ end}}
  &lt;/nav&gt;
{{ end }}</code></pre>
<aside class="note note-tip"><p>L'objet <code>.Language</code> est disponible pour toutes les pages. En plus des paramètres principaux de langues, il contient les valeurs personnalisées définir dans la configuration des langues comme la description et le pseudo twitter dans notre exemple.</p></aside>
<h2 id="les-bundles-de-page">Les bundles de page</h2>
<p>Hugo vous permet de partager des ressources entre traductions et vous laisse aussi la possibilité de traduire une ressource !</p>
<p>Revenons à nos pages <code>about</code> et transformons les en bundles (un dossier qui permet de stocker un contenu et ses ressources associées : images, etc.). Afin que ce soit plus clair, nous opterons pour la gestion par dossiers :</p>
<pre><code class="language-sh hljs bash">content
├── english
│   └── about
│       ├── index.md
│   └── header.jpg
├── español
│   └── about
│       └── index.md
└── french
    └── about
        └── index.md</code></pre>
<pre><code class="language-sh hljs bash">content
├── english
│   └── about
│       ├── index.md
│   └── header.jpg
├── spanish
│   └── about
│       └── index.md
└── french
    └── about
        └── index.md</code></pre>
<p>Dans cette configuration, toutes les traductions utilisent la ressource de la langue anglaise <code>header.jpg</code>. Hugo nous évite des duplications inutiles en partageant les ressources avec toutes les traductions d'une même page. On peut donc utiliser cette image quelque que soit la langue utilisée à l'aide de la fonction <code>.Resources</code>, en écrivant par exemple ici <code>.Resources.GetMatch "header.jpg"</code>. Vous n'êtes pas obligé de stocker la ressource dans le dossier de la langue par défaut, ça marchera aussi si la ressource se trouve dans un autre dossier de langue.</p>
<p>C'est bien pratique.
Mais que se passe-t-il si nous devons localiser cette image pour notre audience espagnole ? Comment ajouter une image spécifique pour la page espagnole ?</p>
<p>Il suffit de déposer notre image dans le dossier de la langue espagnole :</p>
<pre><code class="language-sh hljs bash">content
├── english
│   └── about
│       ├── index.md
│   └── header.jpg
├── spanish
│   └── about
│       ├── index.md
│   └── header.jpg ✨
└── french
    └── about
        └── index.md</code></pre>
<p>C'est tout, Hugo prendra en compte qu'une ressource dédiée pour la version espagnole de notre page <code>about</code>.</p>
<p>Et pour la version française ? Quelle image va-t-elle utiliser ? Celle de la version espagnole ou celle de la version anglaise ?</p>
<p>Dans ce cas Hugo va se baser sur la langue qui a le plus de poids et retourner la version correspondante. Comme dans notre configuration des langues, l'anglais a un indice de poids de 1, la version française héritera de la version de la ressource en anglais.</p>
<p>Sachez qu'il est possible de renommer n'importe quel fichier pour lui affecter une langue. Si nous avions choisi ici de nous baser sur la méthode qui repose sur la nomenclature des fichiers, notre bundle pour la page <code>about</code> ressemblerait à ceci :</p>
<pre><code class="language-sh hljs bash">content
└── about
    ├── index.md
    ├── index.es.md
    ├── index.fr.md
    ├── header.jpg
    └── header.es.jpg</code></pre>
<aside class="note note-tip"><p>Comme la fonction <code>.GetMatch</code> teste la valeur <code>.Title</code> d'une ressource, qui correspond par défaut à son nom de fichier (langue incluse), faites bien attention si vous vous basez sur les nomenclatures de fichier de bien englober toutes les ressources quelle que soit leur langue, comme ceci : <code>.Resources.GetMatch "header*.jpg"</code></p></aside>
<h2 id="configurer-nos-urls">Configurer nos URLs</h2>
<p>Qu'en est-il des URLs de nos pages ? Nous pouvons redefinir le slug d'une URL depuis le front matter d'une page, mais qu'en est-il de l'URL de base de chacune de nos langues ?</p>
<p>Par défaut, Hugo va stocker les pages de la langue par défaut à la racine du dossier de destination <code>public</code> et les autres langues dans leurs répertoires respectifs.</p>
<p>Donc pour un site en anglais par défaut, les URLs de la page <code>about</code> et de ses traductions seront :</p>
<ul>
<li><code>about/index.html</code> 🇬🇧</li>
<li><code>fr/about/index.html</code> 🇫🇷</li>
<li><code>es/about/index.html</code> 🇪🇸</li>
</ul>
<p>C'est pas mal, mais je doute que l'équipe chargée du référencement soit vraiment satisfaite. Pour nous assurer que les URLs des pages correspondent à leur titre, il nous faut encore mettre à jour le slug des pages traduites :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># about.fr.md</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">À</span> <span class="hljs-string">Propos</span>
<span class="hljs-attr">slug:</span> <span class="hljs-string">a-propos</span></code></pre>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># acerda.es.md</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">Acerda</span>
<span class="hljs-attr">slug:</span> <span class="hljs-string">acerda</span></code></pre>
<p>Ce qui a pour effet d'avoir des URLs traduites :</p>
<ul>
<li><code>fr/a-propos/index.html</code> 🇫🇷 👌</li>
<li><code>es/acerda/index.html</code> 🇪🇸 👌</li>
</ul>
<p>Nous pourrions décider de stocker les pages en anglais dans un répertoire dédié simplement en définissant le paramètre <code>defaultContentLanguageInSubdir</code> à <code>true</code> dans notre fichier <code>config.yaml</code></p>
<h2 id="localisation-des-chaines-de-caracteres">Localisation des chaînes de caractères</h2>
<p>La convention pour la traduction des chaînes de caractères avec Hugo ressemble un peu à celle des fichiers <code>.po</code> de gettext. Les chaînes de chaque langue sont enregistrées dans un fichier nommé en fonction du code de la langue utilisée et stockées dans un dossier <code>i18n/</code>.</p>
<p>Ce dossier peut se trouver à la racine de votre projet ou d'un thème.</p>
<ul>
<li><code>i18n/en.yaml</code> ✅</li>
<li><code>themes/academic/i18n/en.yaml</code> ✅</li>
</ul>
<p>Pour nos trois langues, ça ressemble à quelque chose comme :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># i18n/en.yaml 🇬🇧</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">hello</span>
  <span class="hljs-attr">translation:</span> <span class="hljs-string">"Hello"</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">how_are_you</span>
  <span class="hljs-attr">translation:</span> <span class="hljs-string">"How are you doing?"</span></code></pre>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># i18n/fr.yaml 🇫🇷</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">hello</span>
  <span class="hljs-attr">translation:</span> <span class="hljs-string">"Bonjour"</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">how_are_you</span>
  <span class="hljs-attr">translation:</span> <span class="hljs-string">"Comment allez-vous ?"</span></code></pre>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># i18n/es.yaml 🇪🇸</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">hello</span>
  <span class="hljs-attr">translation:</span> <span class="hljs-string">"Hola"</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">how_are_you</span>
  <span class="hljs-attr">translation:</span> <span class="hljs-string">"¿Como estas?"</span></code></pre>
<p>Comme vous pouvez le voir dans l'exemple ci-dessus, tout ce dont nous avons besoin c'est d'une chaîne qui servira de clé unique et d'une chaîne de caractère pour la traduction.</p>
<p>Ensuite dans nos modèles de page, <a href="https://gohugo.io/functions/i18n/#readout" target="_blank" rel="noopener noreferrer">la fonction i18n</a> d'Hugo se charge du reste.</p>
<ol>
<li>Elle va tester si la clé passée en argument existe et retourner la traduction correspondante si elle existe.</li>
<li>Si la clé n'existe pas pour la langue courante dans le fichier, elle affichera la traduction de la langue par défaut.</li>
<li>Si la clé n'existe pas pour la langue par défaut, elle retourne une chaîne vide.</li>
</ol>
<pre><code class="language-go-html-template hljs go">&lt;header&gt;
    {{ i18n <span class="hljs-string">"hello"</span> }}
    &lt;hr&gt;
    {{ i18n <span class="hljs-string">"how_are_you"</span> }}
&lt;/header&gt;</code></pre>
<pre><code class="language-go-html-template hljs go">&lt;!-- /es/index.html 🇪🇸 --&gt;
&lt;header&gt;
    Hola
    &lt;hr&gt;
    ¿Como estas?
&lt;/header&gt;</code></pre>
<pre><code class="language-go-html-template hljs go">&lt;!-- /fr/index.html 🇫🇷 --&gt;
&lt;header&gt;
    Bonjour
    &lt;hr&gt;
    Comment allez-vous ?
&lt;/header&gt;</code></pre>
<p>La fonction <code>i18n</code> a comme alias <code>T</code>. Si taper <code>i18n</code> est trop fatiguant pour vos petits doigts, vous pouvez donc utiliser la syntaxe abrégée : <code>{{ T "how_are_you" }}</code>.</p>
<h2 id="mettre-les-chaines-au-pluriel">Mettre les chaînes au pluriel</h2>
<p>Les chaînes ne font pas toujours référence à une entité unique. Elles peuvent parfois qualifier une seule chose, parfois plus. Comment donc nous assurer qu'une phrase sera fidèlement traduite au singulier comme au pluriel ?</p>
<p>Hugo possède bien une fonction <a href="https://gohugo.io/functions/pluralize/#readout" target="_blank" rel="noopener noreferrer"><code>pluralize</code></a>, mais elle ne gère que l'anglais.</p>
<p>Heureusement pour nous, les chaînes de traduction d'Hugo nous permettent de gérer parfaitement les autres langues.</p>
<p>Afin de mieux illustrer cette fonctionnalité, nous allons utiliser des exemples dans lesquels figurent… des rongeurs 🐭 ! N'ayez pas peur, ce sont simplement des pluriels intéressants dans les trois langues.</p>
<p>Comment ça marche ? Hé bien, il s'avère que la valeur de notre traduction peut également être une liste de pluriels.</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># i18n/en.yaml 🇬🇧</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">mouse</span>
  <span class="hljs-attr">translation:</span>
    <span class="hljs-attr">one:</span> <span class="hljs-string">Mouse</span>
    <span class="hljs-attr">other:</span> <span class="hljs-string">Mice</span></code></pre>
<p>Excellent, notre chaîne a maintenant un singulier (<code>one</code>) et une autre version (<code>other</code>) qui sera donc notre pluriel.</p>
<p>Renseignons donc nos autres fichiers :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># i18n/es.yaml 🇪🇸</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">mouse</span>
  <span class="hljs-attr">translation:</span>
    <span class="hljs-attr">one:</span> <span class="hljs-string">Ratón</span>
    <span class="hljs-attr">other:</span> <span class="hljs-string">Ratones</span></code></pre>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># i18n/fr.yaml 🇫🇷</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">mouse</span>
  <span class="hljs-attr">translation:</span>
    <span class="hljs-attr">other:</span> <span class="hljs-string">Souris</span></code></pre>
<p>Comme en français le mot souris est invariable au singulier et au pluriel, nous n'avons qu'à renseigner la version générique <code>other</code>.</p>
<p>La fonction <code>i18n</code> peut prendre un entier comme deuxième paramètre, afin de préciser à combien d'éléments fait référence notre chaîne et à pouvoir la mettre au pluriel si nécessaire.</p>
<pre><code class="language-go-html-template hljs go">{{ <span class="hljs-keyword">range</span> .Pages }}
    &lt;h3&gt;{{ $.Title }}&lt;/h3&gt;
    {{ with .Params.mice }}
        {{ i18n <span class="hljs-string">"this_story_has"</span> }} {{ . }} {{ i18n <span class="hljs-string">"mouse"</span> . }}.
    {{ end }}
    &lt;hr&gt;
{{ end }}</code></pre>
<p>Imaginons que nous avons deux histoires, la première avec 24 souris et la seconde avec une seule, voici quel serait le HTML compilé :</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Cinderella<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
This story has 24 Mice.
<span class="hljs-tag">&lt;<span class="hljs-name">hr</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Fantasia<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
This story has 1 Mouse.
<span class="hljs-tag">&lt;<span class="hljs-name">hr</span>&gt;</span></code></pre>
<h3 id="inclure-le-nombre-d-unites-dans-la-traduction">Inclure le nombre d'unités dans la traduction</h3>
<p>Vous pouvez même ajouter le nombre exact à la traduction de votre chaîne à l'aide de <code>.Count</code> et fusionner l'ensemble dans une seule chaîne de caractère (notez l'utilisation des guillemets) :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">story_mice</span>
  <span class="hljs-attr">translation:</span>
    <span class="hljs-attr">other:</span> <span class="hljs-string">"This story has <span class="hljs-template-variable">{{ .Count }}</span> Mice"</span>
    <span class="hljs-attr">one:</span> <span class="hljs-string">This</span> <span class="hljs-string">story</span> <span class="hljs-string">has</span> <span class="hljs-string">only</span> <span class="hljs-string">one</span> <span class="hljs-string">Mouse</span></code></pre>
<p>Dorénavant le nombre de souris sera retourné en sortie de la fonction <code>i18n</code>, nous pouvons mettre à jour notre code pour qu'il utilise plutôt cette chaîne unique :</p>
<pre><code class="language-diff hljs diff"><span class="hljs-deletion">- {{ i18n "this_story_has" }} {{ . }} {{ i18n "mouse" . }}</span>
<span class="hljs-addition">+ {{ i18n "story_mice" . }}</span></code></pre>
<p>Le HTML compilé correspondant sera :</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Cinderella<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
This story has 24 Mice.
<span class="hljs-tag">&lt;<span class="hljs-name">hr</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>Fantasia<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
This story has only one Mouse.
<span class="hljs-tag">&lt;<span class="hljs-name">hr</span>&gt;</span></code></pre>
<aside class="note note-tip"><p>Vous pensez peut-être déjà au cas où il n'y a pas de souris quand le total est <code>0</code> ?<br>
Comme <a href="#traduction-des-chaînes-avec-le-système-de-fichier-d-hugo">expliqué plus bas</a>, cela ne sera pas possible 🙅‍♂️.</p></aside>
<h3 id="inclusion-du-contexte-dans-la-traduction">Inclusion du contexte dans la traduction</h3>
<p>Vous pouvez également passer en second paramètre un contexte à la fonction <code>i18n</code> plutôt qu'un entier.
Là encore cela peut nous éviter de découper nos phrases en plusieurs chaînes de traduction, quand nous avons besoin de plus que de <code>.Count</code>.</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># i18n/en.yaml</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">intro</span>
  <span class="hljs-attr">translation:</span>  <span class="hljs-string">"This is the story of <span class="hljs-template-variable">{{ .Params.lead }}</span><span class="hljs-template-variable">{{ with .Params.location }}</span> which takes place in <span class="hljs-template-variable">{{ . }}</span><span class="hljs-template-variable">{{ end }}</span>"</span></code></pre>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># i18n/en.yaml</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">intro</span>
  <span class="hljs-attr">translation:</span>  <span class="hljs-string">"Voici l'histoire de <span class="hljs-template-variable">{{ .Params.lead }}</span><span class="hljs-template-variable">{{ with .Params.location }}</span> qui se déroule à <span class="hljs-template-variable">{{ . }}</span><span class="hljs-template-variable">{{ end }}</span>"</span></code></pre>
<p>C'est le même principe que le contexte d'un fichier partiel.</p>
<pre><code class="language-go-html-template hljs go">&lt;h3&gt;{{ .Title }}&lt;/h3&gt;
&lt;div class=<span class="hljs-string">"intro"</span>&gt;{{ i18n <span class="hljs-string">"intro"</span> . }}&lt;/div&gt;</code></pre>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">h3</span>&gt;</span>The Great Mouse Detective<span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"intro"</span>&gt;</span>This is the story of Basil which takes place in London<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></code></pre>
<p>Lorsque vous passez un contexte en paramètre d'<code>i18n</code>, vous devez garder certaines choses en tête :</p>
<ol>
<li><code>i18n</code> ne pourra évaluer ce paramètre comme un nombre (puisque ce n'en est pas un), donc impossible de mettre cette chaîne au pluriel à l'aide de <code>one</code> et <code>other</code>.</li>
<li>Si cette chaîne est appelée à différents endroits, assurez-vous de toujours lui passer le même contexte ou bien utilisez <code>with</code> comme nous l'avons fait ci-dessus, si vous ne voulez pas vous retrouver avec une erreur bien moche du type <code>can't evaluate field</code>.</li>
</ol>
<h3 id="traduction-des-chaines-avec-le-systeme-de-fichier-d-hugo">Traduction des chaînes avec le système de fichier d'Hugo</h3>
<p>Rappelez-vous que nos fichiers <code>i18n</code> sont inclus dans le système de fichier global d'Hugo. En conséquence, tous les fichiers <code>en.yaml</code> présents dans l'arborescence de notre projet Hugo seront fusionnés.</p>
<p>Si une des traductions du thème que nous utilisons ne nous plaît pas, nous n'avons qu'à créer un fichier <code>i18n/en.yaml</code> à la racine de notre projet (ou de notre composant de thème prioritaire) pour y ajouter notre version de cette traduction et uniquement celle-ci.</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-comment"># i18n/en.yaml</span>
<span class="hljs-bullet">-</span> <span class="hljs-attr">id:</span> <span class="hljs-string">mouse</span>
  <span class="hljs-attr">translation:</span>
    <span class="hljs-attr">one:</span> <span class="hljs-string">Rodent</span>
    <span class="hljs-attr">other:</span> <span class="hljs-string">Rodents</span></code></pre>
<p>C'est tout ! Pour les autres langues, Hugo se basera sur les <em>Souris</em> et les <em>Ratones</em> 🐁 déclarés dans <code>themes/miceandmen/i18n/</code>.</p>
<h3 id="un-dernier-mot-sur-les-singuliers-et-les-pluriels">Un dernier mot sur les singuliers et les pluriels</h3>
<p>L'anglais comme le français, l'espagnol et bien d'autres langues ne connaît que deux formes de pluralisation, c'est soit du <strong>singulier</strong> soit du <strong>pluriel</strong>.</p>
<p>Donc dans Hugo assez logiquement, pour le traitement d'une chaîne en anglais, les seules possibilités  de mettre au pluriel seront <code>one</code> ou <code>other</code>.</p>
<p>La version à utiliser est déterminée par ce test tout simple :</p>
<p><strong>si</strong> l'entier passé en paramètre de <code>i18n</code> <strong>==</strong> <code>1</code> 👉 <code>one</code><br>
<strong>sinon</strong> 👉 <code>other</code></p>
<p>C'est tout pour la plupart des langues européennes !</p>
<p>Maintenant, d'autres langues comme le Russe ont des pluriels spécifiques pour <code>few</code> et <code>many</code>, l'arabe a une forme pour <code>zero</code> et une pour <code>two</code> <sup id="fnref1:1"><a href="#fn:1" class="footnote-ref">1</a></sup></p>
<p>Si nous pouvons deviner sans mal le nombre correspondant au pluriel de <code>zero</code> ou <code>two</code>, connaître le nombre exact d'éléments correspondants à <code>few</code> ou <code>many</code> en Russe ressemble davantage à un casse-tête.</p>
<p>Heureusement, nous pouvons nous reposer sur Hugo et <a href="https://github.com/nicksnyder/go-i18n" target="_blank" rel="noopener noreferrer">go-i18n</a> de <a href="https://github.com/nicksnyder" target="_blank" rel="noopener noreferrer">Nick Snyder</a> pour nous aider à assembler toutes les pièces du puzzle.</p>
<aside class="note note-info"><p>Voici tous les pluriels supportés pour l'ensemble des langues :
<code>zero</code> <code>one</code> <code>two</code> <code>few</code> <code>many</code> <code>other</code></p></aside>
<p>Mais, cela ne veut pas dire pour autant que vous pouvez les utiliser en anglais.</p>
<p>Si la langue courante est l'anglais, que votre total de souris est nul, et que vous précisez que le pluriel pour <code>zero</code> est <code>This story has no mouse</code>, vous vous retrouverez quand même avec la valeur utilisée pour <code>other</code> : <code>This story has 0 Mice.</code></p>
<p>La valeur <code>zero</code> n'est prise en compte que si la langue courante est l'arabe ou si cette langue supporte un pluriel pour <code>zero</code>.</p>
<h2 id="conclusion-rџЏЃ">Conclusion 🏁</h2>
<p>Traduire des chaînes de caractères dans Hugo consiste à écrire un ou plusieurs fichiers de données pour chacune des langues supportée par votre projet.</p>
<p>Nous avons vu qu'Hugo offre une solution de localisation très simple et très efficace, que ce soit pour aider les contributeurs à traduire des contenus, ou permettre aux développeurs de supporter plusieurs langues dans les modèles de page.</p>
<p>Si vous avez été amenés à gérer des projets multilingues plus complexes que ceux présentés ici, si vous pensez pouvoir enrichir cet article ou que vous avez vérifié le nombre exact de souris présentes dans Cendrillon<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref">2</a></sup>, <a href="https://regisphilibert.com/blog/2018/08/hugo-multilingual-part-2-i18n-string-localization/#disqus_thread" target="_blank" rel="noopener noreferrer">faite-le savoir en commentaire</a>.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="http://www.unicode.org/cldr/charts/33/supplemental/language_plural_rules.html" target="_blank" rel="noopener noreferrer">http://www.unicode.org/cldr/charts/33/supplemental/language_plural_rules.html</a>&#160;<a href="#fnref1:1" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
<li id="fn:2">
<p>Evidemment, j'ai pris un nombre au pif !&#160;<a href="#fnref1:2" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
</ol>
</div>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2018/07/21/hugo-asset-pipeline/</id>
    <title>La gestion des assets avec Hugo</title>
    <published>2018-07-21T18:54:26+00:00</published>
    <link href="https://jamstatic.fr/2018/07/21/hugo-asset-pipeline/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Depuis la <a href="https://gohugo.io/news/0.43-relnotes/" target="_blank" rel="noopener noreferrer">version 0.43</a>, Hugo comble un des reproches qui lui a souvent été fait, le manque de solution native pour gérer les assets, à savoir la génération de fichiers CSS et JS pour la production — et le développement). Cette nouvelle possibilité fait d'Hugo une solution toujours plus performante pour le développement de sites statiques.</p></aside>
<p>Hugo est surtout apprécié pour sa performance et son modèle de structuration de contenu, mais en ce qui concerne le traitement des fichiers CSS et JS, il fallait jusqu'ici avoir recours à l'écosystème <code>npm</code>, tout ça pour simplement pouvoir compiler des fichiers Sass, voire concaténer et minifier des fichiers JS. C'est désormais une dépendance dont on pourra se passer. Vous pouvez dire adieu à Webpack, Gulp et à votre <code>package.json</code> jamais à jour.</p>
<h2 id="traitement-des-assets">Traitement des assets</h2>
<figure>
<picture title="Photo de Neil Cooper">
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/jamstatic/pipes.3ed4ee38ca6a2aa361a6791fdb537793.webp 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/jamstatic/pipes.3ed4ee38ca6a2aa361a6791fdb537793.webp 1024w" width="1024" height="685" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/jamstatic/pipes.3ed4ee38ca6a2aa361a6791fdb537793.avif 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/jamstatic/pipes.3ed4ee38ca6a2aa361a6791fdb537793.avif 1024w" width="1024" height="685" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/jamstatic/pipes.3ed4ee38ca6a2aa361a6791fdb537793.jpg" alt="Photo de Neil Cooper" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="685" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A9I3UZqPNOWmBIKfimrT6BEUp4rIumwTWpO2Aawb6YDPNIZEZQD1q5auCRXPvdgP1rRsLgMRzTA6eE5WpCKr2zZUVa7UAQsKjJxUzCoWoAM0VHmimA8GpFqGpFNICYGn9qjUin54oAqXZwhrktVudpPNdZdqWQ1yGr2rPnApMZzc96d/BrY0i6LMMmsV7CQydDW5pNiyEEikI7SxfKCtDdxWbZKVQVezxVAKzVC7U5jULtQAwtzRUZbmimBZpVNJRSETKakBGKrqakBpgLIoYVm3NkJM8Vpg5oKg0hnPf2Su7O2rlvYrHjitMoKTAFADY0CinE0hamMwpiBjUEjU5mqFjk0AJRRRQMt0UUUhBTxRRQMkFPHSiigBrVG1FFMRG1RtRRQBG1MoooAKKKKBn/9k=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/jamstatic/pipes.3ed4ee38ca6a2aa361a6791fdb537793.jpg 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/jamstatic/pipes.3ed4ee38ca6a2aa361a6791fdb537793.jpg 1024w" sizes="100vw">
</picture>
<figcaption>Photo de <a href="https://unsplash.com/photos/KX2fCzuQoaQ" target="_blank" rel="noopener noreferrer">Neil Cooper</a></figcaption>
</figure>
<p>Le principe est simple : tout ce qui se trouve dans le dossier <code>/assets</code> (que ce soit dans un thème ou pas) pourra être ensuite traité par des fonctions spécifiques aux assets. Pour les plus exigeants, ce chemin par défaut est paramétrable via la directive <code>assetDir</code> dans votre fichier de configuration.</p>
<p>On peut donc par exemple écrire ceci dans un fichier de layout :</p>
<pre><code class="language-go-html-template hljs go">{{ $styles := resources.Get <span class="hljs-string">"scss/main.scss"</span> | toCSS | minify }}
&lt;link rel=<span class="hljs-string">"stylesheet"</span> href=<span class="hljs-string">"{{ $styles.Permalink }}"</span> media=<span class="hljs-string">"screen"</span>&gt;</code></pre>
<p>Ici on récupère le contenu du fichier <code>/assets/scss/main.scss</code> et on lui applique successivement deux fonctions : d'abord <code>toCSS</code> (alias de <code>resources.ToCSS</code>) pour compiler le fichier Sass puis <code>minify</code> (alias de <code>resources.Minify</code>). La syntaxe des <em>pipes</em> en Go template est la même que celle que vous utilisez déjà dans votre shell UNIX ou que celle des filtres Liquid, ça rend les choses plutôt intuitives.
Difficile de faire plus concis et plus simple vous en conviendrez.</p>
<p>Maintenant vous pourriez argumenter que pendant le développement, vous n'avez pas besoin de la minification. Par contre vous aimeriez bien générer des fichiers <em>source map</em> pour développer dans votre navigateur web préféré. Qu'à cela ne tienne, grâce à la fonction <code>isServer</code> qui détecte si vous êtes en train de développer localement avec <code>hugo server</code> ou pas, vous pouvez personnaliser ce qui est généré.</p>
<p>Prenons donc un exemple un peu plus réaliste :</p>
<pre><code class="language-go-html-template hljs go">{{ <span class="hljs-keyword">if</span> .Site.IsServer }}
  {{ $cssOpts := (dict <span class="hljs-string">"targetPath"</span> <span class="hljs-string">"assets/css/main.css"</span> <span class="hljs-string">"enableSourceMap"</span> <span class="hljs-literal">true</span>) }}
  {{ $styles := resources.Get <span class="hljs-string">"scss/main.scss"</span> | toCSS $cssOpts }}
  &lt;link rel=<span class="hljs-string">"stylesheet"</span> href=<span class="hljs-string">"{{ $styles.Permalink }}"</span> media=<span class="hljs-string">"screen"</span>&gt;
{{ <span class="hljs-keyword">else</span> }}
  {{$cssOpts := (dict <span class="hljs-string">"targetPath"</span> <span class="hljs-string">"assets/css/main.css"</span>) }}
  {{ $styles := resources.Get <span class="hljs-string">"scss/main.scss"</span> | toCSS $cssOpts | postCSS | minify | fingerprint }}
  &lt;link rel=<span class="hljs-string">"stylesheet"</span> href=<span class="hljs-string">"{{ $styles.Permalink }}"</span> media=<span class="hljs-string">"screen"</span>&gt;
{{ end }}</code></pre>
<p>Ici, pendant le développement avec <code>hugo server</code>, on stocke d'abord les options à passer à la fonction <code>toCSS</code> dans une liste de clés-valeurs, le chemin où nous voulons générer notre fichier CSS ainsi que le mapping avec le fichier Sass source.
Puis comme précédemment on applique le tout à notre fichier principal Sass. Enfin on pointe vers l'URL du fichier généré dans la balise <code>link</code>.</p>
<p>Les options de la fonction <code>toCSS</code> sont légèrement différentes si c'est pour être hébergé en production, à ce moment-là on ne génère pas de fichier <em>source map</em>. Par contre on peut choisir d'appliquer d'autres fonctions à notre fichier CSS, comme par exemple un post-traitement avec postCSS, au hasard <em>autoprefixer</em> ou l'application d'une empreinte unique pour faciliter l'invalidation de cache à chaque nouvelle version du fichier. Notez que si vous utilisez la fonction <code>postCSS</code>, cela implique d'avoir les packages <code>npm</code> qui vont bien sur votre machine, que ce soit en global ou en local.</p>
<h2 id="et-ensuite聽">Et ensuite ?</h2>
<p>Vous pouvez aller jeter un œil au dépôt d'exemple de <a href="https://github.com/bep/hugo-sass-test" target="_blank" rel="noopener noreferrer">l'utilisation de Sass avec Hugo</a> sur GitHub pour un exemple plus complet, qui montre notamment comment surcharger des variables Sass depuis le fichier de configuration d'Hugo. Bud Parr a également publié un <a href="https://github.com/budparr/hugopipes-tailwindcss" target="_blank" rel="noopener noreferrer">exemple de configuration avec PostCSS, TailwindCSS, PurgeCSS et autoprefixer</a>.</p>
<p>Vous pouvez également placer des images dans le répertoire <code>assets</code> et procéder à des transformations comme c'était déjà possible au niveau des ressources d'une page.</p>
<p>Pour le JS, sachez qu'il existe une <a href="https://gohugo.io/hugo-pipes/bundling/" target="_blank" rel="noopener noreferrer">fonction pour la concaténation</a>, qui vous permettra de faire vos bundle en fonction des fichiers dont vous avez besoin dans vos layouts.</p>
<p>Je vous recommande chaudement de lire <a href="https://regisphilibert.com/blog/2018/07/hugo-pipes-and-asset-processing-pipeline/" target="_blank" rel="noopener noreferrer">l'article de Régis Philibert qui détaille l'utilisation des différentes fonctions offertes par Hugo pour la gestion des assets</a> et bien entendu de vous référer à <a href="https://gohugo.io/hugo-pipes/" target="_blank" rel="noopener noreferrer">la documentation officielle</a> qui est toujours à jour.</p>
<p>Mine de rien, non seulement ces ajouts vont vous permettre de supprimer des dépendances à vos projets Hugo mais en plus ils permettent de bénéficier d'une meilleure expérience de développement. Non parce que lancer Webpack pour démarrer le serveur d'Hugo, c'est… lourd.</p>
<p>On notera qu'il existe maintenant deux versions des binaires d'Hugo, celle avec le support de la gestion des assets c'est la version <code>extended</code>.</p>
<p>Si vous utilisez Netlify pour générer automatiquement votre site à chaque commit, sachez que la version <em>extended</em> n'est pas encore disponible à l'heure où j'écris ces lignes.</p>
<p>La génération des fichiers CSS et des fichiers JS n'est pas quelque chose que vous avez forcément besoin de lancer à <em>chaque</em> build, ou à chaque fois qu'un contributeur édite un fichier Markdown dans votre <a href="/2017/12/15/cms-headless/">headless CMS</a>.</p>
<p>En fonction de votre contexte vous pouvez choisir de :</p>
<ul>
<li>générer les assets localement avec <code>hugo</code> et <em>commiter</em> les fichiers générés dans le répertoire <code>resources</code> à chaque fois que vous modifiez le contenu du dossier <code>assets</code>,</li>
<li>recopier et commiter la version étendue du ficher binaire d'Hugo dans le répertoire <code>bin</code> de votre dépôt et modifier la commande de build en <code>./bin/hugo</code> au lieu de <code>hugo</code>.</li>
</ul>
<p>En combinant ces fonctions aux conventions existantes d'Hugo , les développeurs de thèmes peuvent maintenant fournir des fichiers de configuration pour toujours plus de personnalisation.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Hugo continue de se tailler une bonne place dans le monde des générateurs de site statique et mérite plus que jamais que vous tentiez l'expérience. Il se définit désormais comme un véritable <em>framework</em> de développement, si l'on en croit <a href="https://gohugo.io/" target="_blank" rel="noopener noreferrer">sa baseline officielle</a>. Hugo n'en reste pas moins une application monolithique comparée aux autres SSG de l'écosystème <code>npm</code> comme Gatsby ou Next, à vous de voir si l'ensemble de fonctionnalités dont il dispose maintenant suffit pour votre projet.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2018/05/31/startups-jamstack/</id>
    <title>Les startups de l’écosystème Jamstack</title>
    <published>2018-05-31T18:21:08+00:00</published>
    <link href="https://jamstatic.fr/2018/05/31/startups-jamstack/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Devant la tendance croissante de l'adoption d'outils et de services web modernes pour gérer les sites ou les applications web, les investisseurs de la Silicon Valley misent assez tôt sur les startups issus de cet écosystème : Netlify, Algolia, Contentful et plus récemment Gatsby ont tous levé quelques millions de dollars pour les aider dans leur développement. Et ce n'est que le début, les annonces de financement devraient se poursuivre dans les prochains mois.</p></aside>
<p>Dans cet article <a href="https://www.crv.com/" target="_blank" rel="noopener noreferrer">CRV</a> montre une vue d'ensemble du résultat de plusieurs tendances qui ont permis l'émergence d'une nouvelle architecture frontend et par conséquence d'un écosystème fertile de startups.</p>
<p>La manière dont les sites web et les applications sont développés est en train de prendre un tournant architectural important — que ce soit les outils utilisés par les développeurs pour les construire ou l'UI/UX proposée aux utilisateurs finaux. C'est une véritable révolution qui est en train de chambouler le monde du développement d'applications.</p>
<h2 id="pourquoi-maintenant">Pourquoi maintenant ?</h2>
<p><strong>L'explosion de JavaScript</strong> : JS est devenu tellement puissant, avec un haut niveau d'abstraction il permet le développement d'applications plus interactives, plus rapides et l'amélioration de l'expérience de l'utilisateur final.</p>
<p><strong>La prolifération des APIs</strong> : des microservices pour le backend à la popularité grandissante de <a href="https://graphql.org/" target="_blank" rel="noopener noreferrer">GraphQL</a> et aux frontends librement couplés, les APIs sont un élément central du changement architectural auquel nous assistons.</p>
<p><strong>Davantage de développeurs</strong> : le nombre grandissant de développeurs et la réduction du niveau de compétences moyen dans l'industrie. Tout cela fait qu'il y a beaucoup plus de développeurs qui ont envie d'apprendre et de se former à de nouveaux outils.</p>
<p><strong>L'essor de Git</strong> : les processus basés sur Git sont désormais au cœur de la collaboration entre développeurs et du versionnement de projet.</p>
<p><strong>Les progrès des infrastructures du Cloud</strong> : la réduction des coûts de fonctionnement, les architectures <em>serverless</em> et un outillage plus complet permettent aux développeurs de se concentrer sur le design et le développement de l'interface client et de bâtir de façon plus efficace des applications plus performantes.</p>
<p>Quelle que soit votre idéologie — les clients lourds/légers, les Single Page Applications (SPA) avec rendu côté serveur, les sites dynamiques/statiques — la manière dont la partie cliente est développée est en train de changer</p>
<h2 id="l-histoire-de-la-jamstack">L'histoire de la Jamstack</h2>
<p>Les sites dynamiques ont pris le pas sur les sites statiques, car ils proposaient plus d'interactivité et de personnalisation, cela n'allait pas sans problèmes de performance, de sécurité et s'accompagnait de coûts de fonctionnement élevés. Avec les navigateurs web modernes, les enseignements tirés du développement d'application mobile, les offres de cloud public arrivées enfin à maturité, l'interactivité d'HTML5 couplé à JavaScript et à des APIs comme <a href="https://disqus.com/" target="_blank" rel="noopener noreferrer">Disqus</a> ou <a href="https://www.algolia.com/" target="_blank" rel="noopener noreferrer">Algolia</a>, nous avons l'opportunité de revenir à la simplicité des sites web statiques. Pour faire passer le message aux développeurs, des gens comme Matt Biilmann, le créateur et le CEO de <a href="https://netlify.com/" target="_blank" rel="noopener noreferrer">Netlify</a>, ont diffusé le concept de la Jamstack: une approche différente, <em>serverless</em> pour développer des sites web modernes et performants. JAM signifie JavaScript, APIs et Markup et l'idée centrale est de ne plus avoir à gérer ses propres serveurs, d'héberger le code sur des CDNs rapides, fiables et sécurisés et de faire interagir les navigateurs avec les APIs qui peuvent s'occuper des composants « dynamiques ». La Jamstack personnifie toutes les tendances soulignées plus haut.</p>
<h2 id="le-paysage-de-la-jamstack">Le paysage de la Jamstack</h2>
<p>Cette image illustre le paysage florissant des startups avec quelques projets open source et des membres clés de la communauté, qui profitent des tendances mentionnées plus haut.</p>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1527850625/jamstatic/jamstack-landscape.2910996fa6dd42c94f4d9229e79efd9c.webp 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1527850625/jamstatic/jamstack-landscape.2910996fa6dd42c94f4d9229e79efd9c.webp 800w" width="800" height="587" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1527850625/jamstatic/jamstack-landscape.2910996fa6dd42c94f4d9229e79efd9c.avif 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1527850625/jamstatic/jamstack-landscape.2910996fa6dd42c94f4d9229e79efd9c.avif 800w" width="800" height="587" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1527850625/jamstatic/jamstack-landscape.2910996fa6dd42c94f4d9229e79efd9c.png" alt="" loading="lazy" decoding="async" class="dark:brightness-90" width="800" height="587" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1527850625/jamstatic/jamstack-landscape.2910996fa6dd42c94f4d9229e79efd9c.png 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1527850625/jamstatic/jamstack-landscape.2910996fa6dd42c94f4d9229e79efd9c.png 800w" sizes="100vw">
</picture>
<h2 id="les-benefices">Les bénéfices</h2>
<p>Il en résulte des sites plus sécurisés, des déploiements et des temps de chargement plus rapides, moins onéreux et plus faciles à gérer. Ça a l'air trop beau pour être vrai ? Contrairement à la stack LAMP, il n'y a pas de base de données dans la Jamstack, donc moins d'appels aux backend et une surface d'attaque réduite. Les sites web (comme ceux développés avec WordPress) ont particulièrement été la cible d'attaques malveillantes, mais en l'absence de base de données et de plugins, les menaces sont considérablement réduites.</p>
<p>Le découplage de la partie client de la partie serveur permet également aux développeurs de se connecter aux meilleures APIs tierces comme celle d'Algolia pour la recherche par exemple. La partie CMS reste critique et des CMS headless (ou API-first comme <a href="https://www.contentful.com/" target="_blank" rel="noopener noreferrer">Contentful</a> or <a href="https://tipe.io/" target="_blank" rel="noopener noreferrer">Tipe</a>) permettent plus de collaboration entre le design, le marketing et les développeurs. Avec un process de travail basé sur Git, il est bien plus simple de mettre à jour un site ou une application, on peut déployer à partir de GitHub, s'assurer que le contenu est à jour, revenir à une version précédente et identifier l'origine des anomalies.</p>
<h2 id="et-ensuite">Et ensuite ?</h2>
<p>Permettre <strong>une meilleure collaboration</strong> entre toutes les parties prenantes du frontend — les créateurs de contenus, les gens du marketing, du design et du développement. <em>« Les développeurs détestent avoir à créer et éditer du texte, les créateurs de contenu détestent devoir passer par les développeurs pour apporter des modifications simples »</em> — Scott Moss, CEO de <a href="https://tipe.io/" target="_blank" rel="noopener noreferrer">Tipe</a>.</p>
<p>La <strong>conception d'interface basée sur des composants</strong> est en pleine expansion, l'utilisation de <a href="https://storybook.js.org/" target="_blank" rel="noopener noreferrer">Storybook</a> décolle (10% des téléchargements React) et les composants réutilisables vont accélérer la vitesse de développement, en particulier celle des équipes. « L'adoption de React, Vue et Angular s'accompagne d'un changement de paradigme vers le développement basé sur des composants et Storybook est au cœur de ce mouvement » — Zoltan Olah, CEO de <a href="https://www.chromaticqa.com/" target="_blank" rel="noopener noreferrer">Chroma</a>.</p>
<p><strong>Des services pour le frontend</strong> : des plate-formes comme <a href="https://vercel.com/" target="_blank" rel="noopener noreferrer">Vercel</a> et Netlify sont en train d'émerger, elles permettent de s'affranchir du temps et de la difficulté des différents processus de génération de site, de compilation des assets, de déploiement et de publication continue sur les serveurs d'hébergement.</p>
<p><strong>L'Edge Computing</strong> ouvre tant de nouvelles manières de concevoir des applications. Une fois que vous avez accès à du stockage performant à la périphérie du réseau, vous allez pouvoir facilement segmenter et personnaliser le trafic, gérer le contenu, en devant rarement aller taper sur le serveur d'origine ce qui vous permettra d'avoir des applications plus rapides.</p>
<h2 id="conclusion">Conclusion</h2>
<p>Les tendances du développement frontend et le changement architectural qui en résulte est très prometteur pour l'écosystème des startups et des capital-risqueurs. <a href="https://www.crv.com/" target="_blank" rel="noopener noreferrer">CRV</a> aime prendre des risques au plus tôt, et a déjà investi dans 6 startups qui font partie du paysage de la Jamstack (dont 3 qui seront annoncées publiquement très bientôt). Si vous êtes un créateur de startup lié à cet écosystème, un investisseur ou que vous voulez simplement papoter, écrivez à reid@crv.com.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://strapi.io/blog/building-a-static-website-using-gatsby-and-strapi</id>
    <title>Développer un blog statique avec Gatsby et Strapi</title>
    <published>2018-04-26T00:00:00+00:00</published>
    <link href="https://strapi.io/blog/building-a-static-website-using-gatsby-and-strapi" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-info"><p>Une nouvelle version de ce tutoriel a été publié en Février 2020 sur <a href="https://strapi.io/blog/build-a-static-blog-with-gatsby-and-strapi" target="_blank" rel="noopener noreferrer">https://strapi.io/blog/build-a-static-blog-with-gatsby-and-strapi</a></p></aside>
<h2 id="introduction">Introduction</h2>
<p>Un site statique est un site dont le contenu est fixe. Techniquement, il est composé d’une simple liste de fichiers HTML, qui affiche les mêmes informations quel que soit le visiteur. Contrairement aux sites dynamiques, il ne requiert ni programmation back-end ni base de données. Publier un site statique est simple : il suffit d’uploader les fichiers sur un service de stockage et le tour est joué. Les deux principaux avantages d’un site web sont la sécurité et la vitesse : étant donné qu’il n’y a pas de base de données le site ne peut pas se faire hacker, et puisqu’il n’y a pas de génération de page à chaque requête, la navigation pour l’utilisateur est plus fluide.</p>
<p>Pour faciliter leur création, de nombreux générateurs de sites statiques sont disponibles: <a href="https://jekyllrb.com/" target="_blank" rel="noopener noreferrer">Jekyll</a>, <a href="https://gohugo.io/" target="_blank" rel="noopener noreferrer">Hugo</a>, <a href="https://hexo.io/" target="_blank" rel="noopener noreferrer">Hexo</a>, etc. La plupart du temps, le contenu est géré via des fichiers (idéalement en format Markdown) statiques ou via une API de contenu. Ensuite, le générateur requête le contenu, l’injecte dans les templates définis par le développeur et génère un ensemble de fichiers HTML, CSS et JavaScript.</p>
<p>Les Progressive Web Apps (PWA) sont des applications web, reposant fortement sur l’utilisation de JavaScript, qui se veulent être <a href="https://developers.google.com/web/progressive-web-apps" target="_blank" rel="noopener noreferrer">fiables, rapides et engageantes</a>. Étant donné qu’elles rendent la navigation plus rapide et qu’elles offrent une expérience utilisateur bien meilleure, les PWA sont devenues la manière par défaut de construire des interfaces web. Pour cette raison, de nombreux frameworks front-end ont fait leur apparition au cours des dernières années : Angular, React, et plus récemment, Vue.</p>
<blockquote>
<p>Gatsby : quand les sites statiques rencontrent les Progressive Web Apps.</p>
</blockquote>
<p>Les sites Web statiques et PWA ont tous les deux de solides avantages, ce qui nous incite à trouver un moyen de les utiliser tous les deux dans le même projet. Heureusement, quelques outils ont réussi à faire le pont entre les deux, notamment celui dont on entend le plus parler depuis quelques mois : <a href="https://www.gatsbyjs.org/" target="_blank" rel="noopener noreferrer">Gatsby</a>. Nous avons donc décidé de vous donner un exemple complet pour vous apprendre à commencer à l’utiliser facilement. Un site statique a besoin d’une source de contenu : dans cet exemple, nous allons le délivrer grâce à une API créée avec Strapi.</p>
<h3 id="qu-est-ce-que-gatsby">Qu’est-ce que Gatsby ?</h3>
<p>D’après ses créateurs, <a href="https://www.gatsbyjs.org" target="_blank" rel="noopener noreferrer">Gatsby</a> est un "<em>blazing-fast website framework for React</em>". Il permet aux développeurs de créer des sites construits avec React en quelques minutes. Que vous vouliez développer un blog ou un site vitrine, Gatsby devrait correspondre à vos besoins.</p>
<figure>
<img src="/images/gatsby-logo.d8560250fbd350c6ba73def4f636c445.svg" alt="Logo Gatsby" title="Logo Gatsby" loading="lazy" decoding="async" class="dark:brightness-90">
<figcaption>Logo Gatsby</figcaption>
</figure>
<p>Étant donné que Gatsby utilise React, les pages générées ne sont jamais rechargées, ce qui rend le site extrêmement rapide. De nombreux plugins sont disponibles pour permettre aux développeurs de gagner du temps et de récupérer la donnée depuis n’importe quelle source (fichiers Markdown, CMS, etc.). Gatsby est fortement basé sur le concept de la <a href="https://www.gatsbyjs.org/docs/node-interface/" target="_blank" rel="noopener noreferrer">"node" interface</a>, qui est le centre du système de données de Gatsby.</p>
<p>Créé par <a href="https://twitter.com/kylemathews" target="_blank" rel="noopener noreferrer">Kyle Mathews</a>, le projet a été officiellement <a href="https://www.gatsbyjs.org/blog/gatsby-v1/" target="_blank" rel="noopener noreferrer">publié en juillet 2017</a> et est d’ores et déjà <a href="https://github.com/gatsbyjs/gatsby#showcase" target="_blank" rel="noopener noreferrer">utilisé par des dizaines d’entreprises</a>.</p>
<h3 id="qu-est-ce-que-strapi">Qu’est-ce que Strapi?</h3>
<p><a href="https://strapi.io" target="_blank" rel="noopener noreferrer">Strapi</a> est le Content Management Framework le plus avancé sur la technologie Node.js. À mi-chemin entre un famework Nodejs et un Headless CMS (API-first), il permet d’économiser des semaines de développement.</p>
<figure>
<img src="/images/strapi-logo.db50b281025a1e1510a79d2dfbd18be6.svg" alt="Logo Strapi" title="Logo Strapi" loading="lazy" decoding="async" class="dark:brightness-90" width="512" height="512">
<figcaption>Logo Strapi</figcaption>
</figure>
<p>Grâce à son système extensible de plugin, il propose de nombreuses fonctionnalités : panel d’administration, authentification et gestion des permissions, gestion de contenu, générateur d’API, etc.</p>
<p>Contrairement aux CMS en ligne, <strong>Strapi est 100% open-source</strong>, ce qui veut dire que :</p>
<ul>
<li><strong>Strapi est totalement gratuit</strong></li>
<li>Vous pouvez l’<strong>héberger sur vos propres serveurs</strong>. Vous êtes donc propriétaire de votre donnée.</li>
<li>Il est entièrement <strong>personnalisable et extensible</strong>, grâce au système de plugins.</li>
</ul>
<h2 id="installation-de-l-api">Installation de l’API</h2>
<p>Tout d’abord, nous allons commencer par créer une API avec Strapi et ajouter du contenu.</p>
<h3 id="creation-du-projet-strapi">Création du projet Strapi</h3>
<h4 id="installation-de-strapi">Installation de Strapi</h4>
<p><em>Prérequis</em>: vérifiez que <a href="https://nodejs.org/en/download/" target="_blank" rel="noopener noreferrer">Node 8</a> (ou plus) et <a href="https://docs.mongodb.com/manual/installation/" target="_blank" rel="noopener noreferrer">MongoDB</a> sont installés et démarrés sur votre machine.</p>
<p>Installez Strapi via npm :</p>
<pre><code class="language-bash hljs bash">npm i strapi@alpha -g</code></pre>
<p><em>Note</em> : Strapi v3 est encore en version alpha, mais cela ne posera aucun problème pour la réalisation de ce tutoriel.</p>
<h4 id="creation-d-un-projet-strapi">Création d’un projet Strapi</h4>
<p>Créez un dossier nommé <code>gatsby-strapi-tutorial</code> :</p>
<pre><code class="language-bash hljs bash">mkdir gatsby-strapi-tutorial</code></pre>
<p>Générez l’API au sein de ce nouveau dossier :</p>
<pre><code class="language-bash hljs bash"><span class="hljs-built_in">cd</span> gatsby-strapi-tutorial
strapi new api</code></pre>
<h4 id="demarrage-du-serveur">Démarrage du serveur</h4>
<p>Entrez à l’intérieur du projet généré :</p>
<pre><code class="language-bash hljs bash"><span class="hljs-built_in">cd</span> api</code></pre>
<p>Démarrez le serveur Node.js :</p>
<pre><code class="language-bash hljs bash">strapi start</code></pre>
<p>À partir de maintenant, vous devriez être à même de voir le panel d’administration de votre projet : <a href="http://localhost:1337/admin" target="_blank" rel="noopener noreferrer">http://localhost:1337/admin</a>.</p>
<h3 id="creation-du-premier-utilisateur">Création du premier utilisateur</h3>
<p>Ajoutez votre premier utilisateur depuis la <a href="http://localhost:1337/admin/plugins/users-permissions/auth/register" target="_blank" rel="noopener noreferrer">page d’inscription</a>.</p>
<figure>
<picture title="Écran de création d’un utilisateur">
<source type="image/webp" srcset="/thumbnails/768x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.09.14.png_0ba40390ba.333e449b3f10899886b7af36505a2d58.webp 768w, /thumbnails/1024x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.09.14.png_0ba40390ba.333e449b3f10899886b7af36505a2d58.webp 1024w" width="1024" height="650" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.09.14.png_0ba40390ba.333e449b3f10899886b7af36505a2d58.avif 768w, /thumbnails/1024x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.09.14.png_0ba40390ba.333e449b3f10899886b7af36505a2d58.avif 1024w" width="1024" height="650" sizes="100vw">
<img src="/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.09.14.png_0ba40390ba.333e449b3f10899886b7af36505a2d58.png" alt="Écran de création d’un utilisateur" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="650" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAAsTAAALEwEAmpwYAAADyklEQVR4nO1b23KrMAxcQdJMM/3rnk/upJOmic4DCIQvGHNVUnaGmjjCBi0rS5TQ5+c/PhxKvL2dcD6f8fHx0Wzn8xnv7+84nU44Ho84Ho8oyxJlWYKIQEQA4LUAwAwA3LZV04Hz0RRI7VD1p+qTawVBroDZb5kZ9/sd9/sdt9sNt9sN1+sV39/fuFwu+Pr6arbL5YKfnyt+f+84rHqVNSwTsTWKrU/AKra6aXZCjGEnxBh2QoxhJ8QYdkKMYSfEGHZCjCFKiFSdO9bFrhBj2Akxhp0QY9jk4WIHcy5VlDaxju0JqTEHLy/Ahx1CJjHyCkzUGETIminwmJleiI8wIe5/vlZFznSvxEQNOyHLwRBeKPrheWGWkF5GKLLvfn7Chw3m6xAObOJo2Wdty0/JQ4OD+dMPnB5TKwTWdtIfIeUZologZLHTzg/PmTEjZdCx5YBd3WqyUgRYJChrDZmScTn+rQccagiPBPc9L+HEPYQg74y15qkbgryd9aAIqSK0+LzP98zceSkujdqWJJ3uMXXXBLXftIHjHaH4TmXfLnoK1NqujQOQcFCNseogcsYPrQmRPlmgm4W6Z8EmWUPE8eT0g5C6h7yvU8wtgN6QJYWhJkMXjWmVKK+EnB747G0MPBi+WpyDZTEn5+6myOacYTXuhsoQRCr16QN7yqh60c1Z1Zy6j1uFPBh4QKklNa9WCrUEFE1LnsctKENwWObJSJ8y/Ct114ygSpw+72Bn9JAiClTjaIKAfGUsyVVHISly8sKVRix18ru1EvpISc1GtUqIgKJOhx8MFE16TB375lwGXRb3XtIUNITMq5QRZVkguxpFioSp2vlCBtfO1m3WPbUSBtch7m8gZD9PKXnIJUVnVkXtdFZtUzh2SKE8ZSyMWR8uhlPjoXVzYLzY5oQ1oC38qOdYg4LwECUkXBu0KtHqWEopsZQ1VHW7++Nmi2F5ZQgmKUQIIqKAOtpHFqH+oQiRIVnS4GOyZ90OicKw3XTfUtCPNbQTNRmFskuNlVMUhnvXU4ZgtEJ0ltJVR0wZw+CSImOJKoakvc1YQiA9j2JGESJk6JCVj/j92RRyVGVUEp5yyKjOK6wu3W8NAwlhL3R1vuU2h5ljbS9Qp6vN7K1ysoJIjBDYJAOYvKhX7TQSnIrZGasJUxIeR4wu475IyEq7YImqV5zmKmXsWHpMyzDx1kl1B8ddNacTd0ImwroD58bAV0mXmTy0ZtigYP36Q5B8L2v/Zdu62CRkhZURtFoZ29995t9c/GtYVSHDlBE8amFsrwzBrhBj+A9wcL0JNtNFeAAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.09.14.png_0ba40390ba.333e449b3f10899886b7af36505a2d58.png 768w, /thumbnails/1024x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.09.14.png_0ba40390ba.333e449b3f10899886b7af36505a2d58.png 1024w" sizes="100vw">
</picture>
<figcaption>Écran de création d’un utilisateur</figcaption>
</figure>
<h3 id="creation-d-un-type-de-contenu">Création d’un type de contenu</h3>
<p>Les API Strapi sont basées sur une structure de données appelée Content Types (équivalent des modèles dans les frameworks et des Content Types dans WordPress).</p>
<p>Créez un Content Type nommé <code>article</code> contenant trois champs : <code>title</code> (type <code>string</code>), <code>content</code> (type <code>text</code>) et <code>author</code> (type <code>relation</code>: un utilisateur doit pouvoir être lié à plusieurs articles).</p>
<figure>
<picture title="Écran de définition de relation">
<source type="image/webp" srcset="/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-15.17.40.png_0f40e7faca.f522e7b1982661f62b95ff6b07adcaeb.webp" width="740" height="341">
<source type="image/avif" srcset="/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-15.17.40.png_0f40e7faca.f522e7b1982661f62b95ff6b07adcaeb.avif" width="740" height="341">
<img src="/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-15.17.40.png_0f40e7faca.f522e7b1982661f62b95ff6b07adcaeb.png" alt="Écran de définition de relation" loading="lazy" decoding="async" class="dark:brightness-90" width="740" height="341" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Écran de définition de relation</figcaption>
</figure>
<figure>
<picture title="Écran des champs d’un article">
<source type="image/webp" srcset="/thumbnails/768x/d2zv2ciw0ln4h1.cloudfront.net/uploads/capture-d-e-cran-2018-10-17-a-13.05.50.png_46a756a230.e10247d63ef0fb48361629879bf0d048.webp 768w, /thumbnails/1024x/d2zv2ciw0ln4h1.cloudfront.net/uploads/capture-d-e-cran-2018-10-17-a-13.05.50.png_46a756a230.e10247d63ef0fb48361629879bf0d048.webp 1024w" width="1024" height="606" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/d2zv2ciw0ln4h1.cloudfront.net/uploads/capture-d-e-cran-2018-10-17-a-13.05.50.png_46a756a230.e10247d63ef0fb48361629879bf0d048.avif 768w, /thumbnails/1024x/d2zv2ciw0ln4h1.cloudfront.net/uploads/capture-d-e-cran-2018-10-17-a-13.05.50.png_46a756a230.e10247d63ef0fb48361629879bf0d048.avif 1024w" width="1024" height="606" sizes="100vw">
<img src="/d2zv2ciw0ln4h1.cloudfront.net/uploads/capture-d-e-cran-2018-10-17-a-13.05.50.png_46a756a230.e10247d63ef0fb48361629879bf0d048.png" alt="Écran des champs d’un article" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="606" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/d2zv2ciw0ln4h1.cloudfront.net/uploads/capture-d-e-cran-2018-10-17-a-13.05.50.png_46a756a230.e10247d63ef0fb48361629879bf0d048.png 768w, /thumbnails/1024x/d2zv2ciw0ln4h1.cloudfront.net/uploads/capture-d-e-cran-2018-10-17-a-13.05.50.png_46a756a230.e10247d63ef0fb48361629879bf0d048.png 1024w" sizes="100vw">
</picture>
<figcaption>Écran des champs d’un article</figcaption>
</figure>
<h3 id="ajout-d-articles">Ajout d’articles</h3>
<p>Ajoutez quelques articles en base de données. Pour cela, suivez les étapes suivantes :</p>
<ol>
<li>Visitez la <a href="http://localhost:1337/admin/plugins/content-type-builder/models/article" target="_blank" rel="noopener noreferrer">page listant les articles</a>.</li>
<li>Cliquer sur <code>Add New Article</code>.</li>
<li>Renseignez un titre et un contenu, liez l’article à un auteur, puis valider.</li>
<li>Ajouter deux autres articles.</li>
</ol>
<figure>
<picture title="Écran listant des articles">
<source type="image/webp" srcset="/thumbnails/768x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.14.36.png_2916cd02ab.93070e426f891d184c327c5239ed9b0c.webp 768w, /thumbnails/1024x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.14.36.png_2916cd02ab.93070e426f891d184c327c5239ed9b0c.webp 1024w" width="1024" height="650" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.14.36.png_2916cd02ab.93070e426f891d184c327c5239ed9b0c.avif 768w, /thumbnails/1024x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.14.36.png_2916cd02ab.93070e426f891d184c327c5239ed9b0c.avif 1024w" width="1024" height="650" sizes="100vw">
<img src="/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.14.36.png_2916cd02ab.93070e426f891d184c327c5239ed9b0c.png" alt="Écran listant des articles" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="650" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.14.36.png_2916cd02ab.93070e426f891d184c327c5239ed9b0c.png 768w, /thumbnails/1024x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.14.36.png_2916cd02ab.93070e426f891d184c327c5239ed9b0c.png 1024w" sizes="100vw">
</picture>
<figcaption>Écran listant des articles</figcaption>
</figure>
<h3 id="autorisation-d-acces">Autorisation d’accès</h3>
<p>Pour des raisons de sécurité, l’<a href="http://localhost:1337/article" target="_blank" rel="noopener noreferrer">accès à l’API</a> est, par défaut, restreint.</p>
<p>Pour autoriser l’accès, visitez la <a href="http://localhost:1337/admin/plugins/users-permissions/roles/edit/1" target="_blank" rel="noopener noreferrer">section Auth &amp; Permissions du rôle Guest</a>, sélectionnez l’action <code>Article - find</code> et sauvegardez. À partir de ce moment, vous devriez être à même de <a href="http://localhost:1337/article" target="_blank" rel="noopener noreferrer">requêter la liste d’articles</a>.</p>
<p>L’accès à l’<a href="http://localhost:1337/article" target="_blank" rel="noopener noreferrer">API des auteurs</a> est également restreint. Autorisez l’accès aux anonymes en sélectionnant l’action <code>Users &amp; Permissions - find</code> puis en sauvegardant.</p>
<figure>
<picture title="Écran du rôle Guest">
<source type="image/webp" srcset="/thumbnails/768x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.29.59.png_261ab4d065.de79e0007bf7e144481214e27e6dfdfd.webp 768w, /thumbnails/1024x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.29.59.png_261ab4d065.de79e0007bf7e144481214e27e6dfdfd.webp 1024w" width="1024" height="650" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.29.59.png_261ab4d065.de79e0007bf7e144481214e27e6dfdfd.avif 768w, /thumbnails/1024x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.29.59.png_261ab4d065.de79e0007bf7e144481214e27e6dfdfd.avif 1024w" width="1024" height="650" sizes="100vw">
<img src="/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.29.59.png_261ab4d065.de79e0007bf7e144481214e27e6dfdfd.png" alt="Écran du rôle Guest" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="650" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.29.59.png_261ab4d065.de79e0007bf7e144481214e27e6dfdfd.png 768w, /thumbnails/1024x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.29.59.png_261ab4d065.de79e0007bf7e144481214e27e6dfdfd.png 1024w" sizes="100vw">
</picture>
<figcaption>Écran du rôle Guest</figcaption>
</figure>
<h2 id="developpement-du-site-statique">Développement du site statique</h2>
<p>Bien joué, votre API est prête à l’utilisation ! Nous allons maintenant commencer le développement du site statique.</p>
<h3 id="installation-de-gatsby">Installation de Gatsby</h3>
<p>Commençez par installer Gatsby :</p>
<pre><code class="language-bash hljs bash">npm install --global gatsby-cli</code></pre>
<h3 id="creation-d-un-projet-gatsby">Création d’un projet Gatsby</h3>
<p>Dans le dossier <code>gatsby-strapi-tutorial</code> que vous avez précédemment créé, générez votre tout nouveau blog :</p>
<pre><code class="language-bash hljs bash">gatsby new blog</code></pre>
<h3 id="demarrage-en-mode-developpement">Démarrage en mode développement</h3>
<p>Entrez dans le dossier du projet :</p>
<pre><code class="language-bash hljs bash"><span class="hljs-built_in">cd</span> blog</code></pre>
<p>Démarrez le serveur :</p>
<pre><code class="language-bash hljs bash">gatsby develop</code></pre>
<p>À partir de ce moment, votre site Gatsby devrait être disponible à l’adresse suivante : <a href="http://localhost:8000" target="_blank" rel="noopener noreferrer">http://localhost:8000</a>.</p>
<h3 id="installation-du-plugin-source-strapi">Installation du plugin source Strapi</h3>
<p>Lorsque l’on crée un site statique, la donnée peut venir de différentes sources : fichiers Markdown, fichiers CSV, un site WordPress (utilisant le plugin JSON REST API), etc.</p>
<p>Gatsby l’a bien compris. C’est pourquoi ses créateurs ont décidé de construire une couche spécifique et indépendante : le Data layer. Le système entier repose sur <a href="http://graphql.org" target="_blank" rel="noopener noreferrer">GraphQL</a>.</p>
<p>Pour connecter Gatsby à une nouvelle source de données, il faut <a href="https://www.gatsbyjs.org/docs/create-source-plugin" target="_blank" rel="noopener noreferrer">développer un "plugin source"</a>. Heureusement, <a href="https://www.gatsbyjs.org/docs/plugins" target="_blank" rel="noopener noreferrer">de nombreux plugins source sont déjà disponibles</a>. Vous devriez donc réussir à trouver celui qui correspond le mieux à vos besoins.</p>
<p>Dans cet exemple nous utilisons Strapi. Nous allons donc évidemment avoir besoin d’un plugin source dédié au API Strapi. Bonne nouvelle : <a href="https://github.com/strapi/gatsby-source-strapi" target="_blank" rel="noopener noreferrer">celui-ci existe déjà</a> !</p>
<p>Installons-le :</p>
<pre><code class="language-bash hljs bash">npm install --save gatsby-source-strapi</code></pre>
<p>Le plugin a besoin d’être configuré. Remplacez le contenu du fichier <code>gatsby-config.js</code> avec :</p>
<p><em>Path</em> : <code>gatsby-config.js</code></p>
<pre><code class="language-jsx hljs javascript"><span class="hljs-built_in">module</span>.exports = {
  <span class="hljs-attr">siteMetadata</span>: {
    <span class="hljs-attr">title</span>: <span class="hljs-string">`Gatsby Default Starter`</span>,
  },
  <span class="hljs-attr">plugins</span>: [
    <span class="hljs-string">`gatsby-plugin-react-helmet`</span>,
    {
      <span class="hljs-attr">resolve</span>: <span class="hljs-string">`gatsby-source-strapi`</span>,
      <span class="hljs-attr">options</span>: {
        <span class="hljs-attr">apiURL</span>: <span class="hljs-string">`http://localhost:1337`</span>,
        <span class="hljs-attr">contentTypes</span>: [
          <span class="hljs-comment">// List of the Content Types you want to be able to request from Gatsby.</span>
          <span class="hljs-string">`article`</span>,
          <span class="hljs-string">`user`</span>,
        ],
      },
    },
  ],
};</code></pre>
<p>Ensuite, redémarrez le serveur afin que Gatsby prenne en compte ces changements.</p>
<h3 id="liste-d-articles">Liste d’articles</h3>
<p>Dans un premier temps, nous voulons afficher la liste d’articles. Pour cela, remplacez le contenu de la page d’accueil par le suivant :</p>
<p><em>Path</em> : <code>src/pages/index.js</code></p>
<pre><code class="language-jsx hljs javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"gatsby-link"</span>;

<span class="hljs-keyword">const</span> IndexPage = <span class="hljs-function">(<span class="hljs-params">{ data }</span>) =&gt;</span> (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Hi people<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Welcome to your new Gatsby site.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Now go build something great.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
      {data.allStrapiArticle.edges.map((document) =&gt; (
        <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{document.node.id}</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">to</span>=<span class="hljs-string">{</span>`/${<span class="hljs-attr">document.node.id</span>}`}&gt;</span>{document.node.title}<span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{document.node.content}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
      ))}
    <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">to</span>=<span class="hljs-string">"/page-2/"</span>&gt;</span>Go to page 2<span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> IndexPage;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> pageQuery = graphql<span class="hljs-string">`
  query IndexQuery {
    allStrapiArticle {
      edges {
        node {
          id
          title
          content
        }
      }
    }
  }
`</span>;</code></pre>
<h4 id="que-venons-nous-de-faire">Que venons-nous de faire ?</h4>
<p>À la fin du fichier nous exportons <code>pageQuery</code> : une requête GraphQL qui requête la liste entière des articles. Comme vous pouvez le voir, nous requêtons uniquement les champs <code>id</code>, <code>title</code> et <code>content</code> grâce à la précision du langage GraphQL.</p>
<p>Ensuite, nous passons l’objet déstructuré <code>{ data }</code> comme paramètre de <code>IndexPage</code> et nous bouclons sur son objet <code>allStrapiArticles</code> afin d’afficher la donnée.</p>
<figure>
<picture title="Exemple d’écran d’accueil">
<source type="image/webp" srcset="/thumbnails/768x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.25.48.png_36e5468321.84a08a5ffd4158aa76ca8c66466cd0ee.webp 768w, /thumbnails/1024x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.25.48.png_36e5468321.84a08a5ffd4158aa76ca8c66466cd0ee.webp 1024w" width="1024" height="650" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.25.48.png_36e5468321.84a08a5ffd4158aa76ca8c66466cd0ee.avif 768w, /thumbnails/1024x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.25.48.png_36e5468321.84a08a5ffd4158aa76ca8c66466cd0ee.avif 1024w" width="1024" height="650" sizes="100vw">
<img src="/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.25.48.png_36e5468321.84a08a5ffd4158aa76ca8c66466cd0ee.png" alt="Exemple d’écran d’accueil" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="650" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAAsTAAALEwEAmpwYAAAI9UlEQVR4nO1a25bjKg7dAmEn/WPzBfMJ8/9v3V0dG80DN3Gx41TFSfVZR6scMOYitNkS2EX//c//xBgLxw6zu+Ay/8B1/oEflysu8xXzNMO5GcwMaxnGWJAhEBGIUEt7D4m/VFcS/VwgOdXNKNZOqb4rXZG+6cp6vdr2h0QGWWmqSCkWAUQE4j28X7GuC5Zlwe32gY8/H/j98Qs/f//Cr4+f+P3xEx+337gtN3i/gokIhgjGEIwNl42pYYJhk1NrDIwhkDE1IDsTkzsFEjvIcOTnpfM2R82YNEBhVDZq+6hIOyEJP6KeZ0AIIBIIGRgQjBgYTzCLsrElmDVgIERgywRrCJYNLBuwM+DJgCdbLmfh2MJaG0EJgICom5uo3/a+m4xmRd2sEmr50TCjGLkFrONPlXmMILVyIoOnUuYpIvCesHqAVgGMh5DFCgv2FuwN2BvY1cB6Ay8EeAKzC4ZmtnCThZv7a3IM5xhsGTa7LBMZkiZWG1cgCH8KnGY1bQGQpWFBBUs1tnafsaRHoMGImqdjaZdWzpWf8itl3j66rNUTlhWgmwB2hSeLVSwWsXBisfhwL2RBK8BuCoA4ZzFdGNPMcBfGdGXMF4d5dpgnB+ccrGXFEBNXalq1SaGgXDa+BiHRuXNPjVSuMIxA0coEBQMpiEg3qc28WbYZXEYyYrloXCCZIQIRD+89Vm/AK8HeAPrjAbPC0w0LGJMwVmGssICxMCvAPFuwjSyYIxBXxnx1mK8OFwUIcwjqxWW1xFeGhwQFcz4pig6k1iiZefk+AkEtI0KajatBGcaVcbkaOuf6tTJePVJWYQOIwMegviwREPaRJQ4rGCsYSwQFZg0MmSYLazm4pktkySWC8cPhcpkwT1MGxFoT3dVGDJG4Z4qWT3nxRVFpmVJZprgkSganZPgGCO22MhhHA3y/YzsifRwEyuKqARHxWFeLZTGwNwK56LbgsIjDIhxcFiyEODJkivFjtoEhM2NOLuvqcLkGtzVlhhiQMdFwfUiXjhEx70vqE3PamJLBUIzQgFTgBCMS1YDsBfbq6SZL6G5o2/a1BZiy7fUVQ4SWCES4bitjEQuBBS0SY0gb0BNTroypAsTC2AJInoIOCVLA8FBg+MAS72v3VQBp4gINAInD1mAl+7ZGboGpMvFuDMxjQqCK64klPiy+dYVdCMSAGI8VN9w847ZauHgt3sKLBRkBW2fAbMDO5stNnBmTL8cVIKQA0dumOlZI3G1EZiS35eMuJJ6iRE9Oxw3NigRGyxTlulpARlvd6jbf1OXt+t+PSa1I9BAmMGQlkCWIEaxYsXiGWyzcjcF/os0Xi9UbgAzYsi1nkHgOsa4GiJ0FTwaWQ0BPQb1zWRtgeBF4LzAJGAGMr91bNWflhkzLjgaItMrL+mjC8x1XNvBqfR/NTQtrva1Jc/LwPoABI/Dp/LEEewYbB5tbDucRgQWHE6OpLpsu7i9jLAyZaKiiod7mJhaYCASJwESGhPsCjGSGJMdFNTAEmI4lKcagjyOt0ZS1dFmN04hNjeGph6C0UY3SDksonNc8AITDn12jHaN9O7uLB5MNr0PKqxNdSR/xAzPy6xMqW99amWhkE5gBLyAv8CQh2ETwfFg48ILKR9Tb2UEwV3m9E8ssQWmv7UeD8j5Pw/shqxRC2lOkjQyJRDAEMrKlsnGyv7cGbAgByXiZjat+ZvILxqyYCABSDBFAKCiUwQiX9xKbCEgoAlkt7WKtHMV1Xi0EQmNZDUS7JVZ23HuWWaQBGuWpAS/MI+2wAIKA4GXfrtn2HmBQenNLxS2kSkR1nmrgqq0vhb0Gxe2sB8HE4OB98fmJJWnCEoEkakNpK7URYiuVj6k0rNXt6y6gVK8MXQNGDUg6XzpI5SmG+sBdkAzsmEFAZXsQgWs3QMjuYu8ydSd5yuXQCjIAfEgprRWpXY+IxDEVQ5TsQURCyKQSQIhiiibtT0sVSMNiGoOi0tC6ZVJaGtFGsm/LYGuqbMLJXHXFLf1L41RZ01VPkCBxAqJWlTR++yBDZHCbDJ767pWtSFGVPyKV8Zo0TELBEeabRkkLfbPrBhxAMaRWeDxBUo+oVVQoxIpUO91LAaI+9N1nSJIOqqqgbfcZIKjPNRkd0qpU1U3LMuhI2ZRjUGo7NwwpFbZY0oPWlEdtslLpvunrUwzJk4w8pLyHaALIyD1tAXGfKbveouumVuZe25YZqQ+jV3lrrE8pWvWh+ypXiVcoLhB05yqrL38urdIBU7Zc2R5sG8y4Jy1z9uv2zEgYcN8JDRv2z7cmrFbwQOFWsYcYclj29D7Q+sEw07cn5Y77ZyOdUjGPlGn33C2ipd6eUv3KbXcZR2PIYzJiyoHaO671GdLbkYZj8J5rGnTbpCm/vbr3QTuLIXmEx2o/iRkpr9Oj7VnfjE+jukGdpvz4o01dp3speBpD8qjHnp7MjOHYOx6IRxVG+Z4dX9f8fIYc1eOZfcUNiIohR+yb8l0MaSuWsnF6XNGeKecyZEOPLlP0e6VsubIHY8hz5d0MOXPqX44hrxLNlGcx5EjL78KMe/JyQLTo/dkrDfPdQNDyNkAI5Y0siO7u1I7090iF7wrKJiD3T+k6/YQkesT0NdvN88c4IntxZfMcosv0W9pR+gXV8u8zQvqhWPImUFpbtuVJhueQ09nR9LD5ke8Lfb5T2ndZW++0RucQc3yQcfolITzVgpR//l556zkkyfNf5D23v8fGvs+OvbZv2WXp91/fYD18K3kpIP8CcV9eAkgLxCtB+duAfxlD3sWOp25CXiAPHgwfk9TF6BT+KgNtfcO50+osdeL4Bw6GWw2fuQt7xyrtAaHmfk+v5yp8xJ6nuay9L4mvd1n99+u9+602X9PjWGcvC+ovGGV33P6VxV7d+32fJW/5HnK+j865QdnR+/fsAk4F5NX/MLBRY6jHPUC2+jpbXnwwfNUGoQ/cj7R/5+uk0wFp5/aZD1Hj1bztjmLp3X4+L+cBdvi/Tr4mlHddn+36mIvZD9zHxzqfIZv/dXL6yEAFxqMMeQY7zrPv8zs+Oaj338q/Ypx/GktG8n9Njo56TK/e1gAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.25.48.png_36e5468321.84a08a5ffd4158aa76ca8c66466cd0ee.png 768w, /thumbnails/1024x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.25.48.png_36e5468321.84a08a5ffd4158aa76ca8c66466cd0ee.png 1024w" sizes="100vw">
</picture>
<figcaption>Exemple d’écran d’accueil</figcaption>
</figure>
<h4 id="astuce-generez-vos-requetes-graphql-en-quelques-secondes">Astuce : générez vos requêtes GraphQL en quelques secondes !</h4>
<p>Gatsby inclut l’excellente interface <a href="https://github.com/graphql/graphiql" target="_blank" rel="noopener noreferrer">GraphiQL</a>. Cela facilite grandement la création de requêtes GraphQL. <a href="http://localhost:8000/___graphql" target="_blank" rel="noopener noreferrer">Jetez-y un œil</a> et tentez de créer quelques requêtes.</p>
<h3 id="vue-article">Vue article</h3>
<p>Notre site commence à ressembler à un blog. C’est une bonne nouvelle ! Cependant, une partie importante est encore manquante : la page article.</p>
<p>Commençons par créer le template contenant la requête GraphQL et définissant le contenu affiché :</p>
<p><em>Path</em> : <code>src/templates/article.js</code></p>
<pre><code class="language-jsx hljs javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"gatsby-link"</span>;

<span class="hljs-keyword">const</span> ArticleTemplate = <span class="hljs-function">(<span class="hljs-params">{ data }</span>) =&gt;</span> (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{data.strapiArticle.title}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
      by{" "}
      <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">to</span>=<span class="hljs-string">{</span>`/<span class="hljs-attr">authors</span>/${<span class="hljs-attr">data.strapiArticle.author.id</span>}`}&gt;</span>
        {data.strapiArticle.author.username}
      <span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{data.strapiArticle.content}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> ArticleTemplate;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> query = graphql<span class="hljs-string">`
  query ArticleTemplate($id: String!) {
    strapiArticle(id: { eq: $id }) {
      title
      content
      author {
        id
        username
      }
    }
  }
`</span>;</code></pre>
<p>Tout semble prêt, mais en réalité, Gatsby ne sait pas quand ce template devrait être affiché. Chaque article a besoin d’une URL. Nous allons donc informer Gatsby des nouvelles URLs que nous souhaitons, grâce à la <a href="https://www.gatsbyjs.org/docs/creating-and-modifying-pages" target="_blank" rel="noopener noreferrer">fonction <code>createPage</code></a>.</p>
<p>Tout d’abord, nous allons déclarer une nouvelle fonction nommée <code>makeRequest</code> afin d’exécuter la requête GraphQL. Ensuite, nous exportons une fonction nommée <code>createPages</code> dans laquelle nous récupérons la liste d’articles et créons une page pour chacun d’entre eux. Voici le résultat :</p>
<p><em>Path</em> : <code>gatsby-node.js</code></p>
<pre><code class="language-jsx hljs javascript"><span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">`path`</span>);

<span class="hljs-keyword">const</span> makeRequest = <span class="hljs-function">(<span class="hljs-params">graphql, request</span>) =&gt;</span>
  <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
    <span class="hljs-comment">// Query for nodes to use in creating pages.</span>
    resolve(
      graphql(request).then(<span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (result.errors) {
          reject(result.errors);
        }

        <span class="hljs-keyword">return</span> result;
      })
    );
  });

<span class="hljs-comment">// Implement the Gatsby API “createPages”. This is called once the</span>
<span class="hljs-comment">// data layer is bootstrapped to let plugins create pages from data.</span>
exports.createPages = <span class="hljs-function">(<span class="hljs-params">{ boundActionCreators, graphql }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> { createPage } = boundActionCreators;

  <span class="hljs-keyword">const</span> getArticles = makeRequest(
    graphql,
    <span class="hljs-string">`
    {
      allStrapiArticle {
        edges {
          node {
            id
          }
        }
      }
    }
    `</span>
  ).then(<span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
    <span class="hljs-comment">// Create pages for each article.</span>
    result.data.allStrapiArticle.edges.forEach(<span class="hljs-function">(<span class="hljs-params">{ node }</span>) =&gt;</span> {
      createPage({
        <span class="hljs-attr">path</span>: <span class="hljs-string">`/<span class="hljs-subst">${node.id}</span>`</span>,
        <span class="hljs-attr">component</span>: path.resolve(<span class="hljs-string">`src/templates/article.js`</span>),
        <span class="hljs-attr">context</span>: {
          <span class="hljs-attr">id</span>: node.id,
        },
      });
    });
  });

  <span class="hljs-comment">// Query for articles nodes to use in creating pages.</span>
  <span class="hljs-keyword">return</span> getArticles;
};</code></pre>
<p>Redémarrez le serveur Gatsby.</p>
<p>À partir de maintenant, vous devriez pouvoir visiter les pages de chaque article en cliquant sur les liens affichés sur la page d’accueil.</p>
<figure>
<picture title="Exemple de page">
<source type="image/webp" srcset="/thumbnails/768x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.26.46.png_1c8d1898c6.ff9a62fa026c53f827bb0ce58e941386.webp 768w, /thumbnails/1024x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.26.46.png_1c8d1898c6.ff9a62fa026c53f827bb0ce58e941386.webp 1024w" width="1024" height="650" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.26.46.png_1c8d1898c6.ff9a62fa026c53f827bb0ce58e941386.avif 768w, /thumbnails/1024x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.26.46.png_1c8d1898c6.ff9a62fa026c53f827bb0ce58e941386.avif 1024w" width="1024" height="650" sizes="100vw">
<img src="/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.26.46.png_1c8d1898c6.ff9a62fa026c53f827bb0ce58e941386.png" alt="Exemple de page" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="650" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.26.46.png_1c8d1898c6.ff9a62fa026c53f827bb0ce58e941386.png 768w, /thumbnails/1024x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.26.46.png_1c8d1898c6.ff9a62fa026c53f827bb0ce58e941386.png 1024w" sizes="100vw">
</picture>
<figcaption>Exemple de page</figcaption>
</figure>
<h3 id="vue-auteur">Vue auteur</h3>
<p>Les articles sont rédigés par des auteurs. Eux-aussi méritent une page dédiée.</p>
<p>La création de la page auteur est très similaire à celle de la page article. Premièrement, nous créons le template :</p>
<p><em>Path</em> : <code>src/templates/user.js</code></p>
<pre><code class="language-jsx hljs javascript"><span class="hljs-keyword">import</span> React <span class="hljs-keyword">from</span> <span class="hljs-string">"react"</span>;
<span class="hljs-keyword">import</span> Link <span class="hljs-keyword">from</span> <span class="hljs-string">"gatsby-link"</span>;

<span class="hljs-keyword">const</span> UserTemplate = <span class="hljs-function">(<span class="hljs-params">{ data }</span>) =&gt;</span> (
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{data.strapiUser.username}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
      {data.strapiUser.articles.map((article) =&gt; (
        <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">key</span>=<span class="hljs-string">{article.id}</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">Link</span> <span class="hljs-attr">to</span>=<span class="hljs-string">{</span>`/${<span class="hljs-attr">article.id</span>}`}&gt;</span>{article.title}<span class="hljs-tag">&lt;/<span class="hljs-name">Link</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{article.content}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
      ))}
    <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></span>
);

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> UserTemplate;

<span class="hljs-keyword">export</span> <span class="hljs-keyword">const</span> query = graphql<span class="hljs-string">`
  query UserTemplate($id: String!) {
    strapiUser(id: { eq: $id }) {
      id
      username
      articles {
        id
        title
        content
      }
    }
  }
`</span>;</code></pre>
<p>Ensuite, nous mettons à jour le fichier <code>gatsby-node.js</code> pour créer les URLs :</p>
<p><em>Path</em> : <code>gatsby-node.js</code></p>
<pre><code class="language-jsx hljs javascript"><span class="hljs-keyword">const</span> path = <span class="hljs-built_in">require</span>(<span class="hljs-string">`path`</span>);

<span class="hljs-keyword">const</span> makeRequest = <span class="hljs-function">(<span class="hljs-params">graphql, request</span>) =&gt;</span>
  <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
    <span class="hljs-comment">// Query for article nodes to use in creating pages.</span>
    resolve(
      graphql(request).then(<span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
        <span class="hljs-keyword">if</span> (result.errors) {
          reject(result.errors);
        }

        <span class="hljs-keyword">return</span> result;
      })
    );
  });

<span class="hljs-comment">// Implement the Gatsby API “createPages”. This is called once the</span>
<span class="hljs-comment">// data layer is bootstrapped to let plugins create pages from data.</span>
exports.createPages = <span class="hljs-function">(<span class="hljs-params">{ boundActionCreators, graphql }</span>) =&gt;</span> {
  <span class="hljs-keyword">const</span> { createPage } = boundActionCreators;

  <span class="hljs-keyword">const</span> getArticles = makeRequest(
    graphql,
    <span class="hljs-string">`
    {
      allStrapiArticle {
        edges {
          node {
            id
          }
        }
      }
    }
    `</span>
  ).then(<span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
    <span class="hljs-comment">// Create pages for each article.</span>
    result.data.allStrapiArticle.edges.forEach(<span class="hljs-function">(<span class="hljs-params">{ node }</span>) =&gt;</span> {
      createPage({
        <span class="hljs-attr">path</span>: <span class="hljs-string">`/<span class="hljs-subst">${node.id}</span>`</span>,
        <span class="hljs-attr">component</span>: path.resolve(<span class="hljs-string">`src/templates/article.js`</span>),
        <span class="hljs-attr">context</span>: {
          <span class="hljs-attr">id</span>: node.id,
        },
      });
    });
  });

  <span class="hljs-keyword">const</span> getAuthors = makeRequest(
    graphql,
    <span class="hljs-string">`
    {
      allStrapiUser {
        edges {
          node {
            id
          }
        }
      }
    }
    `</span>
  ).then(<span class="hljs-function">(<span class="hljs-params">result</span>) =&gt;</span> {
    <span class="hljs-comment">// Create pages for each user.</span>
    result.data.allStrapiUser.edges.forEach(<span class="hljs-function">(<span class="hljs-params">{ node }</span>) =&gt;</span> {
      createPage({
        <span class="hljs-attr">path</span>: <span class="hljs-string">`/authors/<span class="hljs-subst">${node.id}</span>`</span>,
        <span class="hljs-attr">component</span>: path.resolve(<span class="hljs-string">`src/templates/user.js`</span>),
        <span class="hljs-attr">context</span>: {
          <span class="hljs-attr">id</span>: node.id,
        },
      });
    });
  });

  <span class="hljs-comment">// Queries for articles and authors nodes to use in creating pages.</span>
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.all([getArticles, getAuthors]);
};</code></pre>
<p>Dernière étape : redémarrez le serveur et visitez la page auteur depuis la page d’un article.</p>
<figure>
<picture title="Exemple de page">
<source type="image/webp" srcset="/thumbnails/768x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.27.47.png_dc95b95310.73aa55ddf6a7e1c9305a1d02d69aa61c.webp 768w, /thumbnails/1024x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.27.47.png_dc95b95310.73aa55ddf6a7e1c9305a1d02d69aa61c.webp 1024w" width="1024" height="650" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.27.47.png_dc95b95310.73aa55ddf6a7e1c9305a1d02d69aa61c.avif 768w, /thumbnails/1024x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.27.47.png_dc95b95310.73aa55ddf6a7e1c9305a1d02d69aa61c.avif 1024w" width="1024" height="650" sizes="100vw">
<img src="/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.27.47.png_dc95b95310.73aa55ddf6a7e1c9305a1d02d69aa61c.png" alt="Exemple de page" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="650" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAAsTAAALEwEAmpwYAAAJhUlEQVR4nOVaW5bbOg4sgJTkzsZmBbOE2f9fko4lYD74Al+23G13cm94DpuSSPFRhQJIuem///mfMjssfsG2XHDZvuFt+4Zvlzdctjds64Zl2eC9h3MezA7EBCICEerU3gNQrSu1qgRUFQqFxoaqdT1A4U0q3ROZ501ddZ2HpcGzydQHdV3S/l5NlaZ1iUDkwHHs2Pcd1+s73n+94+f7D3z/+QM/3r/j5/t3vF9/4rpfIXLAExGYCMwEdiG7WLInsOdcOmYwE4i5JqRjxszbINyRkaqVIimjRVP5S7asn7fToI4Z23bMyBkuqqSpyBc1IQQQKZQYDAIrg4XAu8HYEfgIHCgRvPMExwTnGc4z/MLwK8OvruTFYfEOzrlISiAEVFvewHAyIdmC7L2myd/qAAXwDBwNFIMwJ5R3Gl00dnOrrk6jqsrMskKKylUVIoRDADoUYIGSwwEHLw5eGF4Y7mA4YYgSIATvlwC09w7L6rBsfV4Xj2Xx8M7DZZfFletIE6qMvHFDwT2hEGGum1X2oGT0w4jUkYRiILmuNvvarfUwz8RTVtlO0xqXQUABiS7rEMJ+AHRVwB0QcjjUYVeHRR12CfdKDnQAflkDIcvisF481s1juXisbx7bZcG2LdjWBcuywDlvFML1wshaf3GqqprBT5ZjlVHc1gwIC3gokxKI2jrq7yuCTL8DSdRxhAZ1rQ8sBpbWWtYsEBEcwvAHwV0B+iUAHxC6YofHqh6HehxwADvwAXi/OXgXVbBFIt48trcF29uCiyHE+xDUb7usFAu0+FJbSpTzTCU3iLCEFDcVwW3bot90jGJI68WqFQ02D3VlPemaEIXEoL7vkRAvUSULDngc8NgjKeAjKGRdHZzzwTVdokoukYxvCy6XFdu6ZkKc4+iuLCFhckm6AWgjXVWoJBkH8EW0qGSgDgt4Bj6BFgmxscOqhVrQB4h2G4AbJJB5oQ9Dce1GKokQVcFxOOw7w10JtES3hQW7LtjVB5cFByUfFbLG+LG5oJDNY0su623B5S24rTUrhEHMkYxe9tklQaGSlKAQUYgAKoacSFirkBEZFvgM/uje4tTCPSHBKqcCv90sVMUoOFnvkLa9UilEaY9EhHw9PHZ1UDjQrjGGtAE9KeXNY60IcWBXCMlTi9vWOKd8rkjABzIUehRSREtp9/EWoGnJxVURl7pMUNNRGw3qWDIjwsapfhPRqq0L9CphfccBtxPIA8qCA1dcxeN6OCwx7+Ig6kCs8G5heM/wi8t5WX1WTM6LrwghQ4hFM+2kAiGFDJHopkQhR/KxNsa0IE1KowoiAGyUQYWomoLCaEVGfmbK3JSy6srY5Xnbb0UGFKocFHIQyBGUFQcO7OKx7A7L1cP/ipjvDocwQAzvvCtnkHgOcUtNkF8c/MpwPgT0FNQ7l5XVYd1Un5kVogoWhSj1hNh1VrEDJYA34BPVuXMsNFFGo5DKfSUDgHWN7Wah3dgkgxSIBDLACknnjz3gGTAOmDsfziMKBx9OjFxll7LvM7MDE4OJKt862lmxIeEQAYmCRCEcS1GQaokjAKD1DqqAbYjIYKX7nqjWgmsyxjFiVJYdHFVjVETUjMRdJIXzmgBAOPy5I+IY8e1wV4EnFz6HlE8ntpE94gdl5M8neadlJxOtI+6ghBUQBUSAg0GkIBIQAUIKUKjmqBKKC6os2iihAz6DlnGvzya2n3JrSpqQUr7VFVKoHm/kuoxBkmokQ6EjLA3GCX9xDM+EwGTMPMl1HecPjHliqgCi+6H4sVAIBAHAACQoCQSKfwkA2zNKaFBZHTVA2HhRK6LdCrckGBdm3GBLRurbkpEJyZuGuoMyFlXnLcSVit7GNWMvgIcZNO1aciOi+ppq4pJ/TbNSKCjGA1ECQyFgcCRFVcAc6VA1u5MaLANlQZRsWSx/qo4KuNJtRZStr8YomZps23abAChUAykCCUanAxwzCT3pvrUGa2nTzHUneYEKKAGQqAkOZSaAQxsyIFM6UJEN7KZPy0HOPRmZpEYhZP40nsaMkQyC6nEGa89GaAg3s0U2MU5kzHEELNnhmc9LrBpOknm5WJCdSHyZAYiGpwPph0WVekRXNR27Z8Sg0CPcu61GJvnpbYWQ9YlWMQkLW9YIhPVRIXsCZ0UOYBTSr75HhUxV67uhBCXtgnII5AW33MVNhdRjDnlIfUwW1+JQFt0vb6aQdq1pLqAydNttXoKWiY9JqVfTKKQ0mKmkJ615Hmdj2wWAQwVVq/qkQkz/nQqm875hZBgrZDhw203Vv6Ksd7IWMy+rjNSHtxZlO7kntbOps9Q0/EAh0z5At6oxf5my6lpiagWMFTLzFcORqBjI6GNp3bZXRuLA15M1s8SMlFpRD6eJQu72dKeB3RBYcEoDZDea+juzy3rI+lLXFHZaw99cqme9p/H9Cy2D1DFa2j0812agqBC6Y1Llpfv1ah2HUaNpUm9XS/1MIR80vXpmHY59nAMQ/snhgW6bMl2fBbS8kbJagM6+T3Vpk/HizQbAOKvR9VAheIiRpIx0bctz66KikBGD/Qt1ma7v+cxhR3GRoauHO+jm0rrd2jWVcW9dfyaGPDT7Gx7IjxqMrnt1fMC3ttmi9oE0M5DUZa8IDK6tD7alyQ/NKZqYiSFn8E3XXQxpG5Zn4/LB2VYLLV08qpDc4ZCMQsoZpbwmZtxLM1f2YAz54OCj3Dr4z/R/y1hOKuXZjHw6hrw0DdzAs83gtvu6rZSvVMa99DWE4HXqqHoakWIuZkr5kxj5AkKoFC9Sx3RkM3R6MI4dj268X5emhNw/pdvyfHqVOm6OM4wdn9tNfW5u88Gm5xD7rP8Nui7nA9vvO+OT6atSN9aN2FE1etl86CbGKQ3PIa9QR9/la9lpx+vdV8rPn0f7LWv2Tev0OWQ8yLg8+555cnbIT6UZIen6T4kZbXrZOeTMJ5XnDt26gjtjvYiRM+q49e5Ld1ljddgfOr9u7HuKGbX5HTp6CSHlq6clQE39U0ebzOH8/Znrr0pfrBDgnmt5VfoIIebpK6Y0TE8lxP4eMK5/6mjnWw6a/kmqsOnBg+HHUujq9yhjlj5GwnMmfepgOHvxDDH3lBHa3O3mZPp4R7M5fJ3bvI/ny79l9SfTV4/4Fekjn4zOvfNlX3s/m77id5s/Ib2MkL8DwOev8R+hkL+D3JCeTsjfBF5Jz1vzP0IhKf0NZJ/+r5N76W8A63567EPiKJ1SSPvl0pb3zh+vSP9m8j/tsv7N4PyO9H+RE3f8oDa4hwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.27.47.png_dc95b95310.73aa55ddf6a7e1c9305a1d02d69aa61c.png 768w, /thumbnails/1024x/d2zv2ciw0ln4h1.cloudfront.net/uploads/screen-shot-2018-01-17-at-21.27.47.png_dc95b95310.73aa55ddf6a7e1c9305a1d02d69aa61c.png 1024w" sizes="100vw">
</picture>
<figcaption>Exemple de page</figcaption>
</figure>
<h2 id="conclusion">Conclusion</h2>
<p>Félicitations ! Vous avez créé un blog super rapide et facile à maintenir !</p>
<p>Étant donné que le contenu est géré dans Strapi, les auteurs peuvent écrire leurs articles depuis une vraie interface et vous, en tant que développeur, n’avez qu’à recompiler le site pour mettre à jour le contenu.</p>
<h3 id="que-faire-ensuite">Que faire ensuite ?</h3>
<p>N’hésitez pas à continuer le projet pour découvrir plus en profondeur les avantages de Gatsby et de Strapi. Voici une liste de fonctionnalités que vous pourriez ajouter à votre projet : liste des auteurs, catégories d’articles, système de commentaire avec l’API Strapi ou Disqus, etc. Vous pouvez aussi créer tout type de site (boutique e-commerce, site vitrine, etc.).</p>
<p>Lorsque votre projet sera terminé, vous voudrez probablement le déployer. Le site statique généré par Gatsby peut <a href="https://www.gatsbyjs.org/docs/deploy-gatsby/" target="_blank" rel="noopener noreferrer">facilement être publiée sur des services de stockage</a> tels que Netlify, S3/Cloudfront, GitHub pages, GitLab, Heroku, etc. L’API Strapi n’est rien d’autre qu’une application Node.js. Elle peut donc être mise en ligne sur Heroku ou sur n’importe quelle instance Linux ayant Node.js installé dessus.</p>
<p>Le <a href="https://github.com/strapi/strapi-examples/tree/master/gatsby-strapi-tutorial" target="_blank" rel="noopener noreferrer">code source de ce tutoriel est disponible sur GitHub</a>. Pour le tester, clonez-le repository et suivez les instructions présentes dans le Readme.</p>
<p>Nous espérons que vous avez apprécié ce tutoriel. N’hésitez pas à le commenter, le partager, et indiquer quelle est votre manière favorite de créer des sites avec React et d’en gérer le contenu.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2018/04/06/comparatif-hugo-jekyll/</id>
    <title>Hugo ou Jekyll ? Six critères de comparaison</title>
    <published>2018-04-06T18:10:03+00:00</published>
    <link href="https://jamstatic.fr/2018/04/06/comparatif-hugo-jekyll/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p><a href="https://res.cloudinary.com/jamstatic/image/upload/c_scale,dpr_2.0,f_auto,q_auto,w_862/v1603620676/jamstatic/hugo-jekyll-compared.png" target="_blank" rel="noopener noreferrer"></a></p>
<p>Choisir les bons outils pour bâtir un site web n'est pas chose aisée de nos jours. Il y a tellement d’options ! Générer un site statique est une de ces options, qui comporte son lot d’avantages comme une sécurité de premier plan, une performance de feu et une réduction des coûts d’hébergement.</p>
<p>Quand il est question de générer des sites statiques, les deux solutions qui dominent actuellement le marché sont Jekyll et Hugo. La vraie question est de savoir quel est celui qui est le mieux pour vous ?</p>
<p>Pour répondre à cette question, nous allons examiner ensemble les fonctionnalités, la rapidité et l’extensibilité de chacun d’entre eux, peser les avantages et les inconvénients de ces deux générateurs. Après avoir lu cet article, vous saurez clairement lequel des deux est le bon pour démarrer votre projet.</p>
<p><strong>Version courte :</strong> Jekyll est un générateur de site statique flexible et parfait pour débuter. Hugo a une courbe d’apprentissage un peu plus élevée, mais il est très rapide et intègre plein de fonctionnalités. Lisez la suite pour en apprendre plus sur les différences entre ces deux outils._</p>
<h2 id="jekyll">Jekyll</h2>
<p>Créé par Tom Preston-Werner, le fondateur de GitHub, Jekyll est à l’origine de la <a href="https://frank.taillandier.me/2016/03/08/les-gestionnaires-de-contenu-statique/" target="_blank" rel="noopener noreferrer">mouvance des sites statiques</a> à laquelle nous assistons actuellement.</p>
<p>Commencé en 2008, Jekyll est présenté comme un "générateur de site statique simple, prêt-à-bloguer".</p>
<p>C’est le GSS (générateur de site statique) le plus populaire à l’heure actuelle avec plus de 33 000 étoiles sur GitHub ce qui est largement dû à son intégration dans <a href="https://pages.github.com/" target="_blank" rel="noopener noreferrer">GitHub Pages</a>.</p>
<p>La valeur ajoutée de Jekyll c'est qu'il vous permet de prendre le HTML statique de n'importe quel site web et de le transformer rapidement en site statique fonctionnel grâce à <a href="https://shopify.github.io/liquid/" target="_blank" rel="noopener noreferrer">Liquid</a>, un langage simple utilisé pour définir les gabarits de page.</p>
<h3 id="installation">Installation</h3>
<p>Installer Jekyll n'est pas une mince affaire, surtout sous Windows.</p>
<p>Jekyll est développé en <strong>Ruby</strong> et demande donc d’avoir une version récente de Ruby installé sur votre machine.</p>
<p>Ce n'est pas si terrible que ça, mais ce n'est pas aussi simple que de télécharger une application. Heureusement le procédé d’installation de Jekyll est <a href="https://jekyllrb.com/docs/installation/" target="_blank" rel="noopener noreferrer">bien documenté</a>.</p>
<h3 id="contenu">Contenu</h3>
<p>Dans Jekyll, tout votre contenu est stocké dans des fichiers texte plutôt que dans une base de données. Vous pouvez donc manipuler votre modèle de contenu simplement en ouvrant des fichiers dans votre éditeur de texte favori.</p>
<p>La forme de contenu la plus simple dans Jekyll est stocké à la racine de votre projet sous forme de fichiers au format <strong>Markdown ou HTML</strong>. Ces fichiers de contenus sont traités pendant l’étape de génération, durant laquelle un fichier HTML correspondant est généré à partir des gabarits de votre thème.</p>
<p>Des champs <a href="https://jekyllrb.com/docs/frontmatter/" target="_blank" rel="noopener noreferrer">Front Matter</a> peuvent être ajoutés à ces fichiers, ils vous permettent de définir les données qui peuvent être utilisées dans vos gabarits.</p>
<pre><code class="language-md hljs markdown">---
title: Accueil
date: 2017-01-30
<span class="hljs-section">tags: [bonjour, monde]
---</span>
<span class="hljs-section">## Bonjour monde</span>
C’est le contenu de ma page !</code></pre>
<p>Jekyll supporte les contenus chronologiques (comme des articles de blog) qui sont stockés dans le dossier <code>_posts</code> et qui respectent la nomenclature <code>yyyy-mm-dd-titre-de-l-article.md</code>.</p>
<p>Jekyll supporte aussi le chargement de données modelées à partir de fichiers YAML, JSON ou CSV situés dans le répertoire <code>_data</code>. Ces données sont accessibles dans vos gabarits à l’aide de <code>{{ site.data }}</code>.</p>
<h3 id="themes-et-gabarits">Thèmes et gabarits</h3>
<p>Jekyll possède une large communauté et un choix de thèmes gratuits ou payants prêts à l’emploi.</p>
<p>Les thèmes s'installent facilement, soit en les téléchargeant et en les ajoutant à votre projet Jekyll, soit en les installant comme une gem Ruby.</p>
<p>Les thèmes pour Jekyll sont développés à l’aide du <strong>moteur de templating Liquid</strong> de Shopify. Liquid est un moteur de templating sécurisé conçu pour faire tourner du code tiers sur leurs serveurs. Liquid est conçu pour vous aider à faire ce que vous voulez sans qu'il y ait besoin d’ajouter de code Ruby natif.</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">“container”</span>&gt;</span>
{% for post in site.posts %}
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"article"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>{{ post.title }}<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{{ post.content }}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    {% for tag in post.tags %}
        <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>{{ tag }}<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
    {% endfor %}
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
{% endfor %}
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></code></pre>
<p>C’est génial pour les débutants et les développeurs qui veulent créer des modèles fonctionnels, propres et simples.</p>
<p>Toutefois, cela signifie que vous aurez à étendre les possibilités à l’aide d’extensions personnalisées en Liquid via des plugins Jekyll si vous souhaitez ajouter des fonctionnalités supplémentaires.</p>
<p>Pour les développeurs issus du monde des CMS traditionnels comme WordPress, Liquid devrait être facile à prendre en main.</p>
<h3 id="workflow-de-developpement">Workflow de développement</h3>
<p>Développer avec Jekyll, c'est vraiment génial comparé au développement avec des CMS traditionnels propulsés par une base de données.</p>
<p>Jekyll intègre un <strong>serveur de développement</strong>, qu'on peut lancer avec la commande <code>bundle exec jekyll serve</code>.</p>
<p>Vous pouvez ainsi accéder à votre site statique généré sur une adresse IP locale et voir les changements apportés à votre contenu et à vos modèles.</p>
<h4 id="gestion-des-assets">Gestion des assets</h4>
<p>Jekyll fournit aussi une gestion intégrée des assets très basique, elle compile les fichiers Sass et CoffeeScript.</p>
<p>Tous les fichiers <code>.scss</code>, <code>.sass</code> ou <code>.coffee</code> qui possèdent des délimiteurs Front Matter seront traités par Jekyll et transformés en fichiers <code>.css</code> et <code>.js</code>.</p>
<pre><code class="language-js hljs javascript">---
---
alert <span class="hljs-string">"Hello world!"</span></code></pre>
<p>Le fait de devoir ajouter du Front Matter à chaque fichier fait que beaucoup de sites importants qui tournent en production sous Jekyll, optent pour des outils de génération plus modernes comme Gulp ou <a href="https://forestry.io/blog/write-better-javascript-with-webpack/" target="_blank" rel="noopener noreferrer">Webpack</a>.</p>
<p>Ces outils vous donnent plus de contrôle sur vos fichiers CSS, JS, vos images et votre HTML et permettent la minification et l’optimisation. Ces outils vous donnent aussi accès à BrowerSync ou LiveReload, qui facilitent le développement[^livereload].</p>
<h3 id="fonctionnalites-utiles">Fonctionnalités utiles</h3>
<p>Le cœur de Jekyll propose des fonctionnalités minimales[^core] et n'intègre pas une bonne partie des choses qu'on pourrait attendre d’un site web moderne comme:</p>
<ul>
<li>la gestion des menus,</li>
<li>la génération de sitemap XML[^core-plugin],</li>
<li>la génération d’un flux RSS/Atom[^core-plugin],</li>
<li>la gestion des scripts Analytics,</li>
<li>la gestion des commentaires,</li>
<li>la gestion multilingue/i18n,</li>
<li>et bien plus…</li>
</ul>
<p>Pour cela il faudra utiliser des plugins Jekyll tiers, qui sont de cinq types :</p>
<ul>
<li>les <strong>générateurs</strong>, qui permettent de compléter et de modifier le processus de génération de Jekyll,</li>
<li>les <strong>convertisseurs</strong>, qui permettent d’ajouter le support de nouveaux formats de fichiers,</li>
<li>les <strong>commandes</strong>, qui permettent d’étendre les options de la ligne de commande de Jekyll,</li>
<li>les <strong>tags</strong>, qui permettent d’ajouter de nouvelles balises Liquid,</li>
<li>les <strong>filtres</strong>, qui permettent de modifier le rendu des balises Liquid et des variables.</li>
</ul>
<p>Par exemple, Forestry a dévelopé le <a href="https://github.com/forestryio/jekyll-menus" target="_blank" rel="noopener noreferrer">plugin jekyll-menus</a> qui permet de gérer les menus dans le CMS Forestry.</p>
<p>Une autre fonctionnalité bien pratique de Jekyll est <a href="https://import.jekyllrb.com/docs/wordpress/" target="_blank" rel="noopener noreferrer">l’import de contenus depuis WordPress</a>. Avec 30% de l’Internet enfermé dans WordPress, il est bon de savoir que de migrer vers une stack moderne est aisé.</p>
<h3 id="performance">Performance</h3>
<p>Forestry a déjà publié un <a href="https://forestry.io/blog/hugo-vs-jekyll-benchmark/" target="_blank" rel="noopener noreferrer">comparatif des performances de Jekyll et Hugo</a>.</p>
<p>Les résultats des tests montrent que Jekyll est <em>bien plus lent</em> qu'Hugo, qui s'avère 35 fois plus rapide en moyenne. Pour les sites de taille modeste, la différence n'est pas gênante, mais à force cela peut faire une grande différence.</p>
<p>Jekyll met dans la majorité de ces tests entre 1,4 et 6 secondes. Imaginez que vous ayez une équipe qui fasse une centaine de modifications par semaine sur votre site, votre blog ou votre documentation…</p>
<p>Cela représente potentiellement plus de 10 heures de perdues lors de la génération chaque année !</p>
<h3 id="jekyll-en-resume">Jekyll en résumé</h3>
<p>Maintenant que nous avons passé en revue les fonctionnalités de base de Jekyll, prenons un peu de recul et examinons d’un œil externe ce générateur de site statique en pesant les pour et les contre.</p>
<p><strong>Pour :</strong></p>
<ul>
<li>Un <strong>moteur de gabarits simple.</strong> Les gabarits de page de Jekyll sont très semblables à la syntaxe qu'on trouve dans WordPress ou Craft.</li>
<li>Un <strong>large choix de thèmes.</strong> Il existe plein de thèmes prêt à l’emploi pour Jekyll.</li>
<li>Un <strong>large choix de plugins.</strong> Il existe des dizaines de plugins pour ajouter les fonctionnalités dont vous avez besoin.</li>
<li><strong>Intégration dans GitHub Pages.</strong> Installer un site avec Jekyll et GitHub Pages est un jeu d’enfant.</li>
</ul>
<p><strong>Contre :</strong></p>
<ul>
<li>Une <strong>génération lente.</strong> Si vous développez un petit site, ce n'est pas un problème. Mais les sites plus importants pourraient voir les temps de génération augmenter.</li>
<li>Un <strong>manque de fonctionnalités natives.</strong> Les fonctionnalités de premier ordre sont mieux supportées et intégrées. Ce point fait défaut à Jekyll.[^plugins]</li>
</ul>
<aside class="note note-tip"><p>Vous pouvez consulter <a href="https://forestry.io/docs/guides/developing-with-jekyll/" target="_blank" rel="noopener noreferrer">le guide de Forestry pour développer avec Jekyll</a> pour apprendre à développer un site avec Jekyll et le connecter au CMS Forestry.</p></aside>
<h2 id="hugo">Hugo</h2>
<p>Hugo est le générateur de site statique créé<sup id="fnref1:hugo"><a href="#fn:hugo" class="footnote-ref">1</a></sup> par Steve Francia, un des principaux contributeurs au langage de programmation Go de Google. Hugo est bien entendu développé en Go !</p>
<p>Apparu en 2013, Hugo est rapidement devenu le deuxième GSS le plus populaire derrière Jekyll et compte à ce jour plus de 24 000 étoiles sur GitHub.</p>
<p>Hugo possède un avantage énorme sur tous les autres GSS. Il est <strong>rapide</strong>.</p>
<p>Il possède aussi une des communautés les plus (si ce n'est <em>la</em> plus) actives pour un GSS.</p>
<h3 id="installation-1">Installation</h3>
<p>Installer Hugo est plus simple que d’installer Jekyll, que vous utilisiez Windows ou un système basé sur UNIX.</p>
<p>Vu qu'Hugo est développé un Go – un langage compilé – installer ou mettre à jour Hugo consiste simplement à télécharger un fichier binaire et dire à votre système de l’utiliser.</p>
<p>Hugo propose <a href="https://gohugo.io/getting-started/installing/" target="_blank" rel="noopener noreferrer">une documentation détaillée</a> pour faire cela.</p>
<h3 id="contenu-1">Contenu</h3>
<p>Tout comme Jekyll, tous les contenus de votre projet sont stockés dans des fichiers textes.</p>
<p>Dans Hugo, dans les contenus destinés à être générés sont stockés dans le dossier <code>content</code> de votre projet. Vous pouvez utiliser différents formats : <strong>Markdown, Mark,</strong> et <strong>HTML</strong> sont supportés par défaut, et il existe des extensions tierces pour supporter <strong>Asciidoc</strong> and <strong>reStructuredText</strong><sup id="fnref1:extensions"><a href="#fn:extensions" class="footnote-ref">2</a></sup>.</p>
<p>Hugo supporte aussi <strong>TOML, YAML, et JSON</strong> pour le Front Matter, alors que Jekyll ne supporte que le YAML.</p>
<pre><code class="language-toml hljs ini">+++
<span class="hljs-attr">title</span> = <span class="hljs-string">"Accueil"</span>
<span class="hljs-attr">date</span> = <span class="hljs-string">"2017-01-30"</span>
<span class="hljs-attr">tags</span> = [<span class="hljs-string">"bonjour"</span>, <span class="hljs-string">"monde"</span>]
+++
<span class="hljs-comment">## Bonjour Monde</span>
Ceci est un exemple de Front Matter en TOML</code></pre>
<p>Hugo supporte également les données externes, qui peuvent être stockées dans le répertoire <code>/data</code> de votre projet ou bien récupérées depuis des sources de tierce-partie comme des APIs REST. Les formats supportés pour les sources sont JSON et CSV.</p>
<h3 id="themes-gabarits">Thèmes &amp; gabarits</h3>
<p>Même si Hugo n'a que 4 ans, de nombreux thèmes sont déjà disponibles pour ce GSS en forte croissance.</p>
<p>Si vous utilisez la ligne de commande, <a href="https://gohugo.io/themes/installing-and-using-themes/" target="_blank" rel="noopener noreferrer">installer des thèmes depuis le dépôt des thèmes d’Hugo</a> est assez simple.</p>
<p>Hugo utilise le <a href="https://golang.org/pkg/html/template/" target="_blank" rel="noopener noreferrer">package de template</a> de Go par défaut. Tout comme avec Liquid, il est possible d’ajouter un peu logique dans vos gabarits.</p>
<pre><code class="language-go-html-template hljs go">&lt;div class=“container”&gt;
{{ <span class="hljs-keyword">range</span> .Site.Pages}
    &lt;div class=<span class="hljs-string">"article"</span>&gt;
    &lt;h2&gt;{{ .Title }}&lt;/h2&gt;
    &lt;p&gt;{{ .Content }}&lt;/p&gt;
    {{ <span class="hljs-keyword">range</span> .Tags }}
        &lt;span&gt;{{ . }}&lt;/span&gt;
    {{ end }}
    &lt;/div&gt;
{{ end }}
&lt;/div&gt;</code></pre>
<p>Une fois de plus, c'est très bien pour les débutants mais vous allez devoir étendre les possibilités du moteur de template à l’aide de <em>shortcodes</em> pour ajouter des fonctionnalités supplémentaires.</p>
<p>Malheureusement la syntaxe du package de template de Go n'est pas aussi évidente pour les débutants que celle de Liquid et ne semblera pas aussi familière à première vue.</p>
<p>Toutefois, Hugo propose aussi le support des deux moteurs de template <a href="https://github.com/eknkc/amber" target="_blank" rel="noopener noreferrer">Amber</a> et <a href="https://github.com/yosssi/ace" target="_blank" rel="noopener noreferrer">Ace</a>. Ces deux langages pourront sembler plus familiers aux développeurs issus des CMS traditionnels comme WordPress.</p>
<h3 id="workflow-de-developpement-1">Workflow de développement</h3>
<p>Il est plus agréable de développer avec Hugo qu'avec Jekyll, car la génération est quasi-instantanée et le serveur LiveReload est actif par défaut.</p>
<p>Dans le dossier de votre projet, lancez la commande <code>hugo serve</code> pour lancer le serveur de développement.</p>
<p>Cela vous permet d’accéder à votre site sur une adresse IP locale. Chaque changement effectué dans votre projet, déclenche une génération et recharge automatiquement le site dans votre navigateur.</p>
<h4 id="gestion-des-assets-1">Gestion des assets</h4>
<p>Jusqu'à la version 0.43, Hugo ne procèdait à aucune transformation de vos assets (CSS, JS, SVG, etc.), il se contentait de recopier tous les fichiers qui se trouvent dans le répertoire <code>/static</code> de votre projet. Maintenant vous n'avez plus besoin d'externaliser votre processus de génération à Webpack ou Gulp pour <a href="https://gohugo.io/news/0.43-relnotes/" target="_blank" rel="noopener noreferrer">compiler vos fichiers Sass ou minifier vos CSS et votre JS</a>.</p>
<h3 id="fonctionnalites-utiles-1">Fonctionnalités utiles</h3>
<p>Hugo brille par la multitude de fonctionnalités puissantes qu'il intègre par défaut comparativement à Jekyll et à d’autres GSS.</p>
<p>Avec le support par défaut des menus, des flux ou des sitemaps, la configuration d’un site web pour la production est un jeu d’enfant.</p>
<p>Mais Hugo brille encore plus quand vous travaillez sur un site avec beaucoup de contenus, comme un journal, un site gouvernemental ou un site de documentation.</p>
<p>Par exemple avec la fonctionnalité d’exports personnalisés, vous pouvez générer en même temps : votre site statique, sa version alternative pour Google AMP, ainsi que des fichiers JSON prêts à être consommés par une application mobile.</p>
<p>Parmi les fonctionnalités bien pratiques d’Hugo on peut citer :</p>
<ul>
<li>La gestion des menus,</li>
<li>La génération de Sitemap XML,</li>
<li>La génération de flux RSS/Atom,</li>
<li>L'intégration d’Analytics (via Google Analytics)</li>
<li>L'intégration de commentaires (via Disqus)</li>
<li>La gestion du multilingue/i18n</li>
<li>Les formats d’export personnalisés</li>
</ul>
<aside class="note note-tip"><p>Envie de passer à Hugo, mais encore sous Jekyll ?
<a href="https://gohugo.io/commands/hugo_import_jekyll/" target="_blank" rel="noopener noreferrer">Hugo peut importer votre site Jekyll en ligne de commande !</a></p></aside>
<h3 id="performance-1">Performance</h3>
<p>Hugo est extrêmement rapide. Forestry a publié un article sur la <a href="https://forestry.io/blog/hugo-vs-jekyll-benchmark/" target="_blank" rel="noopener noreferrer">performance de Hugo et de Jekyll</a> et a comparé les deux. Hugo est sorti vainqueur haut la main.</p>
<p>Rendez-vous compte, lors de ces tests Hugo a généré les sites en moyenne 35 fois plus vite que Jekyll, la génération de la plupart de ces sites a pris moins d’une seconde.</p>
<p>Lors d’un test, @darinpope un utilisateur d’Hugo a réussi à <a href="https://discourse.gohugo.io/t/page-generation-performance-expectations/1335/12" target="_blank" rel="noopener noreferrer">générer 600 000 pages en moins de 5 minutes</a> !.</p>
<h3 id="hugo-en-resume">Hugo en résumé</h3>
<p>Maintenant que nous avons passé en revue les fonctionnalités natives d’Hugo, prenons un peu de recul et jetons un regard externe sur ce générateur de site statique en pesant le pour et le contre.</p>
<p><strong>Pour :</strong></p>
<ul>
<li><strong>Extrêmement rapide.</strong> Des temps de génération de l’ordre de la seconde.</li>
<li><strong>Extrêmement versatile.</strong> Plein de fonctionnalités par défaut pour des sites web d’entreprises.</li>
<li><strong>Paré pour l’entreprise</strong> Avec le support des exports multiples et des sites multilingues, vous êtes opérationnel !</li>
<li><strong>Une communauté florissante.</strong> Il est facile d’avoir de l’aide. Posez une question sur le forum et vous <em>aurez</em> une réponse.</li>
</ul>
<p><strong>Contre :</strong></p>
<ul>
<li><strong>Pas d’extensions.</strong> Hugo ne prend pas les plugins en charge, il n'est donc pas possible d’ajouter des fonctionnalités personnalisées.</li>
<li><strong>Une syntaxe de gabarit compliquée.</strong> Bien que le moteur de gabarits d’Hugo soit versatile, il est assez peu intuitif et compliqué pour les débutants.</li>
</ul>
<aside class="note note-tip"><p>Reportez-vous au <a href="https://forestry.io/docs/guides/developing-with-hugo/" target="_blank" rel="noopener noreferrer">guide de Forestry pour développer avec Hugo</a> pour apprendre comment développer un site avec Hugo et le connecter au CMS Forestry.</p></aside>
<h2 id="en-resume">En résumé</h2>
<p>Nous avons passé en revue les fonctionnalités de base de Jekyll et Hugo, en soulignant la facilité d’installation, la gestion de contenu, les langages de gabarits de page, les workflows de développement, les fonctionnalités offertes et la performance.</p>
<p>Ces deux générateurs sont les leaders dans leur domaine, et il y a plein d’exemples de gros projets qui les utilisent comme <a href="https://github.com/springmeyer/healthcare.gov" target="_blank" rel="noopener noreferrer">healthcare.gov</a>, développé avec Jekyll, et le nouveau site de <a href="https://smashingmagazine.com/" target="_blank" rel="noopener noreferrer">Smashing Magazine</a> développé avec Hugo.</p>
<p>Maintenant c'est l’heure de faire votre choix ! Voici un petit récapitulatif pour vous aider :</p>
<ul>
<li><strong>Jekyll</strong> est un excellent choix, si vous êtes familier avec l’écosystème de Ruby ou si vous êtes débutant, grâce à son moteur de templating très simple et à ses nombreux plugins.</li>
<li><strong>Hugo</strong> est génial pour les sites web avec beaucoup de contenus. Il comble son manque d’extensibilité par un lot de fonctionnalités embarquées et une vitesse inégalée par aucun autre générateur de site statique.</li>
</ul>
<div class="footnotes">
<hr>
<ol>
<li id="fn:hugo">
<p>Hugo a été depuis principalement développé par <a href="/2017/10/03/interview-hugo-lead-developer/">Bjørn Erik Pedersen</a>.&#160;<a href="#fnref1:hugo" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
<li id="fn:extensions">
<p>NdT: à l’heure actuelle comme ces extensions ne reposent pas sur des librairies natives en Go, vous perdrez donc le gain de performance apporté par Hugo.&#160;<a href="#fnref1:extensions" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
</ol>
</div>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2018/04/04/contenus-relatifs-dans-hugo/</id>
    <title>Entretenir de bonnes relations avec Hugo</title>
    <published>2018-04-04T20:25:16+00:00</published>
    <link href="https://jamstatic.fr/2018/04/04/contenus-relatifs-dans-hugo/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Même s'il est le plus rapide des générateurs de site statiques,
Hugo continue de s'améliorer et de proposer de nouvelles fonctionnalités pour
nous simplifier la vie. <a href="https://regisphilibert.com/" target="_blank" rel="noopener noreferrer">Régis Philibert</a> a testé
pour vous la gestion des contenus relatifs apparus dans la version <code>0.27</code>.</p></aside>
<p>Je me suis enfin décidé à améliorer la façon dont je gère les relations entre
les contenus dans mes projets en utilisant la fonctionnalité dédiée aux contenus
relatifs proposée par Hugo.[^1]</p>
<p><strong>En faisant cela, j'ai diminué le temps de génération du site d’environ 70%</strong>
⏱️ 👀!</p>
<p>Dans cet article, nous allons voir comme l’implémentation de relations entre vos
contenus est facile à ajouter sur un projet existant et comment cela va changer
à jamais la façon dont nous définissons les relations dans Hugo !</p>
<!--more-->
<h2 id="le-projet">Le Projet</h2>
<p>J'ai créé et commencé à maintenir un site web open source en français sur la
saga des <a href="https://rougon-macquart.com" target="_blank" rel="noopener noreferrer">Rougon-Macquart</a>
d’<a href="https://fr.wikipedia.org/wiki/%C3%89mile_Zola" target="_blank" rel="noopener noreferrer">Émile Zola</a> bien avant que je
commence à coder.</p>
<p>Copier-coller la biographie de tous mes personnages dans WordPress m'a pris pas
mal de temps, mais je me retrouve maintenant avec le projet idéal pour tester de
nouveaux outils : l’API Rest de WordPress, AngularJS et plus récemment Hugo !</p>
<p>Avec un millier d’entrées qui partagent des relations saines, c'est le projet
parfait pour tester une nouvelle manière de gérer nos relations.</p>
<ul>
<li>Chacun des quelque 1300 personnages apparaît dans quelques romans. La liste des romans où il apparaît est affichée sur la page de chaque personnage.</li>
<li>Dans chacun des 20 romans apparaissent de nombreux personnages. Sur la page de chaque roman figure tous les personnages qui y apparaissent.</li>
</ul>
<h2 id="statut-des-relations-avant-related-content-c-est-complique">Statut des relations avant <em>Related Content</em>: c'est compliqué</h2>
<p>Il n'y avait pas de méthode claire pour connecter des pages entre elles et créer
des relations durables et efficaces. La première chose qui venait souvent à
l’esprit était d’utiliser les taxonomies, mais ça ne marchait pas lorsqu'il
s'agissait de connecter des pages entre elles.</p>
<p>Une fois les taxonomies écartées, si vous deviez gérer des relations
<a href="https://fr.wikipedia.org/wiki/Relation_de_plusieurs_%C3%A0_un" target="_blank" rel="noopener noreferrer">de plusieurs à un</a>,
vous avez peut-être utilisé les <code>sections</code> avec la plus grande prudence.</p>
<p>Mais quand il s'agit d’implémenter la plus commune des
<a href="&lt;https://en.wikipedia.org/wiki/Many-to-many_(data_model)&gt;">relations de plusieurs à plusieurs</a>,
je trouve que la solution la plus sensée est de créer une relation via une
entrée Front Matter dans les pages concernées. Pour les Rougon-Macquart de Zola,
c'était indéniablement le cas.</p>
<h3 id="l-implementation-dans-le-front-matter">L'implementation dans le Front Matter</h3>
<p>Dans ce projet, les romans peuvent compter jusqu'à 90 personnages. Ce qui
signifie qui si nous devions lister tous les personnages présents dans un roman,
nous nous retrouverions avec un tableau de 90 entrées en entête de notre fichier
Markdown. C’est vraiment loin d’être idéal.</p>
<p>De plus nous n'avons pas vraiment besoin de référencer la connexion de notre
relation à la fois dans les pages de romans <em>et</em> dans les pages des personnages.
Les personnages ne sont présents que dans 4 à 5 romans tout au plus, il vaut
donc mieux déclarer les <strong>quelques</strong> romans dans lesquels ils apparaissent
plutôt que de lister les <strong>nombreux</strong> personnages pour chaque roman.</p>
<p>Par exemple pour le personnage d’<em>Eugène Rougon</em>, qui figure dans 4 romans, cela
donne :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">title:</span> <span class="hljs-string">Rougon</span> <span class="hljs-string">(Eugène)</span>
<span class="hljs-attr">novel:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">argent</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">curee</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">fortune</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">excellence</span></code></pre>
<p>Maintenant dans le Front Matter du roman, nous avons juste à ajouter une clef
d’identifiant. Pour le roman « Son Excellence Eugène Rougon » dans lequel
apparaît ce bon vieil Eugène nous ajoutons :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">title:</span> <span class="hljs-string">Son</span> <span class="hljs-string">excellence</span> <span class="hljs-string">Eugène</span> <span class="hljs-string">Rougon</span>
<span class="hljs-attr">id:</span> <span class="hljs-string">excellence</span></code></pre>
<aside class="note"><p>Nous pourrions choisir un identifiant existant comme le nom de
fichier, mais je préfère un identifiant unique, facile à lire et à écrire.</p></aside>
<h4 id="les-relations-dans-nos-gabarits-de-page">Les relations dans nos gabarits de page</h4>
<p>Sur
<a href="https://rougon-macquart.com/personnage/2010-03-15-rougon-eugene/" target="_blank" rel="noopener noreferrer">la page d’Eugène</a>
nous voulons afficher les romans dans lesquels il apparaît. Nous pouvons
utiliser <code>intersect</code> pour construire notre liste :</p>
<pre><code class="language-go-html-template hljs go">{{ $characters := where .Site.Pages.ByTitle <span class="hljs-string">".Params.novel"</span> <span class="hljs-string">"intersect"</span> (slice .Params.id) }}</code></pre>
<p>Pour afficher la liste des personnages du roman sur la page
<a href="https://rougon-macquart.com/roman/1876-son-excellence-eugene-rougon/" target="_blank" rel="noopener noreferrer">Son Excellence Eugène Rougon</a>,
nous utilisons l’opérateur <code>in</code> avec <code>where</code>:</p>
<pre><code class="language-go-html-template hljs go">{{ $novels := where .Site.Pages.ByTitle <span class="hljs-string">".Params.id"</span> <span class="hljs-string">"in"</span> .Params.novel }}</code></pre>
<p>Et voilà, nous avons réussi à implémenter une relation de type plusieurs à
plusieurs comme si nous étions en 2016 !</p>
<p>Car cela a le mérite de fonctionner mais…</p>
<ol>
<li>
<p><code>interesect</code> ? <code>where "in"</code> ? N’en faisons-nous pas un peu trop ?</p>
</li>
<li>
<p>🐌 Le temps de génération est <strong>7 fois</strong> supérieur à la moyenne : ~7 secondes pour 1300 pages.</p>
</li>
<li>
<p>💩 C’est moche.</p>
</li>
</ol>
<p>OK… mais que pouvons-nous y faire ? 🤷‍♂️</p>
<p>Rien… enfin jusqu'à la version 0.27 d’Hugo.</p>
<h2 id="la-fonctionnalite-related-content-d-hugo">La fonctionnalité <em>Related Content</em> d’Hugo</h2>
<p><a href="https://gohugo.io/content-management/related/" target="_blank" rel="noopener noreferrer">Les contenus relatifs natifs</a>
ont fait leur apparition dans Hugo 0.27 en novembre 2017.</p>
<p>Ils ont été conçus pour aider à ajouter facilement une section <strong>« Vous aimerez
aussi : »</strong> dans les thèmes et les projets tout en gardant un maximum de
contrôle sur l’algorithme de pondération. Vous pouvez définir plusieurs facteurs
ou index en leur affectant leur propre niveau d’importance. Les tags, le mois de
publication, les auteurs, tout ce qui peut vous aider à construire une liste de
contenus relatifs pertinente.</p>
<p>C’est de loin de meilleur outil pour récupérer des pages relatives à une autre à
l’aide de votre <em>propre</em> formule et si vous ne l’utilisez pas déjà pour générer
votre widget "Articles/Produits liés", vous devriez aller de ce pas consulter
<a href="https://gohugo.io/content-management/related/" target="_blank" rel="noopener noreferrer">la documentation</a> pour commencer
à jouer avec. C’est top !</p>
<p>Néanmoins dans notre cas, nous n'avons pas besoin d’un module "romans relatifs",
nous avons juste besoin d’établir une relation solide et consistante qui
n’impacte pas le temps de génération du site. Et il se trouve que c'est
justement ce que propose la fonctionnalité <em>Related Content</em> !</p>
<p>Nous n'avons même pas besoin de recourir à l’ingénieux facteur de poids d’index
puisque <code>novel</code> est notre seul et unique index.</p>
<h3 id="etablir-des-relations-avec-related-content">Établir des relations avec <em>Related Content</em></h3>
<h4 id="declarer-notre-index">Déclarer notre index</h4>
<p>En premier nous devons déclarer la liste de nos index dans notre fichier de
configuration <code>config.yaml</code>. Vu qu'ici nous n' avons que <code>novel</code> comme index…</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">related:</span>
 <span class="hljs-attr">indices:</span>
   <span class="hljs-bullet">-</span> <span class="hljs-attr">name:</span> <span class="hljs-string">novel</span> <span class="hljs-comment"># Le nom de l’index, tel qu'il est défini la clef `.Param` du Front Matter.</span>
     <span class="hljs-attr">weight:</span> <span class="hljs-number">1</span> <span class="hljs-comment"># Nous n'avons pas vraiment besoin, mais si nous l’omettons cela désactivera notre index.</span>
     <span class="hljs-attr">includeNewer:</span> <span class="hljs-literal">true</span> <span class="hljs-comment"># Ici notre relation est sans fin ! Cette option empêche Hugo d’ignorer les nouveaux articles.</span></code></pre>
<h4 id="bien-se-connecter">Bien se connecter</h4>
<p>L'entête Front Matter de notre personnage est très bien comme elle est. Elle
liste déjà les romans à l’aide d’une clef qui correspond au nom de notre index
<code>novel</code>.</p>
<p>Par contre, nos romans utilisent <code>id</code> pour s'identifier, il faut changer ça car
ils doivent également utiliser le même nom d’index. Donc l’entête Front Matter
de notre roman devient :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">title:</span> <span class="hljs-string">Son</span> <span class="hljs-string">Excellence</span> <span class="hljs-string">Eugène</span> <span class="hljs-string">Rougon</span>
<span class="hljs-attr">novel:</span> <span class="hljs-string">excellence</span> <span class="hljs-comment"># 'id’ précédemment</span></code></pre>
<p>Bien, nos romans et nos personnages partagent maintenant un <code>.Page.Param</code> commun
qui utilise le nom de notre index nouvellement déclaré : <code>novel</code>.</p>
<h4 id="related-content-dans-les-gabarits-de-page"><em>Related Content</em> dans les gabarits de page</h4>
<p>Dans nos gabarits de page, <em>Related</em> offre différentes fonctions pour récupérer
les pages relatives. Nous allons en voir deux succinctement, mais allez lire
<a href="https://gohugo.io/content-management/related/#list-related-content" target="_blank" rel="noopener noreferrer">la documentation</a>
si vous souhaitez en apprendre davantage.</p>
<p><strong>.Related</strong> <em>permet de récupérer toutes les pages relatives d’une page donnée
en fonction des index et du poids déclarés dans le fichier de configuration.
Elle prend un seul paramètre en argument : la page donnée.</em></p>
<p><strong>.RelatedIndices</strong> <em>permet de récupérer toutes les pages qui comportent un ou
plusieurs index donnés. Le premier paramètre est la page donnée, les autres
paramètres sont les index utilisés.</em></p>
<p>Dans nos gabarits de page de détail, nous allons utiliser la fonction
<code>.RelatedIndices</code> pour récuperer les romans ou les personnages reliés. Ceci afin
de limiter les pages reliées à notre index <code>novel</code> et empêcher que de futures
index comme des tags ou un auteur viennent interférer dans notre relation
existante.</p>
<p>Dans le gabarit de page de détail d’un roman comme "Son Excellence Eugène
Rougon", nous pouvons lister tous ses « characters », en anglais dans le texte,
de la façon suivante :</p>
<pre><code class="language-go-html-template hljs go">{{ $characters := where (.Site.RegularPages.RelatedIndices . <span class="hljs-string">"novel"</span> ) <span class="hljs-string">"Type"</span> <span class="hljs-string">"personnage"</span> }}</code></pre>
<p><em>Le premier paramètre c'est le contexte de notre page, le second c'est notre
fameux index</em>.</p>
<p>Et pour la page de présentation d’un personnage comme Eugène, pour récupérer
toutes ses « novels » :</p>
<pre><code class="language-go-html-template hljs go">{{ $novels := where (.Site.RegularPages.RelatedIndices . <span class="hljs-string">"novel"</span> ) <span class="hljs-string">"Type"</span> <span class="hljs-string">"roman"</span> }}</code></pre>
<p>Et voilà ! Nous utilisons maintenant la fonction <em>Related Content</em> d’Hugo pour
gérer nos relations de type plusieurs à plusieurs !</p>
<p>Et qu'avons-nous gagné outre un code plus propre ?</p>
<p>🚀 <strong>6 secondes de moins !</strong> …sur les ~7s auparavant…</p>
<p>Le temps de génération n'excède maintenant pas les 1.5s. Dans le mille Émile !</p>
<aside class="note note-info"><p>Si vous êtes curieux, vous pouvez cloner le
<a href="https://github.com/regisphilibert/rougon" target="_blank" rel="noopener noreferrer">repo</a> et vous en donner à cœur joie
avec la commande <code>hugo --templateMetrics</code>. Vous pouvez même passer sur la
branche
<a href="https://github.com/regisphilibert/rougon/tree/oldRelationships" target="_blank" rel="noopener noreferrer"><code>oldRelationship</code></a>
et comparer avec l’implémentation précédente des relations.</p></aside>
<h2 id="conclusion">Conclusion</h2>
<p>En ayant simplement recours à la fonction <em>Related Content</em> native dans Hugo
plutôt qu'à un horrible patch fait maison, <strong>nous avons réduit le temps de
génération de plus de 70%</strong> et tout ça avec un <strong>minimum de changement dans
notre code</strong>.</p>
<p>Il y a un énorme avantage à tirer profit des super pouvoirs des nouvelles
fonctionnalités natives d’Hugo et ce modeste article a tenté de vous montrer à
quel point il est simple de commencer à utiliser et à implémenter l’une d’entre
elles dans vos projets existants.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2018/03/19/demarrer-avec-algolia/</id>
    <title>Bien démarrer avec Algolia</title>
    <published>2018-03-19T20:13:46+00:00</published>
    <link href="https://jamstatic.fr/2018/03/19/demarrer-avec-algolia/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Algolia fait tout pour faciliter l’ajout d’une recherche performante sur votre site. Jessica West le prouve une fois de plus en nous décrivant pas-à-pas les étapes nécessaires pour y parvenir, ici en vanilla JS avec InstantSearch.</p></aside>
<p>Salut 👋 ! Ça vous est déjà arrivé de développer entièrement un moteur de recherche ? Avez-vous déjà redouté que votre Product Manager vous dise "tu sais ce qui serait super ? Ce serait d’avoir une barre de recherche sur le site" et là votre première réaction est de soupirer et de lever les yeux au ciel…</p>
<p>Ça m'est arrivé malheureusement beaucoup trop souvent. Pour être franche, j'évitais ce genre de demande comme la peste car même quand j'arrivais à faire fonctionner la recherche, je voyais bien que c'est pas "génial" et de plus arrivée à la moitié de la documentation je me demandais, mais bon sang, <em>où est-ce qu'est censé aller ce module ?</em> Vraiment, c'est pas marrant à faire.</p>
<p>Mais maintenant, nous avons des outils et des services à notre disposition qui rendent tout cela bien plus simple. C’est fini le temps où on développait un moteur de recherche à la mano. Ah, que c'est beau le progrès. Ma vie est un peu plus simple chaque jour qui passe.</p>
<p>C’est une des raisons pour lesquelles j'ai commencé à jouer avec Algolia et que j'ai fini par rejoindre leur équipe. Je n'ai vraiment pas envie que vous lisiez cet article en vous disant "oh non, elle veut nous vendre le produit". Non j'aimerais vraiment partager avec vous ce que j'ai appris pour bien démarrer avec Algolia et comment faire pour se mettre à coder avec. Regardons donc quelles sont les quelques étapes pour vous vous puissiez avoir une recherche fonctionnelle sur votre site.</p>
<h2 id="obtenir-vos-clefs-d-api">Obtenir vos clefs d’API</h2>
<figure>
<picture title="Les différentes clés d’API d’Algolia">
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_2.0-f_auto-q_auto-w_880/v1603620867/jamstatic/algoliaapikeysmarkedup.577d1666421006f4af247aff993cad1f.webp 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_2.0-f_auto-q_auto-w_880/v1603620867/jamstatic/algoliaapikeysmarkedup.577d1666421006f4af247aff993cad1f.webp 1024w" width="1024" height="574" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_2.0-f_auto-q_auto-w_880/v1603620867/jamstatic/algoliaapikeysmarkedup.577d1666421006f4af247aff993cad1f.avif 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_2.0-f_auto-q_auto-w_880/v1603620867/jamstatic/algoliaapikeysmarkedup.577d1666421006f4af247aff993cad1f.avif 1024w" width="1024" height="574" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_2.0-f_auto-q_auto-w_880/v1603620867/jamstatic/algoliaapikeysmarkedup.577d1666421006f4af247aff993cad1f.png" alt="Les différentes clés d’API d’Algolia" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="574" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_2.0-f_auto-q_auto-w_880/v1603620867/jamstatic/algoliaapikeysmarkedup.577d1666421006f4af247aff993cad1f.png 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_2.0-f_auto-q_auto-w_880/v1603620867/jamstatic/algoliaapikeysmarkedup.577d1666421006f4af247aff993cad1f.png 1024w" sizes="100vw">
</picture>
<figcaption>Les différentes clés d’API d’Algolia</figcaption>
</figure>
<p>Commencez par créer un compte chez <a href="https://www.algolia.com/cc/devto" target="_blank" rel="noopener noreferrer">Algolia</a>. Et récupérez ensuite vos identifiants dans votre <a href="https://www.algolia.com/licensing" target="_blank" rel="noopener noreferrer">dashboard</a>. Vous aurez besoin de copier <code>App Id</code>, <code>Search Only API Key</code> et <code>Admin API Key</code>.</p>
<p>Une fois que c'est fait, ajoutez-les dans ce que vous utilisez pour stocker vos variables d’environnement (un fichier <code>.env</code> par exemple) de manière à ce que votre application sache comment se connecter à votre application Algolia et à son index. Et voilà ! Le plus dur est fait !</p>
<h2 id="connecter-votre-source-de-donnees">Connecter votre source de données</h2>
<p>Si vos données déjà sont accessibles en ligne, nous pouvons commencer par la création d’une fonction qui va appeler cette URL et venir alimenter l’index de votre application Algolia. Regardons comment faire ça en JavaScript.</p>
<pre><code class="language-javascript hljs javascript"><span class="hljs-keyword">const</span> data_url = <span class="hljs-string">"https://raw.githubusercontent.com/algolia/datasets/master/movies/actors.json"</span>;

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">indexData</span>(<span class="hljs-params">data_url</span>) </span>{
  <span class="hljs-keyword">return</span> axios
    .get(data_url, {})
    .then(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">response</span>) </span>{
      <span class="hljs-built_in">console</span>.log(response.data[<span class="hljs-number">0</span>]);
      <span class="hljs-keyword">return</span>;
    })
    .catch(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">error</span>) </span>{
      <span class="hljs-built_in">console</span>.warn(error);
    });
}</code></pre>
<p>Pour le moment cette fonction ne fait que récupérer l’url de données que nous lui passons en paramètre et affiche dans la console le premier enregistrement trouvé. Ici nous faisons appel à Axios pour effectuer des appels d’API. Axios est une librairie JavaScript utilisée pour faire des requêtes HTTP avec node.js ou depuis le navigateur et elle retourne une promesse, une API native en JavaScript depuis ECMAScript 6. L'avantage de cette librairie, c'est qu'elle peut transformer automatiquement des données JSON.</p>
<h2 id="preparer-les-donnees-pour-algolia">Préparer les données pour Algolia</h2>
<p>Maintenant que nous avons fait un appel à nos données, commençons à utiliser le compte Algolia que nous venons de créer pour mettre à jour notre index avec nos données ! Nous allons faire ça en deux temps, d’abord nous allons parcourir les données retournées par notre appel <code>axios.get</code> et en faire un tableau d’objets. Cela va nous permettre de n'utiliser que les données que nous voulons dans notre index. Après, une fois que c'est fait nous pouvons envoyer ces données à notre index Algolia.</p>
<p><em>Première étape :</em> Plutôt que de juste retourner une réponse positive, créons une fonction qui va gérer cet envoi des données en lui passant la réponse à notre appel <code>axios.get</code>.</p>
<pre><code class="language-javascript hljs javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">indexData</span>(<span class="hljs-params">data_url</span>) </span>{
  <span class="hljs-keyword">return</span> axios
    .get(data_url, {})
    .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
      <span class="hljs-keyword">return</span> dataToAlgoliaObject(response.data);
    })
    .then(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">response</span>) </span>{
      <span class="hljs-keyword">return</span>;
    })
    .catch(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">error</span>) </span>{
      <span class="hljs-built_in">console</span>.warn(error);
    });
}</code></pre>
<p>Maintenant dans notre fonction, nous allons vouloir parcourir toutes les entrées présentes dans nos données et en faire des objets Algolia, à l’aide d’une boucle qui devrait être assez facile à écrire.</p>
<pre><code class="language-javascript hljs javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">dataToAlgoliaObject</span>(<span class="hljs-params">data_points</span>) </span>{
  <span class="hljs-keyword">var</span> algoliaObjects = [];
  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">var</span> i = <span class="hljs-number">0</span>; i &lt; data_points.length; i++) {
    <span class="hljs-keyword">var</span> data_point = data_points[i];
    <span class="hljs-keyword">var</span> algoliaObject = {
      <span class="hljs-attr">objectID</span>: data_point.objectID,
      <span class="hljs-attr">name</span>: data_point.name,
      <span class="hljs-attr">rating</span>: data_point.rating,
      <span class="hljs-attr">image_path</span>: data_point.image_path,
      <span class="hljs-attr">alternative_name</span>: data_point.alternative_name,
    };
    algoliaObjects.push(algoliaObject);
  }

  <span class="hljs-keyword">return</span> algoliaObjects;
}</code></pre>
<p><em>Deuxième étape :</em> Maintenant que nous avons créé nos objets, ils sont prêts à être envoyés à Algolia !</p>
<p>Changeons quelques trucs dans notre fonction <code>indexData</code>. Nous pouvons chaîner notre appel avec un <code>.then</code> grâce la structure de notre promesse axios et utiliser <code>async</code> et <code>await</code> pour nous assurer que tout se passe bien pendant l’envoi de nos données.</p>
<pre><code class="language-javascript hljs javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">indexData</span>(<span class="hljs-params">data_url</span>) </span>{
  <span class="hljs-keyword">return</span> axios
    .get(data_url, {})
    .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
      <span class="hljs-keyword">return</span> dataToAlgoliaObject(response.data);
    })
    .then(<span class="hljs-keyword">async</span> (response) =&gt; {
      <span class="hljs-keyword">await</span> sendDataToAlgolia(response);
      <span class="hljs-keyword">return</span>;
    })
    .then(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">response</span>) </span>{
      <span class="hljs-keyword">return</span>;
    })
    .catch(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">error</span>) </span>{
      <span class="hljs-built_in">console</span>.warn(error);
    });
}</code></pre>
<h2 id="envoi-des-donnees-a-algolia">Envoi des données à Algolia</h2>
<p>Écrivons maintenant la fonction <code>sendDataToAlgolia</code>. C’est le moment où nous allons avoir besoin des clés que nous avons stockées auparavant dans notre fichier <code>.env</code>. Nous allons également devoir nous assurer que nous avons quelque chose qui initialise notre index et nous permette de lui donner le nom de notre choix pour y stocker nos données. Vu que notre jeu de données contient des acteurs de cinéma, ça semble être un bon nom pour notre index.</p>
<pre><code class="language-javascript hljs javascript"><span class="hljs-keyword">const</span> algoliaClient = algoliasearch(
  process.env.ALGOLIA_APP_ID,
  process.env.ALGOLIA_ADMIN_API_KEY
);
<span class="hljs-keyword">const</span> algoliaIndex = algoliaClient.initIndex(<span class="hljs-string">"movie-actors"</span>);

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">sendDataToAlgolia</span>(<span class="hljs-params">algoliaObjects</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(<span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {
    algoliaIndex.addObjects(algoliaObjects, (err, content) =&gt; {
      <span class="hljs-keyword">if</span> (err) reject(err);
      resolve();
    });
  });
}</code></pre>
<h2 id="configuration-des-parametres">Configuration des paramètres</h2>
<p>Nous avons des données dans notre index ! Maintenant, nous voulons dire à Algolia comment nous voulons que ces données soient utilisées. Nous pouvons faire cela dans l’interface d’administration ou avec du code. Je préfère la deuxième méthode, voyons ensemble comment faire cela. Nous avons <em>beaucoup</em> d’options mais tenons nous en pour le moment aux options de base :</p>
<ul>
<li><em>searchableAttributes</em>: listez ce que vous voulez pouvoir rechercher dans l’objet Algolia que vous avez crée</li>
<li><em>attributesToHighlight</em>: mettre en surbrillance le champ recherché</li>
<li><em>customRanking</em>: choisissez la façon donc vous voulez afficher vos données, <code>desc()</code> ou <code>asc()</code></li>
<li><em>attributesToRetrieve</em>: les attributs à afficher dans les résultats de recherche</li>
</ul>
<pre><code class="language-javascript hljs javascript"><span class="hljs-keyword">async</span> <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">configureAlgoliaIndex</span>(<span class="hljs-params"></span>) </span>{
  algoliaIndex.setSettings({
    <span class="hljs-attr">searchableAttributes</span>: [<span class="hljs-string">"name"</span>],
    <span class="hljs-attr">attributesToHighlight</span>: [<span class="hljs-string">"name"</span>],
    <span class="hljs-attr">customRanking</span>: [<span class="hljs-string">"desc(rating)"</span>],
    <span class="hljs-attr">attributesToRetrieve</span>: [<span class="hljs-string">"name"</span>, <span class="hljs-string">"rating"</span>, <span class="hljs-string">"image_path"</span>],
  });
}</code></pre>
<p>Ajoutons maintenant cette fonction, une fois l’envoi de notre index correctement effectué.</p>
<pre><code class="language-javascript hljs javascript"><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">indexData</span>(<span class="hljs-params">data_url</span>) </span>{
  <span class="hljs-keyword">return</span> axios
    .get(data_url, {})
    .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
      <span class="hljs-keyword">return</span> dataToAlgoliaObject(response.data);
    })
    .then(<span class="hljs-keyword">async</span> (response) =&gt; {
      <span class="hljs-keyword">await</span> sendDataToAlgolia(response);
      <span class="hljs-keyword">return</span>;
    })
    .then(<span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">await</span> configureAlgoliaIndex();
      <span class="hljs-keyword">return</span>;
    })
    .then(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">response</span>) </span>{
      <span class="hljs-keyword">return</span>;
    })
    .catch(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">error</span>) </span>{
      <span class="hljs-built_in">console</span>.warn(error);
    });
}</code></pre>
<p>Waouh, nous avons maintenant ajouté les données à notre index comme nous le souhaitions. Nous en avons donc terminé avec la partie serveur, passons maintenant à la partie où les gens peuvent voir et rechercher dans nos données, si chères à nos yeux.</p>
<h2 id="connecter-le-front-end">Connecter le front-end</h2>
<p>Algolia a ce qu'on appelle des <em>widgets</em>, qui nous permettent d’ajouter rapidement des sections dans notre page HTML sans avoir à écrire beaucoup de code. Des éléments comme une barre de recherche, ou bien l’endroit où nos objets Algolia seront vus dans la page, peuvent être ajoutés à l’aide de quelques lignes de JavaScript. Ouvrons notre fichier pour le côté client.</p>
<p>Nous allons commencer par créer une instance d’<code>instantsearch</code> que nous pourrons utiliser dans notre application. Vous pouvez utiliser des cookies pour passer ces données du serveur au client ou bien vous pouvez utiliser les clefs. Pour faire au plus simple, nous allons utiliser les clefs ici.</p>
<pre><code class="language-javascript hljs javascript">$(<span class="hljs-built_in">document</span>).ready(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">var</span> instantsearch = <span class="hljs-built_in">window</span>.instantsearch;

  <span class="hljs-comment">// création d’une instance d’instantsearch</span>
  <span class="hljs-comment">// avec notre identifiant d’application et notre clef d’API</span>
  <span class="hljs-keyword">var</span> search = instantsearch({
    <span class="hljs-attr">appId</span>: Cookies.get(<span class="hljs-string">"app_id"</span>),
    <span class="hljs-attr">apiKey</span>: Cookies.get(<span class="hljs-string">"search_api_key"</span>),
    <span class="hljs-attr">indexName</span>: Cookies.get(<span class="hljs-string">"index_name"</span>),
    <span class="hljs-attr">urlSync</span>: <span class="hljs-literal">true</span>,
    <span class="hljs-attr">searchParameters</span>: {
      <span class="hljs-attr">hitsPerPage</span>: <span class="hljs-number">3</span>,
    },
  });
});</code></pre>
<p>Connectons maintenant notre <em>input</em> de recherche à notre code HTML pour que les gens aient une barre de recherche.</p>
<pre><code class="language-javascript hljs javascript">search.addWidget(
  instantsearch.widgets.searchBox({
    <span class="hljs-attr">container</span>: <span class="hljs-string">"#search-box"</span>,
    <span class="hljs-attr">placeholder</span>: <span class="hljs-string">"Rechercher vos acteurs préférés"</span>,
  })
);</code></pre>
<p>Maintenant, nous voulons ajouter les résultats provenant de nos données, et retourner ce que nous voulons afficher.</p>
<pre><code class="language-javascript hljs javascript">search.addWidget(
  instantsearch.widgets.hits({
    <span class="hljs-attr">container</span>: <span class="hljs-string">"#hits"</span>,
    <span class="hljs-attr">hitsPerPage</span>: <span class="hljs-number">12</span>,
    <span class="hljs-attr">templates</span>: {
      <span class="hljs-attr">empty</span>: <span class="hljs-string">`&lt;div class="col-md-12" style="text-align: center;"&gt; Nous n'avons pas trouvé de résultats correspondants à votre recherche &lt;em&gt;\"{{query}}\"&lt;/em&gt;&lt;/div`</span>,
      <span class="hljs-attr">item</span>: <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">hit</span>) </span>{
        <span class="hljs-keyword">try</span> {
          <span class="hljs-keyword">return</span> <span class="hljs-string">`
              &lt;div class="col-md-4" style="text-align: center;"&gt;
                &lt;p&gt;
                  &lt;h3 class="hit-text"&gt;<span class="hljs-subst">${hit._highlightResult.name.value}</span>&lt;/h3&gt;
                  &lt;img src="https://image.tmdb.org/t/p/w45/<span class="hljs-subst">${hit.image_path}</span>" height="50" width="50"&gt;
                &lt;/p&gt;
                &lt;p&gt;
                  Rating: ⭐️ <span class="hljs-subst">${hit.rating}</span>
                &lt;/p&gt;
              &lt;/div&gt;
            `</span>;
        } <span class="hljs-keyword">catch</span> (e) {
          <span class="hljs-built_in">console</span>.warn(<span class="hljs-string">"Couldn't render hit"</span>, hit, e);
          <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>;
        }
      },
    },
  })
);</code></pre>
<p>Une bonne expérience de recherche ne devrait pas retourner trop de résultats à la fois, ajoutons donc une pagination aux résultats que nous renvoyons.</p>
<pre><code class="language-javascript hljs javascript">search.addWidget(
  instantsearch.widgets.pagination({
    <span class="hljs-attr">container</span>: <span class="hljs-string">"#pagination"</span>,
  })
);</code></pre>
<p>Et enfin pour terminer… lançons la recherche ! Cela va permettre de tout instancier dans votre page.</p>
<pre><code class="language-javascript hljs javascript">search.start();</code></pre>
<p>Naturellement, si vous voulez vous épargner tout ce travail manuel, vous pouvez allez voir <a href="https://glitch.com/~algolia-quickstart" target="_blank" rel="noopener noreferrer">notre application pour démarrer rapidement sur Glitch</a>, la remixer et vous aurez tout ce code et une application basique qui tourne en moins de 5 minutes.</p>
<p>😉 J'espère que cette lecture vous a plu et vous aura été utile !</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2018/03/17/premier-meetup-jamstack-toulouse/</id>
    <title>Meetup Jamstack Toulouse</title>
    <published>2018-03-17T08:24:53+00:00</published>
    <link href="https://jamstatic.fr/2018/03/17/premier-meetup-jamstack-toulouse/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Ce premier meetup Jamstack français a été l’occasion d’accueillir Netlify et Algolia, deux acteurs incontournables du mouvement. Les deux start-ups sont maintenant devenues des références dans leur domaine, l’une pour le déploiement d’applications web servies en statique, l’autre comme service de recherche embarquée. Au programme deux présentations de Phil Hawksworth et Martyn Davies toutes deux axées sur la <em>developer experience</em> et les bénéfices apportés par ces deux services de qualité.</p></aside>
<figure>
<picture title="Photo : Nicolas Manaud">
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/dpr_2.0-f_auto-q_auto/v1523347003/jamstatic/thefrontisback.1150eef643713e9645c73106d11aab11.webp 768w, /res.cloudinary.com/jamstatic/image/upload/dpr_2.0-f_auto-q_auto/v1523347003/jamstatic/thefrontisback.1150eef643713e9645c73106d11aab11.webp 933w" width="933" height="683" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/dpr_2.0-f_auto-q_auto/v1523347003/jamstatic/thefrontisback.1150eef643713e9645c73106d11aab11.avif 768w, /res.cloudinary.com/jamstatic/image/upload/dpr_2.0-f_auto-q_auto/v1523347003/jamstatic/thefrontisback.1150eef643713e9645c73106d11aab11.avif 933w" width="933" height="683" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/dpr_2.0-f_auto-q_auto/v1523347003/jamstatic/thefrontisback.1150eef643713e9645c73106d11aab11.jpg" alt="The Front is back !" loading="lazy" decoding="async" class="dark:brightness-90" width="933" height="683" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8ApRvuNSN0qra8mrj4C0NjsVXXNQmPmpXlUVEJ1LdacSZ6ItW1ruYVt21mQBxVTTSGIro4UG0V204aHi4is07EcMJAqyEwKeFApSOK2SscEp3M694Q1yd9Ltc11moHEZrhdVlxIeayrbHoYFXZHJOKpyvuqu8/vUazjPJriPdtoSFcmik85fWimSbFvc4OakuL7C8mqqKKz9ScohwagYlzqgBPzVWi1TdKBurmby6k8wjNMtbhhKCTVxRMldHrWiXQbbzXWwzrtHNeU6VqflAfNXQx6+Av3q7ITSR4tfDylK6O8FwvrQ1yuOtcN/wkQH8f600+IQRjfTdRGCwczodWvlWMgGuC1G63ynmrN9qxlU/NXPSzl5Sc1hUnc9PC4dw3JmJNVZZSlWoyGWq11HkGsEejcqm+YGiq7RHcaKuwjo01LHeq15deap5rG+0H1oNwSOtQNor3MYZyaZDFg0933Gp4FzTuKxKkjRjg0/7a4HU1HIMCq7UXJ5UWGvZCeGNSxXEh6k1RAya0LeLIoctBqKJWlYryaqtLhqtypgVnyferNSuacqNO2kyKdM4NQW/3KZNJg4q0Q0Bxmiod9FUBRpO1FFQi2JVuCiimxEknSq560UUIQL94VqWv3RRRSkCJJulZkn36KKziaF63+5Ve4+9RRWqIZDRRRVEn/9k=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/dpr_2.0-f_auto-q_auto/v1523347003/jamstatic/thefrontisback.1150eef643713e9645c73106d11aab11.jpg 768w, /res.cloudinary.com/jamstatic/image/upload/dpr_2.0-f_auto-q_auto/v1523347003/jamstatic/thefrontisback.1150eef643713e9645c73106d11aab11.jpg 933w" sizes="100vw">
</picture>
<figcaption>Photo : <a href="https://twitter.com/nmanaud/status/974957331279695872" target="_blank" rel="noopener noreferrer">Nicolas Manaud</a></figcaption>
</figure>
<p>Lors de l’introduction, j'ai rapidement rappelé comment nous en sommes arrivés à ces workflows de déploiement et comment ils permettent aux développeurs front-end de reprendre la main sur ce qui est produit en sortie, puisqu'ils sont libres de choisir la solution de templating qui va générer le HTML et qu'ils ne seront pas contraints par la structure de la base de données d’un CMS. Une vraie aubaine pour les artisans qui aiment faire les choses bien et se soucient de la qualité.</p>
<h2 id="making-platforms-promote-performance">Making platforms promote performance</h2>
<p>La présentation de <a href="https://twitter.com/philhawksworth" target="_blank" rel="noopener noreferrer">Phil Hawksworth</a>, developer relation chez Netlify, n'était pas aussi austère que le titre aurait pu le laisser penser. En effet Phil est revenu sur les mises en production stressantes de projets, où faute de workflow automatisé et prédictible, les équipes vont stresser jusqu'au bout, la prise de risque étant maximale et la sueur perlera sur le front de la personne chargée d’appuyer sur le bouton rouge. Personne ne souhaite vivre de telles expériences.</p>
<p><div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;">
<iframe src="https://www.youtube-nocookie.com/embed/dphhk_7eqGw" loading="lazy" width="640" height="360" frameborder="0" allow="accelerometer;autoplay;encrypted-media;gyroscope;picture-in-picture;fullscreen;web-share;" allowfullscreen="" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background-color:#d8d8d8;"></iframe>
</div></p>
<p>Pour s'éviter de tels tracas, autant commencer par s'assurer que le déploiement automatique fonctionne comme il faut en début de projet. Mieux encore, en versionnant correctement son projet avec Git, en le déployant régulièrement, on va permettre à tous les intervenants de s'investir et de faire des retours au plus tôt. C’est la garantie de plus de sérénité pour tout le monde lors du lancement <em>officiel</em>. Et cette façon de travailler est rendue très accessible par <a href="https://netlify.com" target="_blank" rel="noopener noreferrer">Netlify</a>, nul besoin d’avoir une équipe de devops chevronnés pour vous aider à mettre en place un tel workflow. En quelques clics c'est réglé.</p>
<p>Phil a pu observer comment Netlify utilisait Netlify pour développer et concevoir son application web, servie naturellement elle aussi en statique. Grâce à la mise en ligne du résultat de la génération du site pour chaque commit, il a pu remonter aux origines, voir les premiers prototypes et ainsi parcourir toute la timeline du projet. Si Git permet de faire ce genre de choses dans un terminal et de voir les différences, les ajouts et les suppressions de fichiers, Netlify propose la même chose mais sous forme visuelle, vous pouvez consulter n'importe quelle version du site sur une URL unique basée sur le hash du commit.</p>
<figure>
<picture title="Les premiers prototypes du site de Netlify sont encore accessibles en ligne.">
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/dpr_2.0-f_auto-q_auto/jamstatic/netlify-prototype.9d55f6c936fab259dbd445de2e900bd0.webp 768w, /res.cloudinary.com/jamstatic/image/upload/dpr_2.0-f_auto-q_auto/jamstatic/netlify-prototype.9d55f6c936fab259dbd445de2e900bd0.webp 932w" width="932" height="399" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/dpr_2.0-f_auto-q_auto/jamstatic/netlify-prototype.9d55f6c936fab259dbd445de2e900bd0.avif 768w, /res.cloudinary.com/jamstatic/image/upload/dpr_2.0-f_auto-q_auto/jamstatic/netlify-prototype.9d55f6c936fab259dbd445de2e900bd0.avif 932w" width="932" height="399" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/dpr_2.0-f_auto-q_auto/jamstatic/netlify-prototype.9d55f6c936fab259dbd445de2e900bd0.png" alt="Les premiers prototypes du site de Netlify sont encore accessibles en ligne" loading="lazy" decoding="async" class="dark:brightness-90" width="932" height="399" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/dpr_2.0-f_auto-q_auto/jamstatic/netlify-prototype.9d55f6c936fab259dbd445de2e900bd0.png 768w, /res.cloudinary.com/jamstatic/image/upload/dpr_2.0-f_auto-q_auto/jamstatic/netlify-prototype.9d55f6c936fab259dbd445de2e900bd0.png 932w" sizes="100vw">
</picture>
<figcaption>Les premiers prototypes du site de Netlify sont encore accessibles en ligne.</figcaption>
</figure>
<p>Un webdesigner pourra dès alors présenter une nouvelle itération sur le design du site en ouvrant une pull request sur GitHub et en retour toutes les parties prenantes pourront consulter une version utilisable du site en ligne. Il est même possible de comparer comment différentes versions d’un design performent grâce au <a href="https://www.youtube.com/watch?v=5VgpJJUOng4" target="_blank" rel="noopener noreferrer">split-testing</a>.</p>
<p>Pour faire face à l’accélération et à l’exigeance technique croissante en termes de développement, il est bon de pouvoir déléguer des tâches aussi hardues que le déploiement continu à ceux dont c'est le métier.</p>
<h2 id="ajouter-une-recherche-sur-un-site-statique">Ajouter une recherche sur un site statique</h2>
<p><div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;">
<iframe src="https://www.youtube-nocookie.com/embed/mnySRW94NL4" loading="lazy" width="640" height="360" frameborder="0" allow="accelerometer;autoplay;encrypted-media;gyroscope;picture-in-picture;fullscreen;web-share;" allowfullscreen="" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background-color:#d8d8d8;"></iframe>
</div></p>
<p><a href="https://twitter.com/martynd" target="_blank" rel="noopener noreferrer">Martyn Davies</a>, nouvellement arrivé chez <a href="https://algolia.com" target="_blank" rel="noopener noreferrer">Algolia</a>, est venu nous montrer comment ajouter une recherche performante sur un site généré en statique. Pari réussi puisqu'à la fin de la présentation, son site généré avec <a href="/categories/jekyll">Jekyll</a> disposait d’un champ de recherche avec autocomplétion sur des attributs qu'il avait lui-même défini et affichait des résultats de manière quasi-immédiate à chaque touche tapée sur le clavier.</p>
<p>C’est toujours bluffant de voir à quel point l’intégration de tels services a été pensée pour être la plus simple possible pour les développeurs. Dans cet exemple, Martyn a utilisé le plugin <a href="https://github.com/algolia/jekyll-algolia" target="_blank" rel="noopener noreferrer">jekyll-algolia</a> développé par <a href="https://twitter.com/pixelastic" target="_blank" rel="noopener noreferrer">Tim Carry</a>, passé ingénieur open-source à plein temps chez Algolia.</p>
<p>Dans un premier temps, on va déclarer le plugin dans Jekyll, puis après avoir créé un compte sur le site d’Algolia ainsi qu'un premier projet, on va récupérer une clef d’API et un nom d’index de recherche qu'on va reporter dans les paramètres de configuration du plugin. On est sur du <em>Copier-Coller driven development</em>.</p>
<p>Une fois que c'est fait, on va créer un modèle de page pour la recherche, qui fera appel à un script JS et à une feuille de style hébergés par Algolia sur un CDN. Puis on va ajouter des widgets de recherche et personnaliser leur configuration. Après quelques aller-retours entre <a href="https://www.algolia.com/doc/tutorials/search-ui/instant-search/build-an-instant-search-results-page/instantsearchjs/#binding-the-search-input" target="_blank" rel="noopener noreferrer">les exemples documentés sur le site d’Algolia</a> et le template de recherche pour Jekyll, ainsi que quelques ajustements des attributs à indexer et à retourner dans l’interface d’administration d’Algolia, le site d’exemple disposait d’une recherche instantanée. À tel point que Phil a demandé s’il y avait vraiment des appels à l’API d’Algolia pour retourner les résultats aussi vite ou si c'était un cache en local. Martyn a répondu par l’affirmative, en souriant devant l’incrédulité de son homologue.</p>
<h2 id="une-chouette-soiree">Une chouette soirée</h2>
<p>Deux présentations rondemment menées donc, qui ont été <a href="https://twitter.com/nmanaud/status/974957331279695872" target="_blank" rel="noopener noreferrer">bien appréciées par les personnes présentes</a>.</p>
<p>Maxime Thirouin le développeur de <a href="https://phenomic.io/" target="_blank" rel="noopener noreferrer"><del>Phenomic</del></a> a bien envie de développer à son tour un plugin Algolia après la présentation de Martyn. Il pourra pour cela se baser sur le <a href="https://github.com/chrisdmacrae/atomic-algolia" target="_blank" rel="noopener noreferrer">code source ouvert</a> du paquet <a href="https://www.npmjs.com/package/atomic-algolia" target="_blank" rel="noopener noreferrer">atomic-algolia</a> développé par Chris Macrae de Forestry. C’est beau l’open source.</p>
<p>Nous espérons que ces deux présentations auront donné envie aux développeurs front-end d’utiliser des workflows et des outils matures et qu'ils auront bien compris qu'on peut faire des choses dynamiques avec des sites versionnés servis en statique en s'affranchissant de la gestion de serveur de base de données. Et si c'est toujours pas clair, nous ferons d’autres meetups. :)</p>
<p>Et grâce au soutien de <a href="https://www.front-commerce.com/en/home/" target="_blank" rel="noopener noreferrer">Front-Commerce</a>, un front-end JS qui communique avec des APIS e-commerce, nous publierons les vidéos des deux présentations d’ici quelques semaines, merci à eux !</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2018/03/13/un-site-statique-avec-des-composants-grace-a-nunjucks/</id>
    <title>Un site statique avec des composants à l’aide de Nunjucks</title>
    <published>2018-03-13T19:21:48+00:00</published>
    <link href="https://jamstatic.fr/2018/03/13/un-site-statique-avec-des-composants-grace-a-nunjucks/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>La philosophie de la génération de site statique à l’aide d’un langage de
templating et d’un langage de balisage léger comme Markdown continue d’être
déclinée à l’envi. Le même principe est repris ici par Chris Coyier, le créateur
de CodePen que les développeurs front connaissent bien, pour la création
d’<a href="https://thepowerofserverless.info" target="_blank" rel="noopener noreferrer">un site qui liste quelques-unes des possibilités offertes</a>
par ce que nous appelons plus globalement la <a href="/categories/jamstack">Jamstack</a> et
qui est ici désigné sous le nom de <em>serverless</em> — une des nombreuses composantes
de ces architectures découplées.
Un cas d’étude très simple qui pourrait vous donner des idées. Aucune
configuration puisque c'est un service en ligne payant qui est utilisé ici, mais
on pourrait tout aussi bien utiliser le générateur open source
<a href="/categories/eleventy">Eleventy</a> par exemple, qui utilise aussi
<a href="https://mozilla.github.io/nunjucks/" target="_blank" rel="noopener noreferrer">Nunjucks</a>.</p></aside>
<p>Il est de plus en plus courant de nos jours de bâtir des sites avec des
composants et c'est une très bonne idée. Plutôt que de construire les pages les
unes après les autres, nous développons un système de composants (comme un
formulaire de recherche, un carte d’article, un menu, un pied de page) et nous
assemblons le site avec ces composants.</p>
<p>Des frameworks JavaScript comme React ou Vue reposent en grande partie sur ce
principe. Mais ce n'est pas parce que vous n'utilisez pas de JavaScript côté
client pour développer votre site que vous devez renoncer à l’idée d’utiliser
des composants. En utilisant un préprocesseur HTML, nous pouvons monter un site
statique et bénéficier également de le possibilité d’abstraire notre site et son
contenu sous forme de composants réutilisables.</p>
<p>Les sites statiques font fureur actuellement, et à juste titre, car ils sont
rapides, sécurisés et pas cher à héberger. Croyez-le ou pas mais même Smashing
Magazine est un site statique !</p>
<p>Regardons de plus près un site que j'ai monté récemment à l’aide de cette
technique. J'ai utilisé <a href="https://codepen.io/pro/projects" target="_blank" rel="noopener noreferrer">CodePen Projects</a> pour
le développer qui supporte <a href="https://mozilla.github.io/nunjucks/" target="_blank" rel="noopener noreferrer">Nunjucks</a> comme
préprocesseur, ce qui est parfait pour faire le job.</p>
<h3 id="un-site-de-quatre-pages-avec-un-entete-une-navigation-et-un-pied-de-page-consistants">Un site de quatre pages avec un entête, une navigation et un pied de page consistants</h3>
<p><a href="https://thepowerofserverless.info/" target="_blank" rel="noopener noreferrer">C’est un microsite</a>. Nul besoin de passer
par CMS capable de gérer des centaines de pages, ni de JavaScript pour ajouter
de l’interaction. Par contre il faut qu'une poignée de pages partage la même
mise en page.</p>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/v1603621914/jamstatic/static-site-nunjucks-image1.b07c701fa81c8467df3b8b9b84fb4a2b.webp 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/v1603621914/jamstatic/static-site-nunjucks-image1.b07c701fa81c8467df3b8b9b84fb4a2b.webp 960w" width="960" height="834" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/v1603621914/jamstatic/static-site-nunjucks-image1.b07c701fa81c8467df3b8b9b84fb4a2b.avif 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/v1603621914/jamstatic/static-site-nunjucks-image1.b07c701fa81c8467df3b8b9b84fb4a2b.avif 960w" width="960" height="834" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/v1603621914/jamstatic/static-site-nunjucks-image1.b07c701fa81c8467df3b8b9b84fb4a2b.jpg" alt="Un haut et un pied de page consistants sur toutes les pages" loading="lazy" decoding="async" class="dark:brightness-90" width="960" height="834" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A7OY81esXAArKu5gh5NJaagoOM0AdUsgxQZKzYboOODVjzOKAGXjZU1kj75q7dScGqCNl6AEn6U+07Ukw4p1sMGpYGkozTwtJH0qUUrAVLhflqlEMTVo3A+U1RjH72mlYDWj+4KKWMfIKKYHF6xIyqcVg213IJup611Gp2/mAjFZMGm4lzimBu6bKzKM1sAkrWZZQeWBWtGuRQBn3WcGq0Gd1aN1HwapRJh6AHyjin2y80SDirFmmSKQFlUOKkVDVpIhin+UBUjM64X5Koxj97WrdLhTWYn+uqkI04/uCihPuCimBh3Kg0yCEE9KmuBS2woAsxxACrCDFIi8VIFoAgufu1QT79X7n7tUY/vGgB0nSrVkcEVWl6VPadqANpHGKDIKr5OKYzGpAbdyDaazI2zNVi5Y4NU4D+9poDYT7gooT7gopgZFxTraiigDQTpUo6UUUCKtz0qjH980UUDHy9KntO1FFAGiOlNbpRRSAoXPeqsH+toooQGwn3BRRRTA//9k=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/v1603621914/jamstatic/static-site-nunjucks-image1.b07c701fa81c8467df3b8b9b84fb4a2b.jpg 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/v1603621914/jamstatic/static-site-nunjucks-image1.b07c701fa81c8467df3b8b9b84fb4a2b.jpg 960w" sizes="100vw">
</picture>
<p>HTML n'apporte pas encore de réponse à ce problème. Nous avons besoin de pouvoir
faire des <em>imports</em>. Les langages comme PHP permettent cela avec
<code>&lt;?php include "header.php"; ?&gt;</code>, mais PHP n'est pas disponible (à dessein) chez
les hébergeurs de sites statiques et HTML ne peut encore rien pour nous.
Heureusement nous pouvons préprocesser nos inclusions à l’aide d’un langage de
templating comme <a href="https://mozilla.github.io/nunjucks/" target="_blank" rel="noopener noreferrer">Nunjucks</a>.</p>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image2.881c617761222152414140830d0ea4fc.webp 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image2.881c617761222152414140830d0ea4fc.webp 960w" width="960" height="568" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image2.881c617761222152414140830d0ea4fc.avif 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image2.881c617761222152414140830d0ea4fc.avif 960w" width="960" height="568" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image2.881c617761222152414140830d0ea4fc.png" alt="L&#039;import est possible dans les langages comme PHP" loading="lazy" decoding="async" class="dark:brightness-90" width="960" height="568" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image2.881c617761222152414140830d0ea4fc.png 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image2.881c617761222152414140830d0ea4fc.png 960w" sizes="100vw">
</picture>
<p>Cela fait parfaitement sens ici de créer un gabarit de page, qui inclus des
morceaux de HTML pour le haut de page, la navigation et le pied de page. Le
concept de blocs de Nunjucks nous permet d’insérer du contenu à cet endroit
lorsque nous utilisons le gabarit de page.</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0"</span> /&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>The Power of Serverless<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/styles/style.processed.css"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>

<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  {% include "./template-parts/_header.njk" %} {% include
  "./template-parts/_nav.njk" %} {% block content %} {% endblock %} {% include
  "./template-parts/_footer.njk" %}
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span></code></pre>
<p>Vous remarquerez que les fichiers inclus sont commencent par un tiret bas et
possèdent l’extension <code>.njk</code>. Ce n'est pas obligatoire, ils pourraient se nommer
<code>header.html</code> ou <code>icons.svg</code> mais ils sont nommés ainsi car premièrement c'est
une convention de nommage qu'on rencontre souvent pour les fichiers partiels.
Dans CodePen, cela signifie qu'ils ne seront pas compilés tous seuls, et
deuxièmement utiliser l’extention <code>.njk</code> nous permettra de faire plus de choses
avec Nunjucks si besoin.</p>
<p>Rien de spécial dans ces morceaux de fichiers. Ce sont simplement des petits
bouts de HTML destinés à être utilisés sur toutes nos pages.</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">footer</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Ceci est un simple pied de page, les gens. Circulez, y’a rien à voir.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">footer</span>&gt;</span></code></pre>
<p>De cette manière, un simple changement dans ces fichiers et il sera appliqué sur
toutes nos pages.</p>
<h3 id="utiliser-un-seul-gabarit-pour-nos-pages">Utiliser un seul gabarit pour nos pages</h3>
<p>Maintenant nous pouvons créer un fichier pour chacune de nos pages. Commençons
quand même par <code>index.njk</code> , qui sera automatiquement traité pour créer un
fichier <code>index.html</code> dans CodePen project à chaque enregistrement.</p>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image3.45b513d38e7421d967b2acebc5f5eca0.webp 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image3.45b513d38e7421d967b2acebc5f5eca0.webp 960w" width="960" height="535" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image3.45b513d38e7421d967b2acebc5f5eca0.avif 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image3.45b513d38e7421d967b2acebc5f5eca0.avif 960w" width="960" height="535" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image3.45b513d38e7421d967b2acebc5f5eca0.png" alt="Démarrer avec un fichier index.njk" loading="lazy" decoding="async" class="dark:brightness-90" width="960" height="535" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A8LIxRVgxZqNo8UARGo2FTEU0rSsBXK0bKsbaPLzTAr7KNlWPLo8ugCuFIqQGpPLpCuKAAUUCigAooooA1AvFRulTimSUAU2XmmEVM1NxQAxVqQKKFXmpQlAEe0UFakxSEUCIiKYwqUimkUAQ4pMVLikxQMjxRUmKKAL5OKjds040w0hsZikxT6KYhoGKkB4ptFACk0lFLQAwimkVIRSYoAjxRipMUYoAjxRUmKKAJqY1FFIbG0UUUxBRRRQAUtFFAC0UUUAFFFFABRRRQB//2Q==);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image3.45b513d38e7421d967b2acebc5f5eca0.png 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image3.45b513d38e7421d967b2acebc5f5eca0.png 960w" sizes="100vw">
</picture>
<p>Voici ce que nous pourrions écrirer dans le fichier <code>index.njk</code> pour appliquer
le gabarit de page et ajouter du contenu dans le bloc principal :</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">extends</span></span> "_layout.njk" %}</span><span class="xml">

</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">block</span></span> content %}</span><span class="xml">
<span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Bonjour, monde!<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endblock</span></span> %}</span></code></pre>
<p>Ça suffit pour avoir une page d’accueil fonctionnelle. Sympa ! On peut faire
pareil pour chacune des autres pages, le contenu du bloc sera simplement
différent, et nous aurons un petit site de quatre pages facile à maintenir.</p>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image4.7d10b254cc0a5baf4f118ef6cb802546.webp 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image4.7d10b254cc0a5baf4f118ef6cb802546.webp 960w" width="960" height="591" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image4.7d10b254cc0a5baf4f118ef6cb802546.avif 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image4.7d10b254cc0a5baf4f118ef6cb802546.avif 960w" width="960" height="591" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image4.7d10b254cc0a5baf4f118ef6cb802546.png" alt="Le fichier index.njk est transformé en index.html" loading="lazy" decoding="async" class="dark:brightness-90" width="960" height="591" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A8O2U3bV3yxUTxigCtijFS7aAtAEYXNOCVKqU8JQBBso2VY2UhWgCvjFLUhWmFcUWASijFLiiwC0UUUxF4dKicVKKY9IdivijFSYpMUAItPBpMUooAKCKKD0oAYaZipMUbaYiPbRtqTbRtoAjxRUm2igCXOKaTmg0lIYUUUUAFFFFABSijFKKADFGKWigBMUYpaKAExRS0UAIabRRQAUUUUAFAoooAd2ooopALRRRTAKKKKACiiigD//Z);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image4.7d10b254cc0a5baf4f118ef6cb802546.png 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image4.7d10b254cc0a5baf4f118ef6cb802546.png 960w" sizes="100vw">
</picture>
<p>Entre nous soit dit, je ne suis pas persuadé que ces petits morceaux
réutilisables soient des <em>composants</em> à proprement parlé. Nous sommes simplement
efficients et nous découpons notre gabarit en petits morceaux. Je pense qu'un
composant est plutôt quelque chose qui accepte des données en entrée et génère
une version unique de lui-même avec ces données. Nous allons y venir.</p>
<h3 id="rendre-la-navigation-active">Rendre la navigation active</h3>
<p>Maintenant que nous avons des morceaux de HTML répétés à l’identique sur nos
pages, pouvons-nous appliquer un style CSS unique aux entrées indiviudelles de
la navigation pour identifier la page courante ? Nous pourrions le faire avec
JavaScript en regardant la valeur retournée par <code>window.location</code> par exemple,
mais nous pouvons nous passer de JavaScript pour cela. Le truc c'est d’ajouter
une <code>class</code> unique pour chaque page sur l’élément <code>&lt;body&gt;</code> et de la styler avec
CSS.</p>
<p>Dans notre fichier <code>_layout.njk</code> nous allons générer un nom de classe à l’aide
d’une variable :</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">body</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"{{ body_class }}"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span></code></pre>
<table>
<thead>
<tr>
<th style="text-align: center;">Et avant d’appliquer le gabarit sur chaque page, nous définissons cette variable</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<pre><code class="language-html hljs xml">{% set body_class = "home" %} {% extends "_layout.njk" %}</code></pre>
<p>Imaginons que notre navigation soit structurée de la sorte :</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">nav</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"site-nav"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"nav-home"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/"</span>&gt;</span> Home <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
      …
    <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span></code></pre>
<p>Nous pouvons maintenant cibler ce lien et lui appliquer un style spécifique en
écrivant :</p>
<pre><code class="language-css hljs css"><span class="hljs-selector-tag">body</span><span class="hljs-selector-class">.home</span> <span class="hljs-selector-class">.nav-home</span> <span class="hljs-selector-tag">a</span>,
<span class="hljs-selector-tag">body</span><span class="hljs-selector-class">.services</span> <span class="hljs-selector-class">.nav-services</span> <span class="hljs-selector-tag">a</span> {
  <span class="hljs-comment">/* continue matching classes for all pages… */</span>
  <span class="hljs-comment">/* unique active state styling */</span>
}</code></pre>
<img src="/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image5.06fb6bfdff55a55bfbd95557d3dd7cff.gif" alt="Styler les liens de navigation avec une classe active" loading="lazy" decoding="async" class="dark:brightness-90" width="960" height="234" style="" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image5.06fb6bfdff55a55bfbd95557d3dd7cff.gif 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image5.06fb6bfdff55a55bfbd95557d3dd7cff.gif 960w" sizes="100vw">
<p><em>Oh, c'est quoi ces icônes ?</em> Ce sont simplement des fichiers <code>.svg</code> déposés
dans un dossier et inclus de la sorte :</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">include</span></span> "../icons/cloud.svg" %}</span></code></pre>
<p>Cela me permet de les styler ainsi :</p>
<pre><code class="language-css hljs css"><span class="hljs-selector-tag">svg</span> {
  <span class="hljs-attribute">fill</span>: white;
}</code></pre>
<p>En partant du principe que les éléments SVG du fichier n'ont pas déjà un
attribut <code>fill</code> de défini.</p>
<h3 id="rediger-du-contenu-en-markdown">Rédiger du contenu en Markdown</h3>
<p>La page d’accueil de mon site affiche un gros bloc de contenu. Je pourrais
écrire et maintenir cela en HTML, mais il est parfois bon de
<a href="http://mediatemple.net/blog/tips/you-should-probably-blog-in-markdown/" target="_blank" rel="noopener noreferrer">laisser ce genre de chose à Markdown</a>.
Markdown est plus léger et un peu plus lisible lorsqu'il y a beaucoup de texte.</p>
<p>Rien de plus simple dans CodePen Projects. Je crée un fichier avec l’extension
<code>.md</code> qui sera automatiquement transformé en HTML avant d’être inclus dans le
fichier <code>index.njk</code>.</p>
<img src="/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image6.0fbc0485ef0aaf87c242d2318dac768f.gif" alt="Les fichiers Markdown sont transformés en HTML dans CodePen Projects" loading="lazy" decoding="async" class="dark:brightness-90" width="960" height="438" style="" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image6.0fbc0485ef0aaf87c242d2318dac768f.gif 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image6.0fbc0485ef0aaf87c242d2318dac768f.gif 960w" sizes="100vw">
<pre><code class="language-html hljs xml">{% block content %}
<span class="hljs-tag">&lt;<span class="hljs-name">main</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"centered-text-column"</span>&gt;</span>{% include "content/about.html" %}<span class="hljs-tag">&lt;/<span class="hljs-name">main</span>&gt;</span>
{% endblock %}</code></pre>
<h3 id="developper-de-vrais-composants">Développer de vrais composants</h3>
<p>Partons du principe que les composants sont des modules réutilisables à qui on
injecte des données lors de leur création. Dans des frameworks comme Vue, vous
utiliseriez des
f<a href="https://vuejs.org/v2/guide/single-file-components.html" target="_blank" rel="noopener noreferrer">ichiers uniques par composants</a>,
qui sont des morceaux isolés de HTML modelé, de CSS au périmètre limité et de
JavaScript spécifique au composant. C’est super cool, mais notre microsite n'a
pas besoin de quelque chose d’aussi sophistiqué.</p>
<p>Nous avons besoin de créer des "cartes" qui utilisent un modèle simple, on peut
par exemple faire quelque chose comme :</p>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image7.5e214e28c344beec78b0f58d81da99ad.webp 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image7.5e214e28c344beec78b0f58d81da99ad.webp 960w" width="960" height="626" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image7.5e214e28c344beec78b0f58d81da99ad.avif 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image7.5e214e28c344beec78b0f58d81da99ad.avif 960w" width="960" height="626" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image7.5e214e28c344beec78b0f58d81da99ad.png" alt="Créer des composants réutilisables à l’aide de modèles" loading="lazy" decoding="async" class="dark:brightness-90" width="960" height="626" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A9Dvj+7NcpdE+b+NdNfyDYa5q4dfMoA09MPSugVvkrmLCYKRWybtRH1oAndhuqaNhisWS/AbrU0F8D3oAtXvKGudnhzJmtm5uQy1jyzDdQBe09dpFdBG2Erm7OcAitX7WAnWgC8W5qxEwrCOoAHrVm3vgx60AboYUVSW4yOtFAGJfIxQ1z01vIXNdlPCGFUPsas3SgDGtLeQHvVyVHCd614rNR2pZrYY6UAcnJFKX71atYZO+a1/salulW4bNR2oAx5on2VmvbyM3eutktQR0qBbEE9KAMO1tZB2q1LG4Wt2OzVR0pk1sMdKAOVaKUt3q/ZQyBhnNaS2ilulXoLVR2oAiRG2iitEQjHSigCrKOKhUc1ZkHFQqOaAJo8YpJFzToxUhTNAFMR/NVqJKcIqnjjxQBGYc9qVbf2q0qinhRQBV8rAqvKma0WXiqzpQBREfNWY1xThHzUqpQIAOKKk20UDM6SoV60UUAWEqYUUUAPFSrRRQBIKeKKKAGtUTUUUANqQUUUAOooooA//Z);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image7.5e214e28c344beec78b0f58d81da99ad.png 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image7.5e214e28c344beec78b0f58d81da99ad.png 960w" sizes="100vw">
</picture>
<p>Pour créer un tel composant dans Nunjucks, il faut utilise ce qu'on appelle des
<a href="https://mozilla.github.io/nunjucks/templating.html" target="_blank" rel="noopener noreferrer">Macros</a>. Les Macros sont
d’une simplicité exquise. C’est comme <strong>des fonctions pour HTML</strong> !</p>
<pre><code class="language-html hljs xml">{% macro card(title, content) %}
<span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>{{ title }}<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{{ content }}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
{% endmacro %}</code></pre>
<p>Puis vous les appelez comme bon vous semble :</p>
<pre><code class="language-html hljs xml">{{ card('My Module', 'Lorem ipsum whatever.') }}</code></pre>
<p>L'idée générale est de <strong>séparer les données et le balisage</strong>. Cela nous donne
des bénéfices concrets et assez clairs :</p>
<ol>
<li>Si nous devons changer le HTML, nous pouvons le faire dans la macro et le changement sera reporté partout où la macro est utilisée.</li>
<li>La donnée n'est pas mélangée avec le balisage</li>
<li>La donnée pourrait venir de n'importe où ! Nous pouvons passer la donnée
directement lors de l’appel comme nous l’avons fait ci-dessus. Ou bien nous
pouvons référencer des données en JSON et boucler dessus. Je suis sûr qu'on
pourrait mettre en place un système dans lequel des données JSON proviennent
d’un <a href="/2017/12/15/cms-headless/">CMS headless</a>, d’un processus de
génération, d’une fonction serverless, d’une tâche cron ou de ce que vous
voulez.</li>
</ol>
<p>Maintenant que nous avons juste ce dont nous avons besoin, des cartes répétables
qui combinent des données et du balisage :</p>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image8.c043a95c5f8db3a16f6e64d221ab24af.webp 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image8.c043a95c5f8db3a16f6e64d221ab24af.webp 960w" width="960" height="698" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image8.c043a95c5f8db3a16f6e64d221ab24af.avif 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image8.c043a95c5f8db3a16f6e64d221ab24af.avif 960w" width="960" height="698" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image8.c043a95c5f8db3a16f6e64d221ab24af.png" alt="Le HTML vient de la macro, les données peuvent venir de n&#039;importe où" loading="lazy" decoding="async" class="dark:brightness-90" width="960" height="698" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image8.c043a95c5f8db3a16f6e64d221ab24af.png 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image8.c043a95c5f8db3a16f6e64d221ab24af.png 960w" sizes="100vw">
</picture>
<h3 id="creer-autant-de-composants-que-vous-voulez">Créer autant de composants que vous voulez</h3>
<p>Vous pouvez partir de ce principe et l’appliquer à l’envi. Imaginez par exemple
que Bootstrap est essentiellement des bouts des CSS qui suivent des patrons en
HTML qui définissent leur utilisation. Vous pourriez transformer tous les
patrons du framework en macro et les appeler en fonction de vos besoins. L'idée
est celle d’un
<a href="https://css-tricks.com/componentizing-a-framework/" target="_blank" rel="noopener noreferrer">framework de composants</a>.</p>
<p>Vous pouvez imbriquer les composants si ça vous chante, et tendre vers quelque
chose qui ressemble à la philosophie du
<a href="http://bradfrost.com/blog/post/atomic-web-design/" target="_blank" rel="noopener noreferrer">design atomique</a>.</p>
<p>Nunjucks offre également une couche de logique, qui fait que vous pouvez créer
des composants conditionnels et des variations en passant différents paramètres.</p>
<p><a href="https://thepowerofserverless.info/" target="_blank" rel="noopener noreferrer">Dans le petit site que j'ai crée</a>, j'ai
créé une autre macro pour la section idées du site car les données utilisées et
le design de la carte sont légèrement différents.</p>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image9.b98ae87a53a5cb98955661181d66898e.webp 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image9.b98ae87a53a5cb98955661181d66898e.webp 960w" width="960" height="937" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image9.b98ae87a53a5cb98955661181d66898e.avif 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image9.b98ae87a53a5cb98955661181d66898e.avif 960w" width="960" height="937" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image9.b98ae87a53a5cb98955661181d66898e.png" alt="Vous pouvez créer autant de composants que vous voulez" loading="lazy" decoding="async" class="dark:brightness-90" width="960" height="937" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image9.b98ae87a53a5cb98955661181d66898e.png 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_960/jamstatic/static-site-nunjucks-image9.b98ae87a53a5cb98955661181d66898e.png 960w" sizes="100vw">
</picture>
<h3 id="un-avis-rapide-sur-les-sites-statiques">Un avis rapide sur les sites statiques</h3>
<p>Si la plupart des sites pourraient bénéficier d’une architecture à base de
composants, tous les sites ne sont pas faits pour être statiques. Je travaille
sur beaucoup de sites pour lesquels utiliser un langage côté serveur est
approprié et utile.</p>
<p>Un de mes sites, <a href="https://css-tricks.com/" target="_blank" rel="noopener noreferrer">CSS-Tricks</a>, propose des trucs comme
un compte utilisateur avec un système de permissions complexe : forums,
commentaires, ecommerce. Bien que toutes ces fonctionnalités n'empêchent pas de
travailler en statique, je suis souvent bien content d’avoir une base de données
et des langages côté serveur pour travailler. Cela me permet de développer ce
sont j'ai besoin et de tout avoir au même endroit.</p>
<h3 id="allez-de-l-avant-et-passer-au-statique">Allez de l’avant et passer au statique</h3>
<p>Rappelez-vous qu'un des bénéfices de développer le site comme nous l’avons fait
dans cet article est qu'au final nous obtenons quelques fichiers statiques.
Faciles à héberger, performants et sécurisés. Pourtant, nous avons pu travailler
de manière sympa et efficace. Ce site sera simple à mettre à jour par la suite…</p>
<ul>
<li>Le projet final est un microsite nommé <em>Le pouvoir du Serverless pour les
développeurs Front-End</em> (<a href="https://thepowerofserverless.info/" target="_blank" rel="noopener noreferrer">https://thepowerofserverless.info/</a>).</li>
<li>L'hébergement de fichier statique fait partie selon moi du mouvement
serverless.</li>
<li>Tout le code est visible (et vous pouvez même en faire une copie pour vous)
<a href="https://codepen.io/chriscoyier/project/editor/ZepgLg" target="_blank" rel="noopener noreferrer">directement dans CodePen</a>.
Il est maintenu, généré et
<a href="https://blog.codepen.io/projects/custom-domains/" target="_blank" rel="noopener noreferrer">hébergé</a> entièrement sur
CodePen à l’aide de <a href="https://codepen.io/pro/projects" target="_blank" rel="noopener noreferrer">CodePen Projects</a>.</li>
<li>CodePen Projects s'occupe de toute la partie
<a href="https://mozilla.github.io/nunjucks/" target="_blank" rel="noopener noreferrer">Nunjucks</a> dont nous avons parlé ici,
ainsi que de la compilation Sass et de l’hébergement des images, que j'ai
utilisé pour le site. Vous pourriez faire la même chose avec par exemple un
process de génération basé sur Gulp voire Grunt par exemple.
<a href="https://github.com/ericmotil/gulp-nunjucks-sass" target="_blank" rel="noopener noreferrer">Voici un modèle de départ pour un tel projet</a>
que vous pourriez utiliser.</li>
</ul>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2018/03/10/mettre-en-place-son-premier-site-sous-hugo/</id>
    <title>Mettre en place son premier site sous Hugo</title>
    <published>2018-03-10T13:57:51+00:00</published>
    <link href="https://jamstatic.fr/2018/03/10/mettre-en-place-son-premier-site-sous-hugo/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Pour créer un nouveau projet avec Hugo, <a href="https://forestry.io" target="_blank" rel="noopener noreferrer">Forestry</a> propose
un kit de démarrage en libre téléchargement. Que vous ayez déjà utilisé le
générateur de site statique Hugo ou pas, ce kit est intéressant, car il propose
une configuration complète et un workflow de développement moderne basé sur les
outils de l’écosystème de <code>npm</code>.
<a href="https://twitter.com/chrisdmacrae" target="_blank" rel="noopener noreferrer">Chris Macrae</a> nous montre comment s'en servir
pour créer votre premier site en moins de 30 minutes.</p></aside>
<p><a href="https://gohugo.io" target="_blank" rel="noopener noreferrer">Hugo</a>, le générateur de site statique écrit en Go, a pris la
communauté de vitesse. Il présente tous les avantages d’un générateur de site
statique — 100% flexible, sécurisé et rapide — mais il vole également la vedette
quand on
<a href="https://forestry.io/blog/hugo-vs-jekyll-benchmark/" target="_blank" rel="noopener noreferrer">compare ses performances avec celles de Jekyll</a>.
Le site de <a href="https://forestry.io" target="_blank" rel="noopener noreferrer">Forestry.io</a> est d’ailleurs développé avec
Hugo.</p>
<p>Nous allons voir comment configurer Hugo sur votre ordinateur, comment installer
et personnaliser un thème, en ajoutant nos propres fichiers CSS et JavaScript.</p>
<p>Quelle différence avec le guide de démarrage rapide de la documentation d’Hugo ?
Nous allons utiliser
<a href="https://github.com/forestryio/hugo-boilerplate" target="_blank" rel="noopener noreferrer">notre kit de démarrage</a>
régulièrement mis à jour qui ajoute un workflow de développement moderne à Hugo.</p>
<p><strong>Sommaire</strong></p>
<div id="toc"><ul>
<li><a href="#1-configurer-hugo">1. Configurer Hugo</a></li>
<li><a href="#2-configurer-votre-site">2. Configurer votre site</a><ul>
<li><a href="#mettre-a-jour-un-article">Mettre à jour un article</a></li>
<li><a href="#creer-un-nouvel-article">Créer un nouvel article</a></li>
<li><a href="#utiliser-un-theme">Utiliser un thème</a></li>
</ul>
</li>
<li><a href="#3-personnaliser-votre-site">3. Personnaliser votre site</a></li>
<li><a href="#4-personnaliser-votre-theme">4. Personnaliser votre thème</a><ul>
<li><a href="#css-javascript-personnalise">CSS &amp; Javascript personnalisé</a></li>
</ul>
</li>
<li><a href="#5-prochaine-etape">5. Prochaine étape</a></li>
</ul></div>
<h2 id="1-configurer-hugo">1. Configurer Hugo</h2>
<p>Pou commencer, clonez ou
<a href="https://github.com/forestryio/hugo-boilerplate/archive/master.zip" title="Téléchargez depuis GitHub" target="_blank" rel="noopener noreferrer">téléchargez notre kit de démarrage pour Hugo</a>,
et décompressez l’archive quelque part sur votre ordinateur. Vous avez aussi
besoin de <a href="https://nodejs.org" target="_blank" rel="noopener noreferrer">Node.js</a> et d’<a href="https://www.npmjs.com/" target="_blank" rel="noopener noreferrer">NPM</a>, il
vous suffit de suivre les indications sur la
<a href="https://nodejs.org/fr/download/" target="_blank" rel="noopener noreferrer">page de téléchargement de Node</a> si vous ne les
avez pas déjà installés.</p>
<p>Vous bénéficiez ainsi automatiquement d’une structure de départ pour Hugo. Dans
notre kit, elle est stockée dans le dossier <code>hugo</code>. À l’intérieur se trouvent
divers dossiers qui abritent le contenu de votre site, les gabarits de page et
les fichiers CSS, JS, images, etc. L'arborescence de la structure de base
ressemble à ceci — j'ai laissé quelques fichiers et dossiers de côté de façon à
ce que ce soit plus clair :</p>
<pre><code class="language-sh hljs bash">.
├── hugo/                  // Le site Hugo, avec les fichiers de contenu, de données, statiques.
|   ├── .forestry/         // rassemble les fichiers de configuration pour Forestry.io
|   ├── content/           // Tout le contenu du site est stocké ici
|   ├── data/              // Les fichiers de données du site au format TOML, YAML ou JSON
|   ├── layouts/           // Vos modèles de page
|   |   ├── partials/      // Les fichiers partiels réutilisables de votre site
|   |   ├── shortcodes/    // Les fichiers shortcodes de votre site
|   ├── static/            // Les fichiers statiques de votre site
|   |   ├── css/           // Les fichiers CSS compilés
|   |   ├── img/           // Les images du site.
|   |   ├── js/            // Les fichiers JS compilés
|   |   └── svg/           // Les fichiers SVG vont ici
|   └── config.toml        // Le fichier de configuration d’Hugo
└─── src/
     ├── css               // Les fichiers <span class="hljs-built_in">source</span> CSS/SCSS à compiler vers /css/
     └── js                // Les fichiers <span class="hljs-built_in">source</span> JS à compiler vers /js/</code></pre>
<p>Pour démarrer le projet, ouvrez une fenêtre de terminal et positionnez-vous dans
le dossier qui contient la structure de départ (<code>hugo-boilerplate</code> par défaut) :</p>
<p><code>cd chemin/vers/hugo-boilerplate/</code></p>
<p>Installez ensuite toutes les dépendances du projet en lançant :</p>
<p><code>npm install</code></p>
<p>Pour lancer le serveur de développement et ouvrir le site dans votre navigateur,
lancez simplement :</p>
<p><code>npm start</code></p>
<h2 id="2-configurer-votre-site">2. Configurer votre site</h2>
<p>Nous allons commencer par ajouter de nouveaux contenus au site. Pour ce faire,
nous allons devoir mettre à jour le contenu présent dans le dossier
<code>hugo/content</code>.</p>
<h3 id="mettre-a-jour-un-article">Mettre à jour un article</h3>
<p>Commencer par mettre à jour l’exemple d’article fourni dans notre structure de
départ. Ouvrez le fichier <code>hugo/content/posts/example.md</code> dans votre éditeur de
texte. Il est composé d’un en-tête <em>front matter</em> avec un champ titre et d’un
texte d’exemple au format markdown.</p>
<pre><code class="language-markdown hljs markdown">---
<span class="hljs-section">title: "Bienvenue dans Hugo !"
---</span>

Vous trouverez la source de cet article dans le répertoire <span class="hljs-code">`content/posts`</span>.

Pour ajouter un nouvel article, placez un nouveau fichier dans le dossier
<span class="hljs-code">`content/posts`</span> en respectant la nomenclature <span class="hljs-code">`titre-de-l-article.md`</span> et
ajoutez les métadonnées nécessaires dans l’en-tête de page Front Matter.
Jetez un œil au fichier source de cet article pour voir comment ça marche.

<span class="xml"><span class="hljs-comment">&lt;!--more--&gt;</span></span>

Hugo also offers powerful support for code snippets:

<span class="hljs-code">    ```go</span>
<span class="hljs-code">    package main</span>
<span class="hljs-code">    import "fmt"</span>
<span class="hljs-code">    func print_hi(name string) {</span>
<span class="hljs-code">      fmt.Println("Hi, ", name)</span>
<span class="hljs-code">    }</span>

<span class="hljs-code">    func main() {</span>
<span class="hljs-code">      print_hi("Tom")</span>
<span class="hljs-code">    }</span>
<span class="hljs-code">    //=&gt; prints 'Hi, Tom' to STDOUT.</span>
<span class="hljs-code">    ```</span>

Check out the [<span class="hljs-string">Hugo docs</span>][<span class="hljs-symbol">hugo-docs</span>] for more info on how to get the most
out of Hugo. File all bugs/feature requests at [Hugo’s GitHub
repo][hugo-gh]. If you have questions, you can ask them on [Hugo
Community][hugo-community].

[<span class="hljs-symbol">hugo-docs</span>]: <span class="hljs-link">https://gohugo.io/documentation/</span>
[<span class="hljs-symbol">hugo-gh</span>]: <span class="hljs-link">https://github.com/gohugoio/hugo</span>
[<span class="hljs-symbol">hugo-community</span>]: <span class="hljs-link">https://discourse.gohugo.io/</span></code></pre>
<p>Cet article n'a pas de date ! Essayez d’en définir une en ajoutant l’entrée
suivante dans l’en-tête <em>Front Matter</em> de l’article:</p>
<p><code>date: "YYYY-MM-DDTHH:MM:SS-00:00"</code></p>
<aside class="note note-tip"><p><em>Remplacez</em> <code>YYYY-MM-DDTHH:MM:SS-00:00</code> avec une date valide, comme…
<code>2018-01-01T12:42:00-00:00</code>. Si votre date se situe dans le futur, Hugo ne
générera pas cet article en production.</p></aside>
<p>Sauvegardez vos changements puis affichez l’article mis à jour dans votre
navigateur à l’adresse <a href="http://localhost:3000/" target="_blank" rel="noopener noreferrer">http://localhost:3000/</a>. La date
affichée devant le titre de l’article devrait avoir été mise à jour.</p>
<h3 id="creer-un-nouvel-article">Créer un nouvel article</h3>
<p>Maintenant essayons de créer un nouvel article. Nous utiliserons pour cela la
commande fournie avec Hugo pour générer un nouvel article. Dans notre projet,
Hugo est déclaré comme une dépendance NPM, nous pouvons donc l’utiliser avec la
commande :</p>
<p><code>npm run hugo -- &lt;command&gt; --&lt;param&gt;</code></p>
<p>Créez votre premier article en lançant :</p>
<p><code>npm run hugo -- new posts/mon-premier-article.md</code></p>
<p>Cela va créer un nouvel article au format markdown dans
<code>hugo/content/posts/mon-premier-article.md</code>. Ouvrez ce fichier dans votre
éditeur de texte favori.</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-meta">---</span>
<span class="hljs-attr">title:</span> <span class="hljs-string">"Mon Premier Article"</span>
<span class="hljs-attr">date:</span> <span class="hljs-string">"2018-03-09T14:24:17-04:00"</span>
<span class="hljs-attr">draft:</span> <span class="hljs-literal">true</span>
<span class="hljs-meta">---</span>
</code></pre>
<p>Ce fichier comporte un en-tête Front Matter (des métadonnées structurées
relatives à la page) dont on peut tirer parti dans les gabarits de page. Sous le
<em>front matter</em>, nous pouvons ajouter du contenu au format markdown :</p>
<p>Ajoutez par exemple le contenu suivant dans le fichier et sauvegardez vos
changements :</p>
<pre><code class="language-md hljs markdown"><span class="hljs-section">## Bienvenue</span>

Pratique ce modèle de projet <span class="hljs-emphasis">_Hugo_</span>. j'espère que vous appréciez ce guide !</code></pre>
<p>Vous pouvez voir l’article mis à jour dans votre navigateur à l’adresse
<a href="http://localhost:3000/posts/mon-premier-article/" target="_blank" rel="noopener noreferrer">http://localhost:3000/posts/mon-premier-article/</a>.</p>
<h3 id="utiliser-un-theme">Utiliser un thème</h3>
<p>Pour le moment votre nouveau site n'est pas très beau. Remédions à cela en
ajoutant un thème issu de
<a href="https://themes.gohugo.io/" target="_blank" rel="noopener noreferrer">la galerie de thèmes de Hugo</a>, développé par un des
meilleurs contributeurs de la communauté.</p>
<figure>
<picture title="Le thème Casper de @vjeantet">
<source type="image/webp" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.webp 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.webp 1024w" width="1024" height="512" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.avif 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.avif 1024w" width="1024" height="512" sizes="100vw">
<img src="/images/no-image.158b054bca15d8e77f8ac75c945a532c.png" alt="Le thème Casper de @vjeantet" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="512" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAD7ElEQVR4nO2bUZLbIAyGf3n61NP0VL3/CVAfECAJYTvebcN29c9kwDaObX0IQSLTr5+/Gf+VvvbjHO++gZRVAtlMCWQzJZDN9OPxmfSJd/GvtXHcfw0IxSUFbQgA+Nmz04dgE/xVGXYXs9reDM41EG18UsYiOaQBeEA8G+PWtfTmJZyzBmxrXGEQaskMoLxwf/9A50A0hFUJX5LpfdUQfOuZ155BF8dHG7+r7WVmKeVTpESt7wJlDaQZ/aiGaKUHQgSQPDkpo7RhofdMKU+v5zdfAGDaU7uTjgMAgZkFCIMJKAKCdbM3656HOCi9JAEhBiDpwq3nQXph2wdw356uo6tBUFqzGeMkuVJ/X+sgRbyjgEEM4ACIEd/XG3TqIaQ/hwYhEA4piQYgqGGBGFQA5nqcGb3U1/H1yPiBP8zneBg0DrS4cZQaNogBOqSDtHYbQImBLLpj9YIICjoUYPS4Ugh8AFRYYNRhI4wFJ9fUtXAoIxdjNJgeR8TyxGqoDS72ZigxkNZr/G6ej5GeTiKoq4CuA6tRYAg/PyCp1ZHPeQiz+QrzdezupaiA7u9jWw8B+hQRMuYCqOv6NlU8ABRGIQL1XkfqXB5DV+HzGKItGe0XHP3w1AuoMnJD4vBEuW67p8L9nvoMawMYwFVQF+PzUcu+70Cf0LMawvy5ZlbjZl1Gc0gwTgBw4EQ6eHCvsoJgJlmAAdKCe+gpb9Sph/SiiOGlB7ayBv4RE4jakNSCu+2Zl72QbBPNpJl8xJD5yzxEs6XXIQrE1/MQwMxEGhhAeYYYqI9YrkfafSfX86FhimO8nv66A2x6lL2H7q0NxCYwgDs/nShDNgO1YaoO37xcxHFfkOC1h6Z182lVQnHLcGgErKduBKLptR8XAzjAqA8PuZp63dDCFdRc65k2hKD1/Od3b2i/4Pvog29uuL+l50C8vqkBP1v5j+FmSiCbKYFspgSymRLIZkogmymBbKYEspkSyGZKIJspgWymBLKZEshmSiCbKYFsps/7P+Q7a87GeKwE8op8upJPg9V/bT9MwksgV3L5Aj6X2LTjRdYkj+NXYBLISibFCSr3bBw3baXaXgjSdZMkCJxCSSCBVi8o9WO9oUuOkZw1k2Osc790Wu4CSgLx0hAO5xkRFDmH9PmAMbgZxnx2jlMCceqGV0ae9p0Fd0juHtnQAcC5Tnz9XIdoBT0f5Pb7epTQt8x3vW6TQJzk7YaRzuqy46dkwMXrFVO2Kt9LI84hKxKr4YZGcPbhgRS06HwgSOy+UAIJxMuNIR0GpoU6x+WdtWECiTS/yWDkYUwpzRGQm0QyhpzJvduiS23ovszQC0Bf3tQfiobsB/XBi3AAAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.png 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.png 1024w" sizes="100vw">
</picture>
<figcaption>Le thème Casper de @vjeantet</figcaption>
</figure>
<p>Nous allons utiliser le thème
<a href="https://github.com/vjeantet/hugo-theme-casper" target="_blank" rel="noopener noreferrer">Casper</a> de
<a href="https://github.com/vjeantet" target="_blank" rel="noopener noreferrer"><em>@vjeantet</em></a>. Pour ce faire nous allons ajouter le
thème dans le dossier <code>hugo/themes</code>, plus exactement dans le dossier
<code>hugo/themes/hugo-theme-casper/</code>.</p>
<p>Clonez ou
<a href="https://github.com/vjeantet/hugo-theme-casper/archive/master.zip" target="_blank" rel="noopener noreferrer">téléchargez le thème</a>
et décompressez l’archive dans <code>hugo/themes/hugo-theme-casper/</code>.</p>
<p>Ensuite, mettez à jour la configuration du site aves les options de
configuration spécifiques au thème.</p>
<p>Ouvrez le fichier <code>hugo/config.toml</code> dans votre éditeur de texte favori et
remplacer son contenu par celui-ci :</p>
<pre><code class="language-toml hljs ini"><span class="hljs-attr">baseURL</span>= <span class="hljs-string">"/"</span>
<span class="hljs-attr">languageCode</span>= <span class="hljs-string">"fr"</span>
<span class="hljs-attr">title</span>= <span class="hljs-string">"Hugo Boilerplate"</span>
<span class="hljs-attr">paginate</span> = <span class="hljs-number">5</span>
<span class="hljs-attr">copyright</span> = <span class="hljs-string">"Tous droits réservés - 2018"</span>
<span class="hljs-attr">theme</span> = <span class="hljs-string">"hugo-theme-casper"</span>
<span class="hljs-attr">disableKinds</span> = [<span class="hljs-string">"taxonomy"</span>, <span class="hljs-string">"taxonomyTerm"</span>]

<span class="hljs-section">[params]</span>
  description = "Bien démarrrer avec Hugo"
  metadescription = "Utilisé dans la balise meta 'description' pour l’accueil et les pages d’index, faute de quoi c'est l’entrée 'description' du front matter de la page qui sera utilisé."
  cover = ""
  author = "VOTRE_NOM"
  authorlocation = "Terre, Galaxie de la Voie Lactée"
  authorwebsite = ""
  authorbio= ""
  logo = ""
  hjsStyle = "default"
  paginatedsections = <span class="hljs-section">["posts"]</span></code></pre>
<p>Reportez-vous à la
<a href="https://github.com/vjeantet/hugo-theme-casper#configuration" target="_blank" rel="noopener noreferrer">documentation du thème</a>
pour prendre connaissance de toutes les options de configuration disponibles.</p>
<p>Pour finir, supprimez les exemples de gabarits de page fournis avec notre modèle
de départ. Pour cela lancez la commande :</p>
<pre><code class="language-sh hljs bash">rm -r hugo/layouts/</code></pre>
<p>Regardez maintenant dans votre navigateur et vérifiez que votre site a bien été
mis à jour !</p>
<h2 id="3-personnaliser-votre-site">3. Personnaliser votre site</h2>
<p>Maintenant que nous avons mis en place un site fonctionnel avec un thème, vous
avez probablement envie de le personnaliser.</p>
<p>Nous allons commencer par éditer les paramètres du site dans le fichier
<code>hugo/config.toml</code>. Mettez à jour les valeurs suivantes comme bon vous semble :</p>
<ul>
<li><code>title = "Hugo Boilerplate"</code></li>
<li><code>description = "Bien démarrer avec Hugo</code></li>
<li><code>metadescription = "Utilisé dans la balise meta 'description' pour l’accueil et les pages d’index, faute de quoi c'est l’entrée 'description' du front matter de la page qui sera utilisé"</code></li>
<li><code>author = "VOTRE_NOM"</code></li>
</ul>
<figure>
<picture title="Le thème Casper avec du contenu et les styles par défaut">
<source type="image/webp" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.webp 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.webp 1024w" width="1024" height="512" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.avif 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.avif 1024w" width="1024" height="512" sizes="100vw">
<img src="/images/no-image.158b054bca15d8e77f8ac75c945a532c.png" alt="Le thème Casper avec du contenu et les styles par défaut" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="512" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAD7ElEQVR4nO2bUZLbIAyGf3n61NP0VL3/CVAfECAJYTvebcN29c9kwDaObX0IQSLTr5+/Gf+VvvbjHO++gZRVAtlMCWQzJZDN9OPxmfSJd/GvtXHcfw0IxSUFbQgA+Nmz04dgE/xVGXYXs9reDM41EG18UsYiOaQBeEA8G+PWtfTmJZyzBmxrXGEQaskMoLxwf/9A50A0hFUJX5LpfdUQfOuZ155BF8dHG7+r7WVmKeVTpESt7wJlDaQZ/aiGaKUHQgSQPDkpo7RhofdMKU+v5zdfAGDaU7uTjgMAgZkFCIMJKAKCdbM3656HOCi9JAEhBiDpwq3nQXph2wdw356uo6tBUFqzGeMkuVJ/X+sgRbyjgEEM4ACIEd/XG3TqIaQ/hwYhEA4piQYgqGGBGFQA5nqcGb3U1/H1yPiBP8zneBg0DrS4cZQaNogBOqSDtHYbQImBLLpj9YIICjoUYPS4Ugh8AFRYYNRhI4wFJ9fUtXAoIxdjNJgeR8TyxGqoDS72ZigxkNZr/G6ej5GeTiKoq4CuA6tRYAg/PyCp1ZHPeQiz+QrzdezupaiA7u9jWw8B+hQRMuYCqOv6NlU8ABRGIQL1XkfqXB5DV+HzGKItGe0XHP3w1AuoMnJD4vBEuW67p8L9nvoMawMYwFVQF+PzUcu+70Cf0LMawvy5ZlbjZl1Gc0gwTgBw4EQ6eHCvsoJgJlmAAdKCe+gpb9Sph/SiiOGlB7ayBv4RE4jakNSCu+2Zl72QbBPNpJl8xJD5yzxEs6XXIQrE1/MQwMxEGhhAeYYYqI9YrkfafSfX86FhimO8nv66A2x6lL2H7q0NxCYwgDs/nShDNgO1YaoO37xcxHFfkOC1h6Z182lVQnHLcGgErKduBKLptR8XAzjAqA8PuZp63dDCFdRc65k2hKD1/Od3b2i/4Pvog29uuL+l50C8vqkBP1v5j+FmSiCbKYFspgSymRLIZkogmymBbKYEspkSyGZKIJspgWymBLKZEshmSiCbKYFsps/7P+Q7a87GeKwE8op8upJPg9V/bT9MwksgV3L5Aj6X2LTjRdYkj+NXYBLISibFCSr3bBw3baXaXgjSdZMkCJxCSSCBVi8o9WO9oUuOkZw1k2Osc790Wu4CSgLx0hAO5xkRFDmH9PmAMbgZxnx2jlMCceqGV0ae9p0Fd0juHtnQAcC5Tnz9XIdoBT0f5Pb7epTQt8x3vW6TQJzk7YaRzuqy46dkwMXrFVO2Kt9LI84hKxKr4YZGcPbhgRS06HwgSOy+UAIJxMuNIR0GpoU6x+WdtWECiTS/yWDkYUwpzRGQm0QyhpzJvduiS23ovszQC0Bf3tQfiobsB/XBi3AAAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.png 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.png 1024w" sizes="100vw">
</picture>
<figcaption>Le thème Casper avec du contenu et les styles par défaut</figcaption>
</figure>
<p>Bien, ajoutons maintenant une image de fond pour la bannière d’en-tête. Dans le
fichier <code>hugo/config.toml</code>, vous trouverez une section <code>[params]</code>. Modifiez le
paramètre <code>cover</code> pour qu'il ait la valeur <code>/img/darius-soodmand-116253.jpg</code>,
sauvegardez vos changements.</p>
<figure>
<picture title="Ajout d’une image de fond">
<source type="image/webp" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.webp 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.webp 1024w" width="1024" height="512" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.avif 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.avif 1024w" width="1024" height="512" sizes="100vw">
<img src="/images/no-image.158b054bca15d8e77f8ac75c945a532c.png" alt="Ajout d’une image de fond" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="512" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAD7ElEQVR4nO2bUZLbIAyGf3n61NP0VL3/CVAfECAJYTvebcN29c9kwDaObX0IQSLTr5+/Gf+VvvbjHO++gZRVAtlMCWQzJZDN9OPxmfSJd/GvtXHcfw0IxSUFbQgA+Nmz04dgE/xVGXYXs9reDM41EG18UsYiOaQBeEA8G+PWtfTmJZyzBmxrXGEQaskMoLxwf/9A50A0hFUJX5LpfdUQfOuZ155BF8dHG7+r7WVmKeVTpESt7wJlDaQZ/aiGaKUHQgSQPDkpo7RhofdMKU+v5zdfAGDaU7uTjgMAgZkFCIMJKAKCdbM3656HOCi9JAEhBiDpwq3nQXph2wdw356uo6tBUFqzGeMkuVJ/X+sgRbyjgEEM4ACIEd/XG3TqIaQ/hwYhEA4piQYgqGGBGFQA5nqcGb3U1/H1yPiBP8zneBg0DrS4cZQaNogBOqSDtHYbQImBLLpj9YIICjoUYPS4Ugh8AFRYYNRhI4wFJ9fUtXAoIxdjNJgeR8TyxGqoDS72ZigxkNZr/G6ej5GeTiKoq4CuA6tRYAg/PyCp1ZHPeQiz+QrzdezupaiA7u9jWw8B+hQRMuYCqOv6NlU8ABRGIQL1XkfqXB5DV+HzGKItGe0XHP3w1AuoMnJD4vBEuW67p8L9nvoMawMYwFVQF+PzUcu+70Cf0LMawvy5ZlbjZl1Gc0gwTgBw4EQ6eHCvsoJgJlmAAdKCe+gpb9Sph/SiiOGlB7ayBv4RE4jakNSCu+2Zl72QbBPNpJl8xJD5yzxEs6XXIQrE1/MQwMxEGhhAeYYYqI9YrkfafSfX86FhimO8nv66A2x6lL2H7q0NxCYwgDs/nShDNgO1YaoO37xcxHFfkOC1h6Z182lVQnHLcGgErKduBKLptR8XAzjAqA8PuZp63dDCFdRc65k2hKD1/Od3b2i/4Pvog29uuL+l50C8vqkBP1v5j+FmSiCbKYFspgSymRLIZkogmymBbKYEspkSyGZKIJspgWymBLKZEshmSiCbKYFsps/7P+Q7a87GeKwE8op8upJPg9V/bT9MwksgV3L5Aj6X2LTjRdYkj+NXYBLISibFCSr3bBw3baXaXgjSdZMkCJxCSSCBVi8o9WO9oUuOkZw1k2Osc790Wu4CSgLx0hAO5xkRFDmH9PmAMbgZxnx2jlMCceqGV0ae9p0Fd0juHtnQAcC5Tnz9XIdoBT0f5Pb7epTQt8x3vW6TQJzk7YaRzuqy46dkwMXrFVO2Kt9LI84hKxKr4YZGcPbhgRS06HwgSOy+UAIJxMuNIR0GpoU6x+WdtWECiTS/yWDkYUwpzRGQm0QyhpzJvduiS23ovszQC0Bf3tQfiobsB/XBi3AAAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.png 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.png 1024w" sizes="100vw">
</picture>
<figcaption>Ajout d’une image de fond</figcaption>
</figure>
<p>Retournons maintenant voir notre site dans le navigateur. C’est déjà mieux, mais
il y a encore du travail.</p>
<h2 id="4-personnaliser-votre-theme">4. Personnaliser votre thème</h2>
<p>Maintenant que vous avez adapté le site pour le personnaliser en peu, nous
allons nous attarder sur l’aspect le plus puissant d’Hugo et de ce kit de
démarrage: <strong>un templating simple et puissant</strong>.</p>
<p>Nous venons d’ajouter le thème Casper au site, ce qui permet à Hugo d’utiliser
tous les gabarits HTML présents dans le dossier
<code>hugo/themes/hugo-theme-casper/layouts/</code> lors de la génération du site.</p>
<p>Nous allons maintenant <em>étendre</em> le thème grâce à <strong>l’héritage de gabarits</strong>
d’Hugo.</p>
<p>Tous les fichiers de gabarits présents dans le dossier <code>hugo/layouts/</code>
surchargeront n'importe quel gabarit du même nom présent dans le répertoire des
gabarits du thème, nous permettant ainsi de personnaliser notre site sans
toucher au thème d’origine.</p>
<h3 id="css-javascript-personnalise">CSS &amp; Javascript personnalisé</h3>
<p>À côté d’<em>Hugo</em>, ce kit de démarrage fourni un serveur de développement qui va
post-traiter automatiquement les fichiers CSS et JS pour le navigateur. Tous les
fichiers CSS, JS, images présents dans le dossier <code>src/</code> seront traités
automatiquement et déplacés dans le dossier <code>hugo/static/</code>.</p>
<p>Ajoutons-les à notre thème de manière à pouvoir le personnaliser comme nous
voulons. Nous allons copier des fichiers de gabarits du thème et ajouter les
fichiers CSS et JS personnalisés de notre kit dans ces gabarits.</p>
<p>Premièrement, nous allons copier le fichier partiel <em>header.html</em> du thème dans
le dossier <code>hugo/layouts/partials/</code> :</p>
<pre><code class="language-sh hljs bash">mkdir -p hugo/layouts/partials/
cp hugo/themes/hugo-theme-casper/layouts/partials/header.html hugo/layouts/partials/header.html</code></pre>
<p>Ouvrez le fichier <code>hugo/layouts/partials/header.html</code> dans votre éditeur de
texte et repérez les lignes suivantes :</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/css"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ "</span><span class="hljs-attr">css</span>/<span class="hljs-attr">screen.css</span>" | <span class="hljs-attr">relURL</span>}}" /&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/css"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ "</span><span class="hljs-attr">css</span>/<span class="hljs-attr">nav.css</span>" | <span class="hljs-attr">relURL</span>}}" /&gt;</span></code></pre>
<p>Ajoutez en dessous la ligne suivante :</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/css"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ "</span><span class="hljs-attr">css</span>/<span class="hljs-attr">styles.min.css</span>" | <span class="hljs-attr">relURL</span>}}"
/&gt;</span></code></pre>
<p>Ensuite, recopions le fichier partiel <code>footer.html</code> dans le dossier
<code>hugo/layouts/partials/</code> de manière à pouvoir ajouter notre fichier JS
personnalisé :</p>
<pre><code class="language-sh hljs bash">cp hugo/themes/hugo-theme-casper/layouts/partials/footer.html hugo/layouts/partials/footer.html</code></pre>
<p>Ouvrez maintenant le fichier <code>hugo/layouts/partials/footer.html</code> et repérez les
lignes suivantes :</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/javascript"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"{{"</span><span class="hljs-attr">js</span>/<span class="hljs-attr">jquery.js</span>" | <span class="hljs-attr">relURL</span>}}"&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/javascript"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"{{"</span><span class="hljs-attr">js</span>/<span class="hljs-attr">jquery.fitvids.js</span>" | <span class="hljs-attr">relURL</span>}}"&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/javascript"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"{{"</span><span class="hljs-attr">js</span>/<span class="hljs-attr">index.js</span>" | <span class="hljs-attr">relURL</span>}}"&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></code></pre>
<p>Ajoutez juste en dessous:</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/javascript"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"{{"</span><span class="hljs-attr">js</span>/<span class="hljs-attr">scripts.min.js</span>" | <span class="hljs-attr">relURL</span>}}"&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></code></pre>
<p>Maintenant tout notre code CSS et JS personnalisé sera utilisé sur le site.</p>
<p>Faisons un essai en augmentant la hauteur de l’en-tête principal. Ouvrez le
fichier <code>src/css/styles.css</code> et ajoutez le code suivant à la fin du fichier :</p>
<pre><code class="language-css hljs css"><span class="hljs-selector-class">.tag-head</span><span class="hljs-selector-class">.main-header</span> {
  <span class="hljs-attribute">height</span>: <span class="hljs-number">80vh</span>;
}</code></pre>
<figure>
<picture title="Le résultat final">
<source type="image/webp" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.webp 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.webp 1024w" width="1024" height="512" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.avif 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.avif 1024w" width="1024" height="512" sizes="100vw">
<img src="/images/no-image.158b054bca15d8e77f8ac75c945a532c.png" alt="Le résultat final" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="512" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAD7ElEQVR4nO2bUZLbIAyGf3n61NP0VL3/CVAfECAJYTvebcN29c9kwDaObX0IQSLTr5+/Gf+VvvbjHO++gZRVAtlMCWQzJZDN9OPxmfSJd/GvtXHcfw0IxSUFbQgA+Nmz04dgE/xVGXYXs9reDM41EG18UsYiOaQBeEA8G+PWtfTmJZyzBmxrXGEQaskMoLxwf/9A50A0hFUJX5LpfdUQfOuZ155BF8dHG7+r7WVmKeVTpESt7wJlDaQZ/aiGaKUHQgSQPDkpo7RhofdMKU+v5zdfAGDaU7uTjgMAgZkFCIMJKAKCdbM3656HOCi9JAEhBiDpwq3nQXph2wdw356uo6tBUFqzGeMkuVJ/X+sgRbyjgEEM4ACIEd/XG3TqIaQ/hwYhEA4piQYgqGGBGFQA5nqcGb3U1/H1yPiBP8zneBg0DrS4cZQaNogBOqSDtHYbQImBLLpj9YIICjoUYPS4Ugh8AFRYYNRhI4wFJ9fUtXAoIxdjNJgeR8TyxGqoDS72ZigxkNZr/G6ej5GeTiKoq4CuA6tRYAg/PyCp1ZHPeQiz+QrzdezupaiA7u9jWw8B+hQRMuYCqOv6NlU8ABRGIQL1XkfqXB5DV+HzGKItGe0XHP3w1AuoMnJD4vBEuW67p8L9nvoMawMYwFVQF+PzUcu+70Cf0LMawvy5ZlbjZl1Gc0gwTgBw4EQ6eHCvsoJgJlmAAdKCe+gpb9Sph/SiiOGlB7ayBv4RE4jakNSCu+2Zl72QbBPNpJl8xJD5yzxEs6XXIQrE1/MQwMxEGhhAeYYYqI9YrkfafSfX86FhimO8nv66A2x6lL2H7q0NxCYwgDs/nShDNgO1YaoO37xcxHFfkOC1h6Z182lVQnHLcGgErKduBKLptR8XAzjAqA8PuZp63dDCFdRc65k2hKD1/Od3b2i/4Pvog29uuL+l50C8vqkBP1v5j+FmSiCbKYFspgSymRLIZkogmymBbKYEspkSyGZKIJspgWymBLKZEshmSiCbKYFsps/7P+Q7a87GeKwE8op8upJPg9V/bT9MwksgV3L5Aj6X2LTjRdYkj+NXYBLISibFCSr3bBw3baXaXgjSdZMkCJxCSSCBVi8o9WO9oUuOkZw1k2Osc790Wu4CSgLx0hAO5xkRFDmH9PmAMbgZxnx2jlMCceqGV0ae9p0Fd0juHtnQAcC5Tnz9XIdoBT0f5Pb7epTQt8x3vW6TQJzk7YaRzuqy46dkwMXrFVO2Kt9LI84hKxKr4YZGcPbhgRS06HwgSOy+UAIJxMuNIR0GpoU6x+WdtWECiTS/yWDkYUwpzRGQm0QyhpzJvduiS23ovszQC0Bf3tQfiobsB/XBi3AAAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.png 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.png 1024w" sizes="100vw">
</picture>
<figcaption>Le résultat final</figcaption>
</figure>
<p>Admirez le résultat final dans votre navigateur !</p>
<h2 id="5-prochaine-etape">5. Prochaine étape</h2>
<p>Vous êtes maintenant prêt·e à commencer à créer un site statique avec Hugo !</p>
<p>Vous pouvez continuer à utiliser le thème Casper ou repartir du début en
utilisant les modèles du répertoire <code>hugo/layouts/</code>.</p>
<aside class="note note-tip"><p>Les fichiers des modèles de gabarits de page se trouvent dans le
<a href="https://github.com/forestryio-templates/hugo-boilerplate/tree/master/hugo/layouts" target="_blank" rel="noopener noreferrer"><em>dépôt de notre kit de démarrage</em></a>
si vous souhaitez repartir de zéro.</p></aside>
<p>Pour en apprendre un peu plus sur Hugo, reportez-vous aux sections suivantes de
la documentation officielle :</p>
<ul>
<li><a href="https://gohugo.io/content-management/organization/" target="_blank" rel="noopener noreferrer">L'organisation des contenus dans Hugo</a></li>
<li><a href="https://gohugo.io/templates/introduction/" target="_blank" rel="noopener noreferrer">Introduction au langage de templating d’Hugo</a></li>
<li><a href="https://gohugo.io/getting-started/configuration/" target="_blank" rel="noopener noreferrer">Les options de configuration d’Hugo</a></li>
</ul>
<p>Nous verrons dans un prochain article comment configurer le versionnement avec
Git pour faciliter l’intégration continue et le déploiement chez différents
hébergeurs avec Forestry, un CMS pour les sites statiques générés avec Hugo ou
Jekyll.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2018/02/18/presentation-eleventy/</id>
    <title>Présentation d’Eleventy, un nouveau générateur de site statique</title>
    <published>2018-02-18T09:23:07+00:00</published>
    <link href="https://jamstatic.fr/2018/02/18/presentation-eleventy/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>L'auteur d’Eleventy, le talentueux <a href="https://twitter.com/zachleat" target="_blank" rel="noopener noreferrer">Zach Leatherman</a>, développeur front-end particulièrement attentif à la performance explique pourquoi il a décidé de développer à son tour un générateur de site statique. Eleventy se pose d’emblée comme une alternative intéressante à Jekyll ou aux générateurs qui reposent sur des bibliothèques JavaScript, tout en en s'affranchissant de leurs contraintes.</p></aside>
<p>Eleventy est un nouveau générateur de site statique.</p>
<aside class="note note-info"><p>Si n'êtes pas encore au fait de ce que sont les générateurs de sites statiques et des avantages qu'ils procurent, lisez <a href="https://www.smashingmagazine.com/2015/11/modern-static-website-generators-next-big-thing/" target="_blank" rel="noopener noreferrer">ce très bon article</a> paru dans Smashing Magazine écrit par <a href="https://twitter.com/biilmann" target="_blank" rel="noopener noreferrer">Matt Biilmann</a>.</p></aside>
<p>Encore un générateur de site statique ? Oui. Mais pourquoi ? Bonne question.</p>
<p>Eleventy a été créé pour trois raisons :</p>
<ul>
<li>Flexibilité</li>
<li>Parier sur JavaScript</li>
<li>Ce n'est pas un framework JavaScript</li>
</ul>
<h2 id="flexibilite">Flexibilité</h2>
<h3 id="flexibilite-du-moteur-de-template">Flexibilité du moteur de template</h3>
<p>Eleventy vous permet d’utiliser différents moteurs de template et de migrer ainsi facilement vos contenus existants. Vos fichiers de contenu peuvent utiliser un moteur de templating différent de celui de vos gabarits de page !</p>
<p><em>Linda fait du développement web pour ses clients. Linda maintient un ensemble de documentation transversale pour l’ensemble de ses projets qu'elle fournit en même temps que les composants front-end et les templates. Linda a codé ses documentations à l’aide du langage de templating Liquid avec Jekyll. Linda a maintenant un client qui souhaite qu'elle livre les composants sous forme de fichiers de gabarit Mustache. Facile, Linda passe de Jekyll à Eleventy car Eleventy sait gérer les deux côte-à-côte. Bien joué Linda.</em></p>
<table>
<thead>
<tr>
<th>Générateur de site</th>
<th>Classement <a href="https://www.staticgen.com/" target="_blank" rel="noopener noreferrer">staticgen.com</a></th>
<th>Moteur de templates</th>
</tr>
</thead>
<tbody>
<tr>
<td>Jekyll</td>
<td>#1</td>
<td>Liquid</td>
</tr>
<tr>
<td>Hugo</td>
<td>#2</td>
<td>Go Template</td>
</tr>
<tr>
<td>Hexo</td>
<td>#3</td>
<td>EJS, Pug</td>
</tr>
<tr>
<td>Gatsby</td>
<td>#4</td>
<td>React.js</td>
</tr>
</tbody>
</table>
<p>Eleventy supporte actuellement :</p>
<ul>
<li>HTML</li>
<li><a href="https://github.com/markdown-it/markdown-it" target="_blank" rel="noopener noreferrer">Markdown</a></li>
<li><a href="https://www.npmjs.com/package/liquidjs" target="_blank" rel="noopener noreferrer">Liquid</a> (used by Jekyll)</li>
<li><a href="https://mozilla.github.io/nunjucks/" target="_blank" rel="noopener noreferrer">Nunjucks</a></li>
<li><a href="https://github.com/wycats/handlebars.js" target="_blank" rel="noopener noreferrer">Handlebars</a></li>
<li><a href="https://github.com/janl/mustache.js/" target="_blank" rel="noopener noreferrer">Mustache</a></li>
<li><a href="https://www.npmjs.com/package/ejs" target="_blank" rel="noopener noreferrer">EJS</a></li>
<li><a href="https://github.com/tj/haml.js" target="_blank" rel="noopener noreferrer">Haml</a></li>
<li><a href="https://github.com/pugjs/pug" target="_blank" rel="noopener noreferrer">Pug</a></li>
<li>JavaScript Template Literals (ES2015)</li>
</ul>
<h3 id="flexibilite-de-l-arborescence-de-repertoires">Flexibilité de l’arborescence de répertoires</h3>
<p>Eleventy tient à ce que vous puissiez continuer à travailler avec l’arborescence existante de votre projet. Il n'y a aucune obligation de mettre tous vos fichiers de contenu dans un répertoire <code>source</code>, <code>content</code> ou <code>_posts</code> (sauf si vous le désirez). Vous dites à Eleventy où sont vos fichiers et il se débrouillera avec.</p>
<p>Un simple <code>eleventy</code> en ligne de commande va traiter les fichiers présents dans le répertoire courant et générer un site dans un dossier <code>_site</code>. Vous pouvez préciser votre choix à l’aide des options <code>--input</code> et <code>--output</code>.</p>
<h4 id="traite-les-fichiers-du-repertoire-courant-et-genere-un-dossier-site">Traite les fichiers du répertoire courant et génère un dossier <code>_site</code></h4>
<pre><code class="language-sh hljs bash">eleventy</code></pre>
<h4 id="traite-les-fichiers-du-dossier-src-et-genere-un-dossier-gh-pages">Traite les fichiers du dossier <code>src</code> et génère un dossier <code>_gh_pages</code></h4>
<pre><code class="language-sh hljs bash">eleventy --input=src --output=_gh_pages</code></pre>
<h4 id="traite-les-fichiers-du-repertoire-courant-et-genere-les-fichiers-au-meme-endroit">Traite les fichiers du répertoire courant et génère les fichiers au même endroit</h4>
<pre><code class="language-sh hljs bash">eleventy --input=. --output=.</code></pre>
<h4 id="transformer-un-fichier-a-la-fois">Transformer un fichier à la fois</h4>
<p>Eleventy fonctionne aussi comme un petit utilitaire permettant de traiter un seul fichier. Pour transformer le fichier <code>README.md</code> en <code>README.html</code>.</p>
<pre><code class="language-sh hljs bash">eleventy --input=README.md --output=.</code></pre>
<h2 id="pariez-sur-javascript">Pariez sur JavaScript</h2>
<p>Pariez toujours sur JavaScript. JavaScript vous donne accès à <code>npm</code>. L'écosystème d’npm est immense. Follement immense. Et il continue de gagner en popularité. Selon <a href="http://www.modulecounts.com/" target="_blank" rel="noopener noreferrer">modulecounts.com</a>, npm propose déjà trois fois plus de modules que son deuxième concurrent, Maven Central (Java). Quand vous souhaitez ajouter une fonctionnalité, il y a de grandes chances qu'il existe déjà un module npm pour ça.</p>
<table>
<thead>
<tr>
<th>Générateur de site</th>
<th>Langage</th>
<th>Nombre de modules</th>
</tr>
</thead>
<tbody>
<tr>
<td>Jekyll</td>
<td>Ruby</td>
<td>~140,000 sur rubygems.org</td>
</tr>
<tr>
<td>Hugo</td>
<td>Go</td>
<td>~20,000 sur Gopm</td>
</tr>
<tr>
<td>Hexo</td>
<td>JavaScript</td>
<td>~580,000 sur npm</td>
</tr>
<tr>
<td>Gatsby</td>
<td>JavaScript</td>
<td>~580,000 sur npm</td>
</tr>
</tbody>
</table>
<h2 id="eleventy-n-est-pas-un-framework-javascript">Eleventy n'est pas un framework JavaScript</h2>
<p>Bien qu'Eleventy utilise JavaScript à travers node.js pour transformer les fichiers de gabarit en fichiers de contenu, il est important de savoir que (par défaut) le HTML généré n'inclut aucun fichier JavaScript client spécifique à Eleventy côté client. C’est une des facettes fondamentales des intentions et des objectifs du projet. Ce n'est pas un framework JavaScript. Nous voulons que notre contenu soit découplé autant que possible d’Eleventy, et parce Eleventy utilise des moteurs de templates indépendants d’Eleventy, cela nous permet de nous rapprocher de cet objectif.</p>
<p>Il se peut que par la suite nous insérions un peu de JavaScript pour la partie client spécifique à Eleventy, mais ce ne sera pas une option activée par défaut. Bien entendu, libre à vous d’ajouter votre propre code JavaScript pour la partie client, en fonction de votre projet et de vos besoins.</p>
<p>La moindre des choses à faire est de toujours analyser ce qui est produit en sortie par les générateurs de site statique, surtout ceux qui sont très liés à des frameworks JavaScript. La majorité des frameworks JavaScript incluent du code JavaScript assez dogmatique côté client, même lorsque qu'ils utilisent le rendu côté serveur.</p>
<p>Ces bibliothèques peuvent être lourdes et parfois bloquer le <a href="https://developers.google.com/web/fundamentals/performance/critical-rendering-path/" target="_blank" rel="noopener noreferrer">rendu du contenu critique</a> ou encore causer des congestions au niveau réseau dans le rendu de contenu critique avec <code>preload</code>.</p>
<p>La performance c'est critique. Les fichiers statiques peuvent présenter un gain de performance formidable. Pour maintenir ce niveau de performance, Eleventy vous laisse le contrôle complet sur l’inclusion de code JavaScript dans vos pages.</p>
<h2 id="testez-eleventy">Testez Eleventy !</h2>
<p>J'espère que vous donnerez sa chance à Eleventy ! Installez-le !</p>
<pre><code class="language-sh hljs bash">npm install -g @11ty/eleventy</code></pre>
<p>Vous trouverez des tutoriels sur <a href="https://www.11ty.dev/" target="_blank" rel="noopener noreferrer">11ty.dev</a>. Dites à Zach ce que vous aimez ou que vous n'aimez pas ! <a href="https://twitter.com/zachleat" target="_blank" rel="noopener noreferrer">il adorerait avoir vos retours</a>.</p>
<p>Une chose sympa et facile à faire que vous pouvez faire pour soutenir le projet est de le <a href="https://github.com/11ty/eleventy" target="_blank" rel="noopener noreferrer">marquer d’une étoile sur GitHub</a>. Comme la liste gigantesque des générateurs de site statique de <a href="https://www.staticgen.com/" target="_blank" rel="noopener noreferrer">staticgen.com</a> est classée en fonction du nombre d’étoiles sur GitHub, ça aiderait le projet à gagner pas mal de places au classement. Merci !</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2018/02/09/scratch-hugo/</id>
    <title>La fonction .Scratch d’Hugo</title>
    <published>2018-02-09T19:50:50+00:00</published>
    <updated>2018-08-29T15:09:47+00:00</updated>
    <link href="https://jamstatic.fr/2018/02/09/scratch-hugo/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-update"><p>Vous êtes ici pour apprendre à écraser une variable dans un gabarit de page ? Bonne nouvelle, vous n'avez plus besoin de la fonction <code>.Scratch</code> pour cela depuis la version 0.48 d'Hugo. Malgré cela, <code>.Scratch</code> reste encore utile pour plein d'autres choses !</p></aside>
<p>Le contexte de Page d'Hugo n'est pas seulement la source d'information la plus importante pour vos pages, c'est aussi la source de données principale de tous vos templates. Plus souvent qu'il n'y paraît, vous aurez à ajouter vos propres variables personnalisées en plus de celles définies par défaut.</p>
<p>Avec la fonction <strong>.Scratch</strong> d'Hugo, n'importe quelle <a href="https://gohugo.io/variables/page/#readout" target="_blank" rel="noopener noreferrer">Page</a> ou <a href="https://gohugo.io/variables/shortcodes/#readout" target="_blank" rel="noopener noreferrer">Shortcode</a> peut être enrichie avec autant de variables que nécessaire en plus de celles par défaut.</p>
<h2 id="c-est-quoi-scratch聽">C'est quoi Scratch ?</h2>
<p>Scratch a été ajouté à l'origine pour contourner une <a href="https://github.com/golang/go/issues/10608" target="_blank" rel="noopener noreferrer">limitation</a> du langage de templating de Go, qui empêchait d'écraser des variables. Elle s'est rapidement enrichie d'autres méthodes et constitue désormais une fonctionnalité d'Hugo à part entière.</p>
<aside class="note note-info"><p>À des fins de lisibilité, les extraits de code qui suivent ont des commentaires incompatibles avec le langage de template de Go. Reportez vous à la <a href="http://gohugo.io/templates/introduction/#comments" target="_blank" rel="noopener noreferrer">doc</a> pour comment commenter dans Hugo.</p></aside>
<h3 id="scratch-set"><code>.Scratch.Set</code></h3>
<p><code>Set</code> est utilisé pour mémoriser une valeur voire pour pouvoir surcharger simplement une valeur par la suite.</p>
<pre><code class="language-go-html-template hljs go">{{ .Scratch.Set <span class="hljs-string">"salutations"</span> <span class="hljs-string">"Bonjour"</span> }}
{{ <span class="hljs-keyword">if</span> eq $ciel <span class="hljs-string">"sombre"</span> }}
    {{ .Scratch.Set <span class="hljs-string">"salutations"</span> <span class="hljs-string">"Bonsoir"</span> }}
{{ end }}

{{ .Scratch.Get <span class="hljs-string">"salutations"</span>}}</code></pre>
<h3 id="scratch-add"><code>.Scratch.Add</code></h3>
<p>Cette méthode s'occupe d’ajouter ou de pousser des valeurs multiples dans une
variable ou une clef.</p>
<pre><code class="language-go-html-template hljs go"><span class="hljs-comment">// Pour les chaînes de caractères</span>
{{ .Scratch.Add <span class="hljs-string">"salutations"</span> <span class="hljs-string">"Bonjour"</span> }}
{{ .Scratch.Add <span class="hljs-string">"salutations"</span> <span class="hljs-string">"Bonsoir"</span> }}

{{ .Scratch.Get <span class="hljs-string">"salutations"</span> }}
<span class="hljs-comment">// Affichera : BonjourBonsoir</span></code></pre>
<p>Utilisée avec <code>slice</code>, elle permet d’ajouter une ou plusieurs valeurs à un
tableau.</p>
<pre><code class="language-go-html-template hljs go">{{ .Scratch.Add <span class="hljs-string">"salutations"</span> (slice <span class="hljs-string">"Bonjour"</span>) }}
{{ .Scratch.Add <span class="hljs-string">"salutations"</span> (slice <span class="hljs-string">"Bonsoir"</span>) }}
{{ .Scratch.Add <span class="hljs-string">"salutations"</span> (slice <span class="hljs-string">"Aloha"</span> <span class="hljs-string">"Buenos dias"</span>) }}</code></pre>
<h3 id="scratch-get"><code>.Scratch.Get</code></h3>
<p>Maintenant récupérons tout ça.</p>
<pre><code class="language-go-html-template hljs go"><span class="hljs-comment">// Avec la fonction range</span>
{{ <span class="hljs-keyword">range</span> where .Scratch.Get <span class="hljs-string">"salutations"</span> }}
&lt;ol&gt;
    &lt;li&gt;
        {{ . }}
    &lt;/li&gt;
&lt;/ol&gt;
{{ end }}
<span class="hljs-comment">// ☝️ Affichera une liste ordonnée avec nos 4 salutations.</span>

<span class="hljs-comment">// Ou avec la fonction delimit</span>
{{ delimit (.Scratch.Get <span class="hljs-string">"salutations"</span>), <span class="hljs-string">", "</span> }}
<span class="hljs-comment">// ☝️ Affichera Bonjour, Bonsoir, Aloha, Buenos dias</span></code></pre>
<h3 id="scratch-delete-1">.Scratch.Delete<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref">1</a></sup></h3>
<p>Supprime la paire clé/valeur du contexte.
Lors de l'utilisation de <code>.Scratch.Add</code> dans une boucle, <code>.Scratch.Delete</code> est pratique pour réinitialiser une valeur.</p>
<pre><code class="language-go-html-template hljs go">{{ .Scratch.Delete <span class="hljs-string">"salutations"</span> }}</code></pre>
<h3 id="newscratch-2">newScratch<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref">2</a></sup></h3>
<p>Ce n'est pas une méthode issue de Scratch, mais une fonction qui permet la création d'une instance locale de Scratch dans un template.</p>
<pre><code class="language-go-html-template hljs go">{{ $headerScratch := newScratch }}
{{ $headerScratch.Add <span class="hljs-string">"brand_image"</span> .Params.image }}</code></pre>
<h2 id="manipuler-des-tableaux-et-des-maps">Manipuler des tableaux et des maps</h2>
<h3 id="scratch-setinmap">.Scratch.SetInMap</h3>
<p>Cette fonction-là permet de cibler la clef d’un tableau et de lui assigner une
nouvelle valeur.</p>
<p>Elle prend comme premier paramètre votre clef <code>.Scratch</code>, comme second paramètre
la clef issue du tableau ou de la map, le troisième étant la valeur que vous
définissez.</p>
<p>Si vous ne connaissez pas <a href="https://gohugo.io/functions/dict/#readout" target="_blank" rel="noopener noreferrer">dict</a> je
vous explique tout ça
<a href="https://regisphilibert.com/blog/2017/04/hugo-cheat-sheet-go-template-translator/#associative-arrays" target="_blank" rel="noopener noreferrer">dans cet article</a></p>
<pre><code class="language-go-html-template hljs go">{{ .Scratch.Add <span class="hljs-string">"salutations"</span> (dict <span class="hljs-string">"english"</span> <span class="hljs-string">"Hello"</span> <span class="hljs-string">"french"</span> <span class="hljs-string">"Bonjour"</span>) }}

{{ .Scratch.SetInMap <span class="hljs-string">"salutations"</span> <span class="hljs-string">"english"</span> <span class="hljs-string">"Howdy 🤠"</span> }}

<span class="hljs-comment">// Nous avons modifié la valeur de la clef en anglais de Hello à Howdy 🤠 !</span></code></pre>
<h2 id="attention-au-perimetre-et-au-contexte">Attention au périmètre et au contexte…</h2>
<p><code>.Scratch</code> n'est disponible que pour l’objet page ou l’objet shortcode. Vous ne
pouvez pas l’utiliser sur un autre élément.</p>
<p>Souvenez-vous que si vous vous trouvez à l’intérieur d’une boucle <code>range</code> dans
votre page d’index, alors le <code>.Scratch</code> de votre page d’index sera <code>$.Scratch</code>
alors que la page courante que vous traitez dans votre boucle sera <code>.Scratch</code>.</p>
<p>Retenez également que vous pouvez affecter une paire clef-valeur à <code>.Scratch</code>
depuis n'importe où, même dans un fichier partiel du moment que vous lui passez
le contexte. Heeeeein? Prenons un exemple concret pour illustrer les dangers qui
vous guettent avec l’utilisation de <code>.Scratch</code> et du contexte.</p>
<h3 id="un-exemple-classe-avec-scratch">Un exemple classe avec <code>.Scratch</code></h3>
<p>Je trouve ça bien pratique d’affecter des classes à mon élément <code>body</code> (comme le
fait WordPress) pour pouvoir faire des ajustements CSS/JavaScript en fonction
de la page sur laquelle on se trouve.</p>
<p>Je trouvais ça très fastidieux à faire avec Hugo, jusqu'à ce que je comprenne
comment utiliser <code>.Scratch</code>.</p>
<p>Je veux ajouter une classe CSS <code>rp-body</code> à toutes mes pages ainsi que la valeur
de <code>.Section</code> à mes classes.</p>
<p>Et seule la page d’accueil devrait hériter de la classe <code>rp-home</code>.</p>
<p>Je pourrais écrire ça une bonne foi pour toute, dans un fichier partiel ou un
fichier de gabarit de page qui comprend l’ouverture de la balise <code>body</code> mais… je
pourrais avoir besoin de cette liste de classes ailleurs dans mon code pour
réaliser des tours de magie avec ajax. Disons sous forme d’objet JavaScript.</p>
<p>Comme faire pour créer cette liste, la modifier si je suis sur la page d’accueil
et la stocker dans mon objet <code>.Page</code> pour pouvoir la réutiliser par la suite ?
Pour bien faire, nous allons stocker nos classes dans un tableau.</p>
<pre><code class="language-go-html-template hljs go"><span class="hljs-comment">// Avant la balise body, je peux stocker mon unique et première classe universelle.</span>
{{ .Scratch.Add <span class="hljs-string">"classes"</span> (slice <span class="hljs-string">"rp-body"</span>) }}

<span class="hljs-comment">// Puis ma section. Ce printf me permet d’ajouter la valeur de .Section avec mon préfixe personnalisé.</span>
{{ .Scratch.Add <span class="hljs-string">"classes"</span> (slice (printf <span class="hljs-string">"rp-%s"</span> .Section))) }}

<span class="hljs-comment">// Et maintenant sommes nous sur la page d’accueil ?</span>
{{ <span class="hljs-keyword">if</span> .IsHome }}
    {{ .Scratch.Add <span class="hljs-string">"classes"</span> (slice <span class="hljs-string">"rp-home"</span>) }}
{{end}}
<span class="hljs-comment">// Est-ce que ce sont les vacances ? 🎄</span>
{{ <span class="hljs-keyword">if</span> isset .Site.Params <span class="hljs-string">"season"</span> }}
    {{ .Scratch.Add <span class="hljs-string">"classes"</span> (slice (printf <span class="hljs-string">"rp-body--%s"</span> .Site.Params.season))) }}
{{ end }}</code></pre>
<p>Nous pourrions faire bien plus de vérifications et de contorsions, mais en fin
de compte, nous n'avons plus qu'à écrire dans notre fichier de gabarit ce joli :</p>
<pre><code class="language-go-html-template hljs go">&lt;body class=<span class="hljs-string">'{{ delimit (.Scratch.Get "classes") " " }}'</span>&gt;</code></pre>
<p>Et pour JavaScript, nous pouvons créer notre objet à l’endroit où nous en avons
besoin.</p>
<pre><code class="language-js hljs javascript">&lt;script&gt;
    <span class="hljs-keyword">let</span> bodyClasses = [{{ range .Scratch.Get <span class="hljs-string">"classes"</span> }}<span class="hljs-string">"{{ . }}"</span>, {{end}}];
&lt;<span class="hljs-regexp">/script&gt;</span></code></pre>
<p>Très bon cas de figure, continuons notre chemin.</p>
<h3 id="scratch-dans-un-fichier-partiel"><code>.Scratch</code> dans un fichier partiel</h3>
<p>Comme je l’expliquais plus tôt, comme <code>.Scratch</code> fait partie de l’objet page
généralement passé en tant que contexte (<a href="/2018/02/08/hugo-le-point-sur-le-contexte/">le fameux point</a>) à l’appel de la fonction
<code>partial</code>.Déplaçons le bout de code qui stocke nos classes dans un fichier
partiel pour gagner en lisibilité :</p>
<pre><code class="language-go-html-template hljs go"><span class="hljs-comment">// partials/scratching/body_classes.html</span>
{{ .Scratch.Add <span class="hljs-string">"classes"</span> (slice <span class="hljs-string">"rp-body"</span>) }}
[… ici le code vu précédemment  …]</code></pre>
<p>Dans mon fichier de gabarit, je peux maintenant écrire :</p>
<pre><code class="language-go-html-template hljs go">{{ partial <span class="hljs-string">"scratching/body_classes.html"</span> . }}
&lt;body class=<span class="hljs-string">'{{ delimit (.Scratch.Get "classes") " " }}'</span>&gt;
[…]</code></pre>
<p>Le retour de la fonction <code>.Scratch</code> de la page a été transmis au fichier partiel
via le contexte, de manière à pouvoir continuer de le modifier sans avoir à
toucher au code ailleurs. En plus ça permet d’avoir des fichiers de gabarits de
page plus propres !</p>
<h3 id="scratch-dans-un-fichier-partiel-dans-une-boucle-range-rџ-Yi"><code>.Scratch</code> dans un fichier partiel dans une boucle <code>range</code> 🤯</h3>
<p>Quand vous utilisez la fonction <code>range</code> pour boucler sur des éléments, vous ne
pouvez pas lui passer le contexte en paramètre comme on peut le faire avec la
fonction <code>partial</code>, le contexte que vous manipulez est celui de la boucle, c'est
bien ce que vous souhaitez.</p>
<pre><code class="language-go-html-template hljs go">{{ .Scratch.Set <span class="hljs-string">"section_color"</span> }}
{{ <span class="hljs-keyword">range</span> where .Data.Pages}}
   &lt;h2&gt;{{ .Title }}&lt;/h2&gt;
     &lt;div class=<span class="hljs-string">"Child Child--{{ $.Scratch.Get section_color}}"</span>&gt;
     […]
     &lt;div&gt;
{{ end }}
<span class="hljs-comment">// Affichera le contenu de section_color.</span>

<span class="hljs-comment">// Alors que…</span>
{{ <span class="hljs-keyword">range</span> where .Data.Pages }}
      {{ partial <span class="hljs-string">"enfant.html"</span> . }}
{{ end }}

<span class="hljs-comment">// Le fichier partiel enfant.html ne saura pas récupérer le contenu de la fonction .Scratch de la page, même si nous lui passons le contexte en paramètre…</span></code></pre>
<p>C’est parce que le contexte que nous passons en paramètre de la fonction
<code>partial</code> est celui de l’élément en cours parcouru grâce à la fonction <code>range</code>,
pas celui de la page dont vous êtes en train de coder le gabarit.</p>
<p>Très bien me direz-vous, mais comment faire pour accéder au <code>.Scratch</code> de la
page parente depuis notre fichier partiel ?</p>
<p>Eh bien, vous pouvez toujours stocker ce qui est retourné par la fonction
<code>.Scratch</code> de la page dans une variable, pour la passer ensuite en paramètre de
votre fichier partiel :</p>
<pre><code class="language-go-html-template hljs go">{{ $indexScratch := .Scratch }}
  {{ <span class="hljs-keyword">range</span> where .Data.Pages }}
      {{ partial <span class="hljs-string">"child.html"</span> $indexScratch }}
  {{ end }}</code></pre>
<p>Dans le fichier partiel on écrira alors :</p>
<pre><code class="language-go-html-template hljs go">&lt;div class=<span class="hljs-string">"Child Child--{{ .Get "</span>section_color<span class="hljs-string">" }}"</span>&gt;
[…]
&lt;div&gt;</code></pre>
<p>Si vous avez également besoin de l’ensemble du contexte de la page que vous êtes
en train de parcourir dans la boucle, utilisez alors la fonction <code>dict</code> :</p>
<pre><code class="language-go-html-template hljs go">{{ $indexScratch := .Scratch }}
  {{ <span class="hljs-keyword">range</span> where .Data.Pages }}
      {{ partial <span class="hljs-string">"child.html"</span> (dict <span class="hljs-string">"indexScratch"</span> $indexScratch <span class="hljs-string">"page"</span> . }}
  {{ end }}</code></pre>
<p>Dans le fichier partiel vous pourrez alors écrire :</p>
<pre><code class="language-go-html-template hljs go">&lt;div class=<span class="hljs-string">"Child Child--{{ .indexScratch.Get section_color}}"</span>&gt;
    {{ .page.Content }}
&lt;div&gt;</code></pre>
<h3 id="scratch-dans-un-fichier-partiel-sans-contexte-de-page"><em>.Scratch</em> dans un fichier partiel sans contexte de page</h3>
<p>Tout ce qui figure ci-dessus est important si vous devez accéder à une instance Scratch liée à votre contexte de page, mais avec l'ajout de <code>newScratch</code><sup id="fnref2:2"><a href="#fn:2" class="footnote-ref">2</a></sup>, vous pouvez utiliser désormais utiliser Scratch n'importe où, y compris dans un fichier partiel sans contexte de Page.</p>
<p>Appelons un fichier partiel. Notez que nous ne passons aucun contexte de Page, juste une map issue du Front Matter qui contient <code>class</code>, <code>alt</code> et une potentielle <code>image_src</code> pour remplacer celle par défaut.</p>
<pre><code class="language-go-html-template hljs go">{{ partial <span class="hljs-string">"brand"</span> .Params.brand }}</code></pre>
<p>Dans notre fichier partiel nous pouvons toujours faire appel à Scratch :</p>
<pre><code class="language-go-html-template hljs go">{{ $brandScratch := newScratch }}
{{ $brandScratch.Set <span class="hljs-string">"brand_image"</span> <span class="hljs-string">"default.jpg"</span> }}
{{ with .image_src }}
  {{ $brandScratch.Set <span class="hljs-string">"brand_image"</span> <span class="hljs-string">"."</span> }}
{{ end }}
&lt;div class=<span class="hljs-string">"brand {{ .class }}"</span>&gt;
  &lt;img src=<span class="hljs-string">"{{ $brandScratch.Get "</span>brand_image<span class="hljs-string">" }}"</span> alt=<span class="hljs-string">"{{ .alt }}"</span> /&gt;
&lt;/div&gt;</code></pre>
<h2 id="scratch-apres-go-1-11"><code>.Scratch</code> après Go 1.11</h2>
<p>Oui, avec la version 11 de Golang nous pouvons maintenant nativement écraser les variables dans les templates Go mais …</p>
<p>Dans beaucoup de cas, je trouve que stocker une valeur dans le contexte de Page plus utile qu'autre chose. Par exemple, si un fichier partiel a besoin d'accéder à des variables de Page et à d'autres informations, si vous vous passiez de Scratch, vous vous retrouveriez avec un contexte sous la forme d'un long <code>dict</code>…</p>
<pre><code class="language-go-html-template hljs go">{{ $humeur := <span class="hljs-string">"Joyeux"</span> }}
{{ <span class="hljs-keyword">if</span> $pluie }}
    {{ $humeur = <span class="hljs-string">"Grincheux"</span> }}
{{ end }}
{{ partial <span class="hljs-string">"blancheneige/nain.html"</span> (dict <span class="hljs-string">"humeur"</span> $humeur <span class="hljs-string">"page"</span> . ) }}</code></pre>
<p>Utiliser Scratch pour stocker vos variables dans l'objet de Page vous garantit un code propre et réutilisable.</p>
<h3 id="avec-scratch">Avec <code>.Scratch</code></h3>
<pre><code class="language-go-html-template hljs go">{{ .Scratch.Set <span class="hljs-string">"humeur"</span> <span class="hljs-string">"Joyeux"</span> }}
{{ <span class="hljs-keyword">if</span> $pluie }}
    {{ .Scratch.Set <span class="hljs-string">"humeur"</span> <span class="hljs-string">"Grincheux"</span> }}
{{ end }}
{{ partial <span class="hljs-string">"blancheneige/nain.html"</span> . }}</code></pre>
<p>En plus, je ne pense pas que s'amuser à dénouer des maps complexes soit aussi
pratique que ce que nous permet de faire actuellement <code>.Scratch.SetInMap</code> !</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn:1">
<p>Depuis <a href="https://gohugo.io/news/0.38-relnotes/" target="_blank" rel="noopener noreferrer">Hugo 0.38</a>&#160;<a href="#fnref1:1" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
<li id="fn:2">
<p>Depuis <a href="https://gohugo.io/news/0.43-relnotes/" target="_blank" rel="noopener noreferrer">Hugo 0.43</a>&#160;<a href="#fnref1:2" rev="footnote" class="footnote-backref">&#8617;</a> <a href="#fnref2:2" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
</ol>
</div>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2018/02/08/hugo-le-point-sur-le-contexte/</id>
    <title>Hugo, le point sur le contexte</title>
    <published>2018-02-08T16:27:39+00:00</published>
    <link href="https://jamstatic.fr/2018/02/08/hugo-le-point-sur-le-contexte/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Le <a href="https://gohugo.io/templates/introduction/#context-aka-the-dot" target="_blank" rel="noopener noreferrer">contexte</a>,
c'est un concept assez perturbant quand on commence à vouloir développer des
modèles de page pour Hugo. Il est facile de s'emmêler les pinceaux pour accéder à
ses <a href="https://golang.org/pkg/text/template/#hdr-Variables" target="_blank" rel="noopener noreferrer">variables</a>. Au travers
de quelques exemples très simples, <a href="https://regisphilibert.com/tags/hugo/" target="_blank" rel="noopener noreferrer">Régis
Philibert</a> se propose de nous aider à y
voir plus clair.</p></aside>
<p><strong>Mais pourquoi ma variable n'est pas accessible ici et ici ?</strong> 🙄</p>
<p>Quand est un habitué des bons vieux langages de templates où le périmètre
fonctionnel est rarement un problème, bien comprendre les contraintes de
périmètre en <a href="https://golang.org/pkg/html/template/" target="_blank" rel="noopener noreferrer">Go Template</a> n'est pas
toujours aisé.</p>
<p>Dans cet article, nous essaierons de comprendre l’impact du périmètre et du
contexte à l’intérieur de nos gabarits et de nos fichiers partiels, et comment jongler avec le point 🤹.</p>
<h2 id="le-contexte-et-le-point">Le contexte et le point</h2>
<p>J'utilise ici le mot <em>périmètre</em> dans le titre, car c'est la première chose qui
vient à l’esprit quand on fait face à cette problématique et j'imagine que c'est
le terme que les gens vont utiliser pour rechercher de l’aide. Mais en
définitive c'est plutôt du <em>contexte</em> dont nous allons parler ici.</p>
<p>Le périmètre c'est ce qui est disponible dans une situation donnée dans votre
code. À l’intérieur d’une classe ou d’une fonction par exemple.</p>
<p>Mais dans les gabarits d’Hugo, la plupart du temps, vous n'avez accès qu'à un
seul objet : le <strong>contexte</strong>. Et il est stocké dans un point.</p>
<p>Oui, ce point-là. <code>{{.}}</code></p>
<p>Et vous finissez donc par utiliser les propriétés de cet objet comme ça : \
<code>.Title</code>, <code>.Permalink</code>, <code>.IsHome</code></p>
<h2 id="le-point-de-la-page">Le point de la page</h2>
<p>Le contexte d’origine, celui disponible dans votre fichier de gabarit racine
<code>baseof.html</code> et dans les autres fichiers de gabarits sera toujours le contexte
de la page. En fait, tout ce que vous avez besoin d’afficher dans cette page est
contenu dans ce point.\
<code>.Title</code>, <code>.Permalink</code>, <code>.Resources</code> et tout ce qui vous chante.</p>
<p>Même les informations de votre site sont stockées dans le contexte de page à
l’aide de <code>.Site</code> qui est prêt à l’emploi.</p>
<p>Mais en <em>Go Template</em> dès que vous utilisez une fonction, vous perdez ce
contexte, et votre précieux point, votre contexte est remplacé par celui de la
fonction, qui a son propre… point.</p>
<p>Si par exemple dans mon gabarit de page j'ai :</p>
<h3 id="with">With</h3>
<pre><code class="language-go-html-template hljs go">{{ with .Title }}
    {{<span class="hljs-comment">/* Maintenant le point c’est .Title */</span>}}
    &lt;h1&gt;{{ . }}&lt;/h1&gt;
{{ end }}</code></pre>
<p>À l’intérieur de ce <code>with</code> vous n'êtes plus dans le contexte de page. Le
contexte, le point, c'est maintenant le titre de votre page. Ici, c'est
exactement ce que nous voulons !</p>
<h3 id="range">Range</h3>
<p>Même chose ici, une fois que vous avez commencé à itérer avec <code>range</code>, le
contexte est l’élément actuellement parcouru. Vous perdez le contexte de page au
profit du contexte de la fonction <code>range</code>.</p>
<pre><code class="language-go-html-template hljs go">{{ <span class="hljs-keyword">range</span> .Data.Pages }}
    {{<span class="hljs-comment">/* Ici le point est celui de la page 'en cours'. */</span>}}
    {{ .Permalink }}
{{ end }}</code></pre>
<pre><code class="language-go-html-template hljs go">{{ <span class="hljs-keyword">range</span> .Resources.Match <span class="hljs-string">"gallery/*"</span> }}
    {{<span class="hljs-comment">/* Ici le point designe une des images. */</span>}}
    {{ .Permalink }}
<span class="hljs-comment">// {{ end }}</span></code></pre>
<pre><code class="language-go-html-template hljs go">{{ <span class="hljs-keyword">range</span> (slice <span class="hljs-string">"Hello"</span> <span class="hljs-string">"Bonjour"</span> <span class="hljs-string">"Gutten Tag"</span>) }}
    {{<span class="hljs-comment">/* Ici le point désigne cette chaîne de caractères. */</span>}}
    {{ . }}
{{ end }}</code></pre>
<h3 id="le-contexte-du-plus-haut-niveau-de-la-page-rџ-I">Le contexte du plus haut niveau de la page 💲</h3>
<p>Heureusement pour nous, Hugo stocke le contexte de page dans un <code>$</code> donc cela ne
fait rien si vous vous trouvez au fin fond d’un <code>with</code> ou d’un <code>range</code>, vous
pouvez toujours récupérer le contexte du plus haut niveau de la page.</p>
<h4 id="un-niveau-d-imbrication">Un niveau d’imbrication</h4>
<pre><code class="language-go-html-template hljs go">{{ with .Title }}
    {{<span class="hljs-comment">/* Le point désigne .Title */</span>}}
    &lt;h1&gt;{{ . }}&lt;/h1&gt;
    {{<span class="hljs-comment">/* $ désigne le plus haut niveau de la page */</span>}}
    &lt;h3&gt;From {{ $.Title }}&lt;/h3&gt;
{{ end }}</code></pre>
<h4 id="trois-niveaux-d-imbrication">Trois niveaux d’imbrication</h4>
<pre><code class="language-go-html-template hljs go">{{<span class="hljs-comment">/* 1. Le point désigne le plus haut niveau de la page (de liste) */</span>}}
&lt;h1&gt;{{ .Title }}&lt;/h1&gt;
{{ <span class="hljs-keyword">range</span> .Data.Pages }}
    &lt;article&gt;
        {{<span class="hljs-comment">/* 2. Le point désigne la page en cours */</span>}}
        &lt;h3&gt;{{ .Title }}&lt;/h3&gt;
        &lt;hr&gt;
        {{ <span class="hljs-keyword">range</span> .Resources.Match <span class="hljs-string">"images/.*"</span> }}
            &lt;figure&gt;
                {{<span class="hljs-comment">/* 3. Le point désigne une de ces ressources */</span>}}
                &lt;img src=<span class="hljs-string">"{{ .Permalink }}"</span>&gt;
                {{<span class="hljs-comment">/* $ désigne le contexte de haut niveau de la page */</span>}}
                &lt;caption&gt;{{ .Title }} de l’article {{ $.Title }}&lt;/caption&gt;
            &lt;/figure&gt;
        {{ end }}
    &lt;/article&gt;
{{ end }}</code></pre>
<h2 id="les-fichiers-partiels">Les fichiers partiels</h2>
<p>Par défaut, les fichiers partiels ne passent aucun contexte.\
Mais il suffit d’un seul paramètre pour y remédier. Cet objet sera alors disponible
à l’intérieur du fichier partiel et sera référencé à l’aide, vous l’aviez deviné,
du point.</p>
<p>Donc pour des fichiers partiels simples, vous n'aurez besoin que du contexte de
page. Le <strong>point</strong> de votre page.</p>
<pre><code class="language-go-html-template hljs go">    {{ partial <span class="hljs-string">"page/head"</span> . }}</code></pre>
<p>Ici la fonction <code>partial</code> a pour paramètre votre contexte, à priori celui de
votre page si vous n'êtes pas dans une boucle <code>range</code> ou dans une condition
<code>with</code> ou bien dans un autre fichier partiel.</p>
<pre><code class="language-go-html-template hljs go">    &lt;h1&gt;
        {{ .Title }}
    &lt;/h1&gt;
    &lt;h3&gt;&lt;time datetime=<span class="hljs-string">"{{ .Date }}"</span>&gt;{{ dateFormat <span class="hljs-string">"Écrit le 2 January 2006"</span> .Date }}&lt;/time&gt;&lt;/h3&gt;</code></pre>
<p>Maintenant, imaginons que vous écriviez un fichier partiel pour le rendu de
votre image fantaisiste encadrée, vous n'avez besoin que de son chemin, qui
devient donc votre contexte.</p>
<pre><code class="language-go-html-template hljs go">{{ partial <span class="hljs-string">"img"</span> $path }}</code></pre>
<p>Dans le fichier <code>partials/img.html</code>, on aura donc :</p>
<pre><code class="language-go-html-template hljs go">&lt;figure class=<span class="hljs-string">"Figure Figure--framed"</span>&gt;
    &lt;img src=<span class="hljs-string">"{{ . }}"</span> alt=<span class="hljs-string">""</span>&gt;
&lt;/figure&gt;</code></pre>
<p>Le point ici c'est la valeur de <code>$path</code>.</p>
<p>C’est un exemple tout simple. La plupart du temps, vous aurez besoin de beaucoup
plus de valeurs.</p>
<p>Pas de souci, nous pouvons utiliser la fonction <code>dict</code> pour passer un ensemble
d’éléments en paramètre (entier, chaine de caractère, objet)</p>
<p><code>dict</code> va créer une <em>map</em>, souvent désignée également comme un tableau
associatif.\
Reportez-vous à la <a href="https://gohugo.io/functions/dict" target="_blank" rel="noopener noreferrer">documentation de cette fonction</a>
ou à mon propre article <a href="https://regisphilibert.com/blog/2017/04/hugo-cheat-sheet-go-template-translator/#associative-arrays" target="_blank" rel="noopener noreferrer">sur le sujet</a>.</p>
<pre><code class="language-go-html-template hljs go">{{ partial <span class="hljs-string">"img"</span> dict(<span class="hljs-string">"path"</span> $path <span class="hljs-string">"alt"</span> <span class="hljs-string">"Nice blue sky"</span>) }}</code></pre>
<p>Le point va contenir cet objet à l’intérieur du fichier partiel, donc nous
pouvons préfixer nos clefs avec <code>.</code></p>
<pre><code class="language-go-html-template hljs go">&lt;figure class=<span class="hljs-string">"Figure Figure--framed"</span>&gt;
    &lt;img src=<span class="hljs-string">"{{ .path }}"</span> alt=<span class="hljs-string">"{{ .alt }}"</span>&gt;
&lt;/figure&gt;</code></pre>
<p>Vous pouvez mettre une lettre majuscule à vos clefs pour qu'elles fassent plus
"Hugo" mais j'aime bien mettre tout en minuscules. De cette manière dans un
fichier partiel j'identifie immédiatement les clefs issues d’un contexte
personnalisé de celles issues de celui de la page.</p>
<h3 id="acceder-au-plus-haut-niveau-depuis-un-fichier-partiel">Accéder au plus haut niveau \$ depuis un fichier partiel</h3>
<p>Contrairement à <code>range</code> et <code>with</code> le contexte de page n'est pas disponible dans
<code>$</code>.</p>
<p>Pas de problème, nous allons ajouter le contexte de la page à notre <code>dict</code>.</p>
<p>Vous pouvez appeler cette clef importante comme vous voulez, beaucoup de gens
utilisent "Page" pour pouvoir écrire <code>Page.Title</code>. Comme ça vous chante, du
moment que vous êtes consistent dans votre nomenclature.</p>
<pre><code class="language-go-html-template hljs go">{{ partial <span class="hljs-string">"img"</span> dict(<span class="hljs-string">"Page"</span> . <span class="hljs-string">"path"</span> $path <span class="hljs-string">"alt"</span> <span class="hljs-string">"Nice blue sky"</span>) }}</code></pre>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">figure</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"Figure Figure--framed"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"{{ .path }}"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"{{ .alt }} from {{ .Page.Title }}"</span> /&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">figure</span>&gt;</span></code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>Ce point peut vite devenir votre meilleur ami et s'avère bien pratique une fois
qu'on a appris à jongler avec. Il permet d’écrire du code très lisible même si
parfois on ne sait plus très bien à quel niveau on se situe.</p>
<p>Il y a d’autres fonctions qui prennent le contexte, regardez notamment du côté
de <code>block</code> et <code>template</code>.</p>
<p>Bonne mise au point !</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2018/01/24/eleventy-generateur-statique-simple/</id>
    <title>Un site web simple avec le plus simple des générateurs de site statique</title>
    <published>2018-01-24T19:40:44+00:00</published>
    <link href="https://jamstatic.fr/2018/01/24/eleventy-generateur-statique-simple/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Il existe des centaines de générateurs de site statique et il en arrive toujours de nouveaux. Après avoir longtemps utilisé Jekyll, <a href="https://www.zachleat.com/web/" target="_blank" rel="noopener noreferrer">Zach Leat</a>, développeur front-end chez <a href="https://www.filamentgroup.com/" target="_blank" rel="noopener noreferrer">Filament Group</a>, a décidé de s'inspirer des principes de Jekyll pour les porter et les étendre grâce à l’écosystème de <code>npm</code> qu'il manipule au quotidien.  Ce nouveau générateur vise donc les développeurs front end et leur donne le choix du langage de templating (<a href="https://shopify.github.io/liquid/" target="_blank" rel="noopener noreferrer">Liquid</a> par défaut comme dans Jekyll et aussi <a href="https://github.com/11ty/eleventy/#eleventy-" target="_blank" rel="noopener noreferrer">tout plein d’autres qu'on peut mélanger à loisir</a> bien connus des développeurs JS) tout en leur offrant la puissance de <code>npm</code>. Zach nous propose un premier aperçu de son fonctionnement.</p></aside>
<figure>
<picture title="Photo de Jeremy Bishop">
<source type="image/webp" srcset="/images/2018-01-24_eleventy-generateur-statique-simple/1a45L3MbFiiPn_B_ZZrLvxg.0e6dcbc0f4a223e7bbcc14d68e16ab41.webp" width="700" height="418">
<source type="image/avif" srcset="/images/2018-01-24_eleventy-generateur-statique-simple/1a45L3MbFiiPn_B_ZZrLvxg.0e6dcbc0f4a223e7bbcc14d68e16ab41.avif" width="700" height="418">
<img src="/images/2018-01-24_eleventy-generateur-statique-simple/1a45L3MbFiiPn_B_ZZrLvxg.0e6dcbc0f4a223e7bbcc14d68e16ab41.png" alt="Photo de Jeremy Bishop" loading="lazy" decoding="async" class="dark:brightness-90" width="700" height="418" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Photo de <a href="https://unsplash.com/photos/d3fZSXlJ3Ok?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText" target="_blank" rel="noopener noreferrer">Jeremy Bishop</a></figcaption>
</figure>
<p>Voici <a href="https://github.com/11ty/eleventy/" target="_blank" rel="noopener noreferrer">Eleventy</a>, le générateur de site statique le plus simple et le plus intuitif. Avec Eleventy, vous pouvez générer des sites à partir de données de manière simple et rapide — et vous concentrer sur un contenu facile à maintenir, conçu pour durer longtemps. <strong>Faites en sorte que votre site dure 10 ans, pas 10 mois.</strong></p>
<h3 id="installation">Installation</h3>
<ol>
<li>
<p>Si ce n'est pas déjà fait, <a href="https://docs.npmjs.com/getting-started/installing-node" target="_blank" rel="noopener noreferrer">installez node.js et npm</a> (ils sont dispos sous la forme d’un seul et unique paquet). Eleventy ne fonctionne qu’à partir de <code>node --version</code> 8.0.0 ou plus.</p>
</li>
<li>
<p>Ensuite, installez l’utilitaire en ligne de commande, disponible sur <a href="https://www.npmjs.com/package/@11ty/eleventy" target="_blank" rel="noopener noreferrer">npm</a> : <code>npm install -g @11ty/eleventy</code></p>
</li>
</ol>
<h3 id="en-avant">En avant</h3>
<p>Faisons un site web pour notre collection d’images GIF. Une interface pour notre propre domaine <a href="https://bukk.it/" target="_blank" rel="noopener noreferrer">bukk.it</a>. Appelons ça <em>Giffleball</em>.</p>
<aside class="note note-info"><p>Le code source de la première partie de ce tutoriel est <a href="https://github.com/11ty/giffleball" target="_blank" rel="noopener noreferrer">disponible sur GitHub</a>.</p></aside>
<h4 id="creation-des-fichiers">Création des fichiers</h4>
<p>Créons un dossier pour notre tout nouveau site web.</p>
<pre><code class="language-sh hljs bash">mkdir giffleball</code></pre>
<p>Ajoutons quelques images à notre site. Voici une sélection d’images d’oiseaux tirée de l’honorable site <a href="https://bukk.it" target="_blank" rel="noopener noreferrer">bukk.it</a>.</p>
<picture>
<source type="image/webp" srcset="/thumbnails/350x/images/2018-01-24_eleventy-generateur-statique-simple/19_SoEA-aNHeVF8QKzEUhiw.45020dffa1e52e4cc52ad7947c3187e3.webp" width="350" height="251">
<source type="image/avif" srcset="/thumbnails/350x/images/2018-01-24_eleventy-generateur-statique-simple/19_SoEA-aNHeVF8QKzEUhiw.45020dffa1e52e4cc52ad7947c3187e3.avif" width="350" height="251">
<img src="/images/2018-01-24_eleventy-generateur-statique-simple/19_SoEA-aNHeVF8QKzEUhiw.45020dffa1e52e4cc52ad7947c3187e3.jpeg" alt="img" width="350" loading="lazy" decoding="async" class="dark:brightness-90" height="251" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A8gi61fjPFZsT81oQgsK4pLUxUbsnSYKRW7p12oxzXNyIRzT7e5MZxmuygi5aI6TUboMhwa5qWf8AeGrUlwZF61TaPLZrscdDFSLtrN0rbtJelY9jb7mHFdLa2BKA4rnnA2jMeX3LUOz56uG1ZR0qMx4NefiIXiFSN0WLZc4q1JblkqG1wCK102tHXyeJbhUOaxz7WbbjxRW6Y1zRUrFSHY8Rg5YVv2MO8Csa3j+cV02mqABX2FQ7IWI7q22p0rEkBSSuvuod0fSuZvbdhJnFbUJ2JqIWE5FWVjyarW6mtS3gLkcV3qorHM4O5d0yD5xxXZ2MAMY4rA021II4rrbKLaoqJO5SViCe0G3pWRcRbCa6idRsrn78AE1yVY3Nk9DOWXYaspfYGM1k3Eu0mqwuTnrXh4jBqbuc8tzo/tw9aKwhMcdaK5v7PQjjIVwa2bKUKRWKHxU8NxtYc17tQ1hPU6+MiVMVTubDfzimafcbsc1uxRiQDNTCVjoeqOdh00hulbVnYdOK04rJSelaNvaBccV0wkzNjbKzC44rZiTYtNhiAHSpmGBW9yLFa4fCmufvWzmte8YgGsO4bLGspGqWhh3gPNZyEh+a3LiLINZEse1653F3MZxsydWG2ioATiinymdzlzQOtFFRMaN7Su1ddZ/dFFFZw3OvoasPar8NFFdUDNl6PpTn6UUV0dCDKve9Yc33zRRWbN47FWb7prIn+9RRWcjKoQ0UUUGB/9k=);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<picture>
<source type="image/webp" srcset="/thumbnails/350x/images/2018-01-24_eleventy-generateur-statique-simple/1JIkW2kqH59V9KaKsjEKqdQ.b0aebf9646e395aad3518e7f64fa29b1.webp" width="350" height="293">
<source type="image/avif" srcset="/thumbnails/350x/images/2018-01-24_eleventy-generateur-statique-simple/1JIkW2kqH59V9KaKsjEKqdQ.b0aebf9646e395aad3518e7f64fa29b1.avif" width="350" height="293">
<img src="/images/2018-01-24_eleventy-generateur-statique-simple/1JIkW2kqH59V9KaKsjEKqdQ.b0aebf9646e395aad3518e7f64fa29b1.jpeg" alt="img" width="350" loading="lazy" decoding="async" class="dark:brightness-90" height="293" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A7VqjIqQ0hFepc88gYcVlX8JcGtorUEsAYdKT1BHFzWBLninRWRB6V0zWIJzimm0C9qz5S+Yz7aPyxzU0l2qDGajvJBAp7VzF3qf7wgNWdSpyo6aFFzZ0y3Qc9ac53iuds7vdgk1preADk1zQrXZ11qXJEJ4N1VfI2npVo3iGoWuUJ610XR53MIIzRSi5T1oouh3O74peKoi6HrUyTg96I4iMiHTZYxRtzTVkFL5grXnRPKxCgqpcEKpq00gx1rE1W9WKNuaTkilF3Od169ChgDXESXRec896v67qO92ANYdsC8uTXmYipc9rBU7I6exkOwVZlmYDrVeyTEYp05rKizDMqnKrIT7S/rSG5f1qHNJXVzng+0ZN9pf1oqGijmH7Rnbi6PrViK8x3rB8/wB6lWY+teHSqzgz2eVM6IX4A6006iPWuckuWHeoTdN616UMW2ioYe7Ojm1RVQ81yGuazuDANUGoaiyIea5C9vWmkIzWyrNotYdJhPMZ5SSe9XrGIZBrNgXJzWzZr0rjrSuerh6aSNqH5YxUM7c1InC1WnbmnQkePm0Ru6jdUYNOFdVz54fmigUUXEbY61MvSiivEZ9EiKTrULdKKK3gddLcw9V+41cq3+tNFFdlPY0e5ct+1bNp2oorGod1HY1V+5VSfrRRTo7ni5sRCniiius+cY4dKKKKCT//2Q==);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<img src="/images/2018-01-24_eleventy-generateur-statique-simple/1E9LIfVl7pcVu3_moXc743w.1d2728805e318f81d6747872b82831a8.gif" alt="img" loading="lazy" decoding="async" class="dark:brightness-90" width="35" height="25" style="">
<p>Sauvegardez ces images dans un dossier <code>img</code> à l’intérieur de notre répertoire <code>giffleball</code>.</p>
<pre><code class="language-sh hljs bash">giffleball/
  img/???.jpg      (nouveau)
  img/….jpg        (nouveau)
  img/parrot.gif   (nouveau)</code></pre>
<h4 id="creation-d-un-gabarit-de-page">Création d’un gabarit de page</h4>
<p>Faisons un gabarit de page ! Créez un fichier nommé <code>index.html</code> dans le répertoire <code>giffleball</code>.</p>
<pre><code class="language-sh hljs bash">giffleball/
  index.html       (nouveau)
  img/???.jpg
  img/….jpg
  img/parrot.gif</code></pre>
<p>Créons une liste avec des liens vers nos images GIF dans le fichier <code>index.html</code> :</p>
<pre><code class="language-html hljs xml"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"utf-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Giffleball<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Giffleball<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"img/???.jpg"</span>&gt;</span>???.jpg<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"img/….jpg"</span>&gt;</span>….jpg<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"img/parrot.gif"</span>&gt;</span>parrot.gif<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></code></pre>
<p>Jusqu'ici, rien d’extraordinaire. Mais nous pouvons déjà lancer <code>eleventy</code> et générer notre site. Nous passerons en option les extensions de fichier que nous voulons voir <code>eleventy</code> traiter :</p>
<pre><code class="language-sh hljs bash">~ <span class="hljs-built_in">cd</span> giffleball
~/giffleball $ eleventy --formats=html,gif,jpg
Writing _site/index.html from ./index.html.
Wrote 1 file <span class="hljs-keyword">in</span> 0.07 seconds</code></pre>
<p>Cela a pour effet de créer un nouveau site web dans le répertoire <code>_site</code>. Si vous souhaitez que le site soit généré dans un autre dossier, précisez le nom du répertoire en argument avec l’option <code>--output</code> :</p>
<pre><code class="language-sh hljs bash">~/giffleball $ eleventy --output=ailleurs
Writing ailleurs/index.html from ./index.html.
Wrote 1 file <span class="hljs-keyword">in</span> 0.07 seconds</code></pre>
<h3 id="basons-nous-sur-des-donnees">Basons-nous sur des données</h3>
<p>OK, jusqu'ici nous aurions simplement pu charger notre fichier <code>index.html</code> dans le navigateur et le résultat aurait été le même. Il n'y a aucune différence en entrée et en sortie. Ajoutons donc une petite touche <code>eleventy</code>. Déplaçons certaines données de la page dans notre <a href="https://jekyllrb.com/docs/frontmatter/" target="_blank" rel="noopener noreferrer">front matter</a> :</p>
<pre><code class="language-markdown hljs markdown">---
siteTitle: Giffleball
images:
<span class="hljs-bullet">  - </span>???.jpg
<span class="hljs-bullet">  - </span>….jpg
<span class="hljs-section">  - parrot.gif
---</span>

<span class="xml"><span class="hljs-meta">&lt;!doctype <span class="hljs-meta-keyword">html</span>&gt;</span></span>

<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span></span>
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span></span>
<span class="hljs-code">    &lt;meta charset="utf-8"&gt;</span>
<span class="hljs-code">    &lt;title&gt;{{ siteTitle }}&lt;/title&gt;</span>
  <span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span></span>
  <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span></span>
<span class="hljs-code">    &lt;h1&gt;{{ siteTitle }}&lt;/h1&gt;</span>
<span class="hljs-code">    &lt;ul&gt;</span>
<span class="hljs-code">    {% for filename in images %}</span>
<span class="hljs-code">      &lt;li&gt;&lt;a href="img/{{ filename }}"&gt;{{ filename }}&lt;/a&gt;&lt;/li&gt;</span>
<span class="hljs-code">    {% endfor %}</span>
<span class="hljs-code">    &lt;/ul&gt;</span>
  <span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span></span>
<span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span></code></pre>
<p>Nous avons ajouté le titre du site (utilisé à deux endroits) ainsi que la liste des images dans notre front matter.</p>
<p>Par défaut dans Eleventy, le moteur du rendu <code>liquid</code> est disponible pour les fichiers HTML et les fichiers Markdown. Eleventy supporte une large gamme de moteurs de rendu (jetez un œil sur <a href="https://github.com/11ty/eleventy/#eleventy-" target="_blank" rel="noopener noreferrer">la liste complète</a>) qui sont disponibles lorsque vous utilisez une extension de fichier spécifique. Par exemple notre fichier <code>index.html</code> aurait pu s'appeler <code>index.liquid</code> et le fonctionnement aurait été le même :</p>
<pre><code class="language-sh hljs bash">~/giffleball $ mv index.html index.liquid
~/giffleball $ eleventy --formats=liquid,html,jpg,gif
Writing _site/index.html from ./index.liquid.
Wrote 1 file <span class="hljs-keyword">in</span> 0.07 seconds</code></pre>
<p>Bien sûr vous pouvez modifier les paramètres par défaut, nous verrons ça plus tard (ou vous pouvez dès à présent jeter un œil au fichier <a href="https://github.com/11ty/eleventy/#configuration-optional" target="_blank" rel="noopener noreferrer">README</a>).</p>
<p>L'utilisation d’un moteur de rendu présente plusieurs avantages :</p>
<ol>
<li>
<p>Modifier vos données au même endroit. Pour changer le titre du site, nous n'avons besoin de le modifier qu'à un seul endroit (le front matter) au lieu de deux. Pour ajouter ou supprimer des images nous n'avons pas à toucher au modèle de code HTML.</p>
</li>
<li>
<p>Modifier le balisage des liens vers nos images en une fois. Admettons que nous souhaitions modifier le code HTML de notre liste d’images. Comme nous nous basons sur des données, nous pouvons modifier le modèle de code HTML dans notre boucle plutôt que de devoir modifier chaque <code>&lt;li&gt;</code> individuellement. Trois passent encore mais vous imaginez si notre site listait 300 images ?</p>
</li>
<li>
<p>Les caractères spéciaux contenus dans les noms de fichier. Quand je regarde dans mon navigateur, on dirait que mon serveur web n'aime pas trop des noms tels que <code>???.jpg</code>. Le fichier ne s'affiche pas correctement. Que se passerait-il si nos noms de fichiers comportaient des caractères bizarres que notre serveur web ou notre navigateur ne sait pas traiter ? Nous devons les échapper ! La syntaxe du moteur de template Liquid a juste ce qu'il nous faut : <a href="https://shopify.github.io/liquid/filters/url_encode/" target="_blank" rel="noopener noreferrer">un filtre <code>url_encode</code></a>. Mettons notre gabarit à jour pour en bénéficier :</p>
</li>
</ol>
<pre><code class="language-html hljs xml">{% for filename in images %}
<span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"img/{{ filename | url_encode }}"</span>&gt;</span>{{ filename }}<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
{% endfor %}</code></pre>
<p>Ah c'est bien mieux. Ça marche nickel.</p>
<p>J'espère que vous vous rendez compte de l’avantage d’utiliser des moteurs de rendu et un générateur de site statique pour vos sites web.</p>
<aside class="note note-info"><p>Le code source de la deuxième partie de ce tutoriel est <a href="https://github.com/11ty/giffleball/tree/level-2" target="_blank" rel="noopener noreferrer">disponible sur GitHub</a>.</p></aside>
<h3 id="ajoutons-un-filtre">Ajoutons un filtre</h3>
<p>Faisons un truc plus compliqué. Affichons la taille de chacune des images GIF à côté de leur lien. Nous pouvons faire ça à l’aide d’un filtre. Les filtres s'ajoutent dans le fichier de configuration — un fichier <code>.eleventy.js</code> — créons en un. Il devrait ressembler à ça :</p>
<pre><code class="language-js hljs javascript"><span class="hljs-built_in">module</span>.exports = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">eleventyConfig</span>) </span>{};</code></pre>
<p>Si vous ne le nommez pas <code>.eleventy.js</code>, chaque fois que vous allez lancer la commande <code>eleventy</code> il faudra lui passer le nom du fichier de configuration en option à l’aide de <code>--config=maConfig.js</code>. C’est bien plus simple de s'en tenir au nom par défaut.</p>
<p>Ajoutons notre filtre à l’aide de la méthode <code>.addFilter</code>. Appelons-le <code>filesize</code> et commençons par lui faire retourner un texte tout bête :</p>
<pre><code class="language-js hljs javascript"><span class="hljs-built_in">module</span>.exports = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">eleventyConfig</span>) </span>{
  eleventyConfig.addFilter(<span class="hljs-string">"filesize"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">path</span>) </span>{
    <span class="hljs-keyword">return</span> <span class="hljs-string">"0 KB"</span>;
  });
};</code></pre>
<p>Bien entendu, notre filtre n'est pas bon, car nous nous contentons de retourner <code>"0 KB"</code> à chaque fois. Mais vérifions d’abord qu'il marche.</p>
<p>Ouvrons notre modèle <code>index.html</code> et regardons à quoi ressemble notre boucle pour le moment :</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
  {% for filename in images %}
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"img/{{ filename | url_encode }}"</span>&gt;</span>{{ filename }}<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  {% endfor %}
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span></code></pre>
<p>Vous avez fait attention à la façon dont nous avons utilisé le filtre natif <code>url_encode</code> fourni par le moteur de rendu Liquid ? Maintenant que nous avons créé le nôtre, ajoutons un appel à notre petit filtre maison, comme ceci :</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
  {% for filename in images %} {% capture path %}img/{{ filename }}{% endcapture
  %}
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"img/{{ filename | url_encode }}"</span>&gt;</span>{{ filename }}<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span> {{ path |
    filesize }}
  <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  {% endfor %}
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span></code></pre>
<p>Bien entendu, la magie a lieu dans <code>{{ path | filesize }}</code>. Mais notez comment nous utilisons la balise <code>{% capture %}</code> de Liquid pour créer une nouvelle variable <code>path</code> avec Liquid, que nous passons ensuite à notre filtre.</p>
<p>Maintenant, lançons <code>eleventy</code> pour générer les fichiers.</p>
<pre><code class="language-sh hljs bash">~/giffleball $ eleventy --formats=html,gif,jpg
Writing _site/index.html from ./index.html.
Wrote 1 file <span class="hljs-keyword">in</span> 0.07 seconds</code></pre>
<p>Cela va générer le code suivant dans le fichier <code>_site/index.html</code> (ici nous ne montrons que le rendu de la liste et pas le fichier HTML entier pour faire court) :</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"img/%3F%3F%3F.jpg"</span>&gt;</span>???.jpg<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span> 0 KB<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"img/%E2%80%A6.jpg"</span>&gt;</span>….jpg<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span> 0 KB<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"img/parrot.gif"</span>&gt;</span>parrot.gif<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span> 0 KB<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span></code></pre>
<p>OK, c'est presque ça — mais c'est quoi tous ces espacements ? <em>(Notez que c'est une question purement rhétorique à laquelle je vais m'empresser de répondre tout de suite.)</em> Lors du traitement des modèles, Liquid ne supprime pas les retours à la ligne et les espaces autours des balises Liquid. Heureusement pour nous, Liquid fournit un outil pour contrôler ces espacements. Il faut utiliser <code>{%-</code> à la place de <code>{%</code> pour supprimer l’espacement avant la balise Liquid. Et indépendamment on peut aussi utiliser <code>-%}</code> à la place de <code>%}</code> à la fin pour supprimer l’espace après la balise Liquid. L'un ou l’autre. Les deux. Personnellement je trouve que ça rend mieux avec juste <code>{%-</code> au début. Il est important pour moi d’avoir une vue du code source qui soit propre, alors nettoyons tout ça :</p>
<pre><code class="language-twig hljs twig"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
  </span><span class="hljs-template-tag">{%- <span class="hljs-name"><span class="hljs-keyword">for</span></span> filename in images %}</span><span class="xml">
  </span><span class="hljs-template-tag">{%- <span class="hljs-name">capture</span> path %}</span><span class="xml">img/</span><span class="hljs-template-variable">{{ filename }}</span><span class="hljs-template-tag">{% <span class="hljs-name">endcapture</span> %}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"img/</span></span></span><span class="hljs-template-variable">{{ filename | url_encode }}</span><span class="xml"><span class="hljs-tag"><span class="hljs-string">"</span>&gt;</span></span><span class="hljs-template-variable">{{ filename }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span> </span><span class="hljs-template-variable">{{ path | filesize }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  </span><span class="hljs-template-tag">{%- <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> %}</span><span class="xml">
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span></span></code></pre>
<p>Ce qui produit :</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"img/%3F%3F%3F.jpg"</span>&gt;</span>???.jpg<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span> 0 KB<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"img/%E2%80%A6.jpg"</span>&gt;</span>….jpg<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span> 0 KB<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"img/parrot.gif"</span>&gt;</span>parrot.gif<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span> 0 KB<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span></code></pre>
<p>Magnifique.</p>
<h3 id="c-est-encore-un-peu-tot-pour-nous-rejouir-notre-filtre-n-est-pas-fini">C’est encore un peu tôt pour nous réjouir, notre filtre n'est pas fini</h3>
<p>OK, faisons en sorte que notre filtre serve à quelque chose plutôt que de simplement retourner systématiquement <code>"0 KB"</code>. Modifiez votre fichier <code>.eleventy.js</code> comme ceci :</p>
<pre><code class="language-js hljs javascript"><span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">"fs"</span>);

<span class="hljs-built_in">module</span>.exports = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">eleventyConfig</span>) </span>{
  eleventyConfig.addFilter(<span class="hljs-string">"filesize"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">path</span>) </span>{
    <span class="hljs-keyword">let</span> stat = fs.statSync(path);
    <span class="hljs-keyword">if</span> (stat) {
      <span class="hljs-keyword">return</span> (stat.size / <span class="hljs-number">1024</span>).toFixed(<span class="hljs-number">2</span>) + <span class="hljs-string">" KB"</span>;
    }
    <span class="hljs-keyword">return</span>;
    (<span class="hljs-string">""</span>);
  });
};</code></pre>
<p>C’est la manière la plus simple de le faire marcher, ça n'ajoute aucune nouvelle dépendance lors d’un <code>npm install</code>.</p>
<h3 id="allons-plus-loin-a-l-aide-de-npm">Allons plus loin à l’aide de NPM</h3>
<p>Un des gros avantages d’Eleventy sur d’autres générateurs de site statique comme Jekyll ou Hugo, c'est l’accès à tout l’écosystème de NPM. Il y a tellement d’excellents modules. Si vous êtes assez courageux pour jouer avec <code>npm</code>, lancez cette commande pour générer un fichier <code>package.json</code> pour notre projet :</p>
<pre><code class="language-sh hljs bash">~/giffleball $ npm init -f</code></pre>
<p>Nous pouvons maintenant installer des modules cools à notre projet, comme <a href="https://www.npmjs.com/package/file-size" target="_blank" rel="noopener noreferrer">file-size pour des tailles de fichiers plus lisibles</a>.</p>
<pre><code class="language-sh hljs bash">~/giffleball $ npm install --save file-size
+ file-size@1.0.0
added 1 package <span class="hljs-keyword">in</span> 1.491s</code></pre>
<p>Utilisons-le pour coder notre filtrer dans le fichier <code>.eleventy.js</code>:</p>
<pre><code class="language-js hljs javascript"><span class="hljs-keyword">const</span> fs = <span class="hljs-built_in">require</span>(<span class="hljs-string">"fs"</span>);
<span class="hljs-keyword">const</span> filesize = <span class="hljs-built_in">require</span>(<span class="hljs-string">"file-size"</span>);

<span class="hljs-built_in">module</span>.exports = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">eleventyConfig</span>) </span>{
  eleventyConfig.addFilter(<span class="hljs-string">"filesize"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">path</span>) </span>{
    <span class="hljs-keyword">let</span> stat = fs.statSync(path);
    <span class="hljs-keyword">if</span> (stat) {
      <span class="hljs-keyword">return</span> filesize(stat.size).human();
    }
    <span class="hljs-keyword">return</span> <span class="hljs-string">""</span>;
  });
};</code></pre>
<p>Ce qui nous donne :</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">ul</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"img/%3F%3F%3F.jpg"</span>&gt;</span>???.jpg<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span> 44.52 KiB<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"img/%E2%80%A6.jpg"</span>&gt;</span>….jpg<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span> 55.39 KiB<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">li</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"img/parrot.gif"</span>&gt;</span>parrot.gif<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span> 2.05 KiB<span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span></code></pre>
<p>Félicitations ! Vous avez ajouté un filtre et tiré profit du vaste et immense écosystème NPM.</p>
<p>J'espère que vous appréciez la puissance offerte par l’utilisation de filtres dans nos fichiers de gabarits. Ils peuvent transformer des contenus simples à l’aide de la puissance de l’écosystème NPM.</p>
<h4 id="a-suivre">À suivre</h4>
<p>Dans la prochaine partie nous verrons comment faire marcher ensemble plusieurs fichiers de gabarits avec des fichiers de mise en page et des fichiers de données externes.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2018/01/09/wordpress-comme-cms-pour-vos-sites-statiques/</id>
    <title>Utiliser WordPress comme CMS pour vos sites statiques</title>
    <published>2018-01-09T14:50:46+00:00</published>
    <link href="https://jamstatic.fr/2018/01/09/wordpress-comme-cms-pour-vos-sites-statiques/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>La mode est au <a href="/2017/12/15/cms-headless/">CMS headless</a>,
comprenez au découplage du back et du front. WordPress n'échappe pas à la règle.
Même si son API REST ne permet pas encore de faire tout ce qu'on voudrait, il
est tout à fait possible d’aller récupérer les contenus entrés par les
utilisateurs dans l’interface d’administration qu'ils affectionnent tant pour
ensuite les passer à la moulinette d’un générateur de site statique. Stefan
Baumgartner a testé pour vous avec <a href="http://www.metalsmith.io/" target="_blank" rel="noopener noreferrer">Metalsmith</a>,
voici comment il a procédé.</p></aside>
<p>La toute-puissante <a href="/2017/03/16/5-raisons-de-tester-la-jamstack/">JAMStack</a> offre des sites web statiques rapides
et sécurisés, et avec des systèmes de gestion de contenu <a href="/2017/12/15/cms-headless/">headless</a> ils deviennent même faciles à éditer ! Toutefois il peut
arriver que de temps à autre, vous vous retrouviez devant un blog WordPress avec
tellement d’articles (et d’auteurs qui ont peur du changement !) pour que la
raison vous pousse à ne pas le migrer. Mais WordPress aussi fonctionne en
headless. D'ailleurs, le propre service d’hébergement de WordPress communique
avec le core de WordPress uniquement au travers de son API, l’interface
d’édition fait partie de la toute nouvelle et belle application
<a href="https://developer.wordpress.com/calypso/" target="_blank" rel="noopener noreferrer">Calypso</a>.</p>
<p>Un des gros avantages quand on utilise un générateur de site statique, c'est que
généralement il se moque de la provenance de votre contenu. Utilisons donc
<a href="http://v2.wp-api.org/" target="_blank" rel="noopener noreferrer">l’attrayante API REST de WordPress</a> pour récupérer un
peu de contenu et générer des sites statiques !</p>
<p>Dans mon exemple, j'utilise le générateur de site statique
<a href="http://www.metalsmith.io/" target="_blank" rel="noopener noreferrer">Metalsmith</a>. C’est juste car je travaille avec au
quotidien et qu'il est assez simple de faire tourner de nouveaux plugins dessus.
Mais ça marche aussi avec
<a href="https://jekyllrb.com/docs/plugins/#generators" target="_blank" rel="noopener noreferrer">les générateurs de Jekyll</a> par
exemple. Du moment que votre générateur sait comment utiliser des fichiers JSON
pour les données en entrée, vous pouvez utiliser les exemples de code ci-dessous
pour stocker ce qui est retourné dans l’étape de préparation des données. C’est
parti !</p>
<h2 id="l-api-wordpress">L'API WordPress</h2>
<p>Chaque installation de WordPress possède une API JSON à part entière. Ça veut
dire que vous pouvez accéder aux articles et aux pages via des URLs. Ça m'a tout
l’air d’un CMS headless ça ! Si vous avez une instance de WordPress qui tourne
quelque part, ajoutez <code>/wp-json/wp/v2/posts</code> à la fin de l’URL principale. Vous
devriez avoir quelque chose en sortie !</p>
<p>En fait les 10 derniers articles ainsi que toutes leurs métadonnées vous sont
présentés dans un format JSON facile à comprendre.</p>
<h3 id="recuperer-les-informations-sur-l-auteur">Récupérer les informations sur l’auteur</h3>
<p>Vous remarquerez d’emblée que le champ <code>author</code> de chaque article est un nombre.
C’est ainsi que les données sont structurées dans WordPress. Il faudrait que
vous alliez chercher le numéro dans la table des auteurs et WordPress ne propose
pas une URL d’API pour cela.</p>
<p>Toutefois, vous pouvez activer l’option masquée par défaut <code>_embed</code> qui va
ajouter toutes les données relatives à l’auteur.</p>
<p>Donc avec <code>https://url-vers-votre-blog/wp-json/wp/v2/posts?_embed</code> vous avez
toutes les données dont vous avez besoin !</p>
<h3 id="recuperer-tous-les-articles">Récupérer tous les articles</h3>
<p>Si vous avez un nombre très important d’articles, le prochain défi sera de les
récupérer tous. Malheureusement ce ne sera peut-être pas possible en une seule
requête. Vous pouvez maximiser le nombre d’articles retournés à 100 en ajoutant
le paramètre supplémentaire <code>per_page</code> :</p>
<p><code>https://url-vers-votre-blog/wp-json/wp/v2/posts?_embed&amp;per_page=100</code></p>
<p>Ensuite, il va falloir récupérer les informations de pagination. Il existe un
paramètre <code>page</code> qui permet de sélectionner la page que vous souhaitez
récupérer. Vous avez la possibilité de l’utiliser récursivement pour récupérer
toutes les pages qui existent. Vous pouvez également vérifier les entêtes HTTP
personnalisés de WordPress pour connaître le nombre de pages à récupérer. Ici,
c'est comme ça que je vais procéder. Gardez juste en tête que les paramètres
CORS de votre serveur doivent autoriser le passage de ces entêtes à votre
client. L'entête personnalisé qui contient le nombre de pages est
<code>X-WP-TotalPages</code>.</p>
<p>Pour télécharger les données, j'utilise
<a href="https://www.npmjs.com/package/isomorphic-fetch" target="_blank" rel="noopener noreferrer">isomorphic-fetch</a>, qui fournit
la même API <code>fetch</code> pour Node et pour le navigateur. Regardons tout ça :</p>
<pre><code class="language-js hljs javascript"><span class="hljs-keyword">const</span> fetch = <span class="hljs-built_in">require</span>(<span class="hljs-string">"isomorphic-fetch"</span>);

<span class="hljs-keyword">const</span> mainURL = <span class="hljs-string">"http://chemin-vers-votre-blog"</span>;
<span class="hljs-keyword">const</span> apiURL = <span class="hljs-string">"/wp-json/wp/v2/posts"</span>;
<span class="hljs-keyword">const</span> url = <span class="hljs-string">`<span class="hljs-subst">${mainURL}</span><span class="hljs-subst">${apiURL}</span>?_embed&amp;per_page=100`</span>;

fetch(url) <span class="hljs-comment">/* 1 */</span>
  .then(<span class="hljs-function">(<span class="hljs-params">res</span>) =&gt;</span> {
    <span class="hljs-keyword">const</span> noPages = res.headers.get(<span class="hljs-string">"X-WP-TotalPages"</span>); <span class="hljs-comment">/* 2 */</span>
    <span class="hljs-keyword">const</span> pagesToFetch = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Array</span>(noPages - <span class="hljs-number">1</span>)
      .fill(<span class="hljs-number">0</span>)
      .map(<span class="hljs-function">(<span class="hljs-params">el, id</span>) =&gt;</span> fetch(<span class="hljs-string">`<span class="hljs-subst">${url}</span>&amp;page=<span class="hljs-subst">${id + <span class="hljs-number">2</span>}</span>`</span>)); <span class="hljs-comment">/* 3 */</span>
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.all([res, ...pagesToFetch]); <span class="hljs-comment">/* 4 */</span>
  })
  .then(<span class="hljs-function">(<span class="hljs-params">results</span>) =&gt;</span> <span class="hljs-built_in">Promise</span>.all(results.map(<span class="hljs-function">(<span class="hljs-params">el</span>) =&gt;</span> el.json()))) <span class="hljs-comment">/* 5 */</span>
  .then(<span class="hljs-function">(<span class="hljs-params">pages</span>) =&gt;</span> [].concat(...pages)); <span class="hljs-comment">/* 6 */</span></code></pre>
<ol>
<li>Nous téléchargeons les 100 premiers articles de notre blog. Si notre blog
WordPress contient moins de 100 articles, nous n'avons plus rien à
télécharger.</li>
<li>L'entête <code>X-WP-TotalPages</code> nous indique combien il nous reste de pages à
télécharger.</li>
<li>Nous créons un tableau de promesses pour les pages à télécharger, nous
commençons à la page 2 (la page 1 a déjà été téléchargée)</li>
<li><code>Promise.all</code> nous permet de passer le premier résultat et tous les suivants
issus de notre tableau <code>pagesToFetch</code>.</li>
<li>Appel de promesse suivant : convertir tous les résultats en JSON.</li>
<li>Et enfin nous convertissons tous nos résultats dans un seul et unique
tableau qui contient toutes les données des articles de notre blog.</li>
</ol>
<p>L'appel <code>.then</code> suivant contiendra un tableau avec toutes les entrées du blog.
Vous pouvez stocker ces données sous forme de fichier JSON (si votre générateur
de site n'est pas extensible) ou dans notre cas : créer une vraie page de
données que nous voulons générer.</p>
<h2 id="ajouter-vos-articles-dans-metalsmith">Ajouter vos articles dans Metalsmith</h2>
<p>Metalsmith — comme beaucoup de générateurs de sites statiques — sait quel est le
dossier qui contient vos fichiers source. La plupart du temps au format
Markdown. Ces fichiers sont ensuite convertis en HTML. Toutefois, Metalsmith
permet aussi d’ajouter des données externes. Il est assez simple de manipuler
les tableaux de fichiers et d’ajouter de nouveaux fichiers. La seule chose à
savoir c'est que chaque fichier doit posséder une clef unique : l’URL ou le
chemin où il va être stocké. Le contenu de chaque entrée est un objet qui
contient toutes les données que vous souhaitez stocker. Regardons tout ça !</p>
<h3 id="un-plugin-wordpress-pour-metalsmith">Un plugin WordPress pour Metalsmith</h3>
<p>Metalsmith fonctionne avec des plugins. À chaque fois que vous lancez une
génération avec Metalsmith, il va appliquer tous les plugins que vous avez
définis, un peu comme avec Gulp.</p>
<p>Réutilisons l’exemple de code précédent et améliorons-le pour en faire un plugin
Metalsmith :</p>
<pre><code class="language-js hljs javascript"><span class="hljs-keyword">const</span> { URL } = <span class="hljs-built_in">require</span>(<span class="hljs-string">'url’);

const wordpress = (url) =&gt; (files, smith, done) =&gt; { /* 1 */
  fetch(url)
    /* … include code from above …*/
    .then(allPages =&gt; {
      allPages.forEach(page =&gt; {
        const relativeURL
          = new URL(page.link).pathname;             /* 2 */
        const key = `./${relativeURL}/index.html`;
        let value = page;                            /* 3 */
        value.layout = '</span>post.hbs<span class="hljs-string">';
        value.contents =
          new Buffer(page.content.rendered, '</span>utf8<span class="hljs-string">');
        files[key] = value;                          /* 4 */
      });
      done();                                        /* 5 */
    });
}</span></code></pre>
<ol>
<li>L'interface pour les plugins Metalsmith est <code>(files, metalsmith, done)</code>. Le
premier paramètre désigne l’ensemble des fichiers qui doivent être
transformés en HTML. Le deuxième paramètre est l’objet Metalsmith. Le
troisième paramètre est une fonction de callback. C’est particulièrement
utile pour les opérations asynchrones. Appelez <code>done</code> lorsque votre plugin a
fini son travail.</li>
<li>Une fois que nous avons tous les articles à partir des appels à l’API (voir
ci-dessus), nous avons transformé quelque peu les données. D'abord, nous
devons modifier les permaliens de WordPress pour que Metalsmith puisse s'y
retrouver. Nous utilisons le package <code>URL</code> de Node pour récupérer l’URL
relative (sans le nom de domaine) et à partir de cela nous créons un chemin
relatif dans le système de fichier. Vous remarquerez que nous ajoutons
<code>index.html</code>. De cette manière nous créons tout un tas de dossiers avec un
seul fichier HTML dedans. Nous obtenons ainsi de belles URLs pour nos sites
statiques.</li>
<li>Ensuite, nous créons des paires clé-valeur pour l’objet fichier. Chaque
valeur correspond à une entrée dans le tableau <code>post</code> que nous avons
récupéré plus tôt. Nous précisons ensuite le gabarit à utiliser et indiquons
le contenu (le plugin <code>metalsmith-layouts</code> a besoin de ces deux valeurs pour
fonctionner).</li>
<li>Après ça, nous stockons cette valeur dans le chemin relatif que nous avons
défini plus tôt.</li>
<li>Une fois qu'on a fait ça pour tous les articles, nous appelons la fonction
de callback <code>done</code> pour indiquer la fin du traitement par nos plugins.</li>
</ol>
<p>Parfait. En quelques lignes de code nous avons dit à Metalsmith d’étendre les
fichiers qu'il transforme déjà avec les fichiers que nous récupérons à partir
d’une API. C’est ce qui rend Metalsmith extrêmement puissant, car vous n'êtes
plus lié à un seul et unique CMS. Vous pouvez même vous brancher sur différents
systèmes de gestion de contenu, récents ou plus anciens, et ne produire qu'un
seul fichier en sortie. Trop bien !</p>
<h3 id="script-de-generation-pour-metalsmith">Script de génération pour Metalsmith</h3>
<p>Nous voulons pouvoir utiliser notre nouveau plugin de manière très simple lors
de l’enchaînement des traitements par Metalsmith. Nous ne faisons appel qu'au
plugin <em>layouts</em> qui va générer un contenu un peu plus sémantique à partir de
nos fichiers Handlebars.</p>
<pre><code class="language-js hljs javascript"><span class="hljs-keyword">const</span> Metalsmith = <span class="hljs-built_in">require</span>(<span class="hljs-string">'metalsmith'</span>);
<span class="hljs-keyword">const</span> layouts = <span class="hljs-built_in">require</span>(<span class="hljs-string">'metalsmith-layouts'</span>);

<span class="hljs-comment">/** le plugin  **/</span>

Metalsmith(<span class="hljs-string">'.'</span>)
  .use(wordpress(apiURL))
  .use(layouts({
    <span class="hljs-attr">engine</span>: <span class="hljs-string">'handlebars'</span>
  }))
  .source(<span class="hljs-string">'./source'</span>)
  .destination(<span class="hljs-string">'./build’)
  .build((err) =&gt; {
    if (err) throw err;
    console.log('</span>Finished’);
  });</code></pre>
<p>On commence d’abord par récupérer toutes les données depuis l’API WordPress,
puis on les fait passer dans le plugin <code>metalsmith-layouts</code>. Puis on lance la
génération à proprement parlé. Si vous exécutez ce fichier, vous verrez qu'il
génère un dossier <code>build</code> dans votre système de fichier.</p>
<h3 id="gabarit-de-page">Gabarit de page</h3>
<p>Le fichier de gabarit est un fichier Handlebars qui définit une structure HTML
de base. <code>contents</code> fait référence au champ que nous avons défini plus tôt dans
notre plugin Metalsmith pour WordPress. Le reste vient directement de l’objet et
intègre automatiquement les données de <code>_embedded</code> l’auteur. C’est tout simple :</p>
<pre><code class="language-handlebars hljs handlebars"><span class="xml"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span></span><span class="hljs-template-variable">{{title.rendered}}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span></span><span class="hljs-template-variable">{{title.rendered}}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
  </span><span class="hljs-template-variable">{{{contents}}}</span><span class="xml">

  <span class="hljs-tag">&lt;<span class="hljs-name">aside</span>&gt;</span>
    by </span><span class="hljs-template-variable">{{_embedded.author.0.name}}</span><span class="xml">
  <span class="hljs-tag">&lt;/<span class="hljs-name">aside</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span></code></pre>
<h2 id="etape-suivante">Étape suivante</h2>
<p>Excellent ! Après m'être familiarisé avec l’API de WordPress, avoir récupéré
tous les contenus, créer des sites statiques à partir des données a été un jeu
d’enfant. J'ai créé un
<a href="https://github.com/ddprrt/metalsmith-wordpress-sample" target="_blank" rel="noopener noreferrer">dépôt à valeur d’exemple sur GitHub</a>.
Dites-moi ce que vous en pensez.</p>
<p>L'étape suivante serait de créer un petit plugin WordPress (un vrai avec du PHP
et tout) qui utilise le hook de publication pour activer automatiquement votre
système d’intégration continue. Mais vu la richesse de l’écosystème de
WordPress, il se pourrait bien de quelque chose de ce genre existe déjà.</p>
<p>Un commentaire ? <a href="https://twitter.com/ddprrt" target="_blank" rel="noopener noreferrer">Envoyez un tweet</a> !</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2018/01/07/netlify-en-10-fonctionnalites/</id>
    <title>Netlify en 10 fonctionnalités</title>
    <published>2018-01-07T20:05:43+00:00</published>
    <link href="https://jamstatic.fr/2018/01/07/netlify-en-10-fonctionnalites/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>En l’espace de quelques années <a href="https://www.netlify.com/" target="_blank" rel="noopener noreferrer">Netlify</a> est devenu un acteur incontournable de l’écosystème Jamstack - ils sont d’ailleurs à l’origine de cette appellation - et des sites statiques. Nous sommes nous-mêmes des utilisateurs plus que satisfaits de ce service et l’article que vous lisez en ce moment est lui-même hébergé sur un de leurs CDN. Netlify c'est le genre de service qui a réussi à faire de ses clients ses premiers ambassadeurs, tant leur produit est plus que recommandable.<br>
Netlify — la contraction de <em>Net</em> et <em>Simplify</em> — a pour but de simplifier la mise en production et de fournir tous les outils modernes nécessaires à des stratégies de déploiement agiles à tout un chacun, sans avoir besoin pour cela d’être un devops confirmé. Ce n'est pas simplement une solution pour héberger vos sites statiques à moindre frais, <a href="/2017/03/17/smashing-mag-va-dix-fois-plus-vite/">le passage de Smashing Magazine à une architecture Jamstack</a> hébergée par Netlify a montré que ça pouvait aller bien au delà en faisant appel à différentes APIs et microservices.<br>
<a href="https://twitter.com/philhawksworth" target="_blank" rel="noopener noreferrer">Phil Hawksworth</a>, nouvellement en charge des relations avec les développeurs chez Netlify a publié une liste de fonctionnalités disponibles quelle que soit <a href="https://www.netlify.com/pricing/" target="_blank" rel="noopener noreferrer">la formule utilisée</a>, même celle entièrement gratuite.</p></aside>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/dpr_auto-f_auto-q_auto/v1603628397/jamstatic/paperplane.45c71f1d347b3ea3937a6a301868e0bc.webp 768w, /res.cloudinary.com/jamstatic/image/upload/dpr_auto-f_auto-q_auto/v1603628397/jamstatic/paperplane.45c71f1d347b3ea3937a6a301868e0bc.webp 1000w" width="1000" height="265" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/dpr_auto-f_auto-q_auto/v1603628397/jamstatic/paperplane.45c71f1d347b3ea3937a6a301868e0bc.avif 768w, /res.cloudinary.com/jamstatic/image/upload/dpr_auto-f_auto-q_auto/v1603628397/jamstatic/paperplane.45c71f1d347b3ea3937a6a301868e0bc.avif 1000w" width="1000" height="265" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/dpr_auto-f_auto-q_auto/v1603628397/jamstatic/paperplane.45c71f1d347b3ea3937a6a301868e0bc.png" alt="Image d&#039;illustration" loading="lazy" decoding="async" class="dark:brightness-90" width="1000" height="265" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/dpr_auto-f_auto-q_auto/v1603628397/jamstatic/paperplane.45c71f1d347b3ea3937a6a301868e0bc.png 768w, /res.cloudinary.com/jamstatic/image/upload/dpr_auto-f_auto-q_auto/v1603628397/jamstatic/paperplane.45c71f1d347b3ea3937a6a301868e0bc.png 1000w" sizes="100vw">
</picture>
<h2 id="mais-d-abord-comment-demarrer-simplement">Mais d’abord, comment démarrer simplement</h2>
<p>Si vous ne connaissez pas encore ce service, sachez que c'est extrêmement simple d’héberger un site chez Netlify. Nul besoin de connaître toutes les fonctionnalités avancées pour vous lancer.</p>
<p>La manière la plus simple d’héberger un site chez Netlify est de <a href="https://www.netlify.com/docs/manual-deploys/" target="_blank" rel="noopener noreferrer">glisser-déposer un dossier</a> contenant vos fichiers dans un navigateur sur <a href="https://app.netlify.com" target="_blank" rel="noopener noreferrer">https://app.netlify.com</a>.</p>
<p><figure>
<div title="Netlify’s easy peasy drag and drop deployment" style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;">
<iframe src="https://www.youtube-nocookie.com/embed/fiw2P-UAlII" loading="lazy" width="640" height="360" frameborder="0" allow="accelerometer;autoplay;encrypted-media;gyroscope;picture-in-picture;fullscreen;web-share;" allowfullscreen="" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background-color:#d8d8d8;"></iframe>
</div>
<figcaption>Netlify’s easy peasy drag and drop deployment</figcaption>
</figure></p>
<p>Vous pouvez aussi déployer directement grâce à <a href="https://www.netlify.com/docs/cli/" target="_blank" rel="noopener noreferrer">l’utilitaire en ligne de commande</a>, mais je prefère vous renvoyer à <a href="https://www.netlify.com/docs" target="_blank" rel="noopener noreferrer">la documentation</a> pour ça, sinon vous allez croire que j'essaie de caser discrètement des éléments en plus dans ma liste. Bon OK, c'est ce que je faisais, vous m'avez démasqué. Passons maintenant à la liste à proprement parler.</p>
<h2 id="1-deploiements-atomiques-avec-publication-et-retour-en-arriere-immediats">1. Déploiements atomiques avec publication et retour en arrière immédiats</h2>
<p>Si vous avez déjà rencontré des problèmes de mise en production ou de déploiement sur des projets de développement web, vous apprécierez grandement cette fonctionnalité.</p>
<p>Chaque génération réussie sur Netlify entraîne le déploiement d’une nouvelle instance de votre site. La publication sur les différents extrémités des nœuds du réseau de CDN de Netlify et l’invalidation de cache se font automatiquement et de manière quasi-instantanée, à tel point que que je trouve inutile de mesurer combien de temps ça prend.</p>
<p>Les déploiements sont immutables. Cela signifie que chaque résultat de déploiement correspond à une version du site qui ne changera jamais. Les mises à jour créent de nouvelles instances du site pour remplacer les versions précédentes (qui sont gentiment remerciées pour leur service et mises au repos, sans être supprimées pour autant). Cela veut dire que vous pouvez revenir à tout moment à une version précédente de votre site d’un simple clic dans l’interface d’administration ou via l’API.</p>
<p>En fait, <em>tout</em> ce que vous pouvez faire dans l’interface d’administration, vous pouvez le faire aussi avec l’API. La <a href="https://open-api.netlify.com/" target="_blank" rel="noopener noreferrer">documentation de l’API</a> vous explique comment faire tout cela. Je ne compte pas même pas ça comme une fonctionnalité à part entière ici, c'est juste un petit bonus de plus !</p>
<h2 id="2-notifications-et-permaliens">2. Notifications et permaliens</h2>
<p>Une fois encore, il y a plus d’une fonctionnalité dans cet élément de ma liste, il faudra vous y faire.</p>
<p>Netlify vous permet de configurer des notifications en fonction des différents types d’évènement liés à un déploiement. Vous pouvez définir qui sera informé en cas de nouveau déploiement, ou lorsqu'un déploiement réussit, échoue, est verrouillé ou déverrouillé (je ne vous ai pas dit mais on peut aussi choisir de faire pointer la version du site vers un déploiement en particulier).</p>
<p>Vous pouvez envoyer des notifications par mail ou sur un canal Slack (je suis fan, tous mes projets ont un canal Slack dédié à l’intégration continue). Vous pouvez même décider qu'une notification va déclencher un webhook, ajouter des messages à des commits Git ou commenter sur des pull requests.</p>
<p>Ce qui rend ces notifications encore plus utiles, c'est qu'elles incluent un lien unique vers le déploiement en question. Je vous ai dit que tous les déploiements sont immutables et toujours actifs. Cela signifie que chacun d’eux possède sa propre URL pour qu'on puisse y accéder et voir ce déploiement en particulier.</p>
<p>Avoir des liens uniques pour chaque déploiement c'est énorme. Vous pouvez partager à tout moment n'importe quelle version de votre site avec votre équipe en charge des tests, votre client, ou n'importe qui d’autre. "À quoi ressemblait la version 3.2.14 du site déjà ? Tiens, voilà le lien."</p>
<p>Et cet accès instantané vous est partagé directement à chaque notification.</p>
<h3 id="3-branches-de-deploiement-et-sous-domaines">3. Branches de déploiement et sous-domaines</h3>
<p>C’est bien pratique de pouvoir déployer d’autres branches que celle de production. Pouvoir développer de nouvelles fonctionnalités dans des branches dédiées et ensuite pouvoir les tester et les passer en revue sur votre environnement de production, c'est incroyablement puissant.</p>
<p>Netlify vous permet de garder le contrôle sur la façon dont vous déployez. Vous pouvez choisir de déployer uniquement la branche de production, toutes vos branches, ou seulement certaines branches.</p>
<figure>
<picture title="Paramètres du déploiement continu.">
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/dpr_auto-f_auto-q_auto/v1523347027/jamstatic/controle-deploiement-continu.8dac49c750c50965299fa36764db5b71.webp 768w, /res.cloudinary.com/jamstatic/image/upload/dpr_auto-f_auto-q_auto/v1523347027/jamstatic/controle-deploiement-continu.8dac49c750c50965299fa36764db5b71.webp 800w" width="800" height="489" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/dpr_auto-f_auto-q_auto/v1523347027/jamstatic/controle-deploiement-continu.8dac49c750c50965299fa36764db5b71.avif 768w, /res.cloudinary.com/jamstatic/image/upload/dpr_auto-f_auto-q_auto/v1523347027/jamstatic/controle-deploiement-continu.8dac49c750c50965299fa36764db5b71.avif 800w" width="800" height="489" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/dpr_auto-f_auto-q_auto/v1523347027/jamstatic/controle-deploiement-continu.8dac49c750c50965299fa36764db5b71.png" alt="Paramètres du déploiement continu" loading="lazy" decoding="async" class="dark:brightness-90" width="800" height="489" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAEm0lEQVR4nO1aWw7cMAiEKvc/Y9UehH7EOBjjZ7AdtR1p19k8CZMBQxZ//vpNsADII/KIycgbSI185BKjehFtAQAEwGBTaqEaUf5GYz2G09bHy/1m/iKgsYzWjo74T4iBGhH/ICEngpUdkkrrVuKDhOwHQp0MvbwS3yGENiuDEynYhOwiQOM7hBwAqg/AGVVI/NOEaJxWB8DXCKH4tQ74JHCtjmzXtZaY+BYhggzLGV5UnVRACx8jJGCFSDAZqsrID60Z5EUvAQB+lJAFjKBw3GcVQvQdQmpOoo59Rq9TPxeZi8l50FcZjM8QYsKjNhFJPK56f1ZfiPt0I2T2JnuVMUONdW69zuwtV36yMohkJ3vCOKUMhgshy5+4dgRJgPFLrTOW38N2bN+h6d0QOBBSjckVO8eUMaqP9NE1yVA1T2uabSkj7kekcgolx+RnLd/9K0L0C5r6XmeAy3JI6ticFHtd2JCdCeC2dZqQ/G3ZnBzy56qSM3rjFd43Z6q3Y6IwqwyiujKqPgqYIsQko3kt/8htXkGGIuWY08qgwsMg951XCHOA+o1z7aD2ZtNkxO4nO7uEOG4kE3kpoxi2ChgmJGnKIceH0TOMAxGBgJpepfTrJhl7gkULa5XBmFPIzQaA+MfE8PFd+z2OLN1UEQQ3gRNAxO3KYAwRguqTbRzCKIlMDjWjF5UWOpVyO5Nt3KMMtnKIEH16tDYKxUzneeW4YXWAslUQ01PG3eERAIKjKchFq9VTGYz5OoQAAAmIEAAfNqIp3TalqdxM7IiAFAIQ4t0Ob6eTaGb2Q9hmTlCJYu7h2MXX1yHLSxlsyxQhpB4zLD523fOv6Gy5f6zSHXqMtmJs3DxQErCYjBoJHh3gH/OHcixvpE6deKyPvSqlM0wkpAJnb5+MT7KdeKTqmJ2XyPy07JC4fMu1EkpXSQs4Ga7i8uK/B5UKTJ5lkcgjtdELLxTiB60KeXuIot6JO3jUFSnk0xrHTqV4KIOxSSE2Yo2v5/vAbZDZSmIe2fViEomSEZ2DR9NeKrlONWOjIozrW/Murgs4b6nM745sEqBJgTC7JMPijtlUCZe/+OcQe0cASd5Y7Pcmci7uri0hBT4QEClsf+/LYwp5BYTVud7GE0sBCJ/lhhN7lMH4jEKKU8nkQ8nKI6QsxkGF8AwllzpZv+JQm6P4I6mJBmufEWUwjikkTZq5g5vK2KiOhIwFU26J8zlETm1ln6ihjB18ZF2B3m6xPn4A13wD4j20U1HVHUkvKyrDp7fVi4cU2bxbp5LLseqvA9UU1mggkdiXpEDUa9ituVz7p+KwN8pgbFaI2Vy3q2N4yIikbFZHrEGli6xXiQDg5cd9CukFxUD1jPRkkGMFIjcTQYfWR/oerjyaQzSyKAagFLLdpCe8ynI9wPbcO39+TyEBJJVCu6sPZQs8bavw35fQPrm3e7rwUwphWOHqGBvClqSnBS3Pzfl1q0LeOtaeEuwBZgtrsFEh867kSvlU78p6bdzvtTH/blGIV8jBIJGdvORkYK0UeY0NCnmrDPWe5FDc0i/Uxr3Wd8T6XtYC553MJY8Raxz3B6MXJ+CVc6xRAAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/dpr_auto-f_auto-q_auto/v1523347027/jamstatic/controle-deploiement-continu.8dac49c750c50965299fa36764db5b71.png 768w, /res.cloudinary.com/jamstatic/image/upload/dpr_auto-f_auto-q_auto/v1523347027/jamstatic/controle-deploiement-continu.8dac49c750c50965299fa36764db5b71.png 800w" sizes="100vw">
</picture>
<figcaption>Paramètres du déploiement continu.</figcaption>
</figure>
<p>Une fois déployée, chaque branche sera accessible depuis un sous-domaine généré en fonction du nom de la branche utilisée. Ça donne un truc comme ça :</p>
<p><code>ma-branche--mon-site.netlify.com</code></p>
<p>Grâce à la <a href="https://www.netlify.com/blog/2017/12/19/an-easier-way-to-manage-domains-and-dns-on-netlify/" target="_blank" rel="noopener noreferrer">gestion des DNS de Netlify</a>, vous pouvez aussi choisir d’affecter vos propres sous-domaines à des branches. Vous avez une liberté totale pour définir comment les différentes branches vont pousser du contenu sur les différents sous-domaines de votre site.</p>
<h3 id="4-tests-a-b-tests-a-b-avec-plusieurs-variantes-ou-tests-separes">4. Tests A/B, Tests A/B avec plusieurs variantes ou tests séparés</h3>
<p>Il existe plusieurs variantes et termes pour désigner les tests A/B, Netlify appelle cela le <em>split testing</em> parce que c'est ce que ça fait : découper le trafic de votre site entre les différentes branches de votre choix.</p>
<p>Vous pouvez partager le trafic de votre site en autant de branches que vous le souhaitez et définir le pourcentage de trafic attribué à chacune des branches.</p>
<figure>
<picture title="La configuration du split testing chez Netlify.">
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523347053/jamstatic/split-testing.ccea9f0c6cf3ce5f47515ad31a910cdd.webp 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523347053/jamstatic/split-testing.ccea9f0c6cf3ce5f47515ad31a910cdd.webp 800w" width="800" height="400" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523347053/jamstatic/split-testing.ccea9f0c6cf3ce5f47515ad31a910cdd.avif 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523347053/jamstatic/split-testing.ccea9f0c6cf3ce5f47515ad31a910cdd.avif 800w" width="800" height="400" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523347053/jamstatic/split-testing.ccea9f0c6cf3ce5f47515ad31a910cdd.png" alt="La configuration du split testing chez Netlify" loading="lazy" decoding="async" class="dark:brightness-90" width="800" height="400" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523347053/jamstatic/split-testing.ccea9f0c6cf3ce5f47515ad31a910cdd.png 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523347053/jamstatic/split-testing.ccea9f0c6cf3ce5f47515ad31a910cdd.png 800w" sizes="100vw">
</picture>
<figcaption>La configuration du split testing chez Netlify.</figcaption>
</figure>
<p>Cette fonctionnalité me bluffe. Elle rend les différents types de tests A/B vraiment trivial à mettre en place. Si vous tirez déjà parti du déploiement de branches, il n'y a pas grand-chose à faire de plus.</p>
<p>Vous me direz que beaucoup d’entreprises peuvent vous vendre des services de tests A/B pour votre site. J'ai été un grand adepte de ces services. Mais la plupart, si ce n'est tous, vont faire ça en magouillant un peu à coup de JavaScript une fois votre site servi et chargé dans le navigateur.</p>
<p>Vu le mal qu'on se donne, en tant de développeurs web, à minimiser l’impact qu'ont les ressources externes en JavaScript sur le rendu de nos sites, c'est vraiment bête de réduire tous ces efforts à néant en introduisant un ralentissement de la performance dans notre rendu.</p>
<p>De plus, si la performance des différentes variantes que nous testons diffère de la production, alors comment pouvons nous bénéficier d’une comparaison vraiment fiable de la performance de ces options ? Les tests sont faussés.</p>
<p>L'approche de Netlify c'est de servir chaque variante de test directement depuis son CDN optimisé. Tous les trucs super intelligents comme la répartition du trafic, les variantes de tests et l’assurance de la consistence d’utilisation se passent au niveau du CDN - sur les nœuds les plus proches possibles de l’utilisateur.</p>
<p>Chaque variante de test est servie et rendue comme sur la "production". Fantastique.</p>
<h3 id="5-commandes-de-generation-contextuelles">5. Commandes de génération contextuelles</h3>
<p>Non seulement vous pouvez déployer différentes branches, mais vous pouvez aussi personnaliser le contenu et les environnements de vos déploiements en fonction de différents contextes comme la préproduction, la qualification et la production.</p>
<p>Il fut un temps où c'était compliqué de mettre en place différents environnements de déploiement pour votre projet. Netlify rend les choses plus simples que je ne l’ai jamais vu. Vous pouvez créer <code>staging.votre-projet.com</code> et <code>testing.votre-projet.com</code> et tout ce que vous voulez à côté de votre <code>www.votre-projet.com</code> simplement à l’aide d’un peu de configuration. Et ils tournent tous sur des environnements identiques, c'est très important pour la fiabilité du développement et la stratégie de déploiement.</p>
<p>Vous pourriez vouloir lancer des commandes de génération légèrement différentes en fonction de l’environnement sur lequel vous déployez, ou générer une fonctionnalité pas encore disponible en production. Vous pouvez faire tout cela en configurant différents contextes de déploiement.</p>
<p>Cela vous permet de faire des choses comme générer la production avec <code>npm run build:prod</code> et une branche de fonctionnalité avec <code>npm run build:ma-fonctionnalite</code>. Pratique !</p>
<p>Cela se paramètre à l’aide d’un fichier de configuration <code>netlify.toml</code> qu'on peut laisser à la racine du projet pour accéder à toutes sortes d’options pour vos déploiements sur Netlify.</p>
<p>Par exemple :</p>
<p><figure>
<script src="https://gist.github.com/philhawksworth/61715131c5d229c06f161e82e93db803.js" title="Exemple de fichier de configuration de site netlify.toml.">
<figcaption>Exemple de fichier de configuration de site netlify.toml.</figcaption>
</figure></p>
<p>Vous trouverez plus d’informations à ce sujet dans la <a href="https://www.netlify.com/docs/continuous-deployment/#deploy-contexts" target="_blank" rel="noopener noreferrer">documentation des contextes de déploiement</a>.</p>
<h3 id="6-gestion-des-certificats-ssl-et-ssl-gratuit-avec-let-s-encrypt">6. Gestion des certificats SSL et SSL gratuit avec Let’s Encrypt</h3>
<p>Même si ce n'est pas forcément évident à première vue, il est très important de servir les sites web en HTTPS plutôt qu'en HTTP, même si ce sont des sites servis en statique.</p>
<p>Un des créateurs de Netlify explique tout ça très bien en donnant <a href="https://www.netlify.com/blog/2014/10/03/five-reasons-you-want-https-for-your-static-site/" target="_blank" rel="noopener noreferrer">cinq bonne raisons de servir votre site en HTTPS</a> et Google a également publié de bons articles qui expliquent <a href="https://developers.google.com/web/fundamentals/security/encrypt-in-transit/why-https" target="_blank" rel="noopener noreferrer">pourquoi HTTPS est si important</a>, quelle que soit l’architecture utilisée.</p>
<p>Vous êtes convaincu et vous voulez acheter un certificat numérique ? N'ayez crainte, l’opération peut être bien plus simple que vous ne pourriez le penser.</p>
<p>Netlify rend triviale la configuration de <a href="https://www.netlify.com/docs/ssl/#https-on-custom-domains" target="_blank" rel="noopener noreferrer">HTTPS sur vos noms de domaines</a>.Vous avez le choix entre la gestion automatisée de SSL, la gestion personnalisée de SSL et même une adresse IP dédiée SSL pour les entreprises qui en ont besoin.</p>
<p>La plupart des gens peuvent se contenter de la gestion automatisée grâce aux certificats offerts par <a href="https://www.netlify.com/blog/2016/01/15/a-worlds-first.-free-ssl-with-lets-encrypt/" target="_blank" rel="noopener noreferrer">Let's Encrypt</a>. La configuration se fait en un clic (bon ok peut-être trois, mais ça m'a pris moins d’une minute). En plus le certificat est renouvelé automatiquement, pour que vous n'ayez pas à le faire tous les ans.</p>
<figure>
<picture title="La configuration de SSL chez Netlify avec renouvellement automatique des certificats grâce à Let’s Encrypt.">
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523347047/jamstatic/ssl-config.f8ab94aed6f6162b9010cc631cfbe83c.webp 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523347047/jamstatic/ssl-config.f8ab94aed6f6162b9010cc631cfbe83c.webp 800w" width="800" height="533" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523347047/jamstatic/ssl-config.f8ab94aed6f6162b9010cc631cfbe83c.avif 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523347047/jamstatic/ssl-config.f8ab94aed6f6162b9010cc631cfbe83c.avif 800w" width="800" height="533" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523347047/jamstatic/ssl-config.f8ab94aed6f6162b9010cc631cfbe83c.png" alt="La configuration de SSL chez Netlify avec renouvellement automatique des certificats grâce à Let’s Encrypt" loading="lazy" decoding="async" class="dark:brightness-90" width="800" height="533" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAADPUlEQVR4nO2aa3KFIAyFkw7732Sn60h/CBIgICIEZDwz7VXwciWHD3mIv39/BIpCQAA4fpJ6/zLa8tmnTXaZ0hfYd9g9Rpdj8h2eiWnRxfu017NPl27ulDNKPXwRA0JRRnzOM8iWgvzcl+6ziV1uA0pUV4cK435qynmjjhDxwNrzW2WkR6O1BCHdVSIjyFuHDKftCJHICMMWB10qIz3S0l6EFMgge4wuYzEynLYhhIQDiYxSIGeS4bQXIU43yMBFyHDahhAA364J+ByHjkBmIhmOveaR4bQdIS6kCOHEE/EwBQGB8Gj64eMGp5LhtJ0hTjyAgTmnGQiE/BprRIegPlE3Q/yCyHoi8cSn+ntntUAEVCTD6bEh0nrPqsYApOYgACOFLECRMYr398iQczEO/S0T0VTkayVRc9qAbDRWal4DzNr2GVKrYBDAEg9ywmGyRkPrbginJafuy+4dlAwCeOJpjDsf1431MYR4OwOdpjRQ0gjNPWtGV+2RIW5CTEmKvMlDK6KxmB7P1HcOMUafGuqydFLe+nln/5WY8daHeoviis6mTjJDS0sYsorELioepwyeKE41JB4hz3rml4iI3y4ZrUmGrNNJof0rPiOYEaNJmWLISmQkZmQoGE2KK1HZkDXIiJ8RCHUrDBqkqBoym4w4cFgyYxIpSobMI0MKWRLUhldBk7I6kWJUhtgT/Lg24iADrwKpTIp560yaK1eDbDoKuS0tfAApr54Yliqe5AXD2ob9DSVSjMJcp7uqFv3EgNuV6KKTc0lZkpCaiki9TlpGOOXG8hV1GkyKqRp/L6zS7PoqdNdV9y5WRakDKUsR0tw2yn1QclokhfjOoN+6Dd84dV1frlOsuS9Zl4TMXgqvVZ6UXF7Ynfm9zsMRukFGOoBoJ2UJQrp1mzdJiRWa4tN4+eIvdCRFJITCf69SiZQaRa9rPPvtBlIm74cMGlB0KDduinUlpv3jXVIMIkZv8YW3MiJk7+Punp6QMmk/5N1D7Wu1k2LOTHEtHGH/9jxOLaTkCfm86KOIhitSlhj27q47pHyGaKmSFMGQr68apRpSPkK0dUGKCbM+MjRUIsW4Cz4rlJUhxXx2zJNEyj+I7OOF6TaongAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523347047/jamstatic/ssl-config.f8ab94aed6f6162b9010cc631cfbe83c.png 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523347047/jamstatic/ssl-config.f8ab94aed6f6162b9010cc631cfbe83c.png 800w" sizes="100vw">
</picture>
<figcaption>La configuration de SSL chez Netlify avec renouvellement automatique des certificats grâce à Let’s Encrypt.</figcaption>
</figure>
<h3 id="7-lancer-des-tests-avec-l-integration-continue-de-netlify">7. Lancer des tests avec l’intégration continue de Netlify</h3>
<p>Une des choses qui fait que Netlify est très puissant c'est qu'en plus d’un réseau optimisé de CDN pour héberger vos sites, ils fournissent aussi un environnement de conteneurs pour lancer vos builds. Cela signifie que n'importe quel <em>build</em> lancé dans votre environnement de développement ou sur un serveur d’intégration continue peut en fait être directement exécuté sur Netlify.</p>
<p>Si votre script de déploiement inclus des tests, Netlify les exécutera pour vous et qu'il en résulte un succès ou un échec, <a href="https://www.netlify.com/blog/2016/07/18/shiny-slack-notifications-from-netlify/" target="_blank" rel="noopener noreferrer">vous serez prévenus</a> de l’issue finale.</p>
<p>Remplacer mon infrastructure d’intégration continue, mon infrastructure d’hébergement ainsi que mes scripts de déploiement par un seul et unique service ? Je suis partant.</p>
<h3 id="8-gestion-des-formulaires">8. Gestion des formulaires</h3>
<p>Si votre site a besoin d’intégrer des formulaires, vous vous êtes peut-être dit par le passé que ce n'était pas compatible avec un site statique. Pourtant Netlify propose une solution simple pour régler ce problème.</p>
<p>Si vous avez besoin d’ajouter un formulaire sur votre site qui récolte des informations entrées par vos utilisateurs, Netlify peut s'en charger pour vous. En ajoutant un simple attribut au code HTML de votre formulaire, <a href="https://www.netlify.com/docs/form-handling/" target="_blank" rel="noopener noreferrer">Netlify va exposer le point d’accès qui va bien pour le formulaire</a> et rendre toutes les données postées accessibles pour vous depuis l’interface d’administration et l’API.</p>
<p>Comme les données sont accessibles via l’API, vous pouvez accéder à ces contenus lors de l’étape de génération afin de les utiliser sur votre site. Avec un peu d’imagination, cela ouvre pas mal de possibilités intéressantes.</p>
<p>Les <a href="https://www.netlify.com/docs/form-handling/#receiving-submissions" target="_blank" rel="noopener noreferrer">soumissions de formulaires</a> peuvent aussi déclencher des notifications. Tout devient alors possible : des messages Slack, des webhooks ou même des <a href="https://zapier.com/app/dashboard" target="_blank" rel="noopener noreferrer">intégrations Zapier</a>.</p>
<h3 id="9-redirections-reecritures-et-proxy">9. Redirections, réécritures et proxy</h3>
<p>N'oubliez aucune URL en route ! En ajoutant un fichier <code>_redirects</code> dans votre dossier déployé nous avez accès à tout plein d’options de configuration en ce qui concerne les redirections et les réécritures d’URLs. Elles sont déclenchées sur les nœuds finaux des CDN, ce qui les rend particulièrement rapides et efficientes.</p>
<p>Vous avez aussi la possibilité de préciser le code de réponse HTTP dans le fichier <code>_redirects</code>, ce qui vous permet de personnaliser vos erreurs 404 ou même de rendre d’autres ressources accessibles au travers d’un proxy.</p>
<p>Voici un exemple :</p>
<p><figure>
<script src="https://gist.github.com/philhawksworth/7b12a0785266f8dcf1960d2df93dfc59.js" title="Un exemple de fichier de configuration _redirects.">
<figcaption>Un exemple de fichier de configuration <code>_redirects</code>.</figcaption>
</figure></p>
<p>Vous voulez des <em>splats</em>, des <em>placeholders</em>, des paramètres de requêtes et plus encore ? Jetez un œil à la <a href="https://www.netlify.com/docs/redirects/" target="_blank" rel="noopener noreferrer">documentation sur les redirections</a>.</p>
<h3 id="10-controle-des-entetes-personnalises">10. Contrôle des entêtes personnalisés</h3>
<p>Celui là ravira toux ceux qui ont hébergé leur site sur GitHub Pages et qui ont couru après le score parfait sur <a href="https://developers.google.com/web/tools/lighthouse/" target="_blank" rel="noopener noreferrer">Lighthouse</a> ou <a href="https://developers.google.com/speed/pagespeed/insights/" target="_blank" rel="noopener noreferrer">Page Speed Insights</a>. Vous avez tout bien fait, mais vous avez besoin de pouvoir définir vos entêtes de cache HTTP pour bénéficier de cette dernière optimisation de performance qui vous manque tant… malheureusement vous n'en avez pas la possibilité.</p>
<p>Maintenant vous l’avez.</p>
<p>Netlify utilise pour cela une approche similaire à celle de la gestion des redirections que nous venons de voir plus haut. Grâce à un fichier <code>_headers</code> déposé dans votre dossier de déploiement, vous pouvez ainsi contrôler les entêtes HTTP de toutes les ressources de votre site.</p>
<p>Et vous pouvez faire bien plus que contrôler les entêtes de cache. La possibilité de configurer vos entêtes à l’aide de fichier <code>_headers</code> vous permet de définir votre politique de sécurité en matière de contenu (CSP), vos options <code>X-Frame</code> et plein d’autres choses toutes aussi importantes pour vous aider à contrôler la sécurité de votre site.</p>
<p><figure>
<script src="https://gist.github.com/philhawksworth/d1deda75c8bc3d025e7d62639f904222.js" title="Un exemple de fichier de configuration _headers.">
<figcaption>Un exemple de fichier de configuration <code>_headers</code>.</figcaption>
</figure></p>
<p>Bénéficier d’une telle granularité pour ce type de contrôle est souvent bien plus complexe que cela. Il me semble que cette fonctionnalité rend accessible le contrôle de la sécurité à davantage de développeurs.</p>
<h2 id="ca-en-fait-10-mais-ce-n-est-pas-tout">Ça en fait 10, mais ce n'est pas tout</h2>
<p>J'aurais pu mentionner bien d’autres choses, mais vous ne voulez pas d’une liste interminable.</p>
<p>Plus je creuse, plus je découvre qu'il est possible de contrôler pas mal de choses. Il est important que les développeurs puissent bénéficier d’une solution simple pour mettre en ligne leurs sites statiques, et comme l’écosystème de la Jamstack évolue en permanence, les possibilités sont de plus en plus grandes.</p>
<p>Ça vaut le coup de garder un œil sur Netlify, d’autres fonctionnalités prometteuses sont actuellement testées par des alpha-testeurs enthousiastes.</p>
<p>Vous pouvez <a href="https://twitter.com/Netlify" target="_blank" rel="noopener noreferrer">suivre Netlify sur Twitter</a> pour rester informé des nouvelles fonctionnalités, plonger dans la <a href="https://www.netlify.com/docs/" target="_blank" rel="noopener noreferrer">documentation</a>, ou prendre connaissance des <a href="https://www.netlify.com/open-source/" target="_blank" rel="noopener noreferrer">nos projets open source</a> pour étendre les possibilités de l’écosystème <a href="https://www.jamstack.org/" target="_blank" rel="noopener noreferrer">Jamstack</a>.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2017/12/15/cms-headless/</id>
    <title>C’est quoi un CMS headless ?</title>
    <published>2017-12-15T15:40:50+00:00</published>
    <link href="https://jamstatic.fr/2017/12/15/cms-headless/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>Dans les architectures <a href="/2017/03/16/5-raisons-de-tester-la-jamstack/">Jamstack</a>, chaque service est assuré par un outil spécifique qui va se contenter de faire une chose et une seule, si possible de son mieux. L'édition de contenus pourra par exemple être confiée à un CMS <em>headless</em> — qui contrairement à un CMS dynamique classique comme WordPress ou Drupal ne sera pas chargé de la gestion des modèles, puisque c'est le rôle du générateur de site statique, ni du rendu, puisque les pages HTML générées seront ensuite directement servies depuis un CDN.</p>
<p>Si les architectures Jamstack sont très <a href="/2017/03/17/smashing-mag-va-dix-fois-plus-vite/">performantes</a>, il n'en reste pas moins que tous les intervenants doivent pouvoir contribuer au produit quel que soit leur profil. Une interface conviviale pour l’édition de contenus est donc un passage souvent obligé si vous voulez vous assurer que tout le monde y trouve son compte.</p>
<p>L'adoption d’un CMS headless ravira également les développeurs habitués à consommer des APIs à longueur de journée et outre les bonnes vieilles APIs RESTful on commence aussi à voir apparaître des <a href="https://graphcms.com/" target="_blank" rel="noopener noreferrer">APIs GraphQL</a>. Tout le monde a donc à y gagner, les personnes en charge de la modélisation de contenu auront à leur disposition <a href="https://www.contentful.com/developers/docs/concepts/data-model/" target="_blank" rel="noopener noreferrer">des outils beaucoup plus souples</a> et ne sauront plus contraintes par des bases de données peu flexibles et coûteuses en performance et en maintenance. Au final le CMS headless offre plus de souplesse, de liberté, puisqu'il ne vous impose aucun choix de technologie, sans compter que vous pouvez déléguer la maintenance serveur si vous optez pour un service hébergé. Service tiers payant ou solution open source, c'est vous qui voyez comme dirait l’autre.</p>
<p>Les architectures monolithiques ont leurs limites et si vous maintenez plusieurs applications mobiles natives et différents services web, vous préférerez sûrement avoir un point d’accès unique : votre API de contenus. De plus c'est pratique pour que des services externes comme <a href="https://www.algolia.com/" target="_blank" rel="noopener noreferrer">Algolia</a> puissent au passage indexer votre contenu (<a href="https://kenticocloud.com/blog/searching-content-kentico-cloud-algolia-integration" target="_blank" rel="noopener noreferrer">exemple avec Kentico</a>). L'interaction entre services via des APIs, ce n'est pas nouveau et il n'est guère étonnant de voir les services de gestion de contenu proposer de plus en plus de connecteurs aux générateurs de site statique et inversement. <a href="/categories/gatsby">Gatsby</a> intègre ainsi nativement la notion même <a href="https://www.gatsbyjs.org/docs/create-source-plugin/" target="_blank" rel="noopener noreferrer">de source</a> afin de pouvoir se connecter à n'importe quelle source de données externe.</p>
<p>Le rôle du CMS headless c'est de vous fournir une interface à la fois pour la modélisation et la saisie des contenus mais aussi une interface de programmation pour vous permettre de consommer ensuite ces contenus où vous voulez, comme bon vous semble. C’est la seule chose dont va se préoccuper le CMS headless, le reste est de votre ressort.</p>
<figure>
<picture title="Aperçu d’une architecture de CMS découplée">
<source type="image/webp" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346944/jamstatic/headless-cms-schema.d579250d6804a543f3b8fb593871a6e5.webp" width="764" height="241">
<source type="image/avif" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346944/jamstatic/headless-cms-schema.d579250d6804a543f3b8fb593871a6e5.avif" width="764" height="241">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346944/jamstatic/headless-cms-schema.d579250d6804a543f3b8fb593871a6e5.png" alt="Aperçu d’une architecture de CMS découplée" loading="lazy" decoding="async" class="dark:brightness-90" width="764" height="241" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Aperçu d’une architecture de <a href="https://www.storyblok.com/tp/headless-cms-explained" target="_blank" rel="noopener noreferrer">CMS découplée</a></figcaption>
</figure>
<p>Si vous utilisez un générateur de site statique, le CMS headless est donc une pièce supplémentaire que vous pourriez vouloir ajouter à votre architecture, comme d’habitude cela dépend du projet.</p>
<p><a href="/2019/10/30/git-based-cms-vs-api-first-cms/">Distinguons les CMS headless</a> qui vous permettent de modifier les fichiers de votre dépôt Git (<a href="https://forestry.io" target="_blank" rel="noopener noreferrer">Forestry</a>, <a href="https://siteleaf.com" target="_blank" rel="noopener noreferrer">Siteleaf</a>, <a href="/2017/05/29/configurer-netlify-cms-pour-jekyll/">Netlify CMS</a>, etc.) de ceux qui mettent vos contenus à disposition via une API (<a href="https://www.contentful.com/" target="_blank" rel="noopener noreferrer">Contentful</a>, <a href="https://prismic.io/" target="_blank" rel="noopener noreferrer">Prismic</a>, <a href="https://getdirectus.com/" target="_blank" rel="noopener noreferrer">Directus</a>, <a href="https://graphcms.com/" target="_blank" rel="noopener noreferrer">GraphCMS</a>, etc.).</p>
<p>Chris Macrae détaille la différence entre les deux approches dans <div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;">
<iframe src="https://www.youtube-nocookie.com/embed/KX4G49ZrvY0" loading="lazy" width="640" height="360" frameborder="0" allow="accelerometer;autoplay;encrypted-media;gyroscope;picture-in-picture;fullscreen;web-share;" allowfullscreen="" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background-color:#d8d8d8;"></iframe>
</div>.</p>
<p>Quoi qu'il en soit dans les deux cas, le CMS headless ne s'occupe que de la partie cachée, le back, pas de la partie visible — la tête est coupée comme le suggère le terme anglophone <em>headless</em>, d’ici à parler de révolution, il n'y a qu'un pas. Tout se recoupe !</p>
<p>Si vous suivez de près ce qui se passe autour des architectures Jamstack, vous n'aurez pas manqué de constater que des CMS headless il y en a de plus en plus, et ce n'est pas prêt de s'arrêter vu la popularité croissante des architectures découplées, et de tous ces termes à la mode comme "microservices" ou "serverless" qui désignent des choses bien plus spécifiques.</p>
<p>L'équipe de rédaction de Smashing Magazine édite principalement un site web et l’utilisation de <a href="https://www.netlifycms.org/" target="_blank" rel="noopener noreferrer">Netlify CMS</a> — dont la v1.0 vient de sortir — suffit à leur besoin. Même si les rédacteurs évoluent dans les métiers du Web, une interface de rédaction et un éditeur visuel sont toujours appréciables et évitent quelques manipulations techniques.</p>
<p>Passer au statique ne veut pas dire, devoir tout faire en ligne de commande, au contraire, tout ce beau monde doit pouvoir interagir et ce de façon automatisée et sûre.</p>
<p>Si jamais cela peut vous rassurer, Contentful, un des acteurs majeurs du marché <a href="https://www.contentful.com/blog/2017/12/04/contentful-series-c/" target="_blank" rel="noopener noreferrer">vient de lever 28 millions de dollars</a> tout comme <a href="https://blog.algolia.com/redefining-incredible-search/" target="_blank" rel="noopener noreferrer">Algolia</a> ou <a href="https://www.netlify.com/blog/2017/08/09/netlify-raises-12m-from-a16z/" target="_blank" rel="noopener noreferrer">Netlify</a> avant eux, signe que ce type de solutions ont encore de beaux jours devant elles. Les services de qualité qui proposent des APIs ont la cote.</p>
<blockquote>
<p>If software is eating the world, SaaS APIs are eating the development world.</p>
</blockquote>
<p>Voilà maintenant vous savez ce qui se cache derrière cet énième anglicisme barbare — un découpage fonctionnel des responsabilités des fonctions d’édition, de stockage et d’export des données — et que vous serez un peu plus éclairé la prochaine fois que vous devrez réfléchir à moderniser votre workflow de publication.</p>
<p>Quelques articles en anglais pour approfondir le sujet :</p>
<ul>
<li><a href="https://kenticocloud.com/headless-cms-guide" target="_blank" rel="noopener noreferrer">Le guide ultime du CMS headless de Kentico (PDF, Epub)</a></li>
<li><a href="https://www.forbes.com/sites/forbestechcouncil/2017/11/22/the-benefits-of-a-headless-cms/#3447e5422d85" target="_blank" rel="noopener noreferrer">Les bénéfices d’un CMS headless (Forbes)</a></li>
<li><a href="https://css-tricks.com/what-is-a-headless-cms/" target="_blank" rel="noopener noreferrer">C’est quoi un CMS headless ? (CSS Tricks)</a></li>
<li><a href="https://www.contentful.com/r/knowledgebase/headless-and-decoupled-cms/" target="_blank" rel="noopener noreferrer">CMS headless découplé (Contentful)</a></li>
<li><a href="https://www.storyblok.com/tp/headless-cms-explained" target="_blank" rel="noopener noreferrer">headless CMS en 5 minutes (StoryBlok)</a></li>
<li><a href="https://humanmade.com/wordpress-rest-api-white-paper/" target="_blank" rel="noopener noreferrer">Livre blanc sur l’API REST de WordPress (PDF)</a></li>
</ul>
<p>Listes de CMS headless :</p>
<ul>
<li><a href="https://headlesscms.org/" target="_blank" rel="noopener noreferrer">https://headlesscms.org/</a></li>
<li><a href="https://www.thenewdynamic.org/tools/content-management/headless-cms/" target="_blank" rel="noopener noreferrer">Tools: headless CMS (The New Dynamic)</a></li>
</ul>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2017/12/13/site-ecommerce-statique-scalable-avec-hugo/</id>
    <title>Un site e-commerce statique et extensible avec Hugo</title>
    <published>2017-12-13T12:10:36+00:00</published>
    <link href="https://jamstatic.fr/2017/12/13/site-ecommerce-statique-scalable-avec-hugo/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Les générateurs de site statique sont-ils une solution viable pour les sites de vente en ligne qui encaissent de fortes charges de trafic ?</p></aside>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346937/jamstatic/cart.44e08fdb0bf30a5dbde919823009e107.webp 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346937/jamstatic/cart.44e08fdb0bf30a5dbde919823009e107.webp 800w" width="800" height="312" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346937/jamstatic/cart.44e08fdb0bf30a5dbde919823009e107.avif 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346937/jamstatic/cart.44e08fdb0bf30a5dbde919823009e107.avif 800w" width="800" height="312" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346937/jamstatic/cart.44e08fdb0bf30a5dbde919823009e107.jpg" alt="" loading="lazy" decoding="async" class="dark:brightness-90" width="800" height="312" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8ApGozUhqM0AMNMJpxphoAN1G6oyaN1AEu6jNRbqN1AE2aUGowaetAEi1ItRrUi0APHSigdKKAGmozUhphoAjNMIqQimGgCFhTakao6ACgdaKB1oAkWpFqNakFAEi1ItRrUi0APHSigdKKAENMIqQimGgCM0wipCKYaAInFR4qY03bQBHijFSbaNtAAtSCmAU8UAPFSLTBTxQA8dKKB0ooADTTRRQAw0w0UUAMpKKKACiiigBRThRRQA8VIKKKAHiiiigD/9k=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346937/jamstatic/cart.44e08fdb0bf30a5dbde919823009e107.jpg 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346937/jamstatic/cart.44e08fdb0bf30a5dbde919823009e107.jpg 800w" sizes="100vw">
</picture>
<p>Les générateurs de site statique (GSS) attirent l’attention depuis quelques années déjà. Pour quelqu'un comme moi qui a commencé par développer des sites à la fin des années 90, l’idée de retourner à du pur, du vrai développement web n'est pas dénuée de charme (en prenant bien soin de tirer un trait sur les frames, les images au survol, les tableaux de mise en page et les inclusions côté serveur, hein !)</p>
<p>Mais la nostalgie ne saurait expliquer l’intérêt suscité. Les générateurs de site statique représentent une alternative bien plus simple aux CMS liés à des bases de données comme WordPress et Drupal, pour des sites Web modestes où le contenu est roi. Mais cela marche aussi pour des sites plus importants — <a href="https://www.smashingmagazine.com/" target="_blank" rel="noopener noreferrer">https://www.smashingmagazine.com/</a> est un des sites importants les plus en vue à être passé au statique.</p>
<h2 id="avantages">Avantages</h2>
<p>Les avantages des sites statiques sont clairement documentés, et s'il fallait résumer :</p>
<ul>
<li><strong>Performance</strong> : pas d’application, pas de base de données, tout le HTML est pré-rendu. Le temps pour télécharger le premier byte (TTFB) est réduit au minimum  —  les sites statiques sont en réalité un bon gros cache, stocké au chaud sur un <abbr title="Content Delivery Network">CDN</abbr>.</li>
<li><strong>Montée en charge</strong> : par conséquence, la charge du serveur est très réduite, la possibilité de servir le site entier depuis un CDN implique que les pics de trafic peuvent être aisément absorbés. Moins de temps de consommation CPU, pas de ralentissement dus aux bases de données, une infrastructure simplifiée et bien moins onéreuse.</li>
<li><strong>Sécurité</strong> : Tout est dans le code source des pages HTML.</li>
</ul>
<p>Ce sont de très bonnes raisons d’utiliser un <abbr title="Générateur de Site Statique">GSS</abbr> pour des sites de contenu. Mais pour quelqu'un qui travaille exclusivement dans le <a href="http://www.onstate.co.uk/" target="_blank" rel="noopener noreferrer">e-commerce</a> depuis 10 ans — cela semble être de <em>formidables</em> raisons d’utiliser un générateur de site statique pour une boutique en ligne.</p>
<p>Vous trouverez plein de tutoriels et de guides qui montrent comment mettre en place un site ecommerce basique à l’aide de générateurs de site statique — avec des produits simples, en quantité limitée. L'avis général est toutefois que les GSS ne sont pas adaptés pour des boutiques en ligne dynamiques, plus importantes et plus complexes. Est-ce vraiment le cas ? En y réfléchissant, je me suis demandé comment développer une architecture ecommerce statique extensible (ou SSEA pour Scalable Static Ecommerce Architecture en anglais).</p>
<h2 id="prise-de-conscience">Prise de conscience</h2>
<p>Maintenant, toutes les boutiques n'ont pas les mêmes contraintes. Je ne suggère pas une seule seconde qu'Amazon doive considérer de passer au statique. Ni aucune boutique importante avec un catalogue de dizaines de milliers de produits.</p>
<p>Mais la plupart des boutiques sont loin d’être aussi importantes. Nous travaillons avec bon nombre de marques à la mode très connues. Elles proposent généralement quelques centaines de produits — voire 1000 ou 2000 grand maximum. En faisant quelques recherches, je me suis aperçu que c'était aussi le cas pour d’autres marques, qui ne figurent pas dans notre portfolio — attention, je parle bien ici de produits, une fois les différentes déclinaisons de taille, de couleur prises en compte, un millier de produits peut représenter disons environ 5000 unités de stock.</p>
<p>Et vous savez quoi ? Ces sites ne sont pas si dynamiques que ça. Les nouveaux produits apparaissent en général lors des nouvelles collections. Les catégories ne bougent plus beaucoup. En fait la seule fois où une page produit a besoin d’être mise à jour c'est quand un produit n'est plus en stock ou que son prix est modifié — et ça n'arrive pas en permanence.</p>
<p>Si un produit simple vient à manquer (un produit sans déclinaison de taille ou de couleur), le bouton <em>ajouter au panier</em> doit être désactivé — on peut aussi éventuellement vouloir le supprimer de la vue de liste des produits. Pour un produit configurable qui serait épuisé il faudrait désactiver la possibilité de sélectionner les différentes options (de taille, couleur ou autre) dans la page.</p>
<h2 id="les-fonctionnalites-dynamiques">Les fonctionnalités dynamiques</h2>
<p>Et qu'en est-il des fonctionnalités dynamiques comme les recommandations et la recherche ? Étonnamment dans une majorité des cas — même sur des solutions populaires auprès des entreprises comme Magento — ces fonctionnalités sont assurées par des technologies tierces. Nosto peut assurer <a href="http://www.nosto.com/fr/fonctionnalites/recommandations-produits-personnalisees/" target="_blank" rel="noopener noreferrer">les recommandations personnalisées de produits</a>. Algolia peut proposer une excellente <a href="https://www.algolia.com/ecommerce" target="_blank" rel="noopener noreferrer">expérience de recherche pour le Ecommerce</a>. Les plateformes d’optimisation comme <a href="https://www.optimizely.com/" target="_blank" rel="noopener noreferrer">Optimizely</a> fonctionnent sur le même principe, les sites statiques peuvent aisément intégrer toutes ces technologies.</p>
<h2 id="le-panier-d-achat">Le panier d’achat</h2>
<p>Les fonctionnalités de gestion du panier d’achat peuvent être également intégrées de la sorte. Les plates-formes <em>headless</em> comme <a href="https://moltin.com/" target="_blank" rel="noopener noreferrer">moltin</a> ou les paniers gérés en JS comme <a href="https://snipcart.com/" target="_blank" rel="noopener noreferrer">Snipcart</a> vont vous permettre de fournir toutes les fonctionnalités nécessaires à une expérience d’achat complète avec gestion des promotions, des commandes, des paiements, de la livraison, des comptes clients, etc. Et ne croyez pas que vous y perdrez en flexibilité au passage. Ces systèmes sont entièrement capables, extensibles et peuvent se connecter à des solutions tierces d’<abbr title="Enterprise resource planning">ERP</abbr>, de <abbr title="Warehouse Management System ">WMS</abbr> ou d’<abbr title="Order Management System">OMS</abbr> — tout comme les plates-formes "Entreprise" qui coûtent des milliers d’euros par mois et qui demandent des centaines de milliers d’euros à mettre en place.</p>
<h2 id="en-resume">En résumé</h2>
<p>Nous disposons donc de toutes les fonctionnalités nécessaires pour un site transactionnel. Comment faire pour l’assembler et faire en sorte qu'il soit toujours à jour ? Comment répercutons-nous les changements de stock décrits précédemment ?</p>
<p>Pour y parvenir, notre architecture extensible ecommerce statique doit répondre à plusieurs critères :</p>
<ul>
<li><strong>Vitesse</strong> : elle doit être capable de générer le site rapidement. Même si les changements de fichiers ne sont pas si fréquents, lorsqu'ils se produisent, les changements doivent être répercutés sur le site de production aussi vite que possible pour éviter de frustrer le client.</li>
<li><strong>Données</strong> : le modèle de données doit être suffisamment flexible pour pouvoir gérer les informations liées aux produits : description, niveau de stock, prix, mais aussi pages de contenu, articles de blog et systèmes de navigation.</li>
</ul>
<h2 id="hugo">Hugo</h2>
<p>Lorsqu'il est question de vitesse de génération de site, on pense forcément à <a href="https://gohugo.io/" target="_blank" rel="noopener noreferrer">Hugo</a>. J'ai lu pas mal de choses à son sujet et j'ai voulu le mettre à l’épreuve. Combien de temps cela prendrait-il pour générer toutes les pages produits d’un site ? Pour le mesurer, j'ai repris des exemples de données de produits issues du site d’un de nos clients. Ce site propose des <a href="https://www.crockettandjones.com/charlton-black-calf/" target="_blank" rel="noopener noreferrer">pages de produits très riches en contenu</a>, avec des blocs de contenu inclus dynamiquement dans la page en fonction des attributs du produit. Cela est possible grâce à une pseudo-intégration entre Magento et WordPress, mais il s'avère que c'est extrêmement simple à réaliser à l’aide du langage de gabarit de page d’Hugo.</p>
<p>Afficher les informations d’un produit se fait très simplement :</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>
   Ajustement : <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>{{ .Params.fitting }}<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">br</span>&gt;</span>
   Finition : <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>{{ .Params.last }}<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">br</span>&gt;</span>
   Taille (UK) : <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>{{ .Params.uksize }}<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span> <span class="hljs-tag">&lt;/<span class="hljs-name">br</span>&gt;</span>
   Semelle : <span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>{{ .Params.sole }}<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></code></pre>
<p>Ajouter la logique dans les blocs inclus dynamiquement demande un peu plus d’effort, mais même un piètre développeur comme moi peut y arriver sans peine :</p>
<pre><code class="language-go-html-template hljs go">{{ $f1path := (<span class="hljs-built_in">print</span> <span class="hljs-string">"shoefeatures/"</span> $.Params.feature1 <span class="hljs-string">".html"</span>) }}
{{ partial $f1path}}
{{ $f2path := (<span class="hljs-built_in">print</span> <span class="hljs-string">"shoefeatures/"</span> $.Params.feature2 <span class="hljs-string">".html"</span>) }}
{{ partial $f2path}}
{{ $f3path := (<span class="hljs-built_in">print</span> <span class="hljs-string">"shoefeatures/"</span> $.Params.feature3 <span class="hljs-string">".html"</span>) }}
{{ partial $f3path}}
{{ $f4path := (<span class="hljs-built_in">print</span> <span class="hljs-string">"shoefeatures/"</span> $.Params.feature4 <span class="hljs-string">".html"</span>) }}
{{ partial $f4path}}</code></pre>
<p>Tout ce que nous faisons ici, c'est générer les chemins vers les fichiers partiels à inclure dans la page de manière (semi-)dynamique.</p>
<h2 id="performance">Performance</h2>
<p>Bon et maintenant en pratique, est-ce que ça va vite ? J'ai créé 1000 fiches produit (en les dupliquant) — sous la forme de fichiers JSON qui contiennent chacun le nom, le prix et les différentes données dans les variables <a href="https://gohugo.io/content-management/front-matter/" target="_blank" rel="noopener noreferrer"><code>front matter</code></a>. Ces données sont ensuite insérées dans les gabarits de page et les fichiers d’inclusion, qui sont ensuite assemblés et transformés en pages HTML.</p>
<p>En plus des 1000 pages produit, Hugo a aussi généré une cinquantaine de pages de listes (à raison de 20 produits par page), une page d’accueil et un plan de site au format XML.</p>
<p>Quand je lance la commande <code>hugo</code> pour générer le site, la tâche prend moins d’une seconde — en moyenne 720ms sur un MacBook Pro pour être précis :</p>
<pre><code class="language-sh hljs bash">Built site <span class="hljs-keyword">for</span> language fr:
0 draft content
0 future content
0 expired content
1000 regular pages created
8 other pages created
0 non-page files copied
50 paginator pages created
0 tags created
0 categories created
total <span class="hljs-keyword">in</span> 724 ms</code></pre>
<p>Je trouve cela très impressionnant. Il y a tout de même quelques points à prendre en compte :</p>
<ul>
<li><strong>La complexité</strong> : bien que ces gabarits de page présentent quelques difficultés, le code HTML généré est encore très basique et ne saurait suffire à proposer une interface similaire à celle montrée en exemple. Cela dit j'ai par la suite augmenté la complexité du HTML petit à petit lors de mes tests et je n'ai pas remarqué d’impact significatif sur la performance suite à mes différents changements.</li>
<li><strong>La gestion des assets</strong> : contrairement à d’autres générateurs, Hugo ne s'occupe pas de compiler et d’optimiser les fichiers CSS et JS. Vous pouvez toujours décider de faire appel à des outils comme Gulp, Grunt ou équivalent pour cela. Ce n'est pas gênant en ce qui nous concerne, car les déploiements liés aux outils de développement n'ont pas lieu si souvent que ça, la plupart des mises à jour concernent les données, à savoir les contenus, l’inventaire ou les prix.</li>
</ul>
<p>Malgré cela, les temps de génération restent tout à fait acceptables selon moi. Même en doublant la taille du catalogue et en ajoutant le support de deux langues supplémentaires, le tout serait généré en moins de 5 secondes.</p>
<h2 id="les-donnees">Les données</h2>
<p>Maintenant que nous avons évacué la problématique de la performance, qu'en est-il de celle des données ? Le modèle de données du produit ne pose pas de problème, ni tout ce qui est type de contenu éditorial ou article de blog que nous pourrions avoir envie d’ajouter : Hugo gère parfaitement tout cela. Hugo intègre aussi par défaut la gestion de <a href="https://gohugo.io/content-management/menus/" target="_blank" rel="noopener noreferrer">systèmes de navigation hiérarchiques</a>.</p>
<p>Et pour les données typiques d’un site d’ecommmerce comme le prix ou la mise à jour des quantités en stock ? Il faudrait que nous puissions pousser ces informations sur le site car elles impacteront potentiellement le code HTML des pages.</p>
<p>Si on considère le scénario discuté précedemment : quand une taille n'est plus disponible, nous devons l’indiquer sur la page produit. Le menu déroulant ou le sélecteur doit être mis à jour. Dans la configuration classique d’un site de ecommerce, cette information est souvent remontée par le logiciel ERP.</p>
<p>Idéalement, nous voudrions avoir à n'envoyer que les prix et les informations de stock, nous n'avons pas besoin d’avoir toutes les données des produits à chaque échange. Ça tombe bien, Hugo intègre nativement la gestion des fichiers de données.</p>
<p>Ici mes produits sont des chaussures. Dans les données <em>front matter</em> de chaque produit, j'ai entré les détails relatifs aux différentes variantes de taille de la façon suivante :</p>
<pre><code class="language-json hljs json"><span class="hljs-string">"variants"</span>: [
      {
        <span class="hljs-attr">"title"</span>: <span class="hljs-string">"6"</span>,
        <span class="hljs-attr">"sku"</span>: <span class="hljs-string">"9000-6"</span>
      },
      {
        <span class="hljs-attr">"title"</span>: <span class="hljs-string">"7"</span>,
        <span class="hljs-attr">"sku"</span>: <span class="hljs-string">"9000-7"</span>
      },
      {
        <span class="hljs-attr">"title"</span>: <span class="hljs-string">"8"</span>,
        <span class="hljs-attr">"sku"</span>: <span class="hljs-string">"9000-8"</span>
      },
      {
        <span class="hljs-attr">"title"</span>: <span class="hljs-string">"9"</span>,
        <span class="hljs-attr">"sku"</span>: <span class="hljs-string">"9000-9"</span>
      }
    ]</code></pre>
<p>La gestion des fichiers de données d’Hugo permet de stocker des données additionnelles qui sont ensuite insérées lors de chaque génération du site. Dans ce cas de figure, j'ai besoin de créer des fichiers pour les prix et des fichiers pour l’inventaire. Ces fichiers peuvent être générés et exportés par un logiciel ERP. J'ai créé un fichier d’inventaire très simple au format JSON qui contient les niveaux de stock des différentes références produit :</p>
<pre><code class="language-json hljs json">{
 <span class="hljs-attr">"stock"</span>: {
    <span class="hljs-attr">"9000-1"</span>: {
     <span class="hljs-attr">"allocation"</span>: <span class="hljs-number">3</span>
    },
    <span class="hljs-attr">"9000-2"</span>: {
     <span class="hljs-attr">"allocation"</span>: <span class="hljs-number">9</span>
    },
    <span class="hljs-attr">"9000-3"</span>: {
     <span class="hljs-attr">"allocation"</span>: <span class="hljs-number">3</span>
    },
    <span class="hljs-attr">"9000-4"</span>: {
     <span class="hljs-attr">"allocation"</span>: <span class="hljs-number">1</span>
    },
    …
  }
}</code></pre>
<p>J'ai 6000 enregistrements en tout, puisqu'il y a six tailles de disponibles pour les 1000 produits, cela génère donc un fichier de taille non négligeable. Je range ce fichier dans <code>data/inventory/gb.json</code> (dans Hugo les fichiers de données sont censés être stockés dans le répertoire <code>data</code>).</p>
<p>Dans le gabarit de la page produit, je peux accéder à la valeur de la variable <code>allocation</code> de cette manière :</p>
<pre><code class="language-go-html-template hljs go">&lt;ul&gt;
    {{ <span class="hljs-keyword">range</span> .Params.variants }}
        &lt;li&gt;{{ .sku }}, {{ (index $.Site.Data.inventory.gb.stock .sku).allocation  }}&lt;/li&gt;
    {{ end }}
&lt;/ul&gt;</code></pre>
<p>Cela va permettre d’afficher le stock correspondant à une taille. Vous procéderiez peut-être différemment pour l’affichage une fiche produit sur le site, mais la construction d’une interface à partir de ces données se ferait de manière très similaire.</p>
<p>Nous voilà donc avec 6000 données additionnelles à prendre en compte et à afficher dans les gabarits de page. Cela devrait pas mal augmenter le temps de génération non ? C’est effectivement le cas :</p>
<pre><code class="language-sh hljs bash">Built site <span class="hljs-keyword">for</span> language fr:
0 draft content
0 future content
0 expired content
1000 regular pages created
8 other pages created
0 non-page files copied
50 paginator pages created
0 tags created
0 categories created
total <span class="hljs-keyword">in</span> 826 ms</code></pre>
<p>…mais ça reste acceptable, nous sommes toujours sous la seconde pour un site comportant 1000 pages produit.</p>
<h2 id="exemple-d-architecture">Exemple d’architecture</h2>
<p>Il semble donc que nous ayons à notre disposition tous les éléments nécessaires pour bâtir une architecture ecommerce statique extensible. Une vue d’ensemble simplifiée de notre système pourrait ressembler à ça :</p>
<picture>
<source type="image/webp" srcset="/res.cloudinary.com/jamstatic/image/upload/dpr_auto-f_auto-q_auto/v1603638639/jamstatic/exemple-architecture-ecommerce.943b9bbb791b2203c68e3e4f3014b504.webp" width="637" height="261">
<source type="image/avif" srcset="/res.cloudinary.com/jamstatic/image/upload/dpr_auto-f_auto-q_auto/v1603638639/jamstatic/exemple-architecture-ecommerce.943b9bbb791b2203c68e3e4f3014b504.avif" width="637" height="261">
<img src="/res.cloudinary.com/jamstatic/image/upload/dpr_auto-f_auto-q_auto/v1603638639/jamstatic/exemple-architecture-ecommerce.943b9bbb791b2203c68e3e4f3014b504.png" alt="" loading="lazy" decoding="async" class="dark:brightness-90" width="637" height="261" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAESUlEQVR4nO1bWY6rMBAE5PufMueI35ejTtP7YjLSK2kECcFuXK7ekjlfr9eccx7HcRzrWI3zPMXjgmYHvm49em2Ffxb7K49DevAs8IL/hw6SkF9Bl2J/GSIh0QWxKmPOWaqi8zw/Y8LjX8L1tAEWzDm//iRoccpy75MgFYJ3lnWXPamMNe5fxxchkIjsQ3bsth0LDrOr3Zhz3l1W1hh8P3ztXdCKhfHOaXGLnRjH8a0MyRjuWveO8rjQTA2CXSmVIHQrf2hFWNVk3uAa2dnU0Qt4H0dCFzHHgWKI9aGw0fhaxW6iUldJKR3dBvgMHSRQtl74onenQZ+b3aVS/NFsiMwnjacRXzkfhLlS5xa7etdQPhzbsLPg41SSUYxk+xUppPAu6cpMnizUPCqpfPavLEvy13hySiEZ1XCdX+3aDlSpxGL3iLgEbrdoxkXbGk/VBlSRjM+xXVlVf2KIVMBRvSRJIXhM+BnpPW4cyo7d4MjwungNA07iMY46l4yTCJDGfrpy5lSCCcKIpv5sLwurIqIQjOu6N5c5g6X49RQ4MjiFR7LB0BdU0RgiGU+95yGhmyyJDMpVe9YFQiWkSiGWbMxSYHJzdLXguQQEEwRtxR1jT/a5TSHaLuLGpTYAPqfmzRLjrcu456Hul8Z0VerZGEKB2tlSkKTONbsj9kRSc/w6EtjJ5qJncm8M8UIiopoU6GokV6Uho06y/W7x4Z46ZF2jvluw7CAPEVnFrjE4m6orc4wBb5RSXcpd4fOq3pPWPdAqZMomy+IsIjLqoOC5b+CF5kiA1+EkWiDPkmRxWdx90E7LPJnPVLmy8X6/v27Q1GEdGH5Wap1IwTMTOzjbfwGSPQPueE0ZWCGeDARW6VzwlFJj7rxqsbW58bXKuSFMCsGIGLJI4fy0pf1gaUVQGyxi64Inm6vATSHQICtBUXABFGZg65qVCHzuRSQLrMQtqMNjF6BKqPiybIg053Y9QxdEQjrVAUERAe2wjtFhn1UdVXHlspDRBW+fhxuDU5kXljTaYk8Gpl+/VxHj3fFSxczdUwVLzRGxUcOItjMqIM2TzY4ysNY5HRidC8/l9pgI7uEsytwVvHeR1EoIBlQgRdb6THaOKmgktBSGywdG00wLqBoBz0NlV94WTRd21iIfhUiFGLerI8BZHAyK1KawztmxqXZ6j4WxekyUO8E+P0sKt9CemCKhql0uteC7ISoE77jK2sQ6BvU5rN7IuBZIHegusAqhiFhGVimFS7kt9+JxILJu6wlXtXBTCEUEJIGLMxKkLMVS/2gq8VyzwGNHNXnjui62XqDUwKmoA9ZYEo050fk78fl3hHWUijlKId2kLFhiCeViM/NwiUanS7v92FoiB74v+f5qgiwui/qirXrOHRD/HUHaCZGAXAHOXUWKSescC1u+MeTc1IKFHA7dBHWQgcffXocch/7jMqkjrB270E0GnGcnKf8AgPS2bWIfrZcAAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<ul>
<li><strong>ERP</strong> : chargé de l’enregistrement des commandes, de la gestion du stock et des prix. Ces données sont transmises à la plate-forme de Ecommerce et également à Hugo.</li>
<li><strong>CMS</strong> : Drupal ou si possible une solution <em>headless</em> comme <a href="https://www.contentful.com/" target="_blank" rel="noopener noreferrer">Contentful</a> ou <a href="https://getdirectus.com/" target="_blank" rel="noopener noreferrer">Directus</a>. Son rôle est de fournir une interface conviviale pour l’édition de contenus ainsi que pour l’enregistrement des données de produits enrichis, les contenus éditoriaux et le contenu statique.</li>
<li><strong>Hugo</strong> : éventuellement combiné à Gulp, Grunt ou équivalent pour la gestion des assets.</li>
<li><strong>Plate-forme Ecommerce</strong> : le workflow précis dépend des possibilités offertes par la plate-forme. Si elle permet de déclencher une génération des prix et du stock à chaque modification, l’ERP n'aurait sûrement même pas besoin de communiquer avec Hugo.</li>
</ul>
<h2 id="deploiement">Déploiement</h2>
<p>Pour une efficacité maximale, seuls les fichiers modifiés auraient besoin d’être déployés après un changement. La majorité des mises à jour de l’inventaire n'impacteraient pas forcément le HTML — si le stock passe de 6 à 3, cela n'a aucun impact sur la fiche produit, le produit est toujours disponible. Si le stock passe à 0 ou de 0 à un chiffre positif, alors il faut déclencher un changement. Et seuls les fichiers impactés doivent être déployés.</p>
<picture>
<source type="image/webp" srcset="/res.cloudinary.com/jamstatic/image/upload/dpr_auto-f_auto-q_auto/v1603639120/jamstatic/deploiement-ecommerce.42378f38078f211132943bf38303da18.webp" width="531" height="61">
<source type="image/avif" srcset="/res.cloudinary.com/jamstatic/image/upload/dpr_auto-f_auto-q_auto/v1603639120/jamstatic/deploiement-ecommerce.42378f38078f211132943bf38303da18.avif" width="531" height="61">
<img src="/res.cloudinary.com/jamstatic/image/upload/dpr_auto-f_auto-q_auto/v1603639120/jamstatic/deploiement-ecommerce.42378f38078f211132943bf38303da18.png" alt="" loading="lazy" decoding="async" class="dark:brightness-90" width="531" height="61" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAADjElEQVR4nO1ZWbKEIAxU4f73mjspvK9YmX4JhEXFGrqK0nGJgaaTwKyfzycuE8PAO+ee9mGCwXvvn/ZhgmESMhgmIYNh5pDBMBUyGKZCBsPPELKu69MumPAzhLwF/i0zpxVv6afftk29GePcVbkCqcmRLLHoRY2YNxA2gjJKfMiGrBjjsq7rv8GXyHiSIK0fd/mUGkcaw9xzy5JRiGZc+03nTwyC9s07FCJN2NpvqwqxDCoSEGM82924OzRJMx594MrQ3kdUL9MlEqR2JSwkXEEUJ4O3Ht/8p5CSQeQDH0L4Ot6tlpJBqO0zEkCNV6oUvqz+4HPNG1lEAm8txPSY0ZKNXGiRzqX3t207SeBk4HktToXUKoMrRGt3EyLZy1U5uYKEq4GOfJdj27bz3Zo+0DtNCpHIOI7j68hJKXHM8m3L+xohWKFZCCEynHPq863ENCV1TSXHcSz7vleppJUQtIUJmH+Dl6spUjBfxBi/1EH3+Pu1Su+SQ5AM3kpUUtKJGkJohuO3MM/h5OFk4GYsJvgWMpalU8hKKYWTIqnEGttL75FNnN2clFw/eOhBMshmCOFLOa1VZZe/C7U1CeYVqaOtyVZ7RiKDvs1JSZXt3Bb+DiF85QtLpWbB5f/fSmsVfu8uQugaDrbknzTTUwqy+GjFJYRoK1kssaWEy0GEWeJy6hkpuWMOwcSM65bc2qYXuhCCiZPkzOMu77DWaQmWWafFbmntIBGCpaoUVnkOwlyUU3kJmgiRYjSRgURYKq3UClpDzp7UpBxCuUUrPDghzjmRZK0fJehCCO+I9B89EWKpQjCspWC1pRHDbaR8QzKdc2dDxbSqpJoQHtuRFCwXSxaHVxFCx1S+ytniK3WuFFRLC3xNZYA5g6AtplLVi2Yb7bVAKh5KvyPlI00hNaSQD80hizvrnBNzhpWMnLOtfqLPNTak3V6JkFqlVCkEneQhi9+TwtgosA6YVP5KBUKrMgjNOQSdJgdLiXiSsNT6RTvXioRHcggHOtojRD0NHNQUMXQsLXu1sem6MMxdS2EE8lJESL/xmuX5HEwKKdmrqZVsj1VuL9QQk7PBt4FSUBWi7V5KBqWVbSneqhAN0phIORfhQwhFhq33SjGSQgg4gJYBzdnCc4QYsqzXfgUaMbVIKuQ4jibjv4Q7JuUkZDBMQgaD3/f9aR8mGKZCBsNUyGCYhAyGSchgmDlkMPwBZ2zvUlAtpOkAAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<p>Les changements relatifs au prix ou à la description du produit doivent eux toujours déclencher une génération, mais cela se produirait déjà moins souvent.</p>
<p>Pour les besoins de ma preuve de concept, j'ai testé si <a href="https://www.netlify.com/" target="_blank" rel="noopener noreferrer">Netlify</a> pouvait gérer cela. J'ai été ravi de constater que c'est comme cela que Netlify gère les déploiements par défaut.</p>
<p>Afin de tester cela, j'ai mis à jour les niveaux de stock de quelques produits et j'ai poussé le fichier modifié sur le dépôt Git. Netlify a déclenché une génération avec Hugo comme prévu, en identifiant les fichiers modifiés et en ne mettant à jour que ceux-ci. L'opération a pris une vingtaine de secondes en tout.</p>
<pre><code class="language-sh hljs bash">Built site <span class="hljs-keyword">for</span> language fr:
0 draft content
0 future content
0 expired content
1000 regular pages created
8 other pages created
0 non-page files copied
50 paginator pages created
0 tags created
0 categories created
total <span class="hljs-keyword">in</span> 834 ms
9:10:18 PM: Build complete: <span class="hljs-built_in">exit</span> code: 0
9:10:19 PM: Cleaning up docker container
9:10:19 PM: Starting to deploy site from <span class="hljs-string">'public'</span>
9:10:19 PM: Deploying to CDN
9:10:22 PM: Uploading 5 files
9:10:22 PM: Uploading file product/cadogan-tobacco-calf-16/index.html
9:10:22 PM: Uploading file product/cadogan-tobacco-calf-340/index.html
9:10:22 PM: Uploading file product/cadogan-tobacco-calf-1/index.html
9:10:22 PM: Uploading file product/cadogan-tobacco-calf-267/index.html
9:10:22 PM: Uploading file product/cadogan-tobacco-calf-102/index.html
9:10:23 PM: Starting post processing
9:10:28 PM: Finished uploading cache <span class="hljs-keyword">in</span> 473.751738ms
9:10:28 PM: Post processing <span class="hljs-keyword">done</span>
9:10:28 PM: Site is live
9:10:28 PM: Finished processing build request <span class="hljs-keyword">in</span> 20.097144616s</code></pre>
<h2 id="questions-en-suspend">Questions en suspend</h2>
<p>Nous approchons du but, cependant il reste encore des questions auxquelles nous n'avons pas encore totalement répondu, par exemple :</p>
<h3 id="workflow">Workflow</h3>
<p>Des services comme Netlify gère les déploiements de code via Git. Dans la configuration proposée ici, cela implique un nouveau commit pour chaque mise à jour d’inventaire, ce qui n'est peut-être pas forcément l’idéal. On pourrait imaginer gérer des branches distinctes pour le code et les contenus et les fusionner ensuite au besoin, mais ça pourrait vite devenir problématique.</p>
<h3 id="mises-a-jour-de-l-inventaire-en-temps-reel">Mises à jour de l’inventaire en temps réel</h3>
<p>Les inventaires sont en général remis complètement à jour pendant la nuit et des deltas sont appliqués au cours de la journée pour refléter le mouvement des stocks dans les entrepôts. Ici nous nous sommes penchés seulement sur le premier cas de figure, je n'ai pas encore réfléchi à comment nous pourrions gérer le second.</p>
<h3 id="apercu-et-preproduction">Aperçu et préproduction</h3>
<p>Cela pourrait s'avérer problématique si des développeurs, des contributeurs et des processus automatisés mettent tous le code à jour.</p>
<h2 id="pour-aller-plus-loin">Pour aller plus loin</h2>
<p>Voilà où j'en suis de mes expérimentations, mais tout cela me pousse à penser que c'est tout à fait envisageable et que cette architecture présente des avantages intéressants. Cela semble particulièrement adapté pour des boutiques de marques de taille moyenne — avec une taille de catalogue produit raisonnable et des fiches produits potentiellement complexes en termes de contenus.</p>
<p>En plus des aspects liés à la sécurité, à la performance et à la montée en charge déjà cités, j'aimerais aussi ajouter :</p>
<ul>
<li><strong>Gestion de contenu</strong> : Hugo est capable de gérer différents types de contenu contrairement aux plates-formes de Ecommerce qui ne brillent généralement pas dans ce domaine. À une époque où les vendeurs et les marques doivent mettre de plus en plus du contenu intégré en avant, c'est un vrai plus.</li>
<li><strong>Flexibilité</strong> : les modèles de données dans Hugo sont très flexibles et <a href="https://gohugo.io/templates/" target="_blank" rel="noopener noreferrer">le langage de gabarits de pages</a> suffisamment puissants pour gérer les différents scénarios possibles — je n'ai fait qu'effleurer ce qu'il est possible de faire dans cet article.</li>
<li><strong>Simplicité</strong> : les gérants de boutiques sont souvent tributaires des développeurs quant à la connaissance des arcanes des plates-formes de Ecommerce utilisées. Ce type de configuration est beaucoup plus accessible — vous avez surtout à vous familiariser avec la modélisation des données et comprendre les bases du langage de gabarits des pages.</li>
</ul>
<p>Je continuerais d’explorer cette preuve de concept en fonction du temps que je pourrais y consacrer, mais je serais d’ores et déjà très intéressé par des retours de personnes qui auraient testé, considéré ou écarté l’approche décrite ici.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2017/12/05/gatsby-contentful-netlify-algolia/</id>
    <title>Gatsby + Contentful + Netlify et Algolia</title>
    <published>2017-12-05T00:00:00+00:00</published>
    <link href="https://jamstatic.fr/2017/12/05/gatsby-contentful-netlify-algolia/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Le passage d’un CMS traditionnel comme WordPress à un processus moderne de développement faisant appel à différents services dans le Cloud et à des générateurs de site statique open source peut sembler encore risqué. C’est sans doute pour cela qu'une fois le pas franchi, vous avez immédiatement envie de partager votre enthousiasme, afin de montrer que non seulement c'est possible, mais que ça peut se faire sans douleur et à moindre coût. Le témoignage de <a href="https://twitter.com/3cordguy" target="_blank" rel="noopener noreferrer">Josh Weaver</a> sur la migration d’un site de documentation vient s'ajouter à la longue liste des heureux convertis à la <a href="/2017/03/16/5-raisons-de-tester-la-jamstack/">Jamstack</a>.</p></aside>
<p>Gatsby connaît un vif succès et une <a href="https://github.com/gatsbyjs/gatsby#showcase" target="_blank" rel="noopener noreferrer">adoption</a> croissante depuis peu et ce pour de bonnes raisons. C’est un outil suffisamment flexible pour s'adapter à pas mal de situations.</p>
<p>Si votre budget est serré et que vous ne voulez pas sacrifier l’expérience de développement ou les déploiements à la pointe, je me suis arrêté sur un ensemble d’outils (dont Gatsby bien entendu) pour développer des sites statiques, qui résolvent plusieurs problématiques d’un coup.</p>
<p>L'exemple que je vais couvrir ici est <a href="http://rollcalldocs.netlify.com/" title="Version beta" target="_blank" rel="noopener noreferrer">le site de documentation</a> pour le principal logiciel édité par notre entreprise. Le site regroupe beaucoup de contenus avec des centaines d’articles.</p>
<p>La liste de prérequis pour ce site est la suivante :</p>
<ul>
<li>Vitesse — autant pour le développement que pour la performance du site</li>
<li>Facilité d’utilisation — autant pour le développeur que pour le contributeur</li>
<li>Recherche — c'est un site de documentation après tout</li>
<li>Hébergement pas cher — maximisation de la valeur (qui ne la recherche pas ?)</li>
<li>Déploiement en continu automatisé</li>
</ul>
<p>Voici donc un retour d’expérience global sur l’utilisation de <a href="https://www.gatsbyjs.org" target="_blank" rel="noopener noreferrer">Gatsby</a> avec <a href="https://www.contentful.com/" target="_blank" rel="noopener noreferrer">Contentful</a>, <a href="https://netlify.com" target="_blank" rel="noopener noreferrer">Netlify</a> et <a href="https://algolia.com" target="_blank" rel="noopener noreferrer">Algolia</a>, les problèmes qu'ils aident à résoudre — sans mettre le nez dans le code.</p>
<p>Je sais que ce n'est pas bien de formuler des hypothèses, mais je vais quand même partir du principe que si vous lisez ceci c'est que vous en savez déjà un peu sur <a href="/2017/03/16/5-raisons-de-tester-la-jamstack/">les avantages offerts par les sites statiques et la Jamstack</a>. Si ce n'est pas le cas, allez faire un tour sur <a href="https://jamstack.org/" target="_blank" rel="noopener noreferrer">jamstack.org</a> pour comprendre en détail pourquoi le développement web, à défaut d’une meilleure formule, "revient aux sources".</p>
<p>Je me suis pas mal pris la tête avec Joomla dans un passé, en passe d’être enfoui et plus récemment avec WordPress, je suis alors entré en quête d’une façon de simplifier les choses. Je ne veux pas à avoir à m'inquiéter qu'un plugin ou qu'un thème puisse se faire hacker ou à être constamment obligé d’appliquer des mises à jour. J'aimerais tant qu'à faire ne pas avoir à gérer de thèmes du tout et simplement avoir à ma disposition des blocs de construction flexibles pour définir l’apparence de mon site à l’aide de mon propre code. Les sites statiques excellent dans ce domaine.</p>
<p>Mais si les sites statiques possèdent de nombreux avantages, ils ne sont pas dépourvus de nouveaux défis à relever.</p>
<p>Bien qu'ils soient rapides par défaut, les sites statiques ne font rien pour faciliter l’édition de contenu. Après tout, le contenu d’un site statique est juste statique. Cela veut dire que traditionnellement vous devez modifier le code de la page ou ajouter un fichier Markdown, lancer une génération, puis un déploiement. Bien que les générateurs de sites statiques aient résolu cela de façons diverses, j'ai le sentiment que Gatsby résout ce problème de façon particulièrement élégante à l’aide de sa couche d’abstraction des données avec GraphQL (on y reviendra) et son vaste écosystème de <a href="https://www.gatsbyjs.org/docs/plugins/" target="_blank" rel="noopener noreferrer">plugins de sources de données</a>.</p>
<p>Avant que j'aborde la thématique du contenu et des données, je voudrais juste dire que développer un gabarit de page pour un site statique avec une architecture basée sur React et le rechargement à chaud des modules, c'est juste du bonheur. L'outil en ligne de commande de Gatsby vous aide à faire ça très vite. C’est vraiment super plaisant à utiliser. À en croire les <a href="http://twitter.com/gatsbyjs" target="_blank" rel="noopener noreferrer">nombreux tweets</a> qui clament la même chose, je pense que c'est un sentiment partagé.</p>
<p>OK, revenons aux problèmes posés par le statique.</p>
<p>Notre site a beaucoup de contenus (environ 300 articles) qui ont besoin d’être maintenus par mes collègues, qui ne sont pas des développeurs. Nous avions donc besoin d’une interface accessible pour copier et modifier le contenu. Je voulais que ce soit aussi simple que de se connecter dans WordPress et pouvoir publier depuis l’outil, mais sans WordPress. L'expérience de rédaction ne pouvait donc pas se résumer à la création d’un fichier et à enregistrer les changements dans un dépôt Git.</p>
<aside class="note note-tip"><p>Il existe un plugin <a href="https://www.gatsbyjs.org/packages/gatsby-source-wordpress/" target="_blank" rel="noopener noreferrer">Gatsby-Source-WordPress</a> qui récupère le contenu via l’API de WordPress. Toutefois, en ce qui me concerne, ce n'était pas vraiment souhaitable, car je voulais éviter d’avoir à héberger un CMS traditionnel.</p></aside>
<p>Contentful est un CMS headless hébergé avec une expérience utilisateur fantastique. C’est comme avoir une interface comme WordPress, sauf que vous êtes entièrement responsable de la couche client. La beauté de Contentful est triple :</p>
<ul>
<li>Une interface utilisateur attractive et intuitive,</li>
<li>Un modèle de contenu simple,</li>
<li><a href="https://www.contentful.com/pricing/" target="_blank" rel="noopener noreferrer">Une formule gratuite</a>.</li>
</ul>
<p>L'utilisation de l’interface d’administration de Contentful est intéressante et la modélisation du contenu est bien plus avancée comparé à ce que proposent d’autres gestionnaires de contenu headless. Contenful ne se contente pas de faire le job, le service se révèle très agréable à l’utilisation. Et ils viennent juste d’inclure <a href="https://www.contentful.com/blog/2017/11/28/work-smarter-with-our-new-search-features/" target="_blank" rel="noopener noreferrer">de nouvelles fonctionnalités</a> qui permettent de rechercher et de filtrer les articles encore plus facilement.</p>
<p>Contentful propose également <a href="https://www.contentful.com/pricing/" target="_blank" rel="noopener noreferrer">une formule gratuite généreuse</a> avec des fonctionnalités bien utiles pour une petite agence ou pour quelques projets. Actuellement, l’offre comprend plusieurs espaces (ou projets si vous préférez), dix mille enregistrements et cinq utilisateurs qui peuvent être administrateurs, éditeurs ou auteurs de contenu. Tout ce que Contentful demande en échange c'est d’afficher leur logo dans votre pied de page ou de les mentionner dans le fichier README de votre dépôt.</p>
<p>Maintenant que je vous ai fait ce petit topo, comment Contentful vient s'interfacer avec un site sous Gastsby ?</p>
<p>Notre documentation est composée de 40 entrées et chaque entrée comporte plusieurs articles. Notre gros challenge était de concevoir une navigation par entrée.</p>
<figure>
<picture title="Navigation documentation.">
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346549/jamstatic/rollcall-docs.da8465353e3743a20529c684429bfe23.webp 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346549/jamstatic/rollcall-docs.da8465353e3743a20529c684429bfe23.webp 786w" width="786" height="653" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346549/jamstatic/rollcall-docs.da8465353e3743a20529c684429bfe23.avif 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346549/jamstatic/rollcall-docs.da8465353e3743a20529c684429bfe23.avif 786w" width="786" height="653" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346549/jamstatic/rollcall-docs.da8465353e3743a20529c684429bfe23.png" alt="Navigation documentation" loading="lazy" decoding="async" class="dark:brightness-90" width="786" height="653" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346549/jamstatic/rollcall-docs.da8465353e3743a20529c684429bfe23.png 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346549/jamstatic/rollcall-docs.da8465353e3743a20529c684429bfe23.png 786w" sizes="100vw">
</picture>
<figcaption>Navigation documentation.</figcaption>
</figure>
<p>La façon dont Gastby gère les données permet de résoudre facilement ce genre de problématique, car il est très simple de récupérer des données depuis des sources externes. Ce n'est pas le <em>seul</em> générateur de site statique à faire cela — il existe aussi des plugins pour d’autres générateurs qui permettent de récupérer du contenu, mais je trouve l’insertion des données dans vos composants React et vos pages à l’aide de GraphQL très élégante.</p>
<p>Après avoir installé le <a href="https://www.gatsbyjs.org/packages/gatsby-source-contentful/" target="_blank" rel="noopener noreferrer">plugin</a> <code>gatsby-source-contentful</code> avec NPM et avoir ajouté vos paramètres de connexion à l’API de Contentful au fichier <code>gatsby-config</code>, on va pouvoir enfin s'amuser.</p>
<p>Dès que vous lancez la commande <code>develop</code> ou <code>build</code> de Gatsby, le plugin va vérifier à l’aide de l’API de Contentful si de nouveaux contenus sont disponibles et les télécharger. Toutes ces données sont dès lors disponibles pour que vous puissiez faire vos requêtes dans votre environnement de développement. Cela veut dire que vous pouvez commencer à récupérer les assets et le contenu depuis Contentful (les assets comprennent les images et autres médias, le contenu désigne les pages, les articles, tous vos contenus texte et vos fichiers Markdown) à l’aide de requêtes GraphQL directement dans vos fichiers de gabarits de page.</p>
<aside class="note note-tip"><p>J'ai mis en place un blog pour ma femme à l’aide de Gatsby avant de travailler sur ce site de documentation, j'avais donc un peu d’expérience dans l’utilisation des APIs de Gatsby. Mais je me considère encore comme un grand débutant dès qu'il s'agit de travailler avec GraphQL. Heureusement pour moi, les tutos de Gatsby et de la communauté sont excellents et répondent aux questions qu'on peut se poser, ainsi qu'à celles liées à l’utilisation globale.</p></aside>
<p>À l’aide d’une seule requête GraphQL, j'ai été capable de récupérer toutes les entrées et les articles relatifs définis dans mon modèle de contenu dans Contentful pour la navigation. Grâce à l’efficacité de React et à un peu de GraphQL, j'ai été capable de créer une barre de menu latérale générée dynamiquement à partir du contenu récupéré depuis Contentful. Je dois dire que c'est un sentiment assez grisant de pouvoir créer du contenu statique à partir de données dynamiques de la sorte.</p>
<p>Les articles quant à eux sont écrits en Markdown dans l’éditeur de Contentful. Ils sont convertis en HTML à l’aide d’un plugin dans Gatsby. L'édition de contenus en Markdown est super pratique grâce à des fonctionnalités similaires à celles que l’on retrouve dans n'importe quel éditeur WYSIWYG. J'ai n'ai eu aucun retour négatif de mes collègues.</p>
<p>Un autre "problème" avec les sites statiques, c'est qu'ils n'embarquent pas une recherche par défaut. La plupart des solutions de recherche fait appel à un serveur et à une base de données. Sur un site de documentation, les utilisateurs s'attendent à bénéficier d’une recherche efficace. Il existe quelques bibliothèques front-end uniquement en JavaScript (comme <a href="https://lunrjs.com/" target="_blank" rel="noopener noreferrer">lunr.js</a>) qui vont prendre une requête et parcourir un index pré-construit au format JSON de votre contenu.</p>
<p>J'aurais pu créer cet index en allant taper dans la méthode <code>onPostBuild</code> de l’API de Gatsby. Cet évènement se déclenche une fois que toutes les pages ont été générées, tous les nœuds de pages sont prêts à être parcourus pour créer un index de recherche.</p>
<p>J'ai rapidement compris que cette approche n'aurait pas bien fonctionné dans notre cas à cause du nombre important d’articles. Le fichier d’index à lui seul aurait été assez gros et aurait représenté une grosse part du téléchargement du site, ce qui me semblait être complètement antithétique avec les bénéfices de performance offerts par l’utilisation de Gatsby (ou d’un site statique). J'avais besoin d’une solution qui opère côté client, mais dont la logique applicative réside quelque part dans le Cloud. Et même si ça aurait pu être une option, je n'avais pas le temps de développer ma propre solution.</p>
<p>La solution s'est profilée petit à petit en testant et en échouant. Lors de mes pérégrinations de développeur j'avais vu que pas mal de sites de documentation utilisaient Algolia en production. Je savais qu'Algolia propose une formule gratuite (là aussi en faisant figurer leur logo) avec un nombre d’appels à l’API suffisant pour notre audience. Par contre je ne savais pas comment faire pour que tout mon contenu soit indexé proprement. La documentation d’Algolia est d’une grande aide en ce qui concerne l’indexation.</p>
<p>Le plus difficile était de savoir comment découper le contenu des articles en petits morceaux pour respecter les prérequis de l’indexation. <a href="https://www.algolia.com/doc/guides/indexing/structuring-your-data/?language=php#indexing-long-documents" target="_blank" rel="noopener noreferrer">La documentation d’Algolia</a> indique que les enregistrements de l’index ne doivent pas dépasser 10kb chacun, ce qui équivaut à peu près à un ou deux paragraphes. C’est devenu soudainement un défi de parcourir le contenu de mes articles par section. Il n'y avait pas d’exemple assez parlant à ma disposition pour savoir comme faire cela.</p>
<p>J'ai fini par me tourner vers une bibliothèque HTML vers JSON qui transforme la hiérarchie de la page en objet JSON parcourable. J'ai ajouté un script sur l’évènement <code>onPostBuild</code> de l’API de Gatsby qui récupère le HTML généré de chaque article. La bibliothèque s'est occupée de transformer magiquement le HTML en JSON, je n'avais plus qu'à parcourir le JSON. Tout en gardant la trace du dernier niveau de titre lié (les balises <code>h</code>), j'ai défini le lien de page de l’enregistrement d’index en conséquence pour chaque section d’article. L'index est en suite transféré chez Algolia via leur client en node.js.</p>
<p>C'était pas super propre, mais ça marchait.</p>
<p>J'ai fini par coupler la méthode d’indexation avec <a href="https://community.algolia.com/react-instantsearch/" target="_blank" rel="noopener noreferrer">React InstantSearch</a>. C’est la bibliothèque du composant React officiel d’Algolia pour utiliser leur service. Au final j'avais un champ de recherche avec des suggestions de résultats en surbrillance qui permettaient aux gens de cliquer sur un de ces résultats pour être amené directement sur le titre parent d’un article en particulier.</p>
<p>Sympa.</p>
<p>Toutefois une fois que j'ai eu mis tout ça en place, il s'est avéré que j'avais quelques problèmes dans mon implémentation qui m'ont obligé à demander de l’aide au support. Je recevais des mails relatifs à l’utilisation du quota alors que j'étais persuadé d’être encore très loin d’avoir atteint les limites de l’usage autorisé.</p>
<p>L'ironie a voulu que je découvre <a href="https://community.algolia.com/docsearch/" target="_blank" rel="noopener noreferrer">DocSearch</a> d’Algolia à ce moment-là. Et comme le ferait tout bon développeur, j'ai mis tout mon travail à la poubelle et je me suis inscrit sur DocSearch. Pour faire cours, DocSearch va crawler votre site toutes les 24 heures et mettre à jour l’index pour vous. Vous ajoutez une balise script qui relie votre champ de rechercher à leur API. Vous affinez les styles avec un peu de CSS et hop, c'est terminé.</p>
<figure>
<picture title="Algolia DocSearch FTW.">
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346556/jamstatic/algolia-search.9034d726adfe46a66db6b51d515a9293.webp 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346556/jamstatic/algolia-search.9034d726adfe46a66db6b51d515a9293.webp 786w" width="786" height="957" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346556/jamstatic/algolia-search.9034d726adfe46a66db6b51d515a9293.avif 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346556/jamstatic/algolia-search.9034d726adfe46a66db6b51d515a9293.avif 786w" width="786" height="957" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346556/jamstatic/algolia-search.9034d726adfe46a66db6b51d515a9293.png" alt="Algolia DocSearch FTW" loading="lazy" decoding="async" class="dark:brightness-90" width="786" height="957" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAIeklEQVR4nN1aYbrkKAgsnNz/krvXiOwPRQHRmHT6zczyvjzTiW3AogDt0D///sv4QAgAEbUD9bNu/xZZ6f9TthyfDsAAwB3Tvw2EXWHWfvs9Gz8GZCXWiD9XZk7EzJN7T+26BvIVQDxLgP8PUzwziAZTX5WvMeRvYMcTZtw3655jvgbI/y2XRMwY+7z/3D+CId9kU+QY32fGzBmvB3kVEM2SK4bovj8R3nYZu8OM4TvPVArltzFEAGktt6vvCVFZJ5lL4wy/wwyaEOOeTa8D0jy/TsbsfgQIvwzIyrk9AE+Y4eUN7b/CEAZAHE/vEhDm10DZyR08dZwbzNBj8nByW766MNQyBUJaAWQC5B3Z9m6iwHF+HzsI316pAz1x1/MtQD4ApUwmDRflSndiDu9vsyOsg80TbgvjJiD08HFzEBaAPAAlBEPrICfELtQwWDNjkv9+Qi4B8Yr5z0/9geUoSMSA3ABlBUakZ8tzweSvHG+rfP6AKVNAdkpF3y8ag0EgsGkBVojgYzCmoiZmGIvRZ35ixNPdhid6y5NCQIqzrWk7VCxAuJdQNuMIRAxmKuFCT8ZCw/V07E8W63h1GXfjDo/AucEU6RUzRIFxueLWTAmZBBApN1STw7PJIeA6igdl7VrTwtIWptg4ytiuFox7slUbuM8DIKbDhjJa4XgbvozKkkxbZwlh10oOdyf5gvRtdUF0ID/6T2XuBVP81cOXf7Z3r0ZWeaR9XY1hwoS+P4XBDDK/tQsIEUiD4Vrbb97nDbmIzEYO1jcEAAVEG3SjOGf7b6JYS+uhdjR9jJ8ocndrOwHgPiBfkGBupgxhFBZI3PTt9Blh+eLOOLhWxQzNAEdbHvX/DJB7YPQxZoB8K5RNS2l1fmTmV58/Y0nLL7WkDetNKmsDfzEOJbQJhA63Nn/EgPyAuPnRFh/5zMoAbu2aHTPN5+yIy+IKjEycY8gqtrdpvMGK2BRVz/0QIuPc9Hk5cj5hDSjn+j2rnj+kn6+k6JoZapxo0UcoYNDwLKtbfeDtMGUdy+Wfn2JGkx4p2IFynIYhHYj45be6uKsy3V8Dxrzhfx2crcQXVc8ngFyGJLcQ/mSXQNdFutUy7kpUQIQhEgaICJQIKQRk7WnmgYFlPF6yhiiLSIem1qq1hMq932DHU9I0fodgWBAyMzhnC8h55mZcYwVSMV4dW1VX4ArbJYPPHR4E1Q591NfH1oIxVz2+cYspyklk1d+2I2rDJlzlgSXHmc+mEFFlhkxASkh3FJpJwJJLy9oBxP56p9iYT/iOJo+/KHgAbg7qHzMyZ3DWgJxnZUcCJQJSQsoZWbf6CYuWOmGbJeF6ksf1u7u9MRFmSXv5zdnC1gM1bAXdlhIlZFZEQ5Nb2YeuzpR0nifOnM1FScKtrdXTXjsR7ge7y0rPOgb1fq4tv5+oa3VN02uFsaJ7KvTw8GNo4WrrAErOOM8TR84ZKaUh679dC44LeuHTWG2155sWPRQP7md8cEsuQ9gT+2+Arx1KAGHmsttrgIAkTJVLtkvLfW1MxcXunMQ4P/PGnHZN/exVAiezwVPkTh554o6PuajYfQzrDlXZyOdiyGKj7pYoZrhEp1GaGjdhCgP1ty+pBhcl7c+vBKeiq0UiwpFSKtVVSkipJHYDUP2MBk4faseu5rljRdzE5Ip2AWi/Ll4ypX8q5LIlelW/fPs3g2EBQF9eJELKaQSkA1OO5Bikhr2lhLzVocNJm2dNDkubQLj/d/c7+Ne71HD9ovbSrpshkDUAhLbMAJXqFsQKEErqoHZQvU9mu2JPYYgSVSFx4YEJ8pHH64Ugqxhm+8YMqWusVDQRx4ilus/GZN+t4Fol5tNESki1xDx+pV8AoQCRaogShlByWyjUY/hqneGUaAYQ+g9QVAYyb6GY8TQqHJ2Gz1oxRMrznKP8IkBs2PQg7Omtp7buI4ASkHKujkc40q/UEkrZw+pM6fkkmUQ+Ru656PkttJUBesxSS5Ta11Nl75WgGUNSIgCpDpshvmqra3kJwoM15srhp+vN0CjnCQBSdfBMYFl2ADiSICXUqQYIOJ1agPi7ZseVR4mqXMNOG8ZHIcklPZm4G3uwSEjQDMkMJGTkLLbKMb5pgvqbUJyD4kJmN1/5z5K7c2aAGMSMI1Wk2g6vD1XJ7bxKUnWvzgxKuhMZQ3tXG62VrmXy5W3G+qD+zA1ASvmrGFKrl0wAUUZKVJngynfq13SZ319hEnvnk+zFbMM40DoYGSkVM5kIB2mG0FhVtXVISx9Uc4j1rkEZUSRIyArWdvRd0I4Bgw0gO0yRsCigIAOZcnEspqqzZX1vSc2F7qvDWGfJnfe2fCE0/JxRdT68EvaAUoYkDwPBy2UiU2boxG2xgMQq/deZ0oHYYYowRINCzfsVA5qzKduUA/pDIomf0NnkD3pNwKNGj2LT0XODBmYEqD9M+qtk6PeYr5JK7qww2zbM5Z7ZyumbnPYZMTCaIYYpYpNiAgz7R4b0I9W+qZXOb4mtQglHB6LfDpE08VBiq8olwLh+GMSypIHAAoL7wWbYsdXhL+aJgFfMIdP6yW95UYAI2JGSgJeQUgZz/4Xoap6s7os+cr2ELEAzxNPSM6Ta17fJV4s2p4zihQLBMmQAhLkFqo5HUET4YqEqGQFj2iU7CjN6VEmmfH1Ler4dGGKT1/DFITFhLF+d2NKWVRWrJ7qDUc6zuqa4sEjoWi/NkhkwzcIJOzQzShVTytJPX8A24h3dM8Sy49bIWMLC3a87Q9ASOeRuwBDzyhDP4IhDwwoYrXmUxEuImuhzc8tkJn6KB4Y8BX6bKTzzdHv+FiD+2RoYwBYA8BUVA+BaHKiS/CsijI0ZYplyc2SsYRFxFVPUQ4PxAighGPouM5i4saM4mcTXa30/EZ1D/gO6OEgJGK1/8gAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346556/jamstatic/algolia-search.9034d726adfe46a66db6b51d515a9293.png 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346556/jamstatic/algolia-search.9034d726adfe46a66db6b51d515a9293.png 786w" sizes="100vw">
</picture>
<figcaption>Algolia DocSearch FTW.</figcaption>
</figure>
<p>Et ça marchait bien mieux que mon implémentation. Je me suis senti tout bête d’avoir dû fournir autant d’efforts pour rien, car j'ai réalisé que la réponse était dans le <a href="https://github.com/reactjs/reactjs.org/search?utf8=%E2%9C%93&amp;q=docsearch&amp;type=" target="_blank" rel="noopener noreferrer">code source</a> du dépôt de Reactjs.org. Ils utilisent DocSearch au lieu de construire leur propre indexation et leur propre interface de recherche. Très bien.</p>
<p>Un truc qui est super avec les sites statiques, c'est qu'on peut les héberger partout. Vous vous retrouvez avec un dossier rempli de fichiers générés que vous pouvez déposer sur n'importe quel serveur et vous êtes bons. Vous pouvez même le mettre dans un bucket Amazon S3 et économiser un paquet d’argent pour un effort minime.</p>
<p>Mais si l’hébergement est aisé, les sites statiques demandent une étape supplémentaire pour déployer les changements effectués sur le contenu ou le code d’un site — à l’inverse de WordPress et des autres CMS traditionnels où chaque changement est immédiatement enregistré sur le serveur.</p>
<p>Si vous ne mettez pas en place une sorte de déploiement automatisé, vous devez déclencher une génération manuellement et la mettre en ligne vous-même. Je voulais un processus de déploiement continu — je pousse un <em>commit</em> sur mon dépôt et Gatsby lance une génération dans le Cloud et déploie automatiquement une nouvelle version du site chez un hébergeur.</p>
<p>Est-ce que je peux faire ça avec AWS ? Bien sûr, mais ça demande un peu de paramétrage et du travail ingrat de configuration. Est-ce que je ne pourrais pas faire ça ailleurs sans avoir autant de choses à configurer ? Est-ce que tout ça peut être gratuit ?</p>
<p>Heureusement je connaissais déjà les réponses à ces questions, car j'avais déjà découvert Netlify à l’occasion de projets précédents.</p>
<p>Brancher mon site statique sur le workflow de Netlify se fait tout seul, et après être tombé sur Gatsby, je savais que c'était la seule option possible. Les deux sont parfaitement complémentaires !</p>
<p>Netlify a récemment revu <a href="https://www.netlify.com/pricing/" target="_blank" rel="noopener noreferrer">ses tarifs</a> pour améliorer ce qui était déjà un hébergement au top vu le prix. Je suis obligé de lister dans cette partie toutes les raisons qui font que Netlify est tellement extra :</p>
<ul>
<li>Formule gratuite dans le cadre de projets personnels ou commerciaux (c'est vraiment une super offre gratuite),</li>
<li>activation du HTTPS en un clic grâce à Let's Encrypt,</li>
<li>réseau de CDN ultra-rapide,</li>
<li>support des noms de domaines personnalisés,</li>
<li>déploiements automatiques</li>
<li>un moteur de génération intégré super cool,</li>
<li><a href="https://www.netlify.com/features/" target="_blank" rel="noopener noreferrer">et bien plus…</a></li>
<li>Et si je vous dis que tout ça est GRATUIT ?</li>
</ul>
<p>Voyons maintenant son utilisation avec Gatsby.</p>
<p>Après avoir lié votre site Netlify à un de vos dépôts de code, les robots chargés de la génération chez Netlify s'occupent de tout le reste. À partir de là, dès qu'il y aura un changement dans votre dépôt, le bot va dire "Hé regarde : un changement ! Il faut que je lance la commande <code>gatsby build</code>", ensuite il va respecter ce qui est défini dans le fichier <code>package.json</code> (ou dans le fichier de yarn) du dépôt et télécharger les dépendances nécessaires si elles ne sont pas encore en cache, enfin il va générer le site statique.</p>
<p>Et pendant le processus de génération, les APIs intelligentes de Gatsby vont prendre soin de rapatrier le contenu de Contentful et de générer les pages statiques pour les articles. Trop bien. Quand c'est terminé, vous pouvez même recevoir une notification sur Slack ou par mail.</p>
<p>Netlify c'est le robot magique qui résout votre problème de déploiement et d’hébergement.</p>
<p>Associé à votre site Gatsby, la performance du site est exceptionnelle. Que ce soit la performance perçue ou la performance mesurée. Les temps d’obtention du premier byte sont de l’ordre de quelques millisecondes. Le découpage du code et les avantages de pré-téléchargement de Gatsby aident aussi à ce que votre site obtienne de bons scores aux tests de performances. Tout ça sans n'avoir rien à faire.</p>
<figure>
<picture title="Indicateurs de performance de la page.">
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346563/jamstatic/webpage-test.efabf52171307f11fc78718f95b28058.webp 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346563/jamstatic/webpage-test.efabf52171307f11fc78718f95b28058.webp 786w" width="786" height="164" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346563/jamstatic/webpage-test.efabf52171307f11fc78718f95b28058.avif 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346563/jamstatic/webpage-test.efabf52171307f11fc78718f95b28058.avif 786w" width="786" height="164" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346563/jamstatic/webpage-test.efabf52171307f11fc78718f95b28058.png" alt="Indicateurs de performance de la page" loading="lazy" decoding="async" class="dark:brightness-90" width="786" height="164" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346563/jamstatic/webpage-test.efabf52171307f11fc78718f95b28058.png 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346563/jamstatic/webpage-test.efabf52171307f11fc78718f95b28058.png 786w" sizes="100vw">
</picture>
<figcaption>Indicateurs de performance de la page.</figcaption>
</figure>
<p>Pour boucler la boucle, nous avions besoin de pouvoir déclencher une nouvelle génération du site à chaque édition ou ajout de contenu depuis Contentful. Une fois de plus Contentful et Netlify disposent de tout ce qu'il faut.</p>
<p>Contentful propose une fonctionnalité de <em>webhook</em> qui vous permet de déclencher une requête quand une action est effectuée sur un contenu ou qu'un contenu est crée. Parfait, à l’aide de ce hook Contentful va pouvoir indiquer à Netlify quand il y a un changement, et Netlify va générer le site et le déployer.</p>
<figure>
<picture title="Déclenchement de la génération par webhook.">
<source type="image/webp" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346571/jamstatic/netlify-build-webhook.8c4b674ca4e9aba82a9bc0bc552137a6.webp" width="702" height="146">
<source type="image/avif" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346571/jamstatic/netlify-build-webhook.8c4b674ca4e9aba82a9bc0bc552137a6.avif" width="702" height="146">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346571/jamstatic/netlify-build-webhook.8c4b674ca4e9aba82a9bc0bc552137a6.png" alt="Déclenchement de la génération par webhook" loading="lazy" decoding="async" class="dark:brightness-90" width="702" height="146" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAERUlEQVR4nO1c27acIAxNnJz+/4eeL+jF9GEAQ7hlVBBt91qe8UaIbDaBaIvf398MDwIiJvtHf0diGV5jZzDfu3/R3R/Ao0dvvqJtHqOQp3Qsgrs/yEOU4XF/hdy9QyncVyEPU4YH1S9f6WCjwZm7kHI1CHg13DaSmEYjP1QZHgRQI2S0o+jqRHVO4KHK8CDg34bbRhCDhc1ffrYyPAigRsgohyUBi9oXrjxYGR4E60/Dbb2I8Q3sSVgAcHXHjgxUxJyAGZXhQcAWQlrY+4BSCXKDjSteAJBVmefCqJAWWP1aoMjABQBebnO2orb3Kjrg5cTK8DhZIXsJ8USwsoFuyJL2/yskgzCeiHM5UkoEaXWsEJERgrcM9vvJuIMyPAj4lzi0OK6mo8CiGEPay3M2BSH4gkQVgSxn69miiECwymmvbkwJ3WtlMakOTUqJEB83eDuHkojVbZowO+6kDA8C/iMODYRwhpCobIUQ9nZQmEDYGl+VR1X+H8CWXORWr27BQgaUjyXwiB/9lSHfTp5dFzEuLl5aCJHqUHEkBN6MnSgey/jhNxSbtG0LHqNDTE/C6R1UAezqKBFS+9Xl/bDlgjq+BDmeIJlCsWGkMnrVTYzylYjVqHasMatKnkOpJKxD5Gq9pMaK2c4YMUkgwC9draFYTh2fQhIilfKCZChr4EplaBz1hRi/Dna1PQ7kkopeJSW1yNJjV+wjp88E+MNXO6zSN1TKPShFq0O/G9nI6N5QiLtoZ4DdH18QL43X6t0g44NUSkkZDMAIiP5Z3w/c9XNP5uHdNBNDRkOrYFHKACEKDh0P3fR4hEo+xgGfiMO09yrkEolOHVGOLMamkm3VvzWdO3cGWacS3iaXmK8mxKOw5gihQ0+tRUwJ608MSpoz49J2ilaeIZWKal8cJz1UZgVEWUcEMsbmpkkw2tqZeJ2BEA81k8JSYyqFhB8ExvuqAwCA1qE9qEU+Vw/zF4VNQcZM3SxG3TMyfbh4Vr0J+TXn5HSqxozMKG/D15QiAYCWZ4MUIqavEQx1f+Qeh/CCst6KweuIy3dGWht5wUZ54y1uzBcLOrthOTQZCawq5ERCOhSOFXKgAnQZpu0440LW/geVlrL5+lQm1cV6b9djW2LcsehFuVllEi4bnVNf3tIbKfaPkJwILBdeEN6h5D395aROFn/3kFLLDKBVxRWkiaykdesG9FidzzTI6/3SHRj/iXwKKRdwC0gRb7bzZzpivRwfEWL63ewn6Rt9a4uQ5PCDVsi0deEe0U0cGd6vWuLeQsrRZGZaOs770LIcn7EnpFTuSBrk9AVDTAaAU0hSHSbOjHjLUrS/EXL8y/I2IRu4ecd5aAqxGPj7ofXktJzwPuETC32pwOyutd45COmgkDmgcl0GzEHIY/5VUkkd9tewcxBygkLmg35sW1PP0DXpiv+CqB+Oq+RqPFQh98VVn5xMg9lGiL8Je4VxRsgbRgAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Déclenchement de la génération par webhook.</figcaption>
</figure>
<p>C’est l’association rêvée au paradis de la Jamstack.</p>
<p>La génération avec Gatsby se fait sous soucis et sans stress. Gatsby sait se faire oublier pour vous permettre d’exprimer votre créativité et votre habilité — et offre quelques avantages de folie comme la gestion des images reponsive et le <em>lazy loading</em>, sans que cela ne nécessite beaucoup d’effort de votre part. Contentful vous permet de vous concentrer sur vos contenus, de la même manière que Gatsby vous laisse vous concentrer sur votre développement et Netlify… marche, tout simplement. Il vous suffit de cliquer sur quelques boutons et vous vous retrouvez à vous demander "Non, mais c'est vraiment aussi simple que ça ?".</p>
<p>Maintenant, j'espère que nos clients partageront ce sentiment avec notre site.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2017/12/02/entretien-michael-rose/</id>
    <title>Entretien avec Michael Rose, designer et développeur front-end</title>
    <published>2017-12-02T00:00:00+00:00</published>
    <link href="https://jamstatic.fr/2017/12/02/entretien-michael-rose/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>Michael Rose est un des webdesigner les plus actifs dans la communauté Jekyll, il est l’auteur de thèmes populaires comme <a href="https://mmistakes.github.io/minimal-mistakes/" target="_blank" rel="noopener noreferrer">Minimal Mistakes</a>, <a href="https://mmistakes.github.io/jekyll-theme-basically-basic/" target="_blank" rel="noopener noreferrer">Basically Basic</a> ou <a href="https://mmistakes.github.io/hpstr-jekyll-theme/" target="_blank" rel="noopener noreferrer">Hpster</a>. Michael partage son expérience et son point de vue de designer qui travaille avec des générateurs de site statique comme Jekyll, Gatsby ou Hugo. Aucun logiciel n'est parfait et il y a toujours de la place pour des améliorations selon lui.</p>
<figure>
<picture title="Michael Rose">
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523347062/jamstatic/michael-rose.33c8a9296796784fa4d3a28a3edada90.webp 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523347062/jamstatic/michael-rose.33c8a9296796784fa4d3a28a3edada90.webp 826w" width="826" height="826" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523347062/jamstatic/michael-rose.33c8a9296796784fa4d3a28a3edada90.avif 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523347062/jamstatic/michael-rose.33c8a9296796784fa4d3a28a3edada90.avif 826w" width="826" height="826" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523347062/jamstatic/michael-rose.33c8a9296796784fa4d3a28a3edada90.jpg" alt="Michael Rose" loading="lazy" decoding="async" class="dark:brightness-90" width="826" height="826" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAlgCWAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A5RDVmM5qnGc1chFUYEzDK1EkeZAatqmRQE2nNTJm8ImtYLhRWupGKwLe5CDFW/tuB1rM2saTgEVn3cO5Timrfg96c10hU5NMEchq9oeeK5pkKviu11R0kziudNqHlziqRlNE+mxnit5UO2q9habVHFaZi2rTuRYq4op5HNFFxWMGI81fhNZydauwtVEI1YcEU90yKrQyYFWg+aiR002QBGBqTaxFWEQNUoh46VFzZIz9rKahmmdRWoYBVS4gGKaJZiTM7mltostk1ZeD5qmhhAIqkZSNC1UBRUkpGKjQ7VqOWTigi4wnmiod9FOxNzESrEZxVdKsJVEFyN+Kso9UkqcEis5G8DUt3Bq+uMVhQzYNaEc5IrO50pFiVgorOnmHNSXEpxWPcz4J5poclYslwTT1kArKFx708XBrRI5Js1DOAKryTZqn5pPejfVWM7ljfRVbfRTEVkqwlFFIRYSpx0ooqJHRTEX71X4elFFZM6oiXH3TWDed6KKqIT2KaVMKKK2RxSJBS0UUzNhRRRQB/9k=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523347062/jamstatic/michael-rose.33c8a9296796784fa4d3a28a3edada90.jpg 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523347062/jamstatic/michael-rose.33c8a9296796784fa4d3a28a3edada90.jpg 826w" sizes="100vw">
</picture>
<figcaption>Michael Rose</figcaption>
</figure>
<h2 id="bonjour-michael-comment-te-presenter">Bonjour Michael, comment te présenter ?</h2>
<p>La réponse la plus simple à cette question est de me présenter comme un designer et un développeur front-end, je suppose. J'ai commencé par travailler dans le monde de l’impression et je suis passé petit à petit au design et au développement Web, je fais ça depuis un peu plus de 17 ans maintenant.</p>
<p>Quand je ne suis pas devant l’écran de mon ordinateur, j'aime bien jouer un peu à la Xbox One et à la Nintendo Switch ou bien je fais des dessins et des peintures numériques à l’aide de mon iPad. Je passe aussi beaucoup de temps avec ma femme et mes deux filles jumelles.</p>
<h2 id="comment-en-es-tu-venu-a-adopter-jekyll-comme-outil-de-publication">Comment en es-tu venu à adopter Jekyll comme outil de publication ?</h2>
<p>Le fait que Jekyll utilise <a href="https://shopify.github.io/liquid/" target="_blank" rel="noopener noreferrer">Liquid</a> pour la conception des gabarits de page m'a tout de suite attiré. À l’époque je développais des sites Web et des blogs avec WordPress et j'avais du mal à comprendre ce qui se passait dans la fameuse "boucle".</p>
<p>Je trouve Liquid clair et facile à comprendre. Prendre un ensemble de documents HTML et ajouter des balises et quelques conditions si/sinon a fait tilt chez moi.</p>
<p>Bien entendu, tous les bénéfices du passage au statique sont aussi appréciables, mais c'est vraiment le processus de création des gabarits de page que j'apprécie le plus. Ce qui m'a amené par la suite à développer des thèmes open source pour la communauté.</p>
<h2 id="sur-quels-types-de-sites-travailles-tu">Sur quels types de sites travailles-tu ?</h2>
<p>Dans mon travail, je suis principalement designer frontend pour des sites de e-commmerce, j'administre aussi les contenus. J'utilise souvent Jekyll pour m'aider à prototyper de nouveaux blocs de contenus et de nouveaux agencements de pages avant de les intégrer dans OpenText, un CMS d’entreprise que nous utilisons sur plus d’une trentaine de sites.</p>
<p>En dehors du boulot, je m'occupe de quelques blogs WordPress avec beaucoup de contenus, <a href="https://mademistakes.com/" target="_blank" rel="noopener noreferrer">mon site personnel</a> est sous Jekyll, et je maintiens <a href="https://mademistakes.com/work/jekyll-themes/" target="_blank" rel="noopener noreferrer">plusieurs thèmes populaires pour Jekyll</a>.</p>
<h2 id="qu-est-ce-qui-te-plait-dans-le-fait-de-travailler-avec-des-generateurs-de-site-statique">Qu'est-ce qui te plait dans le fait de travailler avec des générateurs de site statique ?</h2>
<p>Il y a trois chose que j'aime bien quand je travaille avec des <abbr title="Générateur de Site Statique">GSS</abbr> :</p>
<ol>
<li>Leur <strong>simplicité</strong>. Des fichiers faciles d’accès, faciles à éditer, à modifier et à transformer en HTML. Il n'y a rien de magique dans le processus et on peut adopter le workflow que l’on souhaite.</li>
<li>Leur <strong>interopérabilité</strong>. La plupart des <abbr title="Générateur de Site Statique">GSS</abbr> utilisent des fichiers Markdown pour <em>stocker</em> les contenus, ce qui rend les migrations vers d’autres générateurs beaucoup plus simples.</li>
<li>Leur <strong>performance</strong>. Comme ils ne se reposent pas sur une connexion à une base de données, les pages peuvent être facilement optimisées et mises en cache pour que ça aille plus vite. C’est quelque chose qu'il n'est pas aussi aisé à réaliser avec des sites dynamiques servis par WordPress ou ses copains.</li>
</ol>
<h2 id="quelle-est-ta-fonctionnalite-preferee-dans-jekyll">Quelle est ta fonctionnalité préférée dans Jekyll ?</h2>
<p>L'utilisation de <a href="https://shopify.github.io/liquid/" target="_blank" rel="noopener noreferrer">Liquid</a>. J'ai essayé <abbr title="Générateur de Site Statique">GSS</abbr> et Jekyll est de loin le plus facile à apprendre et avec lequel on peut vite construire des trucs.</p>
<h2 id="quelle-est-la-fonctionnalite-qui-te-manque-le-plus">Quelle est la fonctionnalité qui te manque le plus ?</h2>
<p>Comme c'est juste un générateur, le rôle de Jekyll n'est pas de gérer les médias. Toutefois, c'est une des fonctionnalités qui me manque le plus quand je ne travaille pas avec WordPress.</p>
<p>Le gestionnaire de médias de WordPress est excellent et il me manque à chaque fois que je travaille sur des contenus avec beaucoup d’images dans Jekyll. Bien entendu il y a des outils qui peuvent aider dans le processus, mais ce n'est pas la même chose que de pouvoir uploader en masse, éditer, retailler et générer le balisage des images reponsive qui va bien.</p>
<p>Mis à part les différences de langages dans lequel ils sont développés, l’expérience est la même entre les différents <abbr title="Générateur de Site Statique">GSS</abbr>. Quelques années auparavant, j'aurais dit que le manque d’interface de gestion pour l’édition des articles et des pages était un gros frein pour les personnes issues du monde WordPress. Mais avec des services comme <a href="https://forestry.io" target="_blank" rel="noopener noreferrer">Forestry.io</a>, <a href="https://cloudcannon.com" target="_blank" rel="noopener noreferrer">Cloudcannon</a>, et même le <a href="https://github.com/jekyll/jekyll-admin/" target="_blank" rel="noopener noreferrer">plugin <code>jekyll-admin</code></a> ce vide a été en partie rempli.</p>
<p>J'ai aussi regardé des services comme Cloudinary. Mais ils sont payants… du moins si je choisis un plan qui couvre mes besoins, alors que WordPress c'est entièrement gratuit, difficile de rivaliser avec ça ;-)</p>
<p>J'essaie de plus en plus de ne pas polluer mes fichiers Markdown avec des balises personnalisées. Je rêve de pouvoir avoir un fichier en pur Markdown, portable, qui s'affiche partout où Markdown est supporté, et qui peut être prévisualisé facilement (avec les images et tout le reste).</p>
<p>J'ai pas mal étudié <a href="https://www.gatsbyjs.org/" target="_blank" rel="noopener noreferrer">Gatsby</a> qui est un générateur qui embarque un ensemble d’outils modernes et qui permet de ce genre de choses et bien plus. Il peut convertir automatiquement les liens vers les images écrits en Markdown, les redimensionner, les optimiser et générer le balisage adéquat pour une image responsive avec <code>srcset</code> grâce aux plugins <a href="https://www.gatsbyjs.org/packages/gatsby-remark-images/" target="_blank" rel="noopener noreferrer">gatsby-remark-images</a> et <a href="https://www.gatsbyjs.org/packages/gatsby-image/" target="_blank" rel="noopener noreferrer">gatsby-images</a> et ce de manière assez rapide à l’aide de <a href="https://github.com/lovell/sharp" target="_blank" rel="noopener noreferrer">sharp</a>.</p>
<p>Je n'ai pas testé <a href="https://envygeeks.io/2017/11/21/jekyll-assets-3-released" target="_blank" rel="noopener noreferrer">la version 3 du plugin <code>jekyll-assets</code></a> mais les plugins Jekyll écrits en Ruby qui font des choses similaires sont plutôt lents, du fait qu'ils reposent sur des bibliothèques comme Imagemagick ou équivalentes.</p>
<h2 id="pourquoi-y-a-t-il-aussi-peu-de-themes-de-qualite-pour-jekyll-selon-toi">Pourquoi y a-t-il aussi peu de thèmes de qualité pour Jekyll selon toi?</h2>
<p>Jusqu'à récemment peu de thèmes étaient supportés nativement par GitHub Pages. Pour avoir accès à des thèmes de qualité, il fallait connaître Git et savoir <em>forker</em> un dépôt. Mais je pense que la majorité des utilisateurs veulent juste profiter de GitHub Pages pour bénéficier d’un site <em>gratuit</em> avec lequel ils pourront publier des articles.</p>
<p>Maintenant que <a href="https://github.com/blog/2464-use-any-theme-with-github-pages" target="_blank" rel="noopener noreferrer">GitHub Pages supporte les thèmes distants</a> peut-être verrons-nous plus de thèmes de qualité apparaître. Surtout que l’installation et la mise à jour se font sans peine maintenant.</p>
<p>L'autre chose qui pose potentiellement problème pour les développeurs est peut-être le manque de standardisation. Par exemple WordPress possède une nomenclature et un ensemble de standards pour le <a href="https://codex.wordpress.org/Theme_Development" target="_blank" rel="noopener noreferrer">développement de thème</a>. Actuellement, on trouve des thèmes Jekyll un peu partout, et chaque développeur fait les choses à sa sauce. Arriver à se mettre d’accord sur un ensemble de bonnes pratiques pour le nommage des <code>_layouts</code>, les variables de configuration à ajouter au fichier <code>_config.yml</code> et un support natif de i18n dans Jekyll, aiderait pas mal je pense.</p>
<p>Sur mes derniers thèmes, j'ai essayé de me baser sur ce qu'à fait <a href="https://github.com/jekyll/minima/" target="_blank" rel="noopener noreferrer">Minima, le thème par défaut de Jekyll</a>. Puisque c'est par là que commence la plupart des gens, adopter la même nomenclature et une configuration similaire a l’air de plutôt bien fonctionner.</p>
<p>Il y aura toujours des particularités si des thèmes ont des fonctionnalités spécifiques, mais une espèce de base commune pourrait bénéficier à tout le monde. C’est dur de rivaliser avec WordPress à ce niveau. Vous pouvez installer n'importe quel thème et modifier l’apparence de votre site sans trop d’effort.</p>
<h2 id="pourquoi-n-y-a-t-il-pas-plus-de-designers-web-qui-travaillent-avec-des-generateurs-de-site-statique">Pourquoi n'y a-t-il pas plus de designers Web qui travaillent avec des générateurs de site statique ?</h2>
<p>Je n'en suis pas certain, mais je ne pense pas que ce soit par méconnaissance, car il n'y a pas un jour où je tombe sur un billet de blog ou un article sur l’utilisation des sites statiques. Mais c'est peut-être l’environnement dans lequel j'évolue ;)</p>
<p>Il est possible que beaucoup pensent que c'est juste pour faire de petits sites perso. Avec tous les services qui arrivent comme <a href="https://netlify.com" target="_blank" rel="noopener noreferrer">Netlify</a> et les lancements de site visibles comme <a href="/2017/03/17/smashing-mag-va-dix-fois-plus-vite/">Smashing Magazine avec Hugo</a>, je ne pense pas que ça soit le cas. Donc c'est peut-être qu'il faut juste continuer de faire passer le mot que les générateurs de site statique sont un ensemble d’outils puissants qui permettent de monter en charge.</p>
<h2 id="ton-theme-hpster-https-dldx-github-io-hpstr-hugo-theme-a-ete-porte-pour-hugo-est-ce-que-tu-as-teste">Ton <a href="https://dldx.github.io/hpstr-hugo-theme/" target="_blank" rel="noopener noreferrer">thème hpster</a> a été porté pour Hugo, est-ce que tu as testé ?</h2>
<p>Oui j'ai regardé. Quand ce thème a été porté, Hugo commençait à peine à faire parler de lui. Depuis Hugo s'est considérablement développé dans la communauté des générateurs et c'est vraiment quelque chose sur lequel il faut que je me penche un peu plus. J'aimerais bien voir Jekyll s'inspirer de la vitesse avec laquelle il arrive à générer les pages. Comme j'ai un site personnel assez important avec des milliers de fichiers Markdown, la génération prend plusieurs minutes avec Jekyll. Alors qu'avec Hugo, je pense que ce sera quelques secondes.</p>
<p>La seule chose qui me retienne véritablement de creuser un peu plus dans Hugo est <a href="https://gohugo.io/templates/" target="_blank" rel="noopener noreferrer">son langage pour les gabarits de page</a>. Ça vient peut-être de moi, mais ce n'est pas aussi lisible que les gabarits Liquid et j'ai vraiment de mal à comprendre ce que fait le code.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2017/11/27/entretien-bud-parr/</id>
    <title>Entretien avec Bud Parr de The New Dynamic</title>
    <published>2017-11-27T00:00:00+00:00</published>
    <link href="https://jamstatic.fr/2017/11/27/entretien-bud-parr/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>Bud Parr est impliqué dans la communauté qui gravite autour des générateurs de site statique depuis plusieurs années. Il possède <a href="https://www.thenewdynamic.com/" target="_blank" rel="noopener noreferrer">sa propre agence</a> et agrège sa curation autour de l’écosystème de la Jamstack sur <a href="https://thenewdynamic.org" target="_blank" rel="noopener noreferrer">thenewdynamic.org</a>. Bud organise également des <a href="http://www.meetup.com/the-new-dynamic/" target="_blank" rel="noopener noreferrer">meetups à New-York</a> et il est également à l’origine du récent redesign du <a href="https://gohugo.io/" target="_blank" rel="noopener noreferrer">site d’Hugo</a>. Il a gentiment accepté de répondre à nos questions.</p>
<figure>
<picture title="Bud Parr">
<source type="image/webp" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346302/jamstatic/bud_parr.f99b6681cf84c76e65b73ccd7f0d0ab8.webp" width="460" height="460">
<source type="image/avif" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346302/jamstatic/bud_parr.f99b6681cf84c76e65b73ccd7f0d0ab8.avif" width="460" height="460">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346302/jamstatic/bud_parr.f99b6681cf84c76e65b73ccd7f0d0ab8.jpg" alt="Bud Parr" loading="lazy" decoding="async" class="dark:brightness-90" width="460" height="460" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAlgCWAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A9vJphNBNITQAE1SuxlDVonFVLmQbTSY0zk9UiyTXOTptJrptUnQEjIrmbiZWJwa5aiPRoSuipj5q0bGHc4rPU5atzTFBYVnFXZrUdkdNpcZVRW/H92suwQBRWqnSuyKsjy5u7JRTwajBpwNWQPzRQKKAIyaidwtNeQKKyL/UlhUktQIuXF4qA5Nc7qmuxxKwDCuf1nxKE3AP+tcDqviN5CwD1pGFyXI6PVPEIZyA1Z8WpiU9a4aa/kkcksatWV+VYZNKrR0NqNazO8S4HXNa2naiiOMmuITUh5f3qrnWjFJw1csKep01at0e66dqMbouGFbcU4Yda8S0bxRhlBf9a9D0jWlnVfmrp5bHFzXOxBzUi1Ut5g6irQNSMkFFNzRQBiX115cZOa858R66U3ANXUa/dFIGwe1eO+Ib1nmYZ71rTjczkyhqWqvO5+Y1jSSEnJNDsSahbmulKxkIW5oWUqcg00000mrjRbF6wXGaryTs5zmoqKz5Fc0cm0XrS7eJxzXofhjWGLIC1eZJ1rpPD9yUuFGe9EloSnqfQ2kXXmwqc1uociuJ8M3BeFea7OE5UVzs1RPRRRSGed+I/wDUN9K8b1v/AI+G+tFFdFMxkYj1EaKK3IGmmGiigaENAooqRkiVtaJ/x8r9aKKmWwlue2eFf9Un0rvIPuCiiuWRuixRRRSGf//Z);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Bud Parr</figcaption>
</figure>
<h2 id="bonjour-bud-tu-es-un-observateur-et-un-defenseur-de-ce-que-beaucoup-designent-encore-comme-de-simples-sites-statiques-comment-les-choses-ont-elles-evolue-depuis-quelques-annees">Bonjour Bud, tu es un observateur et un défenseur de ce que beaucoup désignent encore comme de simples sites statiques. Comment les choses ont-elles évolué depuis quelques années ?</h2>
<p>Pour être tout à fait honnête, cela m'a causé pas mal d’insomnies au début. Ce paradigme faisait totalement sens pour moi, mais il y avait peu de personnes impliquées ou qui avaient conscience de tout cela. J'étais vraiment inquiet que s'il m'arrivait quelque chose, mes clients éprouveraient des difficultés à trouver des développeurs pour prendre la suite de leur projet. Et puis, il n'y avait pas énormément de choix dans l’écosytème. Le manque d’un bon éditeur pour les profils non-techniques était un vrai problème. J'ai même dû apprendre à quelques clients comment utiliser Markdown et GitHub, mais ce n'est clairement pas l’idéal. Nous voyons que les choses ont bien évolué depuis, certains des <a href="https://thenewdynamic.org/tools/content-management/" target="_blank" rel="noopener noreferrer">éditeurs</a> actuels sont aussi bons si ce n'est meilleurs que ceux que l’on trouve dans les CMS traditionnels. Cette année, nous avons assisté à l’écclosion de <a href="https://www.thenewdynamic.org/tools/content-management/headless-cms/" target="_blank" rel="noopener noreferrer">CMS headless</a> propriétaires, à vous de voir si vous préférez privilégier une approche basée sur Git ou sur l’utilisation d’une API.</p>
<p>Nous avons aussi assisté à l’émergence du <a href="https://aws.amazon.com/serverless/" target="_blank" rel="noopener noreferrer">Serverless d’Amazon</a> ou des <a href="https://cloud.google.com/functions/" target="_blank" rel="noopener noreferrer">"Cloud functions" de Google</a> et de services bâtis autour du concept de microservices. Ces services facilitent l’accès à des fonctions qui auraient nécessité la gestion d’un serveur auparavant : la gestion de formulaires, l’authentification, les bases de données en temps réel et bien plus.</p>
<h2 id="en-2015-tom-preston-werner-disait-lors-de-la-jekyllconf-que-80-du-web-pourrait-etre-statique-https-www-youtube-com-watch-v-bmve1ockj6m-t-39m55s-embed-false-est-ce-le-cas-aujourd-hui">En 2015 Tom Preston-Werner disait lors de la JekyllConf que <a href="https://www.youtube.com/watch?v=BMve1OCKj6M&amp;t=39m55s" target="_blank" rel="noopener noreferrer">80% du Web pourrait être statique</a>, est-ce le cas aujourd’hui ?</h2>
<p>Peter Levine, un des associés d’Andreessen Horowitz, fait également ce constat lorsqu'il écrit :
<a href="https://a16z.com/2017/08/09/netlify/" target="_blank" rel="noopener noreferrer">&gt; Il y a plus de 300 millions de sites Web déployés chaque année, la plupart de ces sites et applications Web pourraient être hébergées chez Netlify</a>.<br>
Par Netlify, il sous-entend déployé en statique ou basé sur la <a href="https://jamstack.org/" target="_blank" rel="noopener noreferrer">Jamstack</a>. Il me semble que nous sommes encore loin du compte.</p>
<p>Divisons le Web en trois grandes catégories : le Web développé par des ingénieurs, le Web manipulé par des utilisateurs (de thèmes WordPress par exemple) et le Web complétement abstrait (avec Squarespace par exemple). Je suis loin d’être persuadé que les deux derniers groupes s'intéressent à l’adoption de ces technologies, pourtant cela représente une part importante du Web. Le premier groupe lui sera largement composé d’applications Web et de sites statiques. On devrait assister à un rapprochement de ces deux mondes, et les outils que nous utilisons pour développer ces types de sites vont continuer d’évoluer dans les années à venir.</p>
<h2 id="les-administrations-publiques-aux-etats-unis-avec-18f-https-18f-gsa-gov-au-royaume-uni-avec-alphagov-https-github-com-alphagov-ou-en-france-avec-etalab-https-www-etalab-gouv-fr-semblent-privilegier-ce-type-de-workflow-pourquoi-a-ton-avis-est-ce-si-interessant-pour-les-administrations-publiques-et-les-organismes-ouverts">Les administrations publiques aux États-Unis (avec <a href="https://18f.gsa.gov/" target="_blank" rel="noopener noreferrer">18F</a>), au Royaume-Uni (avec <a href="https://github.com/alphagov" target="_blank" rel="noopener noreferrer">Alphagov</a>) ou en France (avec <a href="https://www.etalab.gouv.fr" target="_blank" rel="noopener noreferrer">Etalab</a>) semblent privilégier ce type de workflow. Pourquoi à ton avis est-ce si intéressant pour les administrations publiques et les organismes ouverts ?</h2>
<p>Ce sont des organismes qui ont un large public et une forte exigence de qualité de service : performance, stabilité et sécurité. Il est donc naturel qu'ils veuillent bénéficier des avantages du statique. De plus, ces organismes sont en général composés d’équipes distribuées, qui peuvent être réparties dans différents services, la collaboration permise par un processus de travail basé sur Git — donc sur de la gestion de version — fait totalement sens. Tout le monde est gagnant et cela facilite l’adoption de cette technologie, puisqu'elle est utilisée par des acteurs majeurs et donc crédibles.</p>
<h2 id="nous-voyons-aussi-de-plus-en-plus-d-agences-de-start-ups-adopter-cette-maniere-de-travailler-par-defaut-quand-il-s-agit-de-publier-des-contenus-quels-sont-selon-toi-les-projets-les-plus-susceptibles-de-faire-reflechir-a-deux-fois-avant-de-degainer-son-bon-vieux-cms">Nous voyons aussi de plus en plus d’agences, de start-ups adopter cette manière de travailler par défaut quand il s'agit de publier des contenus. Quels sont selon toi les projets les plus susceptibles de faire réfléchir à deux fois avant de dégaîner son bon vieux CMS ?</h2>
<p>L'équipe qui s'était occupée de la dernière campagne de Barack Obama — site propulsé par Jekyll — s'est également occupée du site de campagne d’Hillary Clinton — site généré avec Assemble en NodeJS. C'étaient des sites relativement importants et relativement sophistiqués. Nous avons <a href="https://www.thenewdynamic.org/showcase/" target="_blank" rel="noopener noreferrer">une galerie sur The New Dyanmic</a> qui a pour but de mettre en avant des projets bien conçus. L'idée c'est de montrer que ce n'est pas uniquement fait pour <a href="http://tom.preston-werner.com/2008/11/17/blogging-like-a-hacker.html" target="_blank" rel="noopener noreferrer">bloguer comme un hacker</a>, on peut utiliser ces outils pour maintenir de la documentation, présenter des services et des produits et faire des choses plus sophistiquées. <a href="https://www.smashingmagazine.com/" target="_blank" rel="noopener noreferrer">Smashing Magazine</a> est un des parfaits derniers exemples en date. C’est un site complet avec beaucoup de contenus, de la vente en ligne, un espace membre, etc. C’est vraiment une chouette vitrine pour la Jamstack.</p>
<h2 id="mais-alors-pourquoi-les-agences-n-adoptent-elles-pas-ce-processus-de-travail">Mais alors pourquoi les agences n'adoptent-elles pas ce processus de travail ?</h2>
<p>C’est difficile à dire pour moi, vu que j'ai choisi de travailler <em>uniquement</em> de cette façon. J'imagine que tout le monde n'a pas encore conscience des possibilités offertes. Je lis encore souvent que la Jamstack, c'est juste bon pour les sites vitrines et les blogs, cette idée reçue est encore tenace. Partir sur les CMS les plus utilisés c'est la sécurité, même si ce n'est pas forcément la bonne solution. Quand un client me dit "Je veux un site WordPress", ce que je comprends c'est qu'il veut "un éditeur Web riche que je pourrais utiliser sans l’aide d’un profil technique". Je crois que malheureusement beaucoup de gens prennent encore ce genre de demande au pied de la lettre.</p>
<h2 id="si-ce-workflow-est-naturel-pour-les-developpeurs-web-les-utilisateurs-finaux-preferent-utiliser-une-interface-graphique-complete-ces-deux-manieres-de-travailler-sont-elles-compatibles">Si ce workflow est naturel pour les développeurs Web, les utilisateurs finaux préfèrent utiliser une interface graphique complète. Ces deux manières de travailler sont-elles compatibles ?</h2>
<p>Tout à fait. On a le choix entre deux approches : une basée sur Git, où tout votre contenu est stocké dans un dépôt Git, et une autre basée sur une API, où vous générez vos contenus qui sont ensuite mis à disposition via une API, pour que vous puissiez les récupérer et les afficher dans votre application ou votre site. Il existe de bons éditeurs Web pour les sites dont le contenu est stocké dans un dépôt Git : <a href="https://forestry.io/" target="_blank" rel="noopener noreferrer">Forestry.io</a>, <a href="https://siteleaf.com" target="_blank" rel="noopener noreferrer">Siteleaf</a>, <a href="https://cloudcannon.com" target="_blank" rel="noopener noreferrer">Cloudcannon</a> et <a href="http://netlifycms.org/" target="_blank" rel="noopener noreferrer">Netlify CMS</a> par exemple. Jusqu'à encore récemment il n'y avait que quelques acteurs majeurs du côté des APIs de contenu (comme <a href="https://www.contentful.com/" target="_blank" rel="noopener noreferrer">Contentful</a> ou <a href="https://prismic.io/" target="_blank" rel="noopener noreferrer">Prismic</a>), maintenant il existe <a href="https://www.thenewdynamic.org/tools/content-management/headless-cms/" target="_blank" rel="noopener noreferrer">des dizaines de CMS headless</a> sans base de données. Il y a donc le choix entre différentes approches et de plus en plus de compétition sur ce plan. C’est une très bonne nouvelle !</p>
<h2 id="quel-est-ton-workflow-actuel-qu-est-ce-qui-te-plait-tant-dedans">Quel est <em>ton</em> workflow actuel ? Qu'est-ce qui te plaît tant dedans ?</h2>
<p>En ce moment j'utilise des services et des outils qui me permettent de répondre à des projets variés : <a href="https://gohugo.io" target="_blank" rel="noopener noreferrer">Hugo</a> pour la génération de site, <a href="http://tachyons.io/" target="_blank" rel="noopener noreferrer">la bibliothèque CSS Tachyons</a> pour construire des interfaces, <a href="https://webpack.js.org/" target="_blank" rel="noopener noreferrer">Webpack</a> pour la gestion des assets, <a href="https://netlify.com" target="_blank" rel="noopener noreferrer">Netlify</a> pour le déploiemet et l’hébergement, <a href="https://forestry.io/" target="_blank" rel="noopener noreferrer">Forestry</a> pour que mes clients puissent rédiger leurs contenus.</p>
<p>Je trouve ce workflow particulièrement efficace. Hugo peut gérer n'importe quel type de site, quelle que soit sa taille. Netlify me permet de déployer des mises à jour en quelques secondes et Forestry permet à mes clients d’être autonomes pendant la phase d’édition. En gros, cet ensemble d’outils me permet d’être plus efficace dans mon travail et de me concentrer en priorité sur les besoins de mes clients.</p>
<p>Cela dit, je continue de tester les nouveaux outils qui sortent, un des avantages d’avoir sa propre agence de développement et de design Web est que je peux faire évoluer ma façon de travailler en adoptant les outils les mieux adaptés.</p>
<p>Par exemple, pour le projet <a href="https://www.retroreport.org/" target="_blank" rel="noopener noreferrer">www.retroreport.org</a> que j'ai réalisé avec Hugo, je me suis reposé sur les <a href="https://gohugo.io/templates/output-formats/" target="_blank" rel="noopener noreferrer">formats d’export personnalisés d’Hugo</a> pour générer des versions alternatives du contenu dans des fichiers JSON pour le lecteur vidéo. La Jamstack est parfaitement indiquée pour ce projet, car le site subit de forts pics de charge quand une des vidéos devient virale sur le Web et il est rassurant de savoir que nous n'aurons pas à nous soucier de la latence lorsque ces pics de charge aléatoires se produisent.</p>
<figure>
<picture title="retroreport.org">
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1603642625/jamstatic/retroreportorg.dba28a66517c262bf0c5d64667e89b5a.webp 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1603642625/jamstatic/retroreportorg.dba28a66517c262bf0c5d64667e89b5a.webp 862w" width="862" height="494" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1603642625/jamstatic/retroreportorg.dba28a66517c262bf0c5d64667e89b5a.avif 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1603642625/jamstatic/retroreportorg.dba28a66517c262bf0c5d64667e89b5a.avif 862w" width="862" height="494" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1603642625/jamstatic/retroreportorg.dba28a66517c262bf0c5d64667e89b5a.jpg" alt="retroreport.org" loading="lazy" decoding="async" class="dark:brightness-90" width="862" height="494" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A9n3Um8VGzVGz0AWC4xVO5mCqaUyVnX8pEZpAZ95qiwk5NURrqZ+8K5rXrp0dsE1zB1SRWPzGkmB6iNcj/vCkfXYwvUV5f/bLD+I0yTWn2n5qfMhHfXfiOJCfmFUF8URFvvCvLtT1qTJwxrJh1mVpPvGlJ6Fxjc90h8Qxt0YVfh1tG/iFeP6fqUjAfMa6WyuXYjk1kp6luB6QurrjqKK5JJX2jk0Vpcix6g849aha5X1rn7jVdo+9WTNrwU/eqnoJK52TXK+tUruUNGea5P8A4SAE/epz6zvX71K9ynGxma7GHZq466hIJxXT6heq+cmueuZ0OealoDGkDA1C5faatySIWoKoUNTyiZy+o55rOt+HzWxqijJxWXEnzU5bGkDotMkxiuy0xwcVwVixVhXYaXL92ue9matHZxMvliiqUch8sUVfOZ2L99M2081zN3M+44Nb983ymuemXcxrplqZwdmV0lkJ6mrizvt61HHBntVpLfIqYoucrmPfTuAeawZ7iQk9a6y6stwPFZUmnDJ4pmZz3mybu9TCZ9uK1TpvtTDp5Hai4HOXgZ81WihOeldHNYe1VPsm09KTV0NOxFaw8iul0tGDCsu2g5HFdHp8OCOK53DU25tDaiH7sUVPHGdgop8pFxb77tYb/fNFFdRkiaKrsVFFIbG3P3ay5OtFFSxIZTSKKKSKKV1Wc/Wiiq6Ek9t96uisO1FFZS3NEdBF/qxRRRQI/9k=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1603642625/jamstatic/retroreportorg.dba28a66517c262bf0c5d64667e89b5a.jpg 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1603642625/jamstatic/retroreportorg.dba28a66517c262bf0c5d64667e89b5a.jpg 862w" sizes="100vw">
</picture>
<figcaption><a href="https://www.retroreport.org" target="_blank" rel="noopener noreferrer">retroreport.org</a></figcaption>
</figure>
<h2 id="comment-vends-tu-cette-maniere-de-travailler-a-tes-clients">Comment vends-tu cette manière de travailler à tes clients ?</h2>
<p>Je n'ai pas à le faire. Mes clients m'engagent pour que je prenne ce genre de décision à leur place. Si je dois aborder le sujet, je vais insister sur les gains de performance, cet argument à lui seul suffit à les convaincre, et les sites statiques permettent une continuité de service proche de 100% tout en réduisant fortement la probabilité de se faire hacker. Ce que je ne mentionne pas trop, mais dont ils bénéficient aussi, c'est la facilité avec laquelle on peut publier les changements suite à leurs retours.</p>
<h2 id="que-souhaites-tu-pour-cette-stack-web-moderne-a-court-terme">Que souhaites-tu pour cette stack Web moderne à court terme ?</h2>
<p>Je pense que le découplage qu'induit naturellement la Jamstack peut perturber ceux qui ne sont pas encore familiers avec le concept. Et il n'y a pas vraiment d’acteur dominant qui impose une "manière standard de faire", comme le font React ou Angular dans le domaine du développement de <em>Single Page Applications</em>. Je trouve que Netlify a fait un super boulot de vulgarisation, en nommant le concept et en faisant la promotion de la Jamstack, mais de ce que je peux observer, les gens sont encore assez désorientés, ils ne savent pas trop par où commencer ni quel serait le meilleur outil à utiliser dans leur cas de figure. J'aimerais donc que les gens puissent se familiariser davantage avec cet écosystème, je ne sais pas très bien comment. La mission de The New Dynamic est de contribuer à cela, et il y a encore beaucoup de travail à faire.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2017/10/03/creer-un-theme-jekyll/</id>
    <title>Créer votre premier thème pour Jekyll</title>
    <published>2017-10-03T12:45:36+00:00</published>
    <updated>2018-01-17T09:10:00+00:00</updated>
    <link href="https://jamstatic.fr/2017/10/03/creer-un-theme-jekyll/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Si vous êtes designer web, que vous savez écrire des pages HTML, les mettre en forme avec CSS, voire les enrichir avec du JavaScript, vous n’aurez aucun mal à développer des thèmes pour Jekyll. Le langage de templating <a href="https://shopify.github.io/liquid/" target="_blank" rel="noopener noreferrer">Liquid</a> a été conçu par Shopify pour les web designers et se prend rapidement en main. Développer un thème pour Jekyll demande de respecter quelques conventions et de se familiariser avec la gestion des gems Ruby, rien de bien sorcier. Dans cet article, <a href="https://darn.es/" target="_blank" rel="noopener noreferrer">David Darnes</a>, développeur du thème <a href="https://alembic.darn.es" target="_blank" rel="noopener noreferrer">Alembic</a>, explique comment utiliser une 💎 gem de thème pour Jekyll, puis comment développer la vôtre.</p></aside>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-q_auto-w_1028/v1523345884/jamstatic/making-jekyll-theme-intro.0c2b227d8eec8faa7a3acac8af8dfa30.webp 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/c_scale-q_auto-w_1028/v1523345884/jamstatic/making-jekyll-theme-intro.0c2b227d8eec8faa7a3acac8af8dfa30.webp 1024w" width="1024" height="426" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-q_auto-w_1028/v1523345884/jamstatic/making-jekyll-theme-intro.0c2b227d8eec8faa7a3acac8af8dfa30.avif 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/c_scale-q_auto-w_1028/v1523345884/jamstatic/making-jekyll-theme-intro.0c2b227d8eec8faa7a3acac8af8dfa30.avif 1024w" width="1024" height="426" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/c_scale-q_auto-w_1028/v1523345884/jamstatic/making-jekyll-theme-intro.0c2b227d8eec8faa7a3acac8af8dfa30.jpg" alt="" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="426" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A080ZpmaTNfYNH5qSZozUeaTdUNAS5ozUO+k8ysp1FHc1hTctifNGah35o3UQqRlsEqco7k2aM1Fuo3VqkZEuaM1HmlzVpASZoqPNFVYRHmk3UwtTS1NoaHlqaWqMtTS1LlLSJC9N3+9Qs1Rl68nHUZte6ergJQTtIuq1KXqosuKd5ma5cFCqpWkdeOVJx90sb6cGquGpwavfjHQ8BoshqXNQBqeGq0jNkuaKjzRVWEQF6aWpuaQmqaFcUtTC1BNNJpWLUhCajNOJpKTgnuaRnYQE08GmYpRUqlFbDdVvclDU8NUINPBrSxk5EwanBqiBp1OxDkS76KizRTsK4lJRRQxDTTDRRSKQ2iiigsKKKKAFFPFFFBLHinUUU0QwoooqgP/Z);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-q_auto-w_1028/v1523345884/jamstatic/making-jekyll-theme-intro.0c2b227d8eec8faa7a3acac8af8dfa30.jpg 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/c_scale-q_auto-w_1028/v1523345884/jamstatic/making-jekyll-theme-intro.0c2b227d8eec8faa7a3acac8af8dfa30.jpg 1024w" sizes="100vw">
</picture>
<p>Tout site correctement structuré permet de présenter facilement ses contenus à l’aide d’un thème, à l’image de ce que souhaite son propriétaire ou son créateur. Jekyll n’échappe pas à la règle. Les pages, les articles et autres formes de contenu formatés peuvent être présentés à l’aide de différents modèles.</p>
<p>Les thèmes pour Jekyll existent depuis un moment, mais le processus d’installation laissait un peu à désirer. Il fallait recopier minutieusement les fichiers de contenus et les différents modèles. Avec l’introduction des <a href="https://jekyllrb.com/docs/themes/" target="_blank" rel="noopener noreferrer">gems de thèmes</a>, les thèmes s’installent maintenant comme les plugins à l’aide de bundler.</p>
<h2 id="comment-fonctionnent-les-themes">Comment fonctionnent les thèmes ?</h2>
<p>Les thèmes pour Jekyll permettent de packager tous les modèles et les fichiers relatifs à la présentation dans une <a href="https://guides.rubygems.org/what-is-a-gem/" target="_blank" rel="noopener noreferrer"><code>gem</code> Ruby</a>, exactement comme c’était déjà le cas pour les plugins. Cela signifie qu’un design peut s’appliquer simplement à un ou plusieurs sites, sans que la couche de présentation ne vienne semer la pagaille dans les fichiers de votre site.</p>
<p><strong>Exemple de structure de site sous Jekyll utilisant une gem de thème :</strong></p>
<pre><code>source/
├── _posts/
│   └── mon-super-article-14-09-2017.md
├── index.html
├── Gemfile
├── _config.yml
├── 404.md
└── a-propos.md&lt;/code&gt;&lt;/pre&gt;</code></pre>
<p>Il est également possible de prendre le pas sur les fichiers de thèmes, un peu à la manière des <a href="https://code.tutsplus.com/articles/how-to-modify-the-parent-theme-behavior-within-the-child-thème--wp-31006" target="_blank" rel="noopener noreferrer">thèmes enfants dans WordPress</a>. Si vous ne connaissez pas le principe, cela signifie qu’un fichier de votre site sera prioritaire sur le fichier du thème situé au même endroit et qui porte le même nom.</p>
<h2 id="comment-utiliser-un-theme">Comment utiliser un thème ?</h2>
<p>Installer un thème est vraiment très simple, mais si vous découvrez Jekyll, vous hésitez peut-être encore à les tester.</p>
<p>D’abord, il faut ajouter le thème que vous voulez utiliser à la liste des gems que vous utilisez pour votre site :</p>
<pre><code class="language-ruby hljs ruby"><span class="hljs-comment"># La gem de base pour Jekyll</span>
gem <span class="hljs-string">"jekyll"</span> <span class="hljs-string">"~&gt; 3"</span>

<span class="hljs-comment"># La gem du thème que vous souhaitez utiliser</span>
gem <span class="hljs-string">"alembic-jekyll-theme"</span>, <span class="hljs-string">"~&gt; 2.2"</span>

<span class="hljs-comment"># Les plugins que vous utilisez</span>
group <span class="hljs-symbol">:jekyll_plugins</span> <span class="hljs-keyword">do</span>
  gem <span class="hljs-string">"jekyll-sitemap"</span>
  gem <span class="hljs-string">"jekyll-paginate"</span>
  gem <span class="hljs-string">"jekyll-seo-tag"</span>
<span class="hljs-keyword">end</span></code></pre>
<p>Le code ci-dessous est un exemple de fichier <code>Gemfile</code>. Ce fichier <code>Gemfile</code> sert à gérer les gems de votre projet avec l’aide de <a href="https://bundler.io/" target="_blank" rel="noopener noreferrer">Bundler</a>. Ici j’utilise le thème <code>alembic-jekyll-theme</code>, ainsi que d’autres plugins pour Jekyll.</p>
<p>Ensuite, il faut déclarer l’utilisation du thème dans votre fichier de configuration <code>_config.yml</code>:</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">theme:</span> <span class="hljs-string">alembic-jekyll-thème</span></code></pre>
<p>Une fois que vous avez modifié ces deux fichiers, il va falloir utiliser <a href="https://bundler.io/" target="_blank" rel="noopener noreferrer">Bundler</a> pour installer notre nouveau thème et pouvoir générer et prévisualiser notre site. Dans votre terminal tapez la commande suivante :</p>
<pre><code class="language-sh hljs bash">bundle</code></pre>
<p>Cela va installer les gems manquantes, puis tapez…</p>
<pre><code class="language-sh hljs bash">bundle <span class="hljs-built_in">exec</span> jekyll build</code></pre>
<p>…pour générer le site avec Jekyll.</p>
<p>Pour des exemples plus avancés <a href="https://learn.siteleaf.com/thèmes/gem-based-thèmes/" target="_blank" rel="noopener noreferrer">consultez la documentation de Siteleaf sur la gestion des thèmes Jekyll</a>.</p>
<h2 id="quels-themes-puis-je-utiliser">Quels thèmes puis-je utiliser ?</h2>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1603642926/jamstatic/making-jekyll-theme-slices.90b3d1460c0fecd3e5aae670a5c81302.webp 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1603642926/jamstatic/making-jekyll-theme-slices.90b3d1460c0fecd3e5aae670a5c81302.webp 862w" width="862" height="575" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1603642926/jamstatic/making-jekyll-theme-slices.90b3d1460c0fecd3e5aae670a5c81302.avif 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1603642926/jamstatic/making-jekyll-theme-slices.90b3d1460c0fecd3e5aae670a5c81302.avif 862w" width="862" height="575" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1603642926/jamstatic/making-jekyll-theme-slices.90b3d1460c0fecd3e5aae670a5c81302.jpg" alt="" loading="lazy" decoding="async" class="dark:brightness-90" width="862" height="575" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A9JpRTitJivM5TpHdqpXbYU1dJwKy7+TCmrpx1Jk9DIuJsMar+eKrXU3znmq/ne9elBaHOzTE9TxT81i+d71Yhm+Yc1VhHS284xVn7QKyLZiRVo5xVJCZb+1D1o+1D1qgUJNARqdkTdmh9pFFUdrUUWC7N/FNNONNNeQdoxzxWJqb4U1sv0NYOqn5TWkHqTI5S5m/enmofO96juyRMar769COxgy553vVm3ly4rL31bsyTIKoR11j8yitIR5FUNMjJUVtrFx0ouFioIaeIRVoR04JRcVir5PtRVvZRSuFiQ0w09qYa8tHWRyHiuf1ZgENb8n3TXMa4+2NquFriZyF9MokPNUTcL61T1K5ImIzWc10fWu6OxgzdFwuetamnSq0g5rijeMD1rX0i9YzKM96q4j13R1DIK29mBXP+HnLxL9K6UjK1DYysaKVhzSUXAXNFJRRcBzUw9aKK806CGX7prldd/1bUUVcNxM8w1P/AF7fWsx6KK9COxiyI9a1tG/16/WiimI9j8Nf6lfpXU/wUUVLAgbrSUUUgCiiigD/2Q==);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1603642926/jamstatic/making-jekyll-theme-slices.90b3d1460c0fecd3e5aae670a5c81302.jpg 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1603642926/jamstatic/making-jekyll-theme-slices.90b3d1460c0fecd3e5aae670a5c81302.jpg 862w" sizes="100vw">
</picture>
<p>De nouvelles gems de thèmes arrivent régulièrement. Il existe des annuaires de thèmes pour Jekyll, mais ils recensent également les anciens types de thèmes (ceux à recopier à la main). Si vous cherchez des thèmes sous forme de gem, recherchez plutôt <a href="https://rubygems.org/search?query=jekyll+theme" target="_blank" rel="noopener noreferrer">'jekyll-theme' sur rubygems.org</a>.</p>
<p>Pour ma part j’en ai développé deux :</p>
<ul>
<li><a href="https://alembic.darn.es" target="_blank" rel="noopener noreferrer"><strong>Alembic</strong></a> - un thème prêt à l’emploi, qui peut aussi servir de point de départ pour votre projet,</li>
<li><a href="https://garth.darn.es" target="_blank" rel="noopener noreferrer"><strong>Garth</strong></a> - un thème de blog très simple.</li>
</ul>
<p>Ces deux thèmes sont compatibles avec Siteleaf, vous pouvez donc configurer un nouveau site sur Siteleaf sans problème. Je vous recommande aussi <a href="https://mmistakes.github.io/minimal-mistakes/" target="_blank" rel="noopener noreferrer">Minimal Mistakes</a>, un thème très complet développé par Michael Rose. Michael développe des thèmes pour Jekyll depuis un moment et son code est très propre.</p>
<p>Pour ceux d’entre vous qui utilisent GitHub Pages pour héberger leur site Jekyll, seuls <a href="https://pages.github.com/themes/" target="_blank" rel="noopener noreferrer">quelques thèmes sont autorisés</a> par défaut.</p>
<p>C’est en partie la raison pour laquelle, selon moi, les thèmes n’ont peut-être pas encore l’ampleur qu’ils pourraient avoir.</p>
<p>Beaucoup d’utilisateurs de Jekyll se reposent sur GitHub Pages pour gérer et héberger leur site, et sont donc limités à ces quelques thèmes. Il est néanmoins possible de contourner cette limitation en utilisant par exemple la formule <a href="https://www.siteleaf.com/plans/" target="_blank" rel="noopener noreferrer">Siteleaf Team+ plan</a> qui vous permet <a href="https://learn.siteleaf.com/thèmes/gem-based-themes/" target="_blank" rel="noopener noreferrer">d’utiliser n’importe quel thème Jekyll</a> et <a href="https://learn.siteleaf.com/themes/jekyll-plugins/#third-party-plugins" target="_blank" rel="noopener noreferrer">n’importe quel plugin</a>.[^custom-plugins]</p>
<h2 id="trucs-et-astuces-pour-creer-un-super-theme">Trucs et astuces pour créer un super thème</h2>
<p>Si vous avez envie de développer votre propre thème, permettez-moi de partager avec vous ce que mon expérience m’a enseigné.</p>
<figure>
<picture title="checklist d’un thème jekyll.">
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-q_auto-w_1024/v1523346069/jamstatic/making-jekyll-theme-checklist.cbc958d80d0246805bcd695496bed4ec.webp 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-q_auto-w_1024/v1523346069/jamstatic/making-jekyll-theme-checklist.cbc958d80d0246805bcd695496bed4ec.webp 1024w" width="1024" height="427" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-q_auto-w_1024/v1523346069/jamstatic/making-jekyll-theme-checklist.cbc958d80d0246805bcd695496bed4ec.avif 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-q_auto-w_1024/v1523346069/jamstatic/making-jekyll-theme-checklist.cbc958d80d0246805bcd695496bed4ec.avif 1024w" width="1024" height="427" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/c_scale-q_auto-w_1024/v1523346069/jamstatic/making-jekyll-theme-checklist.cbc958d80d0246805bcd695496bed4ec.jpg" alt="checklist d’un thème jekyll" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="427" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A6bNGai3Ubq+J5jySXNJuqPdRuo5gJd1GajBzUirVwi5bDSuGaTdQ3FRFqJxcdxtWJd1Lmod9G6o5iSbNGai3UbqOYCXNFRbqKOYCLdRuqLNIWrDmGS7qQvURemFqaZNyyknNWklAXrWX5mKRrghetduHqqO5pCSLk9yoPWq4nDHrWTcXDbutOgmJxmrxDjJXQ52ZsCTNLvqosnFSB64GzG5Y3Uu6oA1KGqeYaZPuoqLNFHMUMzTSaSmmufmM7gWphalNMNWpDGlqjZs040w1akBC6bjTo120/FAq/aMLslVqkDGoRTxWbkImDU8GohTxUOQEmaKbRU8wXCmmiioBDTUZooq0MY1Nooq0AUUUUxCinrRRUsCQU8UUVDGOoooqST//2Q==);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-q_auto-w_1024/v1523346069/jamstatic/making-jekyll-theme-checklist.cbc958d80d0246805bcd695496bed4ec.jpg 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-q_auto-w_1024/v1523346069/jamstatic/making-jekyll-theme-checklist.cbc958d80d0246805bcd695496bed4ec.jpg 1024w" sizes="100vw">
</picture>
<figcaption>checklist d’un thème jekyll.</figcaption>
</figure>
<p>Voici quelques trucs à garder en tête quand on développe son propre thème, surtout que vous souhaitez qu’il soit utilisé par d’autres utilisateurs de Jekyll (et de Sitelaf) :</p>
<ol>
<li><strong>Testez votre thème :</strong> Vous ne testerez jamais assez. Le meilleur moyen est encore de suivre votre propre documentation et de repartir de zéro. Testez votre thème avec différentes sortes de contenus. Les thèmes doivent pouvoir habiller différents types et différentes tailles de contenus.</li>
<li><strong>Fournissez une bonne documentation :</strong> Tout bon thème s’accompagne d’une documentation claire et détaillée. C’est même un prérequis spécifique si vous souhaitez soumettre votre thème sur des marketplaces comme ThemeForest. Assurez-vous que le processus d’installation soit simple à suivre et que toutes les fonctionnalités et les options sont documentées. Je fais de mon mieux pour garder la <a href="https://github.com/daviddarnes/alembic#alembic" target="_blank" rel="noopener noreferrer">documentation de l’utilisation d’Alembic</a> à jour.</li>
<li><strong>Évitez les choses trop complexes :</strong> J’ai vu beaucoup de thèmes WordPress échouer, car ils voulaient trop en faire. Ce n’est pas forcément simple mais essayez de trouver un juste équilibre entre le nombre d’options proposées et celles activées par défaut. Vous ne voulez pas générer de frustration chez les gens en vous éloignant trop de l’aspect de la démo. De plus, Jekyll est un générateur de site statique qui prône la simplicité, votre thème devrait s’en inspirer.</li>
<li><strong>Définissez un usage :</strong> Concevoir un thème susceptible de plaire au plus grand monde <em>et</em> à un certaine type d’industrie peut s’avérer difficile. Je ne dis pas qu’il faut faire faire quelque chose de très spécifique pour l’agence immobilière du coin de la rue, mais peut-être quelque chose en relation avec les sites immobiliers en général. Il y a beaucoup de thèmes génériques qui essaient de répondre à un maximum d’attentes, et vous feriez peut-être bien de ne pas essayer d’aller sur ce terrain mais à la rencontre d’une audience plus ciblée.</li>
<li><strong>Concevez avec l’extensibilité en tête :</strong> Il est fort probable que les utilisateurs de votre thème veuillent le personnaliser, essayez de concevoir votre thème de façon standard. Nommez vos modèles et vos fichiers en fonction <a href="https://jekyllrb.com/docs/structure/" target="_blank" rel="noopener noreferrer">des conventions</a>, et utilisez des noms explicites pour vos <code>_includes</code> (par exemple <code>icon.html</code> si c’est pour insérer une icône).</li>
</ol>
<p>Maintenant que vous en savez un peu plus sur les thèmes pour Jekyll, voyons ensemble quelles sont les choses à savoir pour développer sa propre gem de thème pour Jekyll.</p>
<h2 id="bien-configurer-son-environnement">Bien configurer son environnement</h2>
<p>Avant de rentrer dans le vif du sujet, il y a quelques prérequis à respecter. Il est préférable de connaître un minimum le fonctionnement de Jekyll, l’arborescence de fichiers d’un thème ressemble à celle d’un site Jekyll, même chose pour le processus de développement et le versionnement des fichiers avec Git.</p>
<p>Jekyll doit donc être installé sur votre machine à l’aide de Ruby. Si vous êtes sous macOS High Sierra livré avec Ruby 2.3 vous ne devriez avoir qu’à taper une ligne de commande :</p>
<pre><code class="language-sh hljs bash">gem install bundler jekyll</code></pre>
<p>La documentation officielle propose une méthode pour <a href="https://jekyllrb.com/docs/windows/" target="_blank" rel="noopener noreferrer">installer Jekyll sur une machine Windows</a>.</p>
<p>Si vous préférez utiliser <a href="https://github.com/github/pages-gem" target="_blank" rel="noopener noreferrer">la gem de GitHub en vue d’utiliser GitHub Pages</a>, vous serez limité aux gems supportées par cette plate-forme.</p>
<p>Vous aurez dans tous les cas besoin de <a href="http://bundler.io/" target="_blank" rel="noopener noreferrer">Bundler</a>, pour la gestion des gems utilisées par votre thème.</p>
<p>Enfin, si vous souhaitez proposer votre thème sous forme de gem au public, vous aurez besoin d’un compte sur <a href="https://rubygems.org/" target="_blank" rel="noopener noreferrer">RubyGems.org</a>.</p>
<h2 id="c-est-parti">C’est parti</h2>
<p>Nous allons commencer par créer une base pour notre thème à l’aide de la commande <code>new-theme</code> de Jekyll :</p>
<pre><code class="language-sh hljs bash">jekyll new-theme mon-theme</code></pre>
<p>Cette commande va générer les fichiers nécessaires pour commencer à développer notre thème avec le nom que vous aurez choisi, ici je l’ai appelé <code>mon-theme</code>.</p>
<p>Nous devons ajouter quelques informations à notre thème avant de continuer : une courte description et une URL pour donner plus d’informations sur notre thème, généralement c’est l’URL du dépôt GitHub du thème — ou celle du site web du thème si vous en générez un. Pour cela nous éditons le fichier <code>.gemspec</code> qui porte le nom de votre thème. Les deux champs à renseigner sont :</p>
<pre><code class="language-ruby hljs ruby">spec.summary       = <span class="hljs-string">"Une brève description de mon thème"</span>
spec.homepage      = <span class="hljs-string">"http://url-de-mon-theme.com"</span></code></pre>
<p>Une fois que c’est fait et que vous avez sauvegardé vos changements, nous pouvons installer les gems dont dépend notre thème.</p>
<p>Vous remarquerez que plus bas dans le fichier <code>.gemspec</code>, il y a des lignes qui commencent par <code>spec.add_runtime_dependency</code> et <code>spec.add_development_dependency</code>. C’est ici que nous allons pouvoir spécifier les gems dont notre thème aura besoin pour fonctionner : <em>runtime</em> quand le thème est utilisé et comme son nom l’indique <em>development</em> pour le développement du thème à proprement parlé. L’installation des dites gems se fait ensuite via la commande :</p>
<pre><code class="language-sh hljs bash">bundle</code></pre>
<p>Pour prévisualiser votre thème et vous assurer qu’il fonctionne bien, vous devez avoir un fichier <code>index.html</code> à la racine de votre répertoire avec quelque chose comme :</p>
<pre><code class="language-md hljs markdown">---
title: Accueil
<span class="hljs-section">layout: home
---</span>

<span class="hljs-section"># Du Markdown en plus (ou pas)</span></code></pre>
<p>Ce fichier va vous permettre de prévisualiser votre thème localement, comme vous le feriez avec n’importe quel site Jekyll. Pour lancer la génération et la prévisualisation dans votre navigateur, utilisez la commande suivante :</p>
<pre><code class="language-sh hljs bash">bundle <span class="hljs-built_in">exec</span> jekyll serve</code></pre>
<aside class="note note-tip"><p>Si vous utilisez Jekyll v3.7.0, vous pouvez passer l’option <code>--livereload</code> en paramètre pour que votre navigateur rafraîchisse automatiquement la page après modifications des fichiers.</p></aside>
<p>La sortie sur la console devrait ressembler à ça :</p>
<pre><code class="language-sh hljs bash">$ bundle <span class="hljs-built_in">exec</span> jekyll serve --livereload
Configuration file: none
            Source: /Users/frank/code/jekyll/themes/mon-super-theme
       Destination: /Users/frank/code/jekyll/themes//mon-super-theme/_site
 Incremental build: disabled. Enable with --incremental
      Generating…
                    <span class="hljs-keyword">done</span> <span class="hljs-keyword">in</span> 0.095 seconds.
 Auto-regeneration: enabled <span class="hljs-keyword">for</span> <span class="hljs-string">'/Users/frank/code/jekyll/themes/mon-super-theme'</span>
LiveReload address: http://127.0.0.1:35729
    Server address: http://127.0.0.1:4000
  Server running… press ctrl-c to stop.
        LiveReload: Browser connected</code></pre>
<p>Pour ceux qui ne sont pas encore très familiers avec l’écosystème Ruby, préfixer la commande par <code>bundle exec</code> permet de nous assurer que nous utilisons bien les gems définies dans le fichier <code>Gemfile</code> du dossier courant. Ici comme nous travaillons sur une gem, il pointe vers le fichier <code>.gemspec</code>. Ainsi nous sommes dans la même configuration que les futurs utilisateurs de notre thème.</p>
<h2 id="la-structure-de-fichiers">La structure de fichiers</h2>
<p>Pour le moment nous avons donc la structure suivante :</p>
<pre><code class="language-sh hljs bash">├── _includes
├── _layouts
│   ├── default.html
│   ├── page.html
│   ├── post.html
├── _sass
│   ├── LICENSE.txt
│   ├── README.md
│   ├── assets
│   ├── index.html
│   └── mon-super-theme.gemspec
├── assets
├── index.html
├── mon-super-theme.gemspec
├── Gemfile
├── Gemfile.lock
├── LICENSE.txt
├── README.md</code></pre>
<p>Voyons à quoi servent les différents dossiers et fichiers présents :</p>
<ul>
<li><code>_includes</code> : vide pour le moment, il sert à stocker les fichiers de gabarits partiels,</li>
<li><code>_layouts</code> : contient pour le moment trois exemples de gabarits : <code>default.html</code>, <code>post.html</code> and <code>page.html</code>,</li>
<li><code>_sass</code> : vide pour le moment, destiné à stocker vos fichiers Sass,</li>
<li><code>assets</code> : également vide pour le moment, ce dossier contiendra tous les fichiers statiques dont vous aurez besoin pour votre site : CSS, JS, polices de caractères, images, etc. C’est dans ce dossier que nous placerons le fichier de styles principal <code>styles.scss</code> qui génèrera un fichier <code>styles.css</code> auquel nous ferons référence dans notre modèle de page,</li>
<li>le fichier <code>Gemfile</code> - qui indique à Bundler quelles gems sont nécessaires, et qui pointe vers le fichier <code>.gemspec</code>,</li>
<li>le fichier <code>mon-super-theme.gemspec</code> dans lequel nous stockons toutes les inforamtions relatives à notre thème, ainsi que les gems dont il dépend. On y définira le numéro de version ainsi que la liste des fichiers de notre thème défini à l’aide de <code>spec.files</code>. Vous n’avez pas besoin d’éditer cette liste, qui respecte déjà la <a href="https://jekyllrb.com/docs/themes/#creating-a-gem-based-theme" target="_blank" rel="noopener noreferrer">convention standard des thèmes Jekyll</a>,</li>
<li>des fichiers <code>LICENSE.txt</code> et <code>README.md</code> qui contiendront le fichier de licence de votre theme ainsi qu’un fichier README pour les instructions d’installation et d’utilisation de votre thème. Nous avons vu plus haut qu’il est important de <a href="#trucs-et-astuces-pour-créer-un-super-thème">bien documenter votre thème</a>.</li>
</ul>
<p>Voilà pour la structure d’un thème - tout le reste, comme les exemples de contenu qui vous pourriez fournir devraient être ignorés par les fichiers <code>.gitignore</code> et <code>.gemspec</code>.</p>
<h2 id="developper-votre-theme">Développer votre thème</h2>
<p>La base d’un thème Jekyll n’a plus de secrets pour vous. Notez bien les plugins utilisés par votre thème dans le fichier <code>.gemspec</code> et rappelez-vous que par défaut GitHub pages n’autorise qu’une <a href="https://pages.github.com/versions/" target="_blank" rel="noopener noreferrer">liste limitée de plugins</a>. Sachez que le <a href="https://www.siteleaf.com/plans/" target="_blank" rel="noopener noreferrer">formule Team plan</a> de Siteleaf vous permet de vous affranchir de cette limitation, même chose chez <a href="https://netlify.com" target="_blank" rel="noopener noreferrer">Netlify</a>.</p>
<h2 id="ajouter-des-contenus-d-exemple">Ajouter des contenus d’exemple</h2>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1523346121/sample_content.b1fae7ccfc15bef8d37887240f157260.webp 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1523346121/sample_content.b1fae7ccfc15bef8d37887240f157260.webp 862w" width="862" height="577" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1523346121/sample_content.b1fae7ccfc15bef8d37887240f157260.avif 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1523346121/sample_content.b1fae7ccfc15bef8d37887240f157260.avif 862w" width="862" height="577" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1523346121/sample_content.b1fae7ccfc15bef8d37887240f157260.png" alt="Exemple de contenu du thème Alembic" loading="lazy" decoding="async" class="dark:brightness-90" width="862" height="577" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1523346121/sample_content.b1fae7ccfc15bef8d37887240f157260.png 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1523346121/sample_content.b1fae7ccfc15bef8d37887240f157260.png 862w" sizes="100vw">
</picture>
<p>Nous venons d’ajouter un fichier <code>index.html</code> pour vérifier que la génération fonctionne comme prévu. On pourrait aussi s’en servir pour tester des contenus type. Néanmoins, la page d’accueil ne suffira pas pour effectuer un test complet de notre thème. Créons un dossier <code>_posts</code> et ajoutons-y quelques billets types. Utilisez de vrais articles plutôt que du faux texte, ajoutez des images, des exemples de code, voire des vidéos. Il est important de tester tous les types de contenu possible qu’une personne pourrait vouloir ajouter sur son site.</p>
<p>Au cœur de tout site Jekyll, on trouve le fichier de configuration principal (<code>_config.yml</code>). Il permet de définir tout un tas de paramètres comme le nom et la description de votre site. Le thème Alembic possède un <a href="https://github.com/daviddarnes/alembic/blob/master/_config.yml" target="_blank" rel="noopener noreferrer">exemple de fichier de configuration</a> qui permet aux utilisateurs du thème d’avoir une configuration de référence sur laquelle se baser. Si vous voulez en savoir plus sur les possibilités de configuration de Jekyll, reportez-vous <a href="https://jekyllrb.com/docs/configuration/" target="_blank" rel="noopener noreferrer">à la documentation officielle</a>.</p>
<h2 id="soumettre-sa-gem-de-theme">Soumettre sa gem de thème</h2>
<p>Une fois que vous êtes satisfait du résultat de la première itération de votre thème, que vous avez bien enregistrer vos modifications, puis que vous les avez poussées sur votre dépôt Git, vous pouvez procéder à la génération de votre gem. Le fichier <code>.gem</code> va empaqueter tous vos modèles de page, vos styles dans un seul fichier. Il faudra ensuite publier ce fichier sur le <a href="https://rubygems.org" target="_blank" rel="noopener noreferrer">RubyGems.org</a>.</p>
<p>Pour générer votre gem, il vous suffit d’utiliser cette commande :</p>
<pre><code class="language-sh hljs bash">gem build mon-super-theme.gemspec</code></pre>
<p>Une fois que c’est fait, un nouveau fichier est présent à la racine de votre projet - du type <code>mon-super-theme-0.1.0.gem</code>. La nomenclature correspond au nom de votre gem et au numéro de version indiqués dans votre fichier <code>.gemspec</code>. Une fois la gem générée, il ne reste plus qu’à la pousser en ligne avec la commande :</p>
<pre><code class="language-sh hljs bash">gem push mon-super-theme-0.1.0.gem</code></pre>
<p>Lors de la première soumission de gem, vous devrez entrer vos identifiants de connexion à RubyGems.org. Une fois connecté, votre gem est mise en ligne et rendue publique ! Et voilà, vous venez de publier votre première gem de thème pour Jekyll. Elle dispose maintenant de sa propre URL.</p>
<figure>
<picture title="Exemple de page Rubygems">
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1523346185/sample_rubygems_page.51714c5c05628c5cf49dae6fdcf7f411.webp 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1523346185/sample_rubygems_page.51714c5c05628c5cf49dae6fdcf7f411.webp 862w" width="862" height="596" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1523346185/sample_rubygems_page.51714c5c05628c5cf49dae6fdcf7f411.avif 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1523346185/sample_rubygems_page.51714c5c05628c5cf49dae6fdcf7f411.avif 862w" width="862" height="596" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1523346185/sample_rubygems_page.51714c5c05628c5cf49dae6fdcf7f411.png" alt="Exemple de page Rubygems" loading="lazy" decoding="async" class="dark:brightness-90" width="862" height="596" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1523346185/sample_rubygems_page.51714c5c05628c5cf49dae6fdcf7f411.png 768w, /res.cloudinary.com/jamstatic/image/upload/c_scale-dpr_auto-f_auto-q_auto-w_862/v1523346185/sample_rubygems_page.51714c5c05628c5cf49dae6fdcf7f411.png 862w" sizes="100vw">
</picture>
<figcaption>Exemple de page Rubygems</figcaption>
</figure>
<h2 id="les-themes-distants-sur-github-pages">Les thèmes distants sur GitHub Pages</h2>
<p>Récemment GitHub Pages a ajouté le <a href="https://github.com/blog/2464-use-any-theme-with-github-pages" target="_blank" rel="noopener noreferrer">support des thèmes distants</a>, tout dépôt de thème Jekyll public sur GitHub peut être utilisé comme un thème Jekyll.</p>
<p>L’installation d’un thème distant demande l’utilisation du plugin <a href="https://github.com/benbalter/jekyll-remote-theme" target="_blank" rel="noopener noreferrer">jekyll-remote-theme</a>, qui est donc autorisé sur GitHub Page. Pour l’installer il vous faut déclarer le plugin dans votre fichier <code>_config.yml</code> et utiliser une clé spécifique <code>remote_theme</code> dont la valeur correspond au nom d’utilisation GitHub suivi du nom du dépôt de votre thème. Dans mon cas ça donne :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">plugins:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">jekyll-remote-theme</span>

<span class="hljs-attr">remote_theme:</span> <span class="hljs-string">daviddarnes/alembic</span></code></pre>
<p>En pratique ça change quoi pour le développement de votre thème ? Et bien si vous voulez rendre votre thème utilisable sur GitHub Pages, il faudra vous assurer que vous n’utilisez que des plugins autorisés par GitHub. Et donc tester votre thème avec la gem GitHub Pages et vous assurer que tout fonctionne correctement.</p>
<p>Le plugin jekyll-remote-theme vous permet de pointer vers des numéros de releases ou des branches particulières. Générer une release GitHub est un bon moyen pour les gens de pouvoir s’en tenir à une version définie de votre thème, comme ceci :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">remote_theme:</span> <span class="hljs-string">daviddarnes/alembic@2.3.1</span></code></pre>
<h2 id="tests-et-mises-a-jour">Tests et mises à jour</h2>
<p>Une fois votre thème en ligne, assurez-vous une dernière fois qu’il fonctionne <a href="https://jekyllrb.com/docs/themes/#installing-a-theme" target="_blank" rel="noopener noreferrer">comme n’importe quel autre thème Jekyll</a>. Notez les difficultés qu’un utilisateur pourrait rencontrer.</p>
<p>Si vous devez publier des corrections ou des mises à jour, vous allez devoir <a href="https://guides.rubygems.org/patterns/#semantic-versioning" target="_blank" rel="noopener noreferrer">incrémenter le numéro de version de façon appropriée</a> dans votre fichier <code>.gemspec</code>, générer une nouvelle version de votre gem et la publier sur Rubygems.org.</p>
<p>N’hésitez pas à <a href="https://twitter.com/DavidDarnes" target="_blank" rel="noopener noreferrer">m’envoyer un tweet si vous avez des questions</a>. Si vous utilisez Siteleaf, vous pouvez venir discuter avec la communauté sur <a href="http://chat.siteleaf.com/" target="_blank" rel="noopener noreferrer">http://chat.siteleaf.com/</a> pour poser vos questions et partager votre travail.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2017/10/03/interview-hugo-lead-developer/</id>
    <title>Questions à Bjørn Erik Pedersen, le développeur d’Hugo</title>
    <published>2017-10-03T11:45:36+00:00</published>
    <link href="https://jamstatic.fr/2017/10/03/interview-hugo-lead-developer/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p><a href="https://gohugo.io" target="_blank" rel="noopener noreferrer">Hugo</a> est rapidement devenu l’un des gestionnaires de sites statiques les plus populaires comme en attestent ses bientôt <a href="https://github.com/gohugoio/hugo" target="_blank" rel="noopener noreferrer">50 000 étoiles sur GitHub</a>. C’est dû en partie à sa vitesse de génération : en effet il ne lui faut qu'une petite milliseconde pour générer une page. Oui, cela fait 1000 pages à la seconde et c'est plutôt impressionnant. Mais ce n'est pas la seule raison qui devrait vous faire adopter Hugo.<br>
Heureusement ce <abbr title="Générateur de Site Statique">GSS</abbr> propose aussi tout un tas de fonctionnalités comme les contenus imbriqués, les fichiers partiels, les shortcodes, la gestion de l’<abbr title="Internationalisation">i18n</abbr>, les exports personnalisés (JSON, AMP, epub, Atom, etc.) et bien d’autres… Les nouvelles versions et les nouveautés se succèdent à un rythme soutenu. Depuis la v0.14, <a href="https://github.com/bep" target="_blank" rel="noopener noreferrer">Bjørn Erik Pedersen</a> dirige les développements, il a gentiment accepté de répondre à nos questions.</p></aside>
<figure>
<picture title="Bjørn Erik Pedersen">
<source type="image/webp" srcset="/assets/images/hugo/hugo-bjorn-erik-pedersen.31cf0d1e1e259f4d2121d777f80c0f94.webp" width="640" height="429">
<source type="image/avif" srcset="/assets/images/hugo/hugo-bjorn-erik-pedersen.31cf0d1e1e259f4d2121d777f80c0f94.avif" width="640" height="429">
<img src="/assets/images/hugo/hugo-bjorn-erik-pedersen.31cf0d1e1e259f4d2121d777f80c0f94.jpg" alt="Bjørn Erik Pedersen" loading="lazy" decoding="async" class="dark:brightness-90" width="640" height="429" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8Apwyc1eWQFay0BBq1GTQBZzk1Mg4qFBmrKj5aAG5xSl8CkYVDICRxQA2WYVQmlBp06ydqzZ3ZOtAFpHG6r8TZFYkEu5q2rVSwFAEhU1GymtAQZXpUUkWO1AFLFFSleaKAFFtz0qZLf2q+IBTxEBQBUWHFTKmRT2AFNEmKAGtHQsAakeYURTjdQBMdODoTisHUrELniusSYCKsHUpVLGgDnYICkldBYx8Cs2MBnrdsU4FAF1Ivl6VWuIsVqIny1WuI+DQBjMnNFWGj+Y0UAaOwimFTWq9sB2qu8QFAGa6mq0gIrSkUVTnAwaAM2RjmpbVCXzUbj5qv2KAsKALDqyxVzGpyMrmu6a1DQZx2ritej8stQBRtJ8uMmuq075lFef2s5FxjPeu70d9yLQBvqvy1XnXirS/dqCYZoAzinNFTEc0UAbsnSqUveiigCnLVGfpRRQBQf71X9P8AviiigDpV/wCPf8K4PxN1eiigDjrf/j5/Gu/0P/VrRRQB0y/cqCWiigCqetFFFAH/2Q==);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption><a href="https://github.com/bep" target="_blank" rel="noopener noreferrer">Bjørn Erik Pedersen</a></figcaption>
</figure>
<h2 id="bonjour-bjoern-erik-comment-t-es-tu-retrouve-implique-dans-hugo">Bonjour Bjørn Erik, comment t'es-tu retrouvé impliqué dans Hugo ?</h2>
<p>J'ai passé un dimanche à migrer mon <a href="http://bepsays.com/en/" target="_blank" rel="noopener noreferrer">blog</a> de WordPress à <a href="https://jekyllrb.com" target="_blank" rel="noopener noreferrer">Jekyll</a>, et quand j'ai eu fini je me suis dit "OK, et maintenant je fais quoi ?". J'espérais que cela me pousserait à écrire davantage sur mon blog. Au cours de ce même après-midi je cherchais déjà des alternatives à Jekyll sur le net et je suis tombé sur Hugo.</p>
<p>J'ai vu des choses que je souhaitais améliorer. Je suis un développeur très expérimenté mais mes premières lignes de Go avaient pour but d’améliorer la façon de gérer le livereload des CSS, du JavaScript et des images dans Hugo. Je crois que ce patch a survécu à tous mes autres changements ultérieurs. À partir de là j'ai continué à soumettre des <em>Pull Requests</em>, motivé en partie par l’apprentissage d’un nouveau langage mais également encouragé par <a href="https://stevefrancia.com/" target="_blank" rel="noopener noreferrer">Steve Francia</a>, qui a créé les premières versions d’Hugo. Il est très bon pour motiver les gens à contribuer à un projet open source.</p>
<h2 id="les-problemes-resolus-par-hugo">Les problèmes résolus par Hugo</h2>
<p>Hugo est une excellente façon de créer et de publier <strong>de nombreux contenus</strong> sur le web. Nous recevions beaucoup de questions du genre "comment créer un page unique de présentation de produit" au début. Même si nous savons également très bien faire cela, ce n'est pas le cas d’utilisation typique.</p>
<p>Comme Gutenberg en son temps, Hugo est un générateur de sites web pour de la documentation, des livres, des journaux, des magazines, des blogs, etc. C’est manifeste quand vous voyez les dernières fonctionnalités ajoutées comme les <a href="https://github.com/gohugoio/hugo/releases/tag/v0.22" target="_blank" rel="noopener noreferrer">sections imbriquées</a> et <a href="https://github.com/gohugoio/hugo/releases/tag/v0.27" target="_blank" rel="noopener noreferrer">les contenus relatifs</a> : structurez bien votre contenu et trouvez-le facilement.</p>
<p>Nous nous soucions également beaucoup de la typographie et des langues. Hugo est très utilisé en Chine et au Japon, ce qui nous amène à relever de nouveaux défis. Le fait de développer avec le langage Go nous aide bien. Deux de ses créateurs, Ken Thompson et Rob Pike, sont également les créateurs d’<a href="https://en.wikipedia.org/wiki/UTF-8" target="_blank" rel="noopener noreferrer">UTF-8</a>. J'ai justement passé pas mal de temps sur le support des guillemets français dans Hugo il y a peu. Forcément c'est très répandu en France, mais je n'en avais jamais entendu parlé jusqu'ici.</p>
<h2 id="comment-fait-hugo-pour-aller-si-vite">Comment fait Hugo pour aller si vite ?</h2>
<p>Je lis souvent qu'"Hugo est rapide parce qu'il est écrit en Go". C’est en partie vrai, mais Hugo a doublé sa vitesse deux fois d’affilée dans les dernières versions, il y a donc d’autres facteurs. Le mot "rapide" figure dans le slogan d’Hugo depuis le premier jour, donc nous devons faire très attention à cela.</p>
<p>J'essaie de m'amuser à ne pas ajouter de temps supplémentaire lors de l’ajout de nouvelles fonctionnalités : Le temps de traitement ajouté par la nouvelle fonctionnalité doit être compensé par des améliorations dans les fonctionnalités existantes, et Go joue un rôle vital à ce niveau. C’est mieux et ça va plus vite à chaque nouvelle version, mais ce n'est pas simplement que c'est un langage de programmation compilé avec un modèle de concurrence simple et une bibliothèque standard très robuste. C’est aussi grâce à tous les excellents outils fournis pour créer rapidement des applications performantes : un compilateur rapide, le support intégré des tests et un analyseur de code très simple d’utilisation.</p>
<p>Les ralentissements de performance surviennent toujours là où on les attend le moins, vous devez donc faire des tests. Les gains et les pertes de performance sont dus à une succession de petits changements au fil du temps. Et la vitesse compte. Essayez le serveur d’Hugo avec le Livereload et vous verrez par vous-même.</p>
<h2 id="quels-sont-les-sites-les-plus-visibles-qui-utilisent-hugo">Quels sont les sites les plus visibles qui utilisent Hugo ?</h2>
<figure>
<picture title="Capture d&#039;écran du site web labs.usa.gov">
<source type="image/webp" srcset="/thumbnails/768x/assets/images/labs.usa.gov.9564130c9747788930deb63237e94c9c.webp 768w, /assets/images/labs.usa.gov.9564130c9747788930deb63237e94c9c.webp 898w" width="898" height="687" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/assets/images/labs.usa.gov.9564130c9747788930deb63237e94c9c.avif 768w, /assets/images/labs.usa.gov.9564130c9747788930deb63237e94c9c.avif 898w" width="898" height="687" sizes="100vw">
<img src="/assets/images/labs.usa.gov.9564130c9747788930deb63237e94c9c.png" alt="Capture d&#039;écran du site web labs.usa.gov" loading="lazy" decoding="async" class="dark:brightness-90" width="898" height="687" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/assets/images/labs.usa.gov.9564130c9747788930deb63237e94c9c.png 768w, /assets/images/labs.usa.gov.9564130c9747788930deb63237e94c9c.png 898w" sizes="100vw">
</picture>
<figcaption>Capture d'écran du site web labs.usa.gov</figcaption>
</figure>
<p>Parmi les sites que je connais et que j'aime bien il y a <a href="https://labs.usa.gov/" target="_blank" rel="noopener noreferrer">labs.usa.gov</a>, <a href="https://www.netlify.com" target="_blank" rel="noopener noreferrer">netlify.com</a>, <a href="https://www.cdnplanet.com/" target="_blank" rel="noopener noreferrer">cdnplanet.com</a>, <a href="https://support.balsamiq.com/" target="_blank" rel="noopener noreferrer">support.balsamiq.com</a>, <a href="https://www.crossref.org/" target="_blank" rel="noopener noreferrer">crossref.org</a>, <a href="https://1password.com/" target="_blank" rel="noopener noreferrer">1password.com</a>, <a href="http://borisfx.com/" target="_blank" rel="noopener noreferrer">borisfx.com</a>, <a href="https://docs.urbanairship.com/" target="_blank" rel="noopener noreferrer">Urban Airship Documentation</a>.</p>
<p>Il y en a plein d’autres qui arrivent. Par exemple, <a href="https://www.smashingmagazine.com" target="_blank" rel="noopener noreferrer">Smashing Magazine</a> a annoncé qu'ils étaient en train de travailler sur une <a href="/2017/03/17/smashing-mag-va-dix-fois-plus-vite/">refonte entièrement basée sur Hugo</a>.</p>
<figure>
<picture title="La nouvelle version de Smashing Magazine">
<source type="image/webp" srcset="/assets/images/algolia-smashing.6641a8e16893175dbc69416f2b1c2a9c.webp" width="720" height="450">
<source type="image/avif" srcset="/assets/images/algolia-smashing.6641a8e16893175dbc69416f2b1c2a9c.avif" width="720" height="450">
<img src="/assets/images/algolia-smashing.6641a8e16893175dbc69416f2b1c2a9c.png" alt="La nouvelle version de Smashing Magazine" loading="lazy" decoding="async" class="dark:brightness-90" width="720" height="450" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>La nouvelle version de <a href="https://www.smashingmagazine.com" target="_blank" rel="noopener noreferrer">Smashing Magazine</a></figcaption>
</figure>
<h2 id="comment-se-porte-le-projet-actuellement">Comment se porte le projet actuellement ?</h2>
<p>Nous avons adopté maintenant un processus de publication plus ou moins automatisé, je publie donc une nouvelle version à chaque fois que je peux écrire un titre et une note de version à partir des nouveautés, soit environ toutes les cinq semaines. Et Hugo est très utilisé, c'est difficile à mesurer parce qu'il peut être installé à partir de différentes sources, mais j'ai été surpris d’apprendre qu'il y avait plus de 8000 installations mensuelles rien qu'avec <code>brew</code> sous macOS. Et le site <a href="https://gohugo.io" target="_blank" rel="noopener noreferrer">gohugo.io</a> encaisse un trafic élevé.</p>
<h2 id="hugo-est-open-source-comment-se-passe-la-gestion-du-projet">Hugo est open source. Comment se passe la gestion du projet ?</h2>
<p>Je n'ajoute que les fonctionnalités que j'aimerais avoir; si c'est aussi un challenge technique, cela me procure un peu de motivation supplémentaire. Quand Hugo a commencé à devenir populaire et que le coût de chaque erreur a augmenté, j'ai commencé à écrire de la documentation technique pour pouvoir discuter de certains éléments de conception et les affiner avec l’aide de la communauté. Les fonctionnalités peuvent rester un bon moment dans le backlog tant qu'une solution simple et élégante ne me vient pas à l’esprit pendant que je pêche la truite.</p>
<p>Quand vous n'êtes pas nombreux pour maintenir un logiciel open source, vous devez être efficaces. Nous essayons d’être carrés : des instructions précises pour soumettre des modifications, pas de questions ou de discussions sur GitHub.<br>
Le <a href="https://discourse.gohugo.io" target="_blank" rel="noopener noreferrer">forum d’Hugo</a> est fait pour ça. Steve Francia m'a dit une fois de toujours rester poli, même avec ceux qui font des remarques désobligeantes comme souvent sur Internet. Ça pourrait paraître contre-intuitif, mais cela fonctionne très bien.</p>
<h2 id="qui-vous-soutient-dans-le-developpement">Qui vous soutient dans le développement ?</h2>
<p><a href="https://netlify.com" target="_blank" rel="noopener noreferrer">Netlify</a> héberge nos sites gratuitement et <a href="https://www.discourse.org" target="_blank" rel="noopener noreferrer">Discourse</a> se charge de la maintenance du forum. <a href="https://travis-ci.org" target="_blank" rel="noopener noreferrer">Travis</a>, <a href="https://www.appveyor.com" target="_blank" rel="noopener noreferrer">Appveyor</a> et <a href="https://circleci.com" target="_blank" rel="noopener noreferrer">CircleCI</a> pour les tests d’intégration continue. Mais à part ça personne, nous n'avons pas de sponsors. Nous en avons juste parlé brièvement entre nous.</p>
<h2 id="quelle-est-la-prochaine-etape-pour-hugo">Quelle est la prochaine étape pour Hugo ?</h2>
<p>La version 1.0 d’Hugo ne <em>devrait</em> pas tarder. Il manque encore quelques trucs qui sont inscrits dans ma feuille de route mentale, mais rien qui ne demande trop de temps. La fonctionnalité la plus importante et la plus intéressante qui arrivera peut-être dans la prochaine version, et sur laquelle je travaille encore, ce sont les <a href="https://github.com/gohugoio/hugo/issues/3651" target="_blank" rel="noopener noreferrer">"pages bundles"</a> : la possibilité d’associer des contenus comme une page et des images par exemple.</p>
<p>J'ai encore plein d’autres idées. Je testerai peut-être si Hugo est adapté pour de très très gros sites avec des millions de pages, mais j'ai peur que la tâche soit un peu trop ardue par rapport au temps libre que je peux y consacrer.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2017/10/01/templates-hugo-designers-wordpress/</id>
    <title>Les templates Hugo pour les designers WordPress</title>
    <published>2017-10-01T11:45:36+00:00</published>
    <link href="https://jamstatic.fr/2017/10/01/templates-hugo-designers-wordpress/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Loin de réinventer la roue, les gestionnaires de site statique comme Hugo s'appuient sur des conventions existantes. On retrouve beaucoup de fonctionnalités similaires entre un GSS comme Hugo et un CMS comme WordPress. Suite à la refonte du <a href="https://support.balsamiq.com/" target="_blank" rel="noopener noreferrer">site de support et de documentation de Balsamiq</a>, <a href="http://blog.teamtreehouse.com/author/leon_barnard" target="_blank" rel="noopener noreferrer">Leon Barnard</a> en a profité pour pointer les similitudes des deux systèmes. Outre la concision de la syntaxe d’Hugo, c'est l’occasion pour les designers WordPress d’apprécier la souplesse apportée par la gestion de la structuration des contenus sous forme de dossiers et de fichiers, ainsi que l’importance de la convention de nommage adoptée.</p></aside>
<p>L'année dernière j'avais écris un article générique d’<a href="http://blog.teamtreehouse.com/getting-started-static-sites" target="_blank" rel="noopener noreferrer">introduction aux concepts qui se cachent derrière les gestionnaires de site statique</a>. Cette fois j'aimerais aborder quelques spécificités de base d’un des générateurs les plus populaires appelé <a href="https://gohugo.io/" target="_blank" rel="noopener noreferrer">Hugo</a>, en le comparant à son illustre ancêtre WordPress.</p>
<h2 id="introduction">Introduction</h2>
<p>Avant de commencer, je préfère préciser que je n'aborderai pas la migration de WordPress vers Hugo. Un <a href="https://gohugo.io/tools/migrations/#wordpress" target="_blank" rel="noopener noreferrer">outil d’export</a> est mis à disposition et de nombreux billets de blog décrivent comment d’autres ont migré.</p>
<p>Je vais davantage me concentrer sur les templates de thèmes utilisés, puisque c'est l’un des plus gros ajustements à faire quand on plonge dans Hugo, qu'on migre depuis WordPress ou qu'on parte de zéro.</p>
<p>La bonne nouvelle c'est qu'il y a pas mal de points communs entre Hugo et WordPress dans la manière dont fonctionnent les templates et dans les fonctionnalités offertes.</p>
<p>Quand j'ai écrit à propos d’Hugo l’année dernière, il lui manquait quelques-unes des fonctionnalités qui rendent WordPress si populaire. Mais cela a énormément évolué depuis (tout en restant incroyablement rapide) et Hugo offre maintenant des fonctionnalités matures comme l’imbrication de templates ou les contenus relatifs, et les nouvelles fonctionnalités continuent d’affluer en permanence.</p>
<figure>
<picture title="Édition d’un modèle de page pour Hugo.">
<source type="image/webp" srcset="/thumbnails/768x/images/2017-10-01_templates-hugo-designers-wordpress/home-page-templating-example-952x480.fbcf335e97018e85a4cfdbf1ba3eed51.webp 768w, /images/2017-10-01_templates-hugo-designers-wordpress/home-page-templating-example-952x480.fbcf335e97018e85a4cfdbf1ba3eed51.webp 952w" width="952" height="480" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/2017-10-01_templates-hugo-designers-wordpress/home-page-templating-example-952x480.fbcf335e97018e85a4cfdbf1ba3eed51.avif 768w, /images/2017-10-01_templates-hugo-designers-wordpress/home-page-templating-example-952x480.fbcf335e97018e85a4cfdbf1ba3eed51.avif 952w" width="952" height="480" sizes="100vw">
<img src="/images/2017-10-01_templates-hugo-designers-wordpress/home-page-templating-example-952x480.fbcf335e97018e85a4cfdbf1ba3eed51.png" alt="Édition d’un modèle de page pour Hugo" loading="lazy" decoding="async" class="dark:brightness-90" width="952" height="480" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAC/0lEQVR4nO2aa3LrIAyFjzreVmfu/rfUxOoPEAgZuybJjWVH34wLxi/Q8QElDX1//2MQAQB0maoEAqV95GMZxl7StZTvSdTZz1tqSHdnZszM4HnGPDOYpcyb1MFgBhgMNOVYLx8lxactU+hSKXGEDK1W6r4qJmbVac4HGGCSKue7VknGxGAwEYgl8KwEyftZDKJ67xJ4zmJwRwzZ8vnQ5WBPH4UBkMQrxy8VDAbltnQCyRXlZVd9FEHu8wytWq2LflrB1iXbqOvlzVk4JB8TB6rbM0zQ18TouuSdcAmNxIpIvftUB2X3U1s77ul+n2GuqDvNtSNi1PNFCOhpSgsCNOKkIaa/jQOyEOiKIYIgHR/s6SuoL5406EmeluEscWnbpp/7vXv7rUcPdbTMpdYZtb103goCGAFkSrIuETHe7Y4laslAGVApFqos1ubpdusIktcRuzM6WFngSmIgG8TW+S0pguRZlewCLc7Qgtj68WJoqKmQrdqzSm26dR2CZnR64R8ZNMmmMylxSSc70bPlMmvKZbN4VzFG+/Zueu4wRwGsOQSA9T83zQN5llkziihAs4boTlv5l2KU1nIymy4ds45gM+h7yFmWhmtmhuXAmHut21BvvWims9xhsygWQdQzmyz9magPZ41/sx2afZ2dxPr2mle+YZIpEctnGptUt3ambrBqH3lnILtn/QchXsn03Gs2hmRNUqcy+al5KhujyVIUTb6xwhmFEKYjH95NFRqzcleYNUHOLIRwqCB76CXcVpD1cJ9HCMG9IJZ9n4jOJ4RwOkFey5awx4j6dchTg1U+1CH+nCGEQ5zxYQ7x6wwhHOKMSztk33fU/W8DjiIc4oxLOuSMzhDCIc64lEPO7AwhHOKMSzjkof/o8B/OOMg24RBnXMIhQzj/5j4c4ozPcYhzZwjhEGec1yF7fn6CHec4cYYQDnFGCOKM8wri+ZfVT3BeQS5KCOKMEMQZIYgzQhBnhCDOCEGcEYI4IwRxRgjijBDEGSGIM0IQZ4QgzvgFecCJNN1zSlYAAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/2017-10-01_templates-hugo-designers-wordpress/home-page-templating-example-952x480.fbcf335e97018e85a4cfdbf1ba3eed51.png 768w, /images/2017-10-01_templates-hugo-designers-wordpress/home-page-templating-example-952x480.fbcf335e97018e85a4cfdbf1ba3eed51.png 952w" sizes="100vw">
</picture>
<figcaption>Édition d’un modèle de page pour Hugo.</figcaption>
</figure>
<h2 id="fondamentaux-des-templates-hugo">Fondamentaux des templates Hugo</h2>
<p>La première chose à savoir à propos d’Hugo, c'est qu'il est écrit en langage Go. Go n'est pas aussi connu que d’autres langages sur le Web, mais il gagne rapidement en popularité. Treehouse propose <a href="https://teamtreehouse.com/library/go-language-overview" target="_blank" rel="noopener noreferrer">un aperçu du langage Go</a>.</p>
<p>Si l’idée d’apprendre un nouveau langage de programmation représente pour vous un <em>no-Go</em> (haha 😉) pour changer de gestionnaire de contenu, n'abandonnez pas tout de suite, moi-même je ne sais pas programmer en Go et pourtant j'ai été capable d’écrire des modèles Hugo sophistiqués pour le site de documentation de Balsamiq, grâce à ma connaissance d’HTML et en passant en revue la <a href="https://gohugo.io/documentation/" target="_blank" rel="noopener noreferrer">documentation d’Hugo</a>.</p>
<p>Laissez-moi vous convaincre en vous donnant vos premières leçons pour créer des modèles pour Hugo.</p>
<h3 id="une-syntaxe-differente-des-fonctionnalites-similaires">Une syntaxe différente, des fonctionnalités similaires</h3>
<p>Dans WordPress, quand vous souhaitez afficher le contenu d’un article de blog ou d’une page, vous écrivez cette ligne dans un modèle:</p>
<pre><code class="language-php hljs php">&lt;!--?php the_content(); ?--&gt;</code></pre>
<p>Dans Hugo c'est :</p>
<pre><code class="language-go-html-template hljs go">{{ .Content }}</code></pre>
<p>Pas mal, non ? On pourrait même dire que c'est mieux. (Vous allez rapidement vous habituer à ces doubles accolades et à ces points, mais vous allez devoir dire adieu à ces drôles de points d’interrogation qui m'ont toujours fait douter de ce que je faisais.)</p>
<p>Continuons, en mentionnant au passage que, comme en PHP, on peut mélanger le HTML et le langage de templating d’Hugo.</p>
<p>Si vous avez déjà personnalisé un modèle WordPress, vous avez probablement écrit ce genre de mélange d’HTML et de PHP:</p>
<pre><code class="language-php hljs php">&lt;a href=<span class="hljs-string">"/"</span>&gt;<span class="hljs-meta">&lt;?php</span> bloginfo(<span class="hljs-string">'name'</span>); <span class="hljs-meta">?&gt;</span>&lt;/a&gt;</code></pre>
<p>Ce code localise le nom du site et crée un lien vers la page d’accueil. WordPress dispose d’une fonction appelée <code>bloginfo</code> à qui on passe le paramètre <code>name</code> pour le récupérer.</p>
<p>Hugo, de son côté, utilise une variable nommée <a href="https://gohugo.io/variables/site/#site-variables-list" target="_blank" rel="noopener noreferrer"><code>.Site</code></a> avec une propriété appelée <code>.Title</code>. Le même code dans Hugo s'écrit ainsi:</p>
<pre><code class="language-go-html-template hljs go">&lt;a href=<span class="hljs-string">"/"</span>&gt;{{ .Site.Title }}&lt;/a&gt;</code></pre>
<p>Là encore c'est également assez intuitif, non ?</p>
<p>J'espère que vous vous sentez plus serein maintenant. 🙂</p>
<p>Continuons sur notre lancée.</p>
<p>Le dernier exemple basique est l’affichage d’une liste d’articles ou de pages. La structure générale est la même dans les deux systèmes : boucler sur les articles et ajouter un élément de liste à puces pour chacun, en récupérant le lien vers l’article et son titre.</p>
<p>Dans WordPress…</p>
<pre><code class="language-php hljs php">&lt;ul&gt;
    <span class="hljs-meta">&lt;?php</span> <span class="hljs-keyword">while</span> (have_posts()) : the_post(); <span class="hljs-meta">?&gt;</span>
        &lt;li&gt;&lt;a href=<span class="hljs-string">"&lt;?php the_permalink(); ?&gt;"</span>&gt;<span class="hljs-meta">&lt;?php</span> the_title(); <span class="hljs-meta">?&gt;</span>&lt;/a&gt;&lt;/li&gt;
    <span class="hljs-meta">&lt;?php</span> <span class="hljs-keyword">endwhile</span>; <span class="hljs-meta">?&gt;</span>
&lt;/ul&gt;</code></pre>
<p>WordPress utilise une boucle PHP <code>while</code>, qu'on retrouve dans beaucoup de langages de programmation. Hugo utilise une simple fonction appelée <code>range</code> qui a le même objectif:</p>
<pre><code class="language-go-html-template hljs go">&lt;ul&gt;
    {{ <span class="hljs-keyword">range</span> .Data.Pages }}
        &lt;li&gt;&lt;a href=<span class="hljs-string">"{{ .Permalink }}"</span>&gt;{{ .Title }}&lt;/a&gt;&lt;/li&gt;
    {{ end }}
&lt;/ul&gt;</code></pre>
<p>Les éléments de liste se ressemblent beaucoup dans les deux langages, mais une fois encore, celle d’Hugo a l’air un peu plus propre. Et comme il y a moins de texte, c'est plus facile à lire.</p>
<h2 id="prenons-de-la-hauteur">Prenons de la hauteur</h2>
<p>Bon, maintenant que nous avons vu quelques-unes des différences syntaxiques entre WordPress et Hugo, voyons maintenant les différents types de modèles.</p>
<h3 id="les-includes-s-appellent-des-partials">Les includes s'appellent des <code>partials</code></h3>
<p>Une des principales raisons d’utiliser un générateur de site plutôt que d’écrire de simples fichiers HTML est de pouvoir réutiliser du code à travers les différentes pages. Il est par exemple impensable de nos jours de copier-coller le même code HTML pour l’entête et le pied-de-page dans chaque page.</p>
<p>La fonction <code>include()</code> PHP est, à elle seule, une des raisons pour laquelle le langage est devenu si populaire dans le Web et pourquoi WordPress utilise PHP.</p>
<p>Vous pouvez utiliser la fonction <code>include()</code> dans WordPress, bien que maintenant il existe des fonctions qui simplifient la récupération de parties spécifiques communément utilisées dans la page. Pour inclure le pied de page global de page, vous écrivez :</p>
<pre><code class="language-php hljs php"><span class="hljs-meta">&lt;?php</span> get_footer(); <span class="hljs-meta">?&gt;</span></code></pre>
<p>Hugo n'émet aucune hypothèse quant à la structure de votre page comme le fait WordPress. Hugo utilise une fonction similaire à la fonction <code>include()</code> de PHP appelée <a href="https://gohugo.io/templates/introduction/#includes" target="_blank" rel="noopener noreferrer"><code>partial</code></a> pour insérer le contenu d’un fichier dans un autre.</p>
<p>Voilà comment ça marche :</p>
<pre><code class="language-go-html-template hljs go">{{ partial <span class="hljs-string">"footer.html"</span> . }}</code></pre>
<aside class="note note-tip"><p>N'oubliez pas le point (<code>.</code>) à la fin, il désigne le contexte.</p></aside>
<p>Notez que le nom du fichier partiel doit se trouver dans le répertoire <code>partials</code>, un des sous-dossiers du dossier <code>layouts</code> où sont stockés les fichiers des modèles.</p>
<p>C’est le bon moment pour vous présenter une des différences majeures entre WordPress et Hugo concernant les modèles. Dans WordPress, la localisation des éléments est en général cachée, la plupart des choses se trouvent dans une base de données (<a href="https://res.cloudinary.com/jamstatic/image/upload/f_auto,q_auto/v1523346826/php_scheme.png" target="_blank" rel="noopener noreferrer">comme on peut le voir sur cette image</a>).</p>
<p>Alors que les sites statiques sont simplement <strong>des copies de fichiers sur votre ordinateur</strong>, ce qui vous permet de visualiser et de manipuler la structure de votre site.</p>
<figure>
<picture title="Un exemple typique du dossier layouts d’Hugo">
<source type="image/webp" srcset="/thumbnails/768x/images/2017-10-01_templates-hugo-designers-wordpress/layouts.dbbcee43e9b419ea42c686dc950cc699.webp 768w, /thumbnails/1024x/images/2017-10-01_templates-hugo-designers-wordpress/layouts.dbbcee43e9b419ea42c686dc950cc699.webp 1024w" width="1024" height="446" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/2017-10-01_templates-hugo-designers-wordpress/layouts.dbbcee43e9b419ea42c686dc950cc699.avif 768w, /thumbnails/1024x/images/2017-10-01_templates-hugo-designers-wordpress/layouts.dbbcee43e9b419ea42c686dc950cc699.avif 1024w" width="1024" height="446" sizes="100vw">
<img src="/images/2017-10-01_templates-hugo-designers-wordpress/layouts.dbbcee43e9b419ea42c686dc950cc699.png" alt="Un exemple typique du dossier `layouts` d’Hugo" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="446" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/2017-10-01_templates-hugo-designers-wordpress/layouts.dbbcee43e9b419ea42c686dc950cc699.png 768w, /thumbnails/1024x/images/2017-10-01_templates-hugo-designers-wordpress/layouts.dbbcee43e9b419ea42c686dc950cc699.png 1024w" sizes="100vw">
</picture>
<figcaption>Un exemple typique du dossier <code>layouts</code> d’Hugo</figcaption>
</figure>
<p>C’est un peu comme les systèmes d’exploitation ordinateur et mobile. Sur les systèmes de fichiers d’un ordinateur, on peut parcourir l’arborescence de fichiers (via le Finder, l’Explorateur, etc.) alors que les OS pour mobile essaient de masquer cette hiérarchie et se contentent d’associer des fichiers à des applications, vous évitant d’avoir à vous soucier de la structure interne.</p>
<p>Ici, Hugo se comporte comme un système d’exploitation d’ordinateur et WordPress davantage comme un OS mobile. Ce n'est pas forcément une mauvaise chose, car cela vous donne plus de transparence sur la navigation et la structure de votre site web. Maintenant, c'est à vous de vous assurer de bien ranger les choses à leur place.</p>
<p>Heureusement, Hugo fait preuve de consistence dans le nommage des fichiers. C’est bien pratique que la fonction s'appelle <code>partial</code>, c'est une bonne manière de se rappeler de placer vos fichiers partiels dans le dossier <code>partials</code>. 🙂</p>
<h3 id="les-modeles-de-section">Les modèles de section</h3>
<p>Pour continuer de discuter des différences philosophiques entre Hugo et WordPress, Hugo est beaucoup moins normatif quant à l’organisation de votre site. Alors que WordPress utilise un modèle rigide d’articles et de pages, Hugo est façonné à l’aide de "contenu" générique et de répertoires.</p>
<figure>
<picture title="Articles et pages dans WordPress">
<source type="image/webp" srcset="/images/2017-10-01_templates-hugo-designers-wordpress/pages.629618fdd484c6be082f83ede1f29763.webp" width="280" height="262">
<source type="image/avif" srcset="/images/2017-10-01_templates-hugo-designers-wordpress/pages.629618fdd484c6be082f83ede1f29763.avif" width="280" height="262">
<img src="/images/2017-10-01_templates-hugo-designers-wordpress/pages.629618fdd484c6be082f83ede1f29763.png" alt="Articles et pages dans WordPress" loading="lazy" decoding="async" class="dark:brightness-90" width="280" height="262" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Articles et pages dans WordPress</figcaption>
</figure>
<figure>
<picture title="Répertoires de contenu dans Hugo.">
<source type="image/webp" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346845/hugo-content-folder.c3df846291cfc5825e785336b5167d9c.webp" width="638" height="337">
<source type="image/avif" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346845/hugo-content-folder.c3df846291cfc5825e785336b5167d9c.avif" width="638" height="337">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346845/hugo-content-folder.c3df846291cfc5825e785336b5167d9c.png" alt="Répertoires de contenu dans Hugo" loading="lazy" decoding="async" class="dark:brightness-90" width="638" height="337" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAADb0lEQVR4nO1cW3LDIAxcdXz/A7aHUT8ImIfAgCWbONlOB5omBmtZkIUI/f79MTNDC0QEIgr1uIzfI73+TBDcbR6VDpt2857c2NjM/CHGnwEj1oM6IUlTERESKZrKXA/pyO9FQchZIxFRopKvOmpIleFhMmXlqpDK52JOGR4mU1aPwT9XNbIyPDYAJqP22SqQcE4ZHpv1lMLMIBAY9Wt+hlrayvAICik+rkkKGGCkxDCAR/CgowyP7UpXNCGGSpKeqZQ+ZXgki/pV5BwR8x7QVYbHFruoV0MmBjC5U3UczbljyvAwfVLvRUoMMHUnF8M5P/pjRyQkj0NdhZ0YLK8S55H6OoGIo3L+uksoJAcDy6vEK8TVOajFvTa/vjQJuXN9WR1eIbtpCAje4vx4WlIhMVYdDPuDtDN+RMdem5BJFyF3BQRXJQPIAqfBS6Ro+hqzmSfvx6rDnwBm54RwcHE5kMDMUwNq+SlrdTjPUFZGj0ryae2rEBXIyphRyVchipCUUVNJbcEfJ2TddXYZ1IjpwTAh7xUAvBczxMxPWZa8rB01GcZIosc0IZZKoacxMoDzi7omLw/moVclpwnRVEpNGSs/sbdQ2xpvEaPn9p6xWXPgvCcZgJxWG/9Pel2NECs+YnG8i1JGgop5xo/ig6GZRBSufy1Gnz1i6D+pj9htoL/voBSNrBn93N6B9851f00ygHPK8DCIZdlSsqJSNPPJ7IKLLVup9H8NMoB0YJw9gmFGiDUfKyjlyJ2dIcYw/G4ukY52bJEPBI1jfLb7IbmtDEIjdyilZujWKQLz4GIPLuCj0ZphS40Np5yUUVywYxh3vuyg1hHpK5TS6qN4rlLqh9l+yGlQUdfJvjc8TiEpA5TcSkJGxyDLB+RthMR9JdLz5U2VQmU82ucj72/pI4NeP+EzL1SzTgJz6jN/rgz5Wx5ixFkcPWVeV8MriyT+LQTZ2+7r1vP7PcztzUfAWZT2Ts9ZaBjSmpjEvY3s05O1Xw7B9HoFIYUvjazxaZRrxt6Ou7p0DGK2zOuaSNzZbA3pBUGeDfoUEnoy3nDcgSN4Yva/dciwVInmmZZkP6QaBhBMOa+UsY2bmbJV10RwcycVUsOxlyU1tk5cr8AVZCTtgUEMtRNfxbFooJLgpaoUe6wSmh+F6PYWXzjmZZn/fqGOf5UmYPaJ0vcJAAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Répertoires de contenu dans Hugo.</figcaption>
</figure>
<p>L'hypothèse de base que fait Hugo à propos de votre contenu est que vous l’avez organisé de manière délibérée. Une des conséquences est que la structure de votre premier niveau d’arborescence vous permet de définir des comportements différents à l’intérieur de chaque dossier.</p>
<p>La documentation d’Hugo explique que "bien qu'Hugo supporte l’imbrication de contenu à tous les niveaux, les dossiers de <strong>premier niveau sont spéciaux</strong>". Pour renforcer cette idée, les dossiers de premier niveau possèdent une nomenclature à part entière dans Hugo, ils désignent une <a href="https://gohugo.io/content-management/sections/" target="_blank" rel="noopener noreferrer"><strong>section de contenu</strong></a>, ou <code>section</code> pour faire plus court. L'analogie la plus proche dans le vocabulaire WordPress sera de penser à une section d’Hugo comme à une catégorie WordPress.</p>
<p>Pour vous, cela signifie qu'Hugo facilite la création de différents modèles pour chaque dossier("section") de contenu.</p>
<p>en termes d’arborescence de fichiers, nous passons du dossier <code>partials</code> au dossier <code>section</code> situé lui aussi dans le dossier <code>layouts</code> d’Hugo. Une fois de plus, la nomenclature des dossiers est importante.</p>
<p>Cela permet de créer un modèle de section, en le nommant comme le dossier de contenu auquel il s'applique. Par exemple si vous avez un dossier de premier niveau appelé "fonctionnalites", vous allez appeler votre modèle de section "fonctionnalites.html". C’est aussi simple que cela.</p>
<p>Le truc chouette avec les modèles de section, c'est que vous n'avez rien de plus à faire pour qu'ils soient pris en compte (contrairement aux fichiers partiels que vous allez devoir explicitement référencer dans un modèle.) Si un fichier du même nom qu'une section de contenu existe, Hugo va automatiquement utiliser ce fichier comme modèle pour la section. S'il n'existe pas, il utilisera le modèle par défaut.</p>
<p>Pour voir à quoi cela ressemble en pratique, jetons un œil à la structure du <a href="https://support.balsamiq.com/" target="_blank" rel="noopener noreferrer">site de support de Balsamiq</a> qui possède, entre autres, des sections appelées “plugins”, “tutorials”, “sales”.</p>
<figure>
<picture title="Le dossier content du site de support de Balsamiq">
<source type="image/webp" srcset="/thumbnails/768x/images/2017-10-01_templates-hugo-designers-wordpress/sbc-content.18ab2625435be94073d07ee4ab9810ab.webp 768w, /thumbnails/1024x/images/2017-10-01_templates-hugo-designers-wordpress/sbc-content.18ab2625435be94073d07ee4ab9810ab.webp 1024w" width="1024" height="432" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/2017-10-01_templates-hugo-designers-wordpress/sbc-content.18ab2625435be94073d07ee4ab9810ab.avif 768w, /thumbnails/1024x/images/2017-10-01_templates-hugo-designers-wordpress/sbc-content.18ab2625435be94073d07ee4ab9810ab.avif 1024w" width="1024" height="432" sizes="100vw">
<img src="/images/2017-10-01_templates-hugo-designers-wordpress/sbc-content.18ab2625435be94073d07ee4ab9810ab.png" alt="Le dossier content du site de support de Balsamiq" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="432" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAEd0lEQVR4nO1b3ZLsKAiGrbz/652bU/sq7IVBEVHxrzu9la9qxo5RQOATk57BP3//JegC1Q+ItjYeAJBUW4yotAgICIAYNCICYtkCiFZJQQAgBAAKugl4mfcnEi2FXiIAuq8B8pZYjmzv+7EFqx3D5R8qFeTL78M/0g3KxapLtzuIfyjGzgWsXjQ7u7h80/Ryua9nRN1FXb3K2YUIDEOI0lC+jUp4zFlSfRNJfCoQjAsQx1IDSKhsKC9u2WO7a6qoiA41tkFrNcWGQqn1LL+/HMtQGg76hZxq93z1oVQQ8lK1ll0VA407WO20ZjfMw6wp5lnBsIXk60MgiImLnfUbGInJlXsjkt4cPExGvXdYsrQTdcYjVmNMEM4LhN00cdobJiPirO8r8DPlwsID4EjFBlCfxpJslAqYmFZWixvx1GU4Y/WokPsbo9oQDBHpzkwvPCOvtFKZAjlTprMt+3BvV7xqIxCF0wU7sDZnxbSmEOlsa8BYMAL6TMkZInUBDDKlsoEDCUcjAFKVGXktidxIUw0TV1Ffv7VHWceG8cC0Rl92JrMz2jWlVMPOK+fFoGDJOjNAd2e+leFSUHzMkDc7pwcAmCsudabYDGG1KiHIkwlZSeIndMzl1bK9RjJVQ/awAxtXud02HIHoDLG8eVlpg0Iaf5ZXLivQnoHF77tfnwFqO0lR8PsYY8YovEyyUDLFZEhcM2Xrj09jAxWlfW0cYmpJieLmmj97Wd+qK2mMVyYfzb1MyRiSZ2yqI+FIeL9Uc/JECq0Hp7Vt6CKvxlg1xmHLLEoeWAV+VnFiSsaQorji7X7ibQggvT31occU7vQyRd4c82+fGXb1SmNsCQ254lbJFPEccz/vEAiGoJQRHRSCgfdrA4LAnKmH1m8xZUO9sK2beGiOPLtlUKq3/JK0ZEhs84yw3mKNmtK65s79TJljRjlmRK4ahUlOzpRQEIDCKbLOEEsd2nkyjKNM2XyAUpo4UaegMlp+bSCvjVPWpEInzjClIWvIGok1Zmg5endBCKWDi0fBkKo9u5ih8TNMKWvGkOzO66sYk5IhZ5lhGtK45s4zTPkcM7w21BlymhmGXa7gGP1nmdJP1KZs/1cgmTznd+rn8Kya0hDmxhgz9OVVJMKnmKFxiikdWXVD/PmdyZ5kBuPrDGEcYUpL1vZZa8xgpL/L6qzsZ5jinOsw4zgsHY9hCGOaKWfM2QAfMxiXi/N+efuwmSk5xl6h70ZL0+MYwjgXjE9hjBmMYYYMyl/HAlOeBo99j2UI4/eCMccMxjRDJvUtKXru9tTGiJ2PZwjj+cFYYwaj/rZ3EJ900PODEzBj188whPHsYBj76iAG/oPKj0856VnBSFix60hAPoHHBaNpkN/aowF5nNM+gNU1/yxDdmNH8hQy+h0F3oBIbKT0rKg3IArfZsobEAtfZMobkAaW4mI9kjgEvgExgebHTRKbeAPixHRccOxfJt6AdLGXLT0R2wPyf38YXGGKBz/3cvG7OO+tf04JfgNdQ9szxwLyYg7/ASghDgCX62MrAAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/2017-10-01_templates-hugo-designers-wordpress/sbc-content.18ab2625435be94073d07ee4ab9810ab.png 768w, /thumbnails/1024x/images/2017-10-01_templates-hugo-designers-wordpress/sbc-content.18ab2625435be94073d07ee4ab9810ab.png 1024w" sizes="100vw">
</picture>
<figcaption>Le dossier content du site de support de Balsamiq</figcaption>
</figure>
<p>Dans le dossier <code>section</code>, il y a des fichiers de modèles pour quelques-unes d’entre elles, nommées en fonction du dossier de contenu (par exemple "plugins.html").</p>
<figure>
<picture title="Le dossier section du site de support de Balsamiq">
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346854/sbc-section.7716d96bcf7444f62dbfeafa50c4d6b2.webp 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346854/sbc-section.7716d96bcf7444f62dbfeafa50c4d6b2.webp 1024w" width="1024" height="252" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346854/sbc-section.7716d96bcf7444f62dbfeafa50c4d6b2.avif 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346854/sbc-section.7716d96bcf7444f62dbfeafa50c4d6b2.avif 1024w" width="1024" height="252" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346854/sbc-section.7716d96bcf7444f62dbfeafa50c4d6b2.png" alt="Le dossier section du site de support de Balsamiq" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="252" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAD0UlEQVR4nO1bWbalIAys8Nz/1vq/V9CruOkPASGE8SoOz+rji1cGQ4oCVJr+/P3HAMBYIe08bHem0HJqoViO7AdgBluL0H4+Pi8AEBGMMcHxs53/mCiNjIGhtYwra2uxPpOrNE5XLGXyLd4xG5LQngnfVOsQEa2kWIvAsr2+WgBMYGIQE0AMhNYYUEAIiMSB7VDgSspkLWa5vCUsLvMVyQCC3qikaeW2ILicBqAPwMaSwuIGWHusJEbcSQ04xz4xxb/DslrbNKgKOQsxEdtFCnKQLMCbipho7f0hAQjPOR2LbeyJCEQGIKOoxg2iSLo9pZeGlOGwuIZfhQzvgx9n0zyRo2wvMEA+4GZNiOaZwqzoSTGWGE0p5IMUEWOTSsRIl0tYQqbPgiTDESEJ2fIrLDliPBGSEJ9Jd8ASQOJwpEQlC73325FmuQQRUaegbbgKMwX5okTfLYOQRfH/sSeVdSOtRCdkEK1DoasyXk8kc29ohftNWNJGzoGqBkhyAE0MakVhKodKAVoJgSfEkmP/+WqRD75mR3CaQtwoAW8pS0a20+RinJDU9lQVK7QwZh6IUxSSqCEkgoQVZdSKQrB2sa+RvoNIf4dq68N0hWidj+yFEhHNfqpLsQEfG+wRmKqQKNBCFdE1Zd6YEQyJM+49TSFhxy2eExUV8nxCJrUwCXJm3gC4qBB5Hv8+pjEzO8FiJt4MEMORP+fgjWfbHFKe8PcN4VRCZq+y8sMVqyvNsjKO1sZ8LGb2l49kMqeu3t+aNuTUBXD6k7p/aPOKCf6SzJv+vk4o98H0OQRAtWvXJvK6MlpVv5/G9sJyAR9iKP5oK7Srub0XTn3b29tB60vgUWX0ph+H6ynEocGvs+a/I3GuQhwGh3L/efywIWx+dK6rEAX+ewOn6mj6IHSDtl5DIQ4DzjCgfiyMqs0sn9tqr2HfN123UkgO3Z9LL9zmpZ7lPtB2+Gio89GjjNF0HY8ipBXRHHQxtdyckG+2pGklv1FGzpc+325OiEPbYKWtzipFpuMhhEj0bsRRtpgKUHXryz5zykMJWVF6bhnGwUq6LSFyE7tHV8AY3KmM5LNBN0Pl/LclBGjYJzcDO9/sdoTstUu/roxcOY4yjK+e9RK3IwTIvB6Z7sUxDtySkG+wtzKS8t0exSV+HSGH40ulPJ6QcOlbClb2KaNRGa311Uo8nhDgpP+uN3jDX0HICFg8ZowS2lvuJeRodDLyEiKwlzIkWut5CZmFRkZeQixavzZ+i1q9LyGzUWHkJeQk5Hh5CTkLGUaeS8hNNmRLH59LyF0gGPkP4kbkJNTKLjkAAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346854/sbc-section.7716d96bcf7444f62dbfeafa50c4d6b2.png 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346854/sbc-section.7716d96bcf7444f62dbfeafa50c4d6b2.png 1024w" sizes="100vw">
</picture>
<figcaption><a href="https://github.com/balsamiq/support.balsamiq.com/tree/master/themes/support-balsamiq-com/layouts/section" target="_blank" rel="noopener noreferrer">Le dossier section du site de support de Balsamiq</a></figcaption>
</figure>
<p>Chaque contenu qui se trouve dans un dossier et dont le nom correspond à l’un de ces modèles de section se verra automatiquement appliqué ce modèle.</p>
<h3 id="les-shortcodes">Les shortcodes</h3>
<p>Terminons avec une fonctionnalité sur laquelle Hugo et WordPress sont entièrement d’accord. Hugo dispose d’une fonctionnalité familière bien utile appelée… <a href="https://gohugo.io/content-management/shortcodes/" target="_blank" rel="noopener noreferrer">shortcodes</a>, bien connue dans WordPress sous le nom de… <code>shortcodes</code>. 🙂</p>
<p>Un <code>shortcode</code> est un petit bout de texte, une sorte de raccourci pour une portion de code plus importante.</p>
<p>Dans WordPress, vous écrivez un <code>shortcode</code> en l’entourant de crochets et en lui passant des paramètres comme ceci:</p>
<pre><code class="language-html hljs xml">[gallery id="123" size="medium"]</code></pre>
<p>Cela fonctionne presque de la même manière dans Hugo, la syntaxe diffère un peu. On écrira le même shortcode ainsi dans Hugo :</p>
<pre><code class="language-go-html-template hljs go">{{ &lt;gallery id=<span class="hljs-string">"123"</span> size=<span class="hljs-string">"medium"</span>&gt; }}</code></pre>
<p>Dans Hugo, chaque shortcode est un fichier de modèle HTML. Et où devez-vous ranger ces fichiers ? Dans un dossier nommé <code>shortcodes</code> évidemment, vous vous en doutiez.</p>
<p>Écrire vos propres shortcodes peut se révéler délicat - vous allez devoir probablement récupérer les paramètres en écrivant des choses comme <code>{{ .Get 0 }}</code> - mais écrire des <a href="https://codex.wordpress.org/Shortcode_API" target="_blank" rel="noopener noreferrer">shortcodes pour WordPress</a> n'est pas non plus une partie de plaisir.</p>
<p>En pratique, vous allez vraisemblablement utiliser (ou peut-être personnaliser) un shortcode existant, comme vous le faites dans WordPress. Hugo fournit <a href="https://gohugo.io/content-management/shortcodes/#use-hugo-s-built-in-shortcodes" target="_blank" rel="noopener noreferrer">quelques shortcodes bien pratiques par défaut</a> pour YouTube, Instagram, Twitter, etc.</p>
<p>Sur <a href="https://docs.balsamiq.com/" target="_blank" rel="noopener noreferrer">le site de documentation de Balsamiq</a>, nous utilisons par exemple les shortcodes pour les messages d’alerte et d’information, comme on peut le voir ici :</p>
<figure>
<picture title="Les messages d’information et d’alerte dans la documentation de Balsamiq.">
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523216717/alerts.56a7bca056545dc5fca8d5f8bbb61102.webp 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523216717/alerts.56a7bca056545dc5fca8d5f8bbb61102.webp 1024w" width="1024" height="385" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523216717/alerts.56a7bca056545dc5fca8d5f8bbb61102.avif 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523216717/alerts.56a7bca056545dc5fca8d5f8bbb61102.avif 1024w" width="1024" height="385" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523216717/alerts.56a7bca056545dc5fca8d5f8bbb61102.png" alt="Les messages d’information et d’alerte dans la documentation de Balsamiq" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="385" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAADtElEQVR4nO1bUXbcIAwUDfe/U4/R/xyjXfqRZQ1YQhIIRNqdvDwwIIQ1HrC9Jvz89ZkAA156VTP1axBEtVUa6PTWBusvfNWEkA3Cq5yrr8v7o891MXGRH8R4r/2g21g0SACJCdguxBnjfVT2Q5WeLdA0B7tJIT3bZCLS5blU0G4cpBBJBKqQVZZdfwhTIRSEwWuGcceQQmYplC9adJTKmpImUi2ZhDKFSyH52HveipLwjhDQs6nr9MpoFSGOYclS29EhCvkhaTQyVsrmTlR6/V9/VyneB003eqdVpqk4Tnz7Xn4FYli4hrSD/yrTnFLqtm8DFUBACHa727slJnxZ5DFEkUSekFJHkzEBwhgjBXop8fxRPEaQfVvnMcQPokISfA1BKpBPabLmXVIwEprysgzzcYxCMrQBrtprZCI8E44/KSE9N+4KySCDHwRtGDtVW4UdSQY0UxJCSK8vblhrFSIIwNDtwAZCWpOqG0oVk4SP5DHED+FAzNaUTYT0pqxeXgs+8EmpkKlbn+xSgY0KaU0xEgxO/+ZHmscgVggAH/iTFEJ2UZJgycbNVyLK+5hWiMktraStQfBWqKLri8ljQBUydVvLwYkQvSpGHaabtYoQ7LXz0ivIUSF6p/Y9Tk1Z9m+5JPMF8qDQFO+F1DH/wlPSWwydX2aWXCsqQtaNhPW9yKdAIbxjO6VoVtRNq68Y1GB00ZlSiLQTOUaDvJsZl8ULAIQKyTBRisidhzS0Pk3njRdECsEM98LL836/4gfDvd/FeS4epe99Z529RulPqv7rq/8IdgB9MMxw+VqUxFmjWYXud1n/xzV5FiKkh/cY3igQE7wJOQkR0m/vMRwG34k6pvTHdQBv1Ihps0LOvlHwH118bFCI/2l+HxhOWWt2hIz3OPPc4ncJGSpEv8nAAoE8GifE8zWRq0KA+DKDKuHh8x5qDg0hlgrRh7AXtJFfMlcSMjZWHssUAmC110q3h+SykvQ9B/2GVB43hYwHER/KLCkB7aN/2rjN+HgorCCkRpTz0TbsfWVxbTorQyXL17ZI50jBTNAFtuwW38F+EcSH0A5fdNPt6Cu9Jp1y5xSVFzhbCKUz9gqeU2QcMb+Cys/2fp8LWEK653d6455mn3q4DScoU19YPyiuWU+ipJP8JbeWAKzn7/2hxNDueBVYhWRdBBgn4gyFZOxQyjhIhbRTU3g6D7cyep04SyGWI1inFFQh9JXPryNtHjv2x+xV3Z6RnUrIz4DuZODrCId/TyVrHw7/AmNZ2G8RA/8YAAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523216717/alerts.56a7bca056545dc5fca8d5f8bbb61102.png 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523216717/alerts.56a7bca056545dc5fca8d5f8bbb61102.png 1024w" sizes="100vw">
</picture>
<figcaption>Les messages d’information et d’alerte dans la <a href="https://docs.balsamiq.com" target="_blank" rel="noopener noreferrer">documentation de Balsamiq</a>.</figcaption>
</figure>
<p><a href="https://themes.gohugo.io/theme/hugo-theme-learn/en/shortcodes/notice/" target="_blank" rel="noopener noreferrer">Ce thème pour Hugo</a> présente quelques exemples sympas de ce qu'on peut faire avec des shortcodes en pratique.</p>
<h2 id="la-fin-et-le-debut">La fin et le début</h2>
<p>Dans cet article, nous avons mis en lumière quelques-unes des différences et des similitudes entre WordPress et Hugo, ce qui, je l’espère, suffira à vous aider à effectuer le changement de paradigme de l’un à l’autre.</p>
<p>Bien entendu, je suis loin d’avoir tout couvert. Hugo support aussi les tags comme WordPress à l’aide des <a href="https://gohugo.io/content-management/taxonomies/" target="_blank" rel="noopener noreferrer"><code>taxonomies</code></a> et les modèles de contenu par défaut avec les <a href="https://gohugo.io/content-management/archetypes/" target="_blank" rel="noopener noreferrer"><code>archetypes</code></a>, mais vous n'avez pas besoin d’être tout de suite (voire jamais) familier avec ces notions.</p>
<p>Une chose que j'ai remarquée dans l’évolution d’Hugo est qu'il devient <strong>moins ésotérique et beaucoup plus pratique</strong> et fonctionnel dans ses réponses aux besoins exprimés par les gens. Je vois ça comme une bonne chose et c'est sûrement une des raisons pour laquelle Hugo est rapidement en train de gagner en popularité.</p>
<p>Je me rappelle par exemple avoir passé des journées à essayer de comprendre <a href="https://gohugo.io/content-management/taxonomies/#example-taxonomy-movie-website" target="_blank" rel="noopener noreferrer">cette explication des taxonomies</a> au début. Alors que des fonctionnalités plus récentes comme les <a href="https://gohugo.io/content-management/sections/" target="_blank" rel="noopener noreferrer">sections</a> sont beaucoup plus simples à appréhender.</p>
<p>Enfin, voici une liste d’excellentes ressources en anglais sur Hugo pour vous aider dans votre périple. Si vous m'avez lu jusqu'ici c'est que vous avez au moins envie d’en savoir un peu plus.</p>
<ul>
<li><a href="https://gohugo.io/documentation/" target="_blank" rel="noopener noreferrer">La documentation officielle d’Hugo</a>.</li>
<li><a href="https://discourse.gohugo.io/" target="_blank" rel="noopener noreferrer">Le forum officiel d’Hugo</a> – un bon endroit pour trouver des réponses à vos questions et déboguer votre code.</li>
<li><a href="https://www.youtube.com/playlist?list=PLLAZ4kZ9dFpOnyRlyS-liKL5ReHDcj4G3" target="_blank" rel="noopener noreferrer">Les tutoriels vidéo de Giraffe Academy sur YouTube</a> - 23 vidéos !</li>
<li><a href="https://themes.gohugo.io/theme/hugo-theme-learn/en" target="_blank" rel="noopener noreferrer">Le thème “Learn” pour Hugo</a> - Un bon endroit pour commencer à jouer.</li>
<li><a href="https://themes.gohugo.io/" target="_blank" rel="noopener noreferrer">D'autres thèmes pour Hugo</a> - pour que vous n'ayez pas à commencer de zéro.</li>
<li><a href="https://blog.philipphauer.de/moving-wordpress-hugo/" target="_blank" rel="noopener noreferrer">Un des nombreux billets de blog sur le passage de WordPress à Hugo</a>.</li>
<li><a href="http://blog.teamtreehouse.com/getting-started-static-sites" target="_blank" rel="noopener noreferrer">Bien démarrer avec les sites statiques</a> - mon article précédent sur les sites statiques.</li>
</ul>
<p>Merci de m'avoir lu. Bonne génération de site !</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2017/09/02/bibliotheques-de-composants-en-markdown/</id>
    <title>Des bibliothèques de composants avec Shadow DOM en Markdown</title>
    <published>2017-09-02T00:00:00+00:00</published>
    <link href="https://jamstatic.fr/2017/09/02/bibliotheques-de-composants-en-markdown/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>S'il est un domaine où les générateurs de site statique se sont
rapidement imposés c'est bien celui des sites de documentation. Avec la
nécessaire rationalisation des interfaces multi-supports, beaucoup d’équipes en
charge du front-end doivent aujourd’hui maintenir une bibliothèque de composants
qui sert de référence à toutes les équipes et qui permet donc d’assurer la
cohérence et l’évolutivité des composants dans les diverses sites et
applications web de l’entreprise. Un composant comme un formulaire de recherche
associe du code HTML, du CSS et du JavaScript et il n'est pas forcément aisé
d’isoler le comportement de vos composants quand vous les insérez en tant
qu'exemples dans une documentation. Heydon Pickering, consultant UX et
accessibilité chez Paciello Group et auteur du livre
<a href="https://shop.smashingmagazine.com/products/inclusive-design-patterns" target="_blank" rel="noopener noreferrer">Inclusive Design Patterns</a>
s'est dit qu'il pouvait tirer parti des fonctionnalités de templating et de
modularisation du générateur Hugo pour mener à bien cette tâche, il nous
explique tout cela en détail. Une bonne occasion pour découvrir les
fonctionnalités liées aux snippets de code dans Hugo et d’apprendre à créer des
éléments Shadow DOM en JavaScript.</p></aside>
<p>Il y a des gens qui détestent écrire de la documentation et d’autres qui
détestent écrire tout court. Il se trouve que j'aime écrire, sinon vous ne
seriez pas en train de lire ceci. Ça tombe bien, car en tant que consultant en
Design qui fournit un suivi professionnel, écrire représente une part importante
de mon travail. Par contre je déteste, mais alors je déteste les traitements de
texte.</p>
<p>Mon workflow habituel de travail avec un traitement de texte ressemble un peu à ça :</p>
<ol>
<li>Sélectionner du texte que je veux copier dans une autre partie du document,</li>
<li>S'apercevoir que l’application en a sélectionné un peu plus que ce que je lui avais demandé,</li>
<li>Essayer de nouveau, laisser tomber et me résoudre à ajouter la partie manquante (ou supprimer la partie en trop) de la sélection visée plus tard,</li>
<li>Copier-coller la sélection,</li>
<li>S'apercevoir que le formatage du texte collé est quelque peu différent de l’orignal,</li>
<li>Tenter de trouver le style prédéfini qui correspond au texte d’origine,</li>
<li>Essayer d’appliquer le style,</li>
<li>Laisser tomber et appliquer la police de caractère et la taille à la main,</li>
<li>S'apercevoir qu'il y a un espace trop important au-dessus du texte collé, et appuyer sur “Backspace” pour le supprimer,</li>
<li>S'apercevoir que la taille du texte a brusquement changé, car il a été associé au titre qui le précède et a hérité de ses propriétés,</li>
<li>Réfléchir au sens de la vie.</li>
</ol>
<p>Lorsque vous devez écrire de la documentation pour le web (comprenez
<a href="https://www.smashingmagazine.com/taking-pattern-libraries-next-level/" target="_blank" rel="noopener noreferrer">des bibliothèques de composants</a>)
les traitements de texte ne sont pas simplement désobéissants, mais totalement
inadaptés. Idéalement je voudrais pouvoir écrire de manière à pouvoir inclure
les composants que je documente dans le flux du texte, et cela n'est possible
que si la documentation elle-même est écrite en HTML en CSS et en JavaScript.
Dans cet article, je vais vous montrer comme inclure facilement des démos de
code dans des documents Markdown avec l’aide de snippets et de l’encapsulation
du Shadow DOM.</p>
<figure>
<picture title="Un M, une flèche qui pointe vers le bas et un détective caché dans l’obscurité pour symboliser Markdown et Shadow Dom">
<source type="image/webp" srcset="/images/2017-09-02_bibliotheques-de-composants-en-markdown/markdown-shadowdom-preview-opt.104c8bc81466d61bbb2e313b88aa0be8.webp" width="499" height="221">
<source type="image/avif" srcset="/images/2017-09-02_bibliotheques-de-composants-en-markdown/markdown-shadowdom-preview-opt.104c8bc81466d61bbb2e313b88aa0be8.avif" width="499" height="221">
<img src="/images/2017-09-02_bibliotheques-de-composants-en-markdown/markdown-shadowdom-preview-opt.104c8bc81466d61bbb2e313b88aa0be8.png" alt="Un M, une flèche qui pointe vers le bas et un détective caché dans l’obscurité pour symboliser Markdown et Shadow Dom" loading="lazy" decoding="async" class="dark:brightness-90" width="499" height="221" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAABwklEQVR4nO2c4a7DIAiF5Wbv/8rcXyZsmVUB59GcL1mypS1aTkEw26SUooXA8Ld7AuQdCgIGBQFDVLmEIMEIAYOCgEFBwKAgYFAQMCgIGC/7QUS6NbCqimegEdse+yvnvIPpCBl1LPHhSlmzolDEcdxryKiTKcYcoUW95+xbxRARXXVvr/4pPm4W49v7EUaKi3DZe6vjVzASWSl9yOcgFMlPWmNYRaAYMVI7dYoxxpOfuHUCBgUBY1nZexvZ6djas+UwI6TDyibQjlHfT0eIqopngt7rSok/nb3rWw3bL4sUEVFVFVeEzG5nn7T9XdlRMYqILk9ZGWKsFBTtYXEL8usbWTEemhjulGUNRI5nj7fLlrVpXx4b4bK3tVivevoixYG1kTUfS2RedU4pfcink9BSgeVpbru2fuycjmwMI6V369jOfTg7dlqVFc2dnvEyz0eJ6qM79VEnZp+3kiNTlqWXvlZWeiPFzGwqPDpCKi2ne8R4uqYnRgbHR0gloxzuMbsn5vkSxDWClPIuSiRVecXNeCD4k7YHdpTCV6whN0FBwKAgDXZ17hQEDAryhZ37WhQEDCn8NyAoGCFgUBAwKAgY/1/09mnML9aCAAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Un M, une flèche qui pointe vers le bas et un détective caché dans l’obscurité pour symboliser Markdown et Shadow Dom</figcaption>
</figure>
<h3 id="css-et-markdown">CSS et Markdown</h3>
<p>On pourra dire ce qu'on veut sur CSS, c'est un outil de composition certainement
plus consistent et plus sûr qu'un éditeur WYSIWYG ou qu'un traitement de texte.
Pourquoi ? Parce qu'il n'y a pas de boîte noire renfermant des algorithmes de
haut-niveau qui essaient de deviner où les styles doivent <em>vraiment</em>
s'appliquer. Au contraire, c'est beaucoup plus explicite : vous définissez les
<a href="https://www.smashingmagazine.com/2016/11/css-inheritance-cascade-global-scope-new-old-worst-best-friends/" target="_blank" rel="noopener noreferrer">circonstances dans lesquelles les styles s'appliquent aux éléments</a>)
et ces règles sont respectées.</p>
<p>Le seul problème avec CSS c'est que ça vous demande d’écrire du HTML en
contre-partie. Même les amoureux du HTML voudront bien concéder que c'est
pénible lorsque vous souhaitez simplement écrire de la prose. C’est là où
Markdown entre en jeu. Avec sa syntaxe concise et son jeu de fonctionnalités
réduit à l’essentiel, il offre une façon d’écrire qui est simple à apprendre et
qui peut tirer parti — une fois converti automatiquement en HTML — des
fonctionnalités de composition puissantes et prédictives de CSS. Ce n'est pas
par hasard qu'il est devenu le format <em>de facto</em> des générateurs de site
statique et des plate-formes moderne de blog comme Ghost.</p>
<p>Pour des besoins plus complexes, quand un balisage spécifique est requis, la
majorité des parseurs Markdown vous permettent d’écrire directement du HTML.
Toutefois, plus il y a de balisages complexes, moins vote système de création
est accessible aux personnes moins techniques, ou à ceux qui n'ont pas beaucoup
de temps et de patience. C’est là où les snippets de code rentrent en jeu.</p>
<h3 id="les-shortcodes-d-hugo">Les <em>shortcodes</em> d’Hugo</h3>
<p><a href="https://gohugo.io" target="_blank" rel="noopener noreferrer">Hugo</a> est un générateur de site statique écrit en Go — un
langage polyvalent compilé développé par Google. Grâce à la parallélisation (et
sans doute, à d’autres fonctionnalités bas-niveau que je ne comprends pas
vraiment), Go permet à Hugo d’être un générateur de contenu statique
ultra-rapide. C’est l’une des nombreuses raisons pour lesquelles Hugo a été
sélectionné pour la nouvelle version du site de Smashing Magazine.</p>
<p>En dehors de la performance, il fonctionne de la même manière que les
générateurs en Ruby ou en NodeJS avec lesquels vous êtes peut-être déjà
familiers : du Markdown et des méta-données (en YAML, TOML ou JSON) transformés
à l’aide de modèles. Sara Soueidan a écrit une <a href="/2017/06/07/migration-de-jekyll-a-hugo/">excellente introduction aux
principales fonctionnalités d’Hugo</a>.</p>
<p>Pour moi la fonctionnalité qui tue dans Hugo c'est son
<a href="https://gohugo.io/extras/shortcodes/" target="_blank" rel="noopener noreferrer">implémentation des snippets de code</a>. Les
habitués de WordPress seront peut-être déjà familiers avec ce concept : un
raccourci syntaxique destiné principalement à insérer des bouts de codes
complexes ou issus de services tiers. Par example WordPress inclus un raccourci
pour Vimeo qui prend juste l’ID de la vidéo Vimeo en question.</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">iframe</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://player.vimeo.com/video/207263942?h=4a34bbf60a"</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"640"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"360"</span> <span class="hljs-attr">frameborder</span>=<span class="hljs-string">"0"</span> <span class="hljs-attr">allow</span>=<span class="hljs-string">"autoplay; fullscreen; picture-in-picture"</span> <span class="hljs-attr">allowfullscreen</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">iframe</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://vimeo.com/207263942"</span>&gt;</span>Hugo in the garden<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span> from <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://vimeo.com/twistedpoly"</span>&gt;</span>Twistedpoly<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span> on <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://vimeo.com"</span>&gt;</span>Vimeo<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>.<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></code></pre>
<p>Les crochets indiquent que le contenu doit être traité et remplacé par le
balisage HTML complet lorsque le contenu est parsé.</p>
<p>À l’aide des fonctions de templating de Go, Hugo fourni une API très simple pour
créer des <em>shortcodes</em> personnalisés. Par exemple, j’ai créé un <em>shortcode</em>
CodePen très simple que peux inclure dans mon contenu en Markdown :</p>
<pre><code class="language-html hljs xml">Un peu de contenu en Markdown avant le shortcode. Aliquam sodales rhoncus dui,
sed congue velit semper ut. Class aptent taciti sociosqu ad litora torquent.

<span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"codepen"</span> <span class="hljs-attr">data-height</span>=<span class="hljs-string">"300"</span> <span class="hljs-attr">data-default-tab</span>=<span class="hljs-string">"html,result"</span> <span class="hljs-attr">data-slug-hash</span>=<span class="hljs-string">"VpVNKW"</span> <span class="hljs-attr">data-user</span>=<span class="hljs-string">"heydon"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">span</span>&gt;</span>See the Pen <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://codepen.io/heydon/pen/VpVNKW"</span>&gt;</span>
  Vue.js TODO List <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span> by Heydon (<span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://codepen.io/heydon"</span>&gt;</span>@heydon<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>)
  on <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://codepen.io"</span>&gt;</span>CodePen<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>.<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">async</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cpwebassets.codepen.io/assets/embed/ei.js"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

Un peu de contenu en Markdown après le shortcode. Nulla vel magna sit amet dui
lobortis commodo vitae vel nulla sit amet ante hendrerit tempus.</code></pre>
<p>Hugo recherche automatiquement le modèle nommé <code>codePen.html</code> dans le
sous-dossier <code>shortcodes</code> pour pouvoir parser le <em>shortcode</em> pendant l’étape de
compilation. Mon implémentation ressemble à ça :</p>
<pre><code class="language-html hljs xml">{{ if .Site.Params.codePenUser }}
  <span class="hljs-tag">&lt;<span class="hljs-name">iframe</span> <span class="hljs-attr">height</span>=<span class="hljs-string">'300'</span> <span class="hljs-attr">scrolling</span>=<span class="hljs-string">'no'</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"démonstration CodePen"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">'//codepen.io/{{ .Site.Params.codepenUser | lower }}/embed/{{ .Get 0 }}/?height=265&amp;theme-id=dark&amp;default-tab=result,result&amp;embed-version=2'</span> <span class="hljs-attr">frameborder</span>=<span class="hljs-string">'no'</span> <span class="hljs-attr">allowtransparency</span>=<span class="hljs-string">'true'</span> <span class="hljs-attr">allowfullscreen</span>=<span class="hljs-string">'true'</span> <span class="hljs-attr">style</span>=<span class="hljs-string">'width: 100%;'</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"//codepen.io/{{ .Site.Params.codePenUser | lower }}/pen/{{ .Get 0 }}"</span>&gt;</span>Voir la démo sur CodePen<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">iframe</span>&gt;</span>
{{ else }}
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"site-error"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>Attention :<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span> Le paramètre <span class="hljs-tag">&lt;<span class="hljs-name">code</span>&gt;</span>codePenUser<span class="hljs-tag">&lt;/<span class="hljs-name">code</span>&gt;</span> n'a pas été renseigné dans le fichier <span class="hljs-tag">&lt;<span class="hljs-name">code</span>&gt;</span>config.toml<span class="hljs-tag">&lt;/<span class="hljs-name">code</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
{{ end }}</code></pre>
<p>Pour vous faire une meilleure idée de la manière dont fonctionne le langage de
templating de Go, vous devrez consulter
<a href="https://gohugo.io/templates/go-templates/" target="_blank" rel="noopener noreferrer">l’introduction à Go Template</a>
d’Hugo. En attendant, retenez simplement cela :</p>
<ul>
<li>C’est pas super sexy mais c'est très puissant.</li>
<li><code>{{ .Get 0 }}</code> sert à récupérer le premier (et dans cet exemple le seul) argument fourni — l’ID du CodePen. Hugo supporte également les arguments nommés, qui sont déclarés comme des arguments HTML.</li>
<li>Le <code>.</code> référence le contexte actuel. Donc <code>.Get 0</code> signifie “Récupère le premier argument fourni pour le <em>shortcode</em> courant.”</li>
</ul>
<p>Quoi qu'il en soit, je pense que les <em>shortcodes</em> sont la meilleure chose qui
existe depuis le pain de mie en tranches et l’implémentation d’Hugo pour écrire
des <em>shortcodes</em> personnalisés est vraiment impressionnante. Je me dois aussi de
mentionner qu'il est possible d’utiliser les
<a href="https://jekyllrb.com/docs/includes/" target="_blank" rel="noopener noreferrer">includes de Jekyll</a> pour parvenir à un
résultat similaire, mais je les trouve moins souples et moins puissants.</p>
<h3 id="demos-de-code-sans-tierce-partie">Démos de code sans tierce partie</h3>
<dl>
<dt>J'aime beaucoup CodePen (et toutes les autres aires disponibles pour jouer avec</dt>
<dt>du code), mais on se heurte à des problèmes inhérents à ces plate-formes</dt>
<dt>lorsqu'on veut inclure ces extraits de code dans une bibliothèque de composants</dt>
<dd></dd>
</dl>
<ul>
<li>On dépend d’une API et il n'est pas toujours facile de faire fonctionner cela efficacement en mode hors-ligne.</li>
<li>Cela ne représente pas un simple composant, c'est une interface complexe à part entière qui embarque les styles du service utilisé.</li>
<li>Cela crée du bruit et une distraction inutile alors que le focus devrait être sur le composant.</li>
</ul>
<p>Pendant un temps, j'ai tenté d’inclure mes démos de composants en utilisant mes
propres iframes. Je faisais pointer l’iframe vers un fichier en local qui
contenait la page web de la démo. Grâce aux iframes, j'étais capable
d’encapsuler les styles et le comportement des éléments sans me reposer sur une
tierce partie.</p>
<p>Malheureusement, les iframes sont difficiles à manier et à redimensionner
dynamiquement. en termes de complexité de création, cela demande de maintenir
des fichiers distincts et de créer des liens. Je préfèrerais pouvoir écrire mes
composants <em>in situ</em> et inclure juste le code nécessaire pour les faire
fonctionner. Je voudrais pouvoir écrire des démos comme j'écris de la
documentation.</p>
<h3 id="le-shortcode-demo">Le <em>shortcode</em> <code>demo</code></h3>
<p>Heureusement, Hugo vous permet de créer des <em>shortcodes</em> qui incluent du contenu
entre des balises de <em>shortcode</em> ouvrantes et fermantes. Ce contenu est
disponible dans le fichier <em>shortcode</em> en utilisant <code>{{ .Inner }}</code>. Imaginons
donc que je veuille utiliser un <em>shortcode</em> <code>demo</code> de la façon suivante :</p>
<pre><code class="language-go-html-template hljs go">{{&lt;/* demo */&gt;}}
  C’est le contenu !
{{&lt;/* /demo */&gt;}}</code></pre>
<p>“C’est le contenu !” serait accessible via <code>{{ .Inner }}</code> dans le fichier de
modèle <code>demo.html</code> qui va le parser. C’est un bon point de départ pour insérer
des démos de code en ligne, mais il reste encore le problème de l’encapsulation.</p>
<h4 id="encapsulation-du-style">Encapsulation du style</h4>
<p>Il y a trois choses dont il faut se soucier pour l’encapsulation des styles :</p>
<ul>
<li>les styles hérités de la page parente par le composant,</li>
<li>les styles hérités du composant par la page parente,</li>
<li>les styles partagés involontairement entre les composants.</li>
</ul>
<p>Une solution consiste à bien cibler les sélecteurs CSS de manière à ce qu'ils ne
s'appliquent à différents composants ou aux composants et aux pages. Cela
voudrait dire utiliser des sélecteurs ésotériques pour chaque composant, et ce
n'est pas une possibilité que j'ai envie de considérer, alors que je pourrais
écrire du code concis et lisible. Un des avantages des iframes est que les
styles sont encapsulés par défaut, donc je pourrais écrire
<code>button { background: blue }</code> et être sûr que ce ne sera appliqué qu'à
l’intérieur de l’iframe.</p>
<p>Une manière plus simple d’empêcher l’héritage de styles entre composants dans la
page est d’utiliser la propriété <code>all</code> avec la valeur <code>initial</code> sur l’élément
parent de son choix. Je peux définir cet élément dans le fichier <code>demo.html</code> :</p>
<pre><code class="language-go-html-template hljs go">&lt;div class=<span class="hljs-string">"demo"</span>&gt;
    {{ .Inner }}
&lt;/div&gt;</code></pre>
<p>Ensuite, je dois appliquer la règle <code>all: initial</code> à toutes les instances de cet
élément pour qu'elle se propage aux éléments enfants de chaque instance.</p>
<pre><code class="language-css hljs css"><span class="hljs-selector-class">.demo</span> {
  <span class="hljs-attribute">all</span>: initial;
}</code></pre>
<p>Le comportement de <code>initial</code> est assez… particulier. En pratique, tous les
éléments affectés sont censés retrouver les styles par défaut du user agent
(comme <code>display: block</code> pour les éléments <code>&lt;h2&gt;</code>). Toutefois, l’élément auquel
il est appliqué — <code>class="demo"</code> — a besoin qu'on redéfinisse explicitement
certains des styles de l’agent utilisateur. Dans notre cas, c'est juste
<code>display: block</code>, puisque <code>class="demo"</code> est un <code>&lt;div&gt;</code>.</p>
<pre><code class="language-css hljs css"><span class="hljs-selector-class">.demo</span> {
  <span class="hljs-attribute">all</span>: initial;
  <span class="hljs-attribute">display</span>: block;
}</code></pre>
<p><strong>Remarque</strong> : <code>all</code> n'est pour l’instant pas supporté par Microsoft Edge mais
c'est en considération. À part ça le support est
<a href="https://caniuse.com/css-all" target="_blank" rel="noopener noreferrer">assurément large</a>. Pour nos besoins, la
valeur <code>revert</code> aurait été plus robuste et plus sûre, mais elle n'est pas encore
supportée.</p>
<h4 id="faire-du-shortcode-un-shadowdom">Faire du shortcode un ShadowDOM</h4>
<p>L'utilisation de <code>all: initial</code> ne met pas nos composants en ligne totalement à
l’abri de toute influence externe (la spécificité s'applique toujours) mais nous
pouvons raisonnablement estimer que les styles sont désactivés puisque nous
n'utilisons que la classe <code>demo</code>. Les styles hérités de sélecteurs génériques
comme <code>html</code> et <code>body</code> seront généralement exclus.</p>
<p>Toutefois, cela ne règle que le problème des styles issus de l’élément parent
sur les composants. Pour empêcher les styles écrits pour les composants
d’affecter d’autres parties de la page, nous allons avoir besoin de
<a href="https://glazkov.com/2011/01/14/what-the-heck-is-shadow-dom/" target="_blank" rel="noopener noreferrer">shadow DOM</a> afin
de créer une sous-arborescence encapsulée.</p>
<p>Imaginez que je veuille documenter un élément <code>&lt;button&gt;</code> stylé. J'aimerais
pouvoir écrire quelque chose d’aussi simple que l’exemple qui suit, sans
craindre que le sélecteur de l’élément <code>button</code> ne s'applique aussi aux éléments
<code>button</code> de la bibliothèque de composants elle-même ou à d’autres composants
présents sur la même page.</p>
<pre><code class="language-go-html-template hljs go">{{&lt;/* demo */&gt;}}
&lt;button&gt;Mon bouton&lt;/button&gt;
&lt;style&gt;
button {
    background: blue;
    padding: <span class="hljs-number">0.5</span>rem <span class="hljs-number">1</span>rem;
    text-transform: uppercase;
}
&lt;/style&gt;
{{&lt;/* /demo */&gt;}}</code></pre>
<p>L'astuce est d’utiliser la variable <code>{{ .Inner }}</code> du modèle de <em>shortcode</em> et
de l’inclure en tant qu'<code>innerHTML</code> d’un nouveau <code>ShadowRoot</code>. On pourrait
l’implémenter de cette façon :</p>
<pre><code class="language-go-html-template hljs go">{{ $uniq := .Inner | htmlEscape | base64Encode | truncate <span class="hljs-number">15</span> <span class="hljs-string">""</span> }}
&lt;div class=<span class="hljs-string">"demo"</span> id=<span class="hljs-string">"demo-{{ $uniq }}"</span>&gt;&lt;/div&gt;
&lt;script&gt;
    (function() {
        <span class="hljs-keyword">var</span> root = document.getElementById(<span class="hljs-string">'demo-{{ $uniq }}'</span>);
        root.attachShadow({mode: <span class="hljs-string">'open'</span>});
        root.innerHTML = <span class="hljs-string">'{{ .Inner }}'</span>;
    })();
&lt;/script&gt;</code></pre>
<ul>
<li><code>$uniq</code> est une variable définie pour identifier le conteneur du composant. Elle est passée à plusieurs fonctions de templating de Go pour créer une chaîne de caractères unique… enfin espérons-le ! — ce n'est pas une méthode infaillible, c'est simplement pour l’exemple.</li>
<li><code>root.attachShadow</code> fait du conteneur du composant un hôte shadow DOM.</li>
<li>Je peuple le <code>innerHTML</code> du <code>ShadowRoot</code> avec <code>{{ .Inner }}</code>, qui inclus le CSS maintenant encapsulé.</li>
</ul>
<h4 id="autoriser-le-comportement-avec-javascript">Autoriser le comportement avec JavaScript</h4>
<p>J'aimerais aussi inclure des comportements JavaScript dans mes composants. Au
début je pensais que ce serait facile, malheureusement le code JavaScript inséré
via <code>innerHTML</code> n'est ni parsé ni exécuté. On peut résoudre ce problème en
important le contenu d’un élément <code>&lt;template&gt;</code>. J'ai corrigé mon implémentation
en conséquence.</p>
<pre><code class="language-go-html-template hljs go">{{ $uniq := .Inner | htmlEscape | base64Encode | truncate <span class="hljs-number">15</span> <span class="hljs-string">""</span> }}
&lt;div class=<span class="hljs-string">"demo"</span> id=<span class="hljs-string">"demo-{{ $uniq }}"</span>&gt;&lt;/div&gt;
&lt;template id=<span class="hljs-string">"template-{{ $uniq }}"</span>&gt;
    {{ .Inner }}
&lt;/template&gt;
&lt;script&gt;
    (function() {
        <span class="hljs-keyword">var</span> root = document.getElementById(<span class="hljs-string">'demo-{{ $uniq }}'</span>);
        root.attachShadow({mode: <span class="hljs-string">'open'</span>});
        <span class="hljs-keyword">var</span> template = document.getElementById(<span class="hljs-string">'template-{{ $uniq }}'</span>);
        root.shadowRoot.appendChild(document.importNode(template.content, <span class="hljs-literal">true</span>));
    })();
&lt;/script&gt;</code></pre>
<p>Maintenant, je peux inclure la démo d’un bouton interrupteur par exemple:</p>
<pre><code class="language-go-html-template hljs go">{{&lt;/* demo */&gt;}}
&lt;button&gt;Mon bouton&lt;/button&gt;
&lt;style&gt;
button {
    background: blue;
    padding: <span class="hljs-number">0.5</span>rem <span class="hljs-number">1</span>rem;
    text-transform: uppercase;
}

[aria-pressed=<span class="hljs-string">"true"</span>] {
    box-shadow: inset <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">5</span>px #<span class="hljs-number">000</span>;
}
&lt;/style&gt;
&lt;script&gt;
<span class="hljs-keyword">var</span> toggle = document.querySelector(<span class="hljs-string">'[aria-pressed]'</span>);

toggle.addEventListener(<span class="hljs-string">'click'</span>, (e) =&gt; {
  let pressed = e.target.getAttribute(<span class="hljs-string">'aria-pressed’) === '</span><span class="hljs-literal">true</span><span class="hljs-string">';
  e.target.setAttribute('</span>aria-pressed’, !pressed);
});
&lt;/script&gt;
{{&lt;/* /demo */&gt;}}</code></pre>
<p><strong>Note</strong> : J’ai écrit un article détaillé sur
<a href="https://inclusive-components.design/toggle-button/" target="_blank" rel="noopener noreferrer">l’accessibilité des interrupteurs</a>
pour Inclusive Components.</p>
<h4 id="l-encapsulation-de-javascript">L'encapsulation de JavaScript</h4>
<p>JavaScript n'est pas, à ma grande surprise,
<a href="https://robdodson.me/shadow-dom-javascript/" target="_blank" rel="noopener noreferrer">encapsulé automatiquement</a> comme
CSS l’est dans shadow DOM. C’est-à-dire que, s’il y avait un autre bouton
<code>[aria-pressed]</code> dans la page parente situé avant l’exemple de ce composant,
alors <code>document.querySelector</code> ciblerait plutôt celui-là.</p>
<p>Ce dont j'ai besoin c'est d’un équivalent de <code>document</code> qui se limite à la
sous-arborescence de l’élément <code>demo</code>. C’est possible à faire, mais de manière
assez verbeuse :</p>
<pre><code class="language-js hljs javascript"><span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"demo-{{ $uniq }}"</span>).shadowRoot;</code></pre>
<p>Je n'avais pas envie de devoir écrire cette expression à chaque fois que je
devais cibler des éléments dans les conteneurs de démo. J'en suis donc venu à
écrire un hack dans lequel j'assigne cette expression à une variable <code>demo</code>
locale et à des scripts préfixés fournis via le <em>shortcode</em> avec cette
assignation :</p>
<pre><code class="language-js hljs javascript"><span class="hljs-keyword">if</span> (script) {
  script.textContent = <span class="hljs-string">`(function() { var demo = document.getElementById(\'demo-{{ $uniq }}\').shadowRoot; <span class="hljs-subst">${
    script.textContent
  }</span> })()`</span>;
}
root.shadowRoot.appendChild(<span class="hljs-built_in">document</span>.importNode(template.content, <span class="hljs-literal">true</span>));</code></pre>
<p>Grâce à cela, <code>demo</code> devient l’équivalent de <code>document</code> pour n'importe quel
composant de la sous-arborescence et je peux utiliser <code>demo.querySelector</code> pour
cibler facilement mon bouton interrupteur :</p>
<pre><code class="language-html hljs xml">var toggle = demo.querySelector('[aria-pressed]');</code></pre>
<p>Remarquez que j'ai entouré le contenu du script de la démo avec une fonction
immédiatement exécutée (IIFE) de manière à ce que la variable <code>demo</code> — et toutes
les variables traitées et utilisées pour le composant — n'appartiennent pas au
périmètre global. Comme ça <code>demo</code> peut être utilisé dans n'importe quel script
présent dans un <em>shortcode</em> mais se réfèrera uniquement au <em>shortcode</em> en cours.</p>
<p>Lorsque ECMAScript6 est disponible, il est possible de parvenir à localiser la
portée à l’aide du
<a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/block" target="_blank" rel="noopener noreferrer">"block scoping"</a>
, en entourant les déclarations <code>let</code> ou <code>const</code> de simples accolades.
Toutefois, toutes les autres définitions de variables à l’intérieur du block
seraient obligées d’utiliser également <code>let</code> et <code>const</code> (et d’éviter <code>var</code>).</p>
<pre><code class="language-js hljs javascript">{
  <span class="hljs-keyword">let</span> demo = <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"demo-{{ $uniq }}"</span>).shadowRoot;
  <span class="hljs-comment">// Author script injected here</span>
}</code></pre>
<h4 id="support-de-shadow-dom">Support de Shadow DOM</h4>
<p>Bien tout ce qui précède n'est possible que si la version 1 de Shadow DOM est
supportée. Tout a l’air de bien marcher dans Chrome, Safari, Opera et Android,
mais c'est plus problématique avec Firefox ou les navigateurs de Microsoft. On
peut toujours détecter le support de cette fonctionnalité et fournir un message
d’erreur lorsque <code>attachShadow</code> n'est pas disponible :</p>
<pre><code class="language-js hljs javascript"><span class="hljs-keyword">if</span> (<span class="hljs-built_in">document</span>.head.attachShadow) {
  <span class="hljs-comment">// Do shadow DOM stuff here</span>
} <span class="hljs-keyword">else</span> {
  root.innerHTML =
    <span class="hljs-string">"L'affichage des démos encapsulées demande le support de Shadow DOM. Ce n'est pas le code de la démo en lui-même qui pose problème au navigateur."</span>;
}</code></pre>
<p>Ou alors vous pouvez inclure Shady DOM et l’extension Shady CSS, ce qui veut
dire ajouter une dépendance non négligeable (+60KB) et une API différente. Rob
Dodson a été assez gentil pour me fournir une
<a href="https://gist.github.com/robdodson/287030402bad4b496a0361314138f0f9" target="_blank" rel="noopener noreferrer">démo basique</a>,
que je suis ravi de vous partager pour vous aider à vous lancer.</p>
<h3 id="des-legendes-pour-les-composants">Des légendes pour les composants</h3>
<p>Maintenant que notre petite démo de base fonctionne, écrire des démos et les
insérer dans la documentation est super simple, pour notre plus grand bonheur.
Cela permet de nous poser des questions comme : "Et si je veux ajouter une
légende pour identifier la démo ?" C’est parfaitement faisable puisque — comme
nous l’avons déjà vu auparavant — le Markdown permet d’embarquer du code HTML.</p>
<pre><code class="language-go-html-template hljs go">&lt;figure role=<span class="hljs-string">"group"</span> aria-labelledby=<span class="hljs-string">"legende-bouton"</span>&gt;
    {{&lt;/* demo */&gt;}}
    &lt;button&gt;Mon bouton&lt;/button&gt;
    &lt;style&gt;
    button {
        background: blue;
        padding: <span class="hljs-number">0.5</span>rem <span class="hljs-number">1</span>rem;
        text-transform: uppercase;
    }
    &lt;/style&gt;
    {{&lt;/* /demo */&gt;}}
    &lt;figcaption id=<span class="hljs-string">"legende-bouton"</span>&gt;Un bouton standard&lt;/figcaption&gt;
&lt;/figure&gt;</code></pre>
<p>Toutefois, la seule nouveauté apportée par cette modification est la formulation
de la légende. Il serait préférable de fournir une interface de saisie simple,
pour faire gagner du temps à mon futur moi — et à tous ceux qui utiliseront le
<em>shortcode</em> — et minimiser par la même occasion les possibles erreurs de saisie.
C’est faisable en fournir un paramètre nommé au <em>shortcode</em> — ici simplement
appelé <code>legende</code> :</p>
<pre><code class="language-go-html-template hljs go">{{&lt;/* demo legende=<span class="hljs-string">"Un bouton standard"</span> */&gt;}}
    … le contenu de la démo …
{{&lt;/* /demo */&gt;}}</code></pre>
<p>Il peut ensuite assez simple d’accéder au paramètre nommé dans le modèle en
utilisant <code>{{ .Get "legende" }}</code>. Je peux rentre optionnel l’insertion des
balises <code>&lt;figure&gt;</code> et <code>&lt;figcaption&gt;</code> en insérant une condition, comme ça je
n'affiche la légende que si elle est passée en argument du <em>shortcode</em> :</p>
<pre><code class="language-go-html-template hljs go">{{ <span class="hljs-keyword">if</span> .Get <span class="hljs-string">"legende"</span> }}
    &lt;figcaption&gt;{{ .Get <span class="hljs-string">"legende"</span> }}&lt;/figcaption&gt;
{{ end }}</code></pre>
<p>Voici maintenant à quoi ressemble notre modèle <code>demo.html</code> (le code n'est pas
très élégant, mais ça fait le job) :</p>
<pre><code class="language-go-html-template hljs go">{{ $uniq := .Inner | htmlEscape | base64Encode | truncate <span class="hljs-number">15</span> <span class="hljs-string">""</span> }}
{{ <span class="hljs-keyword">if</span> .Get <span class="hljs-string">"legende"</span> }}
&lt;figure role=<span class="hljs-string">"group"</span> aria-labelledby=<span class="hljs-string">"legende-{{ $uniq }}"</span>&gt;
{{ end }}
    &lt;div class=<span class="hljs-string">"demo"</span> id=<span class="hljs-string">"demo-{{ $uniq }}"</span>&gt;&lt;/div&gt;
    {{ <span class="hljs-keyword">if</span> .Get <span class="hljs-string">"legende"</span> }}
        &lt;figcaption id=<span class="hljs-string">"legende-{{ $uniq }}"</span>&gt;{{ .Get <span class="hljs-string">"legende"</span> }}&lt;/figcaption&gt;
    {{ end }}
{{ <span class="hljs-keyword">if</span> .Get <span class="hljs-string">"legende"</span> }}
&lt;/figure&gt;
{{ end }}
&lt;template id=<span class="hljs-string">"template-{{ $uniq }}"</span>&gt;
    {{ .Inner }}
&lt;/template&gt;
&lt;script&gt;
  (function() {
      <span class="hljs-keyword">var</span> root = document.getElementById(<span class="hljs-string">'demo-{{ $uniq }}'</span>);
      root.attachShadow({mode: <span class="hljs-string">'open'</span>});
      <span class="hljs-keyword">var</span> template = document.getElementById(<span class="hljs-string">'template-{{ $uniq }}'</span>);
      <span class="hljs-keyword">var</span> script = template.content.querySelector(<span class="hljs-string">'script'</span>);
      <span class="hljs-keyword">if</span> (script) {
          script.textContent = <span class="hljs-string">`(function() { var demo = document.getElementById(\'demo-{{ $uniq }}\').shadowRoot; ${script.textContent} })()`</span>
       }
       root.shadowRoot.appendChild(document.importNode(template.content, <span class="hljs-literal">true</span>));
  })();
&lt;/script&gt;</code></pre>
<p>Une dernière remarque : si jamais je veux autoriser la syntaxe Markdown dans la
légende, je peux la faire passer dans la fonction <code>markdownify</code> d’Hugo. De cette
manière l’auteur peut s'il le souhaite insérer du Markdown (et du HTML).</p>
<pre><code class="language-go-html-template hljs go">{{ .Get <span class="hljs-string">"legende"</span> | markdownify }}</code></pre>
<h3 id="conclusion">Conclusion</h3>
<p>De par ses performances et son lot de super fonctionnalités, je trouve qu'Hugo
est parfait pour la génération de site statique. Et l’insertion de <em>shortcodes</em>
est ce que je préfère. Ici j'ai été capable de répondre à un besoin pour de la
documentation, que j'avais depuis un bon moment.</p>
<p>De même qu'avec les web components, une bonne partie de la complexité du
balisage (souvent pour rendre le code accessible) peut être masquée à
l’utilisateur grâce aux <em>shortcodes</em>. Ici je pense notamment à l’inclusion de
<code>role="group"</code> et de la relation <code>aria-labelledby</code>, qui fournit un meilleur
support pour "group label" à la balise <code>&lt;figure&gt;</code> — le genre de choses que
personne ne prend plaisir à coder plus d’une fois, surtout quand il faut créer
des valeurs d’attribut uniques pour chaque instance.</p>
<p>Je crois que les <em>shortcodes</em> d’Hugo sont à Markdown et au contenu ce que les
web composants sont à HTML et à la fonctionnalité : une manière de rendre
l’écriture plus facile, plus sûre et plus consistante. Il me tarde de voir
comment ce curieux petit coin du Web va évoluer.</p>
<h4 id="ressources">Ressources</h4>
<ul>
<li><a href="https://gohugo.io/overview/introduction/" target="_blank" rel="noopener noreferrer">La documentation d’Hugo</a></li>
<li><a href="https://golang.org/pkg/text/template/" target="_blank" rel="noopener noreferrer">Package Template du langage Go</a></li>
<li><a href="https://gohugo.io/extras/shortcodes" target="_blank" rel="noopener noreferrer">Les <em>shortcodes</em> d’Hugo</a></li>
<li><a href="https://developer.mozilla.org/en/docs/Web/CSS/all" target="_blank" rel="noopener noreferrer">all (propriété CSS)</a>, Mozilla Developer Network</li>
<li><a href="https://developer.mozilla.org/en-US/docs/Web/CSS/initial" target="_blank" rel="noopener noreferrer">initial (CSS)</a>, sur le Mozilla Developer Network</li>
<li><a href="https://developers.google.com/web/fundamentals/getting-started/primers/shadowdom" target="_blank" rel="noopener noreferrer">Shadow DOM v1 : Self-Contained Web Components</a>, Eric Bidelman, Web Fundamentals, Google Developers</li>
<li><a href="https://www.webcomponents.org/community/articles/introduction-to-template-element" target="_blank" rel="noopener noreferrer">Introduction à l’élément template</a> Eiji Kitamura, WebComponents.org</li>
<li><a href="https://jekyllrb.com/docs/includes/" target="_blank" rel="noopener noreferrer">Les includes de Jekyll</a></li>
</ul>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://snipcart.com/blog/hugo-tutorial-static-site-ecommerce</id>
    <title>Un site ecommerce statique (très performant) avec Hugo</title>
    <published>2017-08-28T12:00:00+00:00</published>
    <link href="https://snipcart.com/blog/hugo-tutorial-static-site-ecommerce" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>C’est fou tout ce qu'on peut faire avec un générateur de site, des APis et du JavaScript. Et rien de mieux qu'un exemple parlant de mise en place d’une boutique de e-commerce pour illustrer les possibilités qui vous sont offertes.<br>
Dans cet exemple nous utiliserons le service <a href="https://snipcart.com/" target="_blank" rel="noopener noreferrer">Snipcart</a> pour la gestion du panier d’achat et <a href="https://gohugo.io/" target="_blank" rel="noopener noreferrer">Hugo</a> pour générer le site à la vitesse de l’éclair.</p></aside>
<blockquote>
<p>Pressé ? Passez directement au <a href="#tutoriel">tutoriel</a> ou <a href="#demo-repo">à la démo et au code dispo sur GitHub</a>.</p>
</blockquote>
<p>Il est temps de nous plonger à nouveau dans le monde en perpétuel mouvement de la <a href="https://frank.taillandier.me/2016/05/21/la-jamstack/" target="_blank" rel="noopener noreferrer">Jamstack</a> et du développement web statique. Nos articles précédents sur la gestion d’un site e-commerce avec des générateurs de site statique comme <a href="https://snipcart.com/blog/static-site-e-commerce-integrating-snipcart-with-middleman" target="_blank" rel="noopener noreferrer">Middleman</a> et <a href="https://snipcart.com/blog/static-site-e-commerce-part-2-integrating-snipcart-with-jekyll" target="_blank" rel="noopener noreferrer">Jekyll</a> ont eu pas mal de succès, alors pourquoi s'arrêter en si bon chemin ?</p>
<p>Mesdames et messieurs, aujourd’hui nous allons une fois de plus vous montrer combien il est facile de configurer la partie e-commerce sur des sites statiques. Et cette fois, nous allons le faire en vous proposant un tutoriel complet pour <a href="https://gohugo.io/" target="_blank" rel="noopener noreferrer">Hugo</a>.</p>
<p>Dans ce tutoriel nous verrons :</p>
<ol>
<li>Comment générer votre site statique avec le générateur de site Hugo,</li>
<li>Comment y intégrer ensuite facilement le panier d’achat de la plate-forme
Snipcart,</li>
<li>Comment déployer votre site e-commerce sur Netlify.</li>
</ol>
<p>Mais d’abord un petit mot sur l’outil central que nous allons utiliser pour faire ceci.</p>
<h2 id="hugo-un-generateur-de-site-statique-super-rapide">Hugo : un générateur de site statique super rapide</h2>
<p>Le nom <strong>Hugo</strong> peut véhiculer différents sens selon les personnes. Les grands lecteurs penseront à l’auteur légendaire des Misérables. Les cinéphiles penseront au petit garçon dans le film de Scorcese de 2011. Mais si vous êtes un <strong>développeur</strong> (il y a de grandes chances que ce soit le cas si vous lisez ces lignes), ça devrait plutôt vous évoquer ceci : un moteur de site statique moderne et <strong>rapide comme l’éclair</strong>.</p>
<p>Écrit en <a href="https://golang.org/" target="_blank" rel="noopener noreferrer">Go</a> par Steve Francia alias <a href="https://twitter.com/spf13" target="_blank" rel="noopener noreferrer"><strong>spf13</strong></a> et Bjørn Erik Pedersen alias <a href="https://github.com/bep" target="_blank" rel="noopener noreferrer"><strong>Bep</strong></a>, Hugo se révèle être, d’après notre expérience, une des manières les plus efficaces de générer, de gérer et de mettre à jour des sites statiques modernes. Il s'installe facilement sur toutes les plate-formes, de plus vous pouvez l’héberger n'importe où — nous vous
recommandons <a href="https://www.netlify.com/blog/2016/09/21/a-step-by-step-guide-victor-hugo-on-netlify/" target="_blank" rel="noopener noreferrer">Netlify</a> comme nous le verrons tout à l’heure. Et les temps de génération sont imbattables — environ ~1 ms par page. Si vous aimez la performance web comme nous, vous allez à n'en pas douter adorer ce générateur de site en Go.</p>
<p>Aujourd’hui, nous allons voir comment utiliser Snipcart et Hugo pour réaliser une boutique en ligne Star Trek sur un site statique. Pourquoi Star Trek me direz-vous ? Parce que <a href="https://snipcart.com/blog/integrating-snipcart-with-kirby-cms-to-enable-e-commerce" target="_blank" rel="noopener noreferrer">nous l’avons déjà fait pour Star Wars</a>.</p>
<blockquote>
<p><em>Psst</em> : Si vous vous demandez encore ce que sont les générateurs de site
statique et pourquoi il faut vous y intéresser, jetez-vous sur
<a href="https://davidwalsh.name/introduction-static-site-generators" target="_blank" rel="noopener noreferrer">l’intro d’Eduardo Bouças</a>.</p>
</blockquote>
<h2 id="tutoriel-hugo-site-produits-modeles-et-deploiement">Tutoriel Hugo : site, produits, modèles et déploiement</h2>
<h3 id="1-installer-hugo-et-generer-votre-nouveau-site-web-statique">1. Installer Hugo et générer votre nouveau site web statique</h3>
<p>Nous allons commencer par installer le générateur sur votre ordinateur et créer un nouveau site web. Cela vous prendra peut-être 10 minutes en suivant la <a href="https://gohugo.io/getting-started/quick-start/" target="_blank" rel="noopener noreferrer">documentation pour démarrer avec Hugo</a>, ou juste <strong>2 minutes</strong> si vous êtes aussi rapide que Dan Hersam.</p>
<p>Une fois que vous avez téléchargé <a href="https://github.com/spf13/hugo/releases" target="_blank" rel="noopener noreferrer">Hugo sur GitHub</a>, l’installation est très rapide, comme vous montre la <a href="https://gohugo.io/getting-started/installing#quick-install" target="_blank" rel="noopener noreferrer">documentation</a>.<br>
Concentrons-nous donc sur la création du nouveau site à l’aide d’Hugo.</p>
<p>Nous n'avons qu'à utiliser la ligne de commande prévue à cet effet :</p>
<pre><code class="language-sh hljs bash">hugo new site snipcart-hugo</code></pre>
<h4 id="architecture">Architecture</h4>
<p>Cette commande va générer un squelette de base pour votre projet. Votre répertoire devrait ressembler à ça :</p>
<pre><code class="language-sh hljs bash">│   config.toml
│
├───archetypes
├───content
├───data
├───layouts
├───static
└───themes</code></pre>
<p>Les options de configuration se trouvent dans le fichier <code>config.toml</code>. Nous n'aurons pas à nous plonger trop dedans vu que nous allons nous contenter de faire au plus simple pour ce qui est du site.</p>
<p>Pas la peine de nous plonger dans les <a href="https://gohugo.io/documentation/" target="_blank" rel="noopener noreferrer">rouages internes d’Hugo</a> ici.</p>
<p>En gros, dans ce tutorial, nous allons créer des fichiers dans le répertoire <code>data</code> qui a pour but de stocker des données additionnelles qui peuvent être utilisées lors de la génération du site.</p>
<p>Nous allons aussi ajouter quelques modèles dans le dossier <code>layouts</code>, l’endroit où les modèles Hugo sont stockés par défaut.</p>
<p>Le dossier <code>static</code> peut être utilisé pour stocker des fichiers de type CSS, JavaScript ou bien encore des images. Dans notre démo, nous ajouterons un dossier <code>images</code> qui contiendra les images des produits.</p>
<p>Naturellement, il est préférable que vous soyez déjà un peu familiarisé avec la documentation d’Hugo avant de vous attaquer à l’intégration complète de Snipcart.</p>
<h4 id="les-themes">Les thèmes</h4>
<p>Nous avons décidé de ne pas installer de thème particulier pour cette démo (nous utiliserons un framework CSS pour mettre en forme notre site plus tard), mais il existe plusieurs thèmes open source à disposition.<br>
<a href="https://code.tutsplus.com/tutorials/make-creating-websites-fun-again-with-hugo-the-static-website-generator-written-in-go--cms-27319" target="_blank" rel="noopener noreferrer">Cet article</a> montre comment installer des thèmes pour votre site Hugo, peut-être voudrez-vous y jeter un œil. Il explique aussi plus en détail la création basique de site avec Hugo (Hello World, Blog, Galerie Photo, etc.).</p>
<p>Vous pouvez aussi aller parcourir l’annuaire officiel de <a href="https://themes.gohugo.io/" target="_blank" rel="noopener noreferrer">quelques-uns des meilleurs thèmes pour Hugo</a>.</p>
<h3 id="2-creer-un-fichier-json-statique-pour-les-produits-de-notre-boutique">2. Créer un fichier JSON statique pour les produits de notre boutique</h3>
<p>OK, passons donc à la configuration de nos produits : un dictionnaire Klingon et un pistolet laser. Nous <strong>aurions pu</strong> utiliser un CMS headless ou statique pour cette partie (comme nous l’avons déjà <a href="https://www.siteleaf.com/blog/jamstack-ecommerce/" target="_blank" rel="noopener noreferrer">fait</a>
<a href="https://www.contentful.com/blog/2016/02/10/snipcart-middleman-contentful/" target="_blank" rel="noopener noreferrer">auparavant</a>).</p>
<p>Vu le modeste objectif de cet article, nous allons simplement créer un fichier <code>.json</code> statique pour référencer nos produits.</p>
<p>Hugo propose une super fonction appelée <code>getJSON</code> qui est bien utile lorsque vos données proviennent d’un CMS headless ou de n'importe quelle API qui retourne du JSON. Ici, comme notre fichier JSON est directement stocké dans le dossier <code>data</code>, nous aurions pu nous contenter d’utiliser <code>.Site.Data.Products</code> à place, mais nous voulions vous monter qu'il était possible d’interagir avec des APIs externes.</p>
<p>Nous allons devoir ajouter un nouveau fichier <code>products.json</code> dans le dossier <code>data</code>.</p>
<pre><code class="language-json hljs json">[
  {
    <span class="hljs-attr">"id"</span>: <span class="hljs-string">"1"</span>,
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Dictionnaire Klingon"</span>,
    <span class="hljs-attr">"price"</span>: <span class="hljs-number">34.87</span>,
    <span class="hljs-attr">"image"</span>: <span class="hljs-string">"/images/dictionary.jpg"</span>,
    <span class="hljs-attr">"description"</span>: <span class="hljs-string">"nIvbogh tlhIngan dictionary qaStaHvIS veng SuvwI'"</span>,
    <span class="hljs-attr">"url"</span>: <span class="hljs-string">"http://snipcart-hugo.netlify.com"</span>
  },
  {
    <span class="hljs-attr">"id"</span>: <span class="hljs-string">"2"</span>,
    <span class="hljs-attr">"name"</span>: <span class="hljs-string">"Phaser du Captain Kirk"</span>,
    <span class="hljs-attr">"description"</span>: <span class="hljs-string">"The Original Series Phaser comprises a small, hand-held Type I Phaser, which slots into a larger Type II Phaser body with a removable pistol-grip."</span>,
    <span class="hljs-attr">"price"</span>: <span class="hljs-number">145.98</span>,
    <span class="hljs-attr">"image"</span>: <span class="hljs-string">"/images/phaser.png"</span>,
    <span class="hljs-attr">"url"</span>: <span class="hljs-string">"http://snipcart-hugo.netlify.com"</span>
  }
]</code></pre>
<h3 id="3-generation-des-modeles-pour-hugo">3. Génération des modèles pour Hugo</h3>
<p>La prochaine étape consiste à configurer les différents modèles pour notre site. Le plus important est le modèle d’en-tête où nous ajouterons <a href="https://docs.snipcart.com/getting-started/installation" target="_blank" rel="noopener noreferrer">les dépendances pour Snipcart</a>.</p>
<p>Nous allons aussi créer un modèle principal dans lequel nous bouclerons sur nos produits pour en afficher une courte description et où nous ajouterons un bouton Snipcart "Ajouter au panier".</p>
<aside class="note note-info"><p><strong>Remarque</strong> : les produits Snipcart sont définis directement dans le code HTML à l’aide de simples attributs data.<br>
<a href="https://docs.snipcart.com/configuration/product-definition" target="_blank" rel="noopener noreferrer">Plus de détails ici</a>.</p></aside>
<p>Dans le répertoire <code>layouts</code> nous allons ajouter un nouveau modèle <strong>index.html</strong>. Ce fichier sera celui utilisé par défaut et sera le premier à être généré par Hugo.</p>
<h4 id="layouts-index-html">layouts/index.html</h4>
<pre><code class="language-go-html-template hljs go">{{ partial <span class="hljs-string">"header.html"</span> . }}

{{ $products := getJSON <span class="hljs-string">"/data/products.json"</span> }}

&lt;section class=<span class="hljs-string">"container"</span>&gt;
    &lt;div class=<span class="hljs-string">"row"</span>&gt;
        {{ <span class="hljs-keyword">range</span> $products }}
            {{ partial <span class="hljs-string">"product.html"</span> . }}
        {{ end }}
    &lt;/div&gt;
&lt;/section&gt;

{{ partial <span class="hljs-string">"footer.html"</span> }}</code></pre>
<p>Nous avons mentionné la méthode <code>getJSON</code> un peu plus haut, nous allons l’utiliser dans notre modèle de page <code>index.html</code>.</p>
<p>Nous allons récupérer les produits depuis le fichier JSON que nous avons créé un peu plus tôt, puis nous allons boucler sur chaque produit pour appeler le fichier de modèle partiel <code>product.html</code> qui va être chargé du rendu.</p>
<p>Comme vous pouvez voir, nous importons aussi les fichiers <strong>header.html</strong>, <strong>footer.html</strong> et <strong>product.html</strong>. Nous verrons ce qu'ils contiennent en détail.</p>
<p>Avant d’aller plus loin, allons d’abord dans le répertoire <code>layouts</code> et créons un dossier <code>partials</code>. Si les fichiers partiels ne se trouvent pas dans ce dossier, Hugo ne sera pas capable de les trouver lorsque nous les déclarerons à l’aide de la syntaxe <code>{{ partial … }}</code>. L'autre chose importante à savoir est pourquoi nous avons mis un point <code>.</code> après <code>product.html</code>. Cela signifie que nous incluons les données du produit courant dans le modèle <code>product.html</code>.</p>
<h4 id="layouts-partials-header-html">layouts/partials/header.html</h4>
<p>Comme nous vous l’avons déjà dit, ce fichier est le plus important. C’est un simple fichier d’entête HTML qui va appeler les dépendances pour Snipcart.<br>
Ajoutez-le dans le dossier <code>layouts/partials</code>.</p>
<pre><code class="language-html hljs xml"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.w3.org/1999/xhtml"</span> <span class="hljs-attr">xml:lang</span>=<span class="hljs-string">"en"</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en-us"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">http-equiv</span>=<span class="hljs-string">"content-type"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"text/html; charset=utf-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span>
      <span class="hljs-attr">name</span>=<span class="hljs-string">"viewport"</span>
      <span class="hljs-attr">content</span>=<span class="hljs-string">"width=device-width, initial-scale=1.0, maximum-scale=1"</span>
    /&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Intégration de Snipcart dans Hugo!<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">link</span>
      <span class="hljs-attr">id</span>=<span class="hljs-string">"snipcart-theme"</span>
      <span class="hljs-attr">type</span>=<span class="hljs-string">"text/css"</span>
      <span class="hljs-attr">href</span>=<span class="hljs-string">"https://cdn.snipcart.com/themes/2.0/base/snipcart.min.css"</span>
      <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span>
    /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span>
      <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span>
      <span class="hljs-attr">href</span>=<span class="hljs-string">"https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.0/css/materialize.min.css"</span>
    /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">link</span>
      <span class="hljs-attr">href</span>=<span class="hljs-string">"https://fonts.googleapis.com/icon?family=Material+Icons"</span>
      <span class="hljs-attr">rel</span>=<span class="hljs-string">"stylesheet"</span>
    /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>

  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">nav</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"nav-wrapper"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"brand-logo"</span>&gt;</span>Star Trek shop<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">ul</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"nav-mobile"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"right hide-on-med-and-down"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">li</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"snipcart-summary"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"snipcart-checkout"</span>&gt;</span>
                View cart (<span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"snipcart-total-items"</span>&gt;</span>0<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>)
              <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">li</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">ul</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">nav</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></code></pre>
<p>Nous avons choisi d’utiliser le framework <a href="http://materializecss.com" target="_blank" rel="noopener noreferrer">MaterializeCSS</a> pour cette démo, mais vous pouvez bien entendu utiliser celui de votre choix. Celui-ci est assez simple à intégrer et fournit suffisamment de composants pour mettre en place quelque chose de pas trop mal.</p>
<p>Vous pouvez également voir que les fichiers requis par Snipcart sont appelés dans ce fichier et que nous avons ajouté un <a href="https://docs.snipcart.com/getting-started/the-cart#adding-a-cart-summary" target="_blank" rel="noopener noreferrer">raccourci vers le panier d’achat</a> pour que les clients puissent accéder à leur commande en cours.</p>
<p>Parfait ! Prochaine étape : le modèle partiel de pied de page pour terminer la structure de base de notre fichier HTML.</p>
<h4 id="layouts-partials-footer-html">layouts/partials/footer.html</h4>
<pre><code class="language-html hljs xml">        <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">footer</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"page-footer"</span>&gt;</span>
                <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"footer-copyright"</span>&gt;</span>
                    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"container"</span>&gt;</span>
                        Snipcart integration with Hugo
                    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
                <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
            <span class="hljs-tag">&lt;/<span class="hljs-name">footer</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/javascript"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">script</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text/javascript"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"snipcart"</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://cdn.snipcart.com/scripts/2.0/snipcart.js"</span> <span class="hljs-attr">data-api-key</span>=<span class="hljs-string">"M2E5YjA3NjMtYzRiYS00YzVjLWEyYWYtNDY5ZDI0OWZhYjg5"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>

        <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="actionscript">
            Snipcart.execute(<span class="hljs-string">'registerLocale'</span>, <span class="hljs-string">'en'</span>, {
                powered_by:
                <span class="hljs-string">"HoS 'ej pong ngaQ "</span>
            });
        </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></code></pre>
<p>Enfin, nous allons devoir générer le modèle qui va afficher le détail d’un produit. Appelons le <strong>product.html</strong>.</p>
<h4 id="layouts-partials-product-html">layouts/partials/product.html</h4>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"col s6"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"header"</span>&gt;</span>{{ .name }}<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card horizontal"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-image"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"{{ .image }}"</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-stacked"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-content"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>{{ .description }}<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"card-action"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span>
          <span class="hljs-attr">class</span>=<span class="hljs-string">"snipcart-add-item waves-effect waves-light btn"</span>
          <span class="hljs-attr">data-item-id</span>=<span class="hljs-string">"{{ .id }}"</span>
          <span class="hljs-attr">data-item-name</span>=<span class="hljs-string">"{{ .name }}"</span>
          <span class="hljs-attr">data-item-price</span>=<span class="hljs-string">"{{ .price }}"</span>
          <span class="hljs-attr">data-item-url</span>=<span class="hljs-string">"{{ .url }}"</span>
        &gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">i</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"material-icons right"</span>&gt;</span>shopping_cart<span class="hljs-tag">&lt;/<span class="hljs-name">i</span>&gt;</span>
          Add to cart
        <span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span></code></pre>
<p>Puisque nous passons le produit en cours dans notre modèle <strong>index.html</strong>, nous pouvons maintenant accéder à tous les champs des données de notre fichier <code>JSON</code>. Ici, je les utilise pour renseigner les champs nécessaires pour le bouton d’achat Snipcart et pour ajouter le titre et la description du produit.</p>
<p>Il est temps de lancer Hugo et de regarder à quoi ressemble ce site fantaisiste !</p>
<pre><code class="language-sh hljs bash">hugo server</code></pre>
<p>(Je garde ma capture d’écran de notre boutique Star Trek pour la fin, tenez-vous prêts)</p>
<h3 id="4-configuration-du-deploiement-d-hugo-sur-netlify">4. Configuration du déploiement d’Hugo sur Netlify</h3>
<p>Dernier point et non des moindres : héberger tout ça !</p>
<p>Nous avons choisi de déployer notre démo avec Hugo à l’aide de l’extraordinaire service de nos amis de chez <a href="https://netlify.com" target="_blank" rel="noopener noreferrer">Netlify</a>.</p>
<p>Avant de toucher à quoi que ce soit dans Netlify, je vous suggère de créer un fichier <code>.gitkeep</code> dans votre dossier <code>content</code>. Ce dossier est requis par Netlify pour générer le site. Et comme nous n'avons pas déposé de fichiers dedans, Git ne va pas le prendre en compte.</p>
<p>Une fois le fichier <code>.gitkeep</code> ajouté, vous pouvez utiliser l’interface de Netlify pour déployer facilement votre site en quelques secondes. Voici un aperçu de la configuration du déploiement de notre boutique Star Trek Old School.</p>
<p>Netlify va récupérer automatiquement le code depuis GitHub et déployer votre site web. Et voilà !</p>
<h2 id="demo-de-site-hugo-et-depot-github">Démo de site Hugo et dépôt GitHub</h2>
<p>Bon, il est temps de vous monter notre chef-d’œuvre.</p>
<p>Alors c'est classe non ? Maintenant allez voir le site et le code source par vous-même :</p>
<ul>
<li>Voir <a href="http://snipcart-hugo.netlify.com" target="_blank" rel="noopener noreferrer">Demo Snipcart + Hugo</a></li>
<li>Voir <a href="https://github.com/snipcart/snipcart-hugo-integration" target="_blank" rel="noopener noreferrer">Dépot GitHub</a></li>
</ul>
<h2 id="conclusion-et-ressources-supplementaires">Conclusion et ressources supplémentaires</h2>
<p>Bon, je crois que notre travail est terminé mes amis.</p>
<p>Au cas où vous vous demanderiez si le résultat final est assez rapide, vous pouvez utiliser un autre outil assez cool de Netlify : <a href="https://testmysite.io" target="_blank" rel="noopener noreferrer">Testmysite.io</a>. Notre démo obtient un score de 87/100, c'est pas si mal.</p>
<p>Au fait, si vous développez un site Jamstack pour un client, vous voudrez peut-être effectuer un suivi de sa performance à l’aide de <a href="https://speedtracker.org/" target="_blank" rel="noopener noreferrer">Speedtracker, un outil open source</a>. Les équipes
techniques seront peut-être intéressées par <a href="https://www.keybits.net/post/publishing-workflow-for-teams-using-static-site-generators/" target="_blank" rel="noopener noreferrer">ce workflow de publication pour Hugo</a>.</p>
<h3 id="pour-les-clients">Pour les clients</h3>
<p>Vous pensez que devoir gérer des fichiers de contenu statiques en Markdown, ça ne va pas être possible pour vos clients ? Il existe des outils bien pratiques pour gérer le contenu d’un site Hugo. Nous vous invitons à ajouter un des CMS statiques suivants :</p>
<ul>
<li><a href="https://forestry.io/" target="_blank" rel="noopener noreferrer">Forestry.io</a></li>
<li><a href="https://www.datocms.com/" target="_blank" rel="noopener noreferrer">DatoCMS</a></li>
<li><a href="https://www.netlifycms.org/" target="_blank" rel="noopener noreferrer">Netlify CMS</a></li>
<li><a href="https://appernetic.io/" target="_blank" rel="noopener noreferrer">Appernatic</a></li>
</ul>
<p>Pour une revue plus détaillée des outils à destination des clients, des limites et des bénéfices, reportez-vous à ce <a href="https://snipcart.com/blog/jamstack-clients-static-site-cms" target="_blank" rel="noopener noreferrer">guide complet</a>.</p>
<p>Hugo est vraiment plaisant à utiliser. Sa documentation est à jour et sa vitesse quasi-instantanée a le don de faire sourire l’ingénieur en moi à chaque fois que je génère mon site. Mettre en place ce site Hugo avec Snipcart m'a pris environ deux heures en tout, en comptant la mise en forme du site avec MaterializeCSS et le déploiement sur Netlify.</p>
<p>C’est toujours agréable de voir à quel point un service de panier d’achat en HTML/JS comme Snipcart s'intègre parfaitement avec des générateurs statiques modernes. 😀</p>
<p>Il est maintenant temps d’arrêter de lire ce blog et d’aller fabriquer quelque chose de génial.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2017/07/11/interview-kyle-matthews-gatsby/</id>
    <title>Questions à Kyle Mathews, créateur de Gatsby un générateur pour les sites basés sur React</title>
    <published>2017-07-11T08:00:00+00:00</published>
    <link href="https://jamstatic.fr/2017/07/11/interview-kyle-matthews-gatsby/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Après deux ans de développement, Gatsby vient de passer en version 1.0. Ce générateur intègre beaucoup d’outils notamment React et GraphQL et permet déjà d’interagir avec les versions headless des CMS WordPress et Drupal. InfoQ vient de publier une interview de son créateur, Kyle Mathews, plus motivé que jamais pour continuer à faire évoluer ce générateur, qui devrait connaître une popularité grandissante. Nous en publions ici la retranscription en français, car nous pensons que Gatsby devrait séduire la communauté JavaScript et devenir un des outils phares pour développer des applications Web très performantes.</p></aside>
<p>Après avoir travaillé pour différentes startups, Kyle Mathews a démissionné pour se consacrer à l’un de ses projets personnels <a href="https://www.bricolage.io/gatsby-open-source-work/" target="_blank" rel="noopener noreferrer">à temps plein</a>. Ce projet, <a href="https://www.gatsbyjs.org/" target="_blank" rel="noopener noreferrer">Gatsby</a>, est né de sa volonté de créer un site web qui lui évite d’avoir à utiliser autre chose que ReactJS.</p>
<p>Entre temps, Gatsby est passé en version 1.0 et s'est étoffé d’un large éventail d’outils comme un système de plugins, une couche de données traitée lors du build à l’aide de GraphQL, le support des Progressive Web Apps (PWA). Gatsby inclut également un utilitaire en ligne de commande et un processus de build préconfiguré basé sur Babel et Webpack.</p>
<p>Pour illustrer à quel point Gatsby est rapide, Mathews a écrit <a href="https://www.gatsbyjs.org/blog/gatsbygram-case-study/" target="_blank" rel="noopener noreferrer">un clone d’Instagram</a> conçu pour démontrer l’utilisation du <a href="https://developers.google.com/web/fundamentals/performance/prpl-pattern/" target="_blank" rel="noopener noreferrer">pattern PRPL</a> de Google afin d’obtenir le plus tôt possible un affichage des pixels à l’écran.</p>
<p>Cette vitesse est en partie dû au fait qu'il crée "un rendu HTML statique de chaque page pour que l’affichage initial soit aussi rapide que possible. À côte de ça, le téléchargement en arrière-plan réalise le gros du travail", <a href="https://www.reddit.com/r/javascript/comments/6locuu/announcing_gatsby_100/djwxqyq/" target="_blank" rel="noopener noreferrer">explique</a> Mathews.</p>
<blockquote>
<p>Gatsby est bien plus rapide puisque qu'il télécharge en arrière-plan les ressources et les transitions du côté client. Beaucoup de gens ont fait la remarque que cliquer sur des liens sur un site fait avec Gatsby, c'est comme naviguer sur un site en local</p>
</blockquote>
<p>Dans une interview à InfoQ, Mathews parle des motivations pour lesquelles il développe Gastby et de son avenir.</p>
<h3 id="quels-problemes-gatsby-essaie-t-il-de-resoudre">Quels problèmes Gatsby essaie-t-il de résoudre ?</h3>
<p>Gatsby essaie de résoudre la problématique de ce à quoi un
framework de site web devrait ressembler en 2017. La plupart des positions
adoptées par les frameworks web datent des premières générations du Web. Bien
que ce soit d’excellents frameworks matures, ils ne sont pas conçus pour la
majorité du web d’aujourd’hui, dominé par des milliards de personnes qui
accèdent au web avec des smartphones bon marché sur des réseaux peu fiables.</p>
<p>Pour qu'un site web soit rapide sur un smartphone, il doit rester assez
indépendant du serveur, être capable de pré-extraire du code et des données et
d’effectuer le rendu du contenu côté client.</p>
<p>Les smartphones et les navigateurs sont largement assez rapides pour proposer de
bonnes expériences de navigation sur le web — nous sommes juste ralentis par de
vieux frameworks qui assument des connexions filaires, rapides et obligent les
petits super-ordinateurs qui se trouvent dans notre proche d’attendre après des
réseaux cellulaires peu fiables.</p>
<p>Gatsby intègre assez d’intelligence pour s'assurer que les sites se chargent
rapidement et que naviguer sur un site soit visiblement rapide quel que soit
l’état du réseau.</p>
<p>Le design adaptatif a été une première étape importante pour le web mobile mais
nous devons absolument aller vers un modèle d’architecture où les sites sont
rendus côté client et téléchargent le contenu de manière intelligente.</p>
<h3 id="comment-se-positionne-gatsby-par-rapport-aux-autres-generateurs-de-sites-statiques-qu-ils-soient-bases-sur-react-ou-non">Comment se positionne Gatsby par rapport aux autres générateurs de sites statiques, qu'ils soient basés sur React ou non ?</h3>
<p>Il hérite de tous les bénéfices des générateurs de site
statique traditionnels, à savoir une super performance, une sécurité accrue, un
coût de montée en charge moindre ainsi qu'une meilleure expérience de
développement (une migration de bases de données ça vous rappelle quelque chose
?). La v1 de Gatsby marque une nouvelle étape pour les générateurs de site
statique, en permettant l’intégration de CMS comme Contentful, WordPress et
Drupal, et en embarquant tout un tas de fonctionnalités, activées par défaut,
qui rendent votre site rapide dès le début — découpage du code en fonction du
chemin demandé, Service Workers, le support du hors-ligne et bien plus.</p>
<p>Tous les autres générateurs de site statique ne font pas grand-chose pour aider
les développeurs à travailler de manière moderne avec CSS et JS. Ils se
contentent de compiler (généralement du Markdown) en HTML et vous laisse le soin
de configurer votre processus de génération d’assets vous-même. Gatsby prend
tout ça en charge par défaut pour vous permettre de développer des sites web
sophistiqués à l’aide des derniers outils et des dernières techniques.</p>
<h3 id="quelle-longevite-peut-on-esperer-pour-gatsby">Quelle longévité peut-on espérer pour Gatsby ?</h3>
<p>Gatsby connait un bel essor et est déjà le 4e générateur de
site statique après seulement deux ans. Il y a déjà quelques sites très visibles
qui ont été lancés ou qui sont en cours de développement avec Gatsby. Nous avons
récemment dépassé les 200 contributeurs au niveau du cœur et les 500 000
téléchargements pour Gatsby. Pour que Gastby continue dans la durée, il va
falloir trouver un modèle économique rentable pour soutenir le développement du
cœur de Gatsby et une solide communauté open-source qui développe et maintienne
des modèles et des intégrations de sources de données.</p>
<h3 id="que-prevoyez-vous-ensuite">Que prévoyez-vous ensuite ?</h3>
<p>Mon objectif personnel est de travailler sur comment financer
le développement du cœur de Gatsby. La priorité principale du projet est de
proposer de plus en plus d’intégration avec des APIs, des CMS et des bases de
données pour rendre triviale la migration de sites existants ou de les refaire
avec un framework web moderne. Gatsby est déjà capable de faire tourner en
production des sites sophistiqués et très rapides. La prochaine case à cocher de
la liste sera de s'assurer de rendre aussi trivial l’import de données dans
Gatsby — où qu'elles se trouvent actuellement.</p>
<p>Bien que Gatsby ait bientôt deux ans, le projet en est encore à ses débuts et
beaucoup de parties utiles vont bientôt faire leur arrivée. Le système de
plugins devrait permettre à la communauté de combler les manques et il en existe
déjà plus d’une trentaine qui permettent d’utiliser des technologies comme Sass,
Typescript ou Preact.</p>
<p>Pour en savoir plus, rendez-vous sur <a href="https://github.com/gatsbyjs/gatsby" target="_blank" rel="noopener noreferrer">le dépôt GitHub</a> ou <a href="https://www.gatsbyjs.org/" target="_blank" rel="noopener noreferrer">le site de Gatsby</a>.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2017/07/05/vincent-voyer-algolia/</id>
    <title>Questions à Vincent Voyer, ingénieur fullstack chez Algolia</title>
    <published>2017-07-05T10:34:00+00:00</published>
    <link href="https://jamstatic.fr/2017/07/05/vincent-voyer-algolia/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Sur un site généré, l’interactivité est reléguée côté client, au navigateur,
donc à JavaScript. Si vous souhaitez intégrer une partie dynamique sur votre
site <em>statique</em>, il faudra donc faire appel à une API externe. C’est tout le
concept de la <a href="/2017/03/16/5-raisons-de-tester-la-jamstack/">JAMStack</a>
dont nous parlons sur ce blog. Et c'est typiquement le cas si vous souhaitez
intégrer un moteur de recherche sur votre site généré. Heureusement pour nous,
en ce qui concerne la recherche, <a href="https://www.algolia.com/" target="_blank" rel="noopener noreferrer">Algolia</a> propose une
API et des bibliothèques qui vont faciliter l’intégration d’une recherche de
qualité.<br>
Avec une <a href="https://www.algolia.com/pricing" target="_blank" rel="noopener noreferrer">formule community gratuite</a> qui
autorise 10 000 enregistrements et 100 000 opérations par mois, c'est
une solution intéressante à tester pour des sites statiques à trafic modeste. En
proposant une personnalisation et une expérience utilisateur poussée, on peut
dire qu'Algolia est ce qui se fait de mieux en la matière.<br>
Nous sommes donc allés interroger Vincent Voyer, ingénieur JavaScript fullStack
chez Algolia pour qu'il nous en dise un peu plus sur ce service en pleine
croissance qui a réussi en l’espace de quelque temps à conquérir la communauté
des développeurs web.</p></aside>
<p>Frank : <strong>Bonjour Vincent, on va peut-être commencer par te présenter ?</strong></p>
<p><strong>Vincent Voyer</strong> : Donc moi je suis Vincent Voyer, sur Twitter j'étais zeroload
maintenant je suis <a href="https://twitter.com/vvoyer" target="_blank" rel="noopener noreferrer">vvoyer</a>, je suis développeur
JavaScript. Pendant un moment j'ai fait expert performance web, j'ai travaillé
chez Fasterize dont j'étais cofondateur, j'ai travaillé chez Mappy, j'ai fait du
conseil en développement freelance pour lemonde.fr, France Télévision…
Aujourd’hui je suis chez Algolia où je suis toujours développeur JavaScript. Je
travaille principalement sur des bibliothèques open source qui permettent
d’intégrer Algolia sur tous les sites web.</p>
<p>J'aime le basket, j'essaie d’inviter des co-workers toutes les semaines, c'est
cool, ce soir on y va, on est trois plus le reste de l’équipe. J'ai 32 ans, une
femme et un enfant. Voilà.</p>
<p>Frank : <strong>T'as l’air de bien t'éclater, qu'est-ce qui te plaît chez Algolia ?</strong></p>
<p><strong>Vincent Voyer</strong> : J'ai une petite anecdote à ce sujet. Quand j'étais
développeur freelance, je suis allé à DotJS, il y avait un stand Algolia, je
suis simplement passé devant. Après il y avait une conférence d’un gars qui
s'appelle <a href="https://twitter.com/Substack" target="_blank" rel="noopener noreferrer">Substack</a>, James Halliday, un gros
développeur JavaScript de la communauté npm, qui disait que la recherche sur le
site de npm n'était pas super, quand on met deux mots ça fait un ET et pas un
OU, il n'y a pas de tolérance aux fautes de frappe, c'est pas super.</p>
<p>Je repasse devant le stand Algolia et là il y avait une énergie, il y avait
Alexandre Colin, qui était Solution Engineer à cette époque-là, qui présentait
Algolia. Ils me disent qu'ils recrutent. Je me dis je pourrais faire un petit
test avec Algolia pour faire un moteur de recherche de paquets npm.</p>
<p>Du coup j'ai fait ça, en fin d’année 2014, un petit test de recherche de paquets
npm : j'ai pris 300 000 paquets npm, j’ai créé un index de recherche
Algolia avec. J'ai commencé à faire les premières requêtes, ça prenait 4
millisecondes.</p>
<p>J'étais capable de faire une recherche dans un champ de recherche, d’exécuter
une requête, élaborée à l’aide du framework Express et d’obtenir des résultats
en temps réel en 4ms en incluant le temps de latence réseau, etc. 4ms, 10ms, je
me suis dit : wouah c'est quand même incroyable !</p>
<p>Voilà, ma première rencontre avec Algolia c'était ça. Et très vite après j'ai
été en contact avec eux, car je voulais refaire leur client JavaScript, qui ne
me plaisait pas en tant que développeur JavaScript…</p>
<p>Frank : <strong>Comment définirais-tu l’activité d’Algolia ?</strong></p>
<p><strong>Vincent Voyer</strong> : <a href="https://www.algolia.com/doc/guides/getting-started/what-is-algolia/" target="_blank" rel="noopener noreferrer">Algolia</a> c'est un moteur de recherche en Saas (Software as a Service), en gros c'est une
API qui permet de créer un index de recherche pour son propre site web. Par
exemple si vous avez un site web e-commerce avec 100 000 produits. Pour
faire votre moteur de recherche : soit vous le faites vous-même, ou vous prenez
ElasticSearch, etc. Du coup, vous devez gérer l’infrastructure, etc. Soit vous
optez pour une solution Saas comme Algolia, qui vous permet de mettre tous vos
produits dans un index de recherche et qui vous expose une API pour ajouter ou
modifier des produits mais aussi pour chercher vos produits.</p>
<p>Aujourd’hui les gros clients d’Algolia dont on peut parler c'est Stripe, Strava,
8tracks, CrunchBase, Periscope, Twitch, etc. Donc c'est des gros noms, ça marche
plutôt bien. Algolia existe depuis cinq ans maintenant.</p>
<p>Frank : <strong>Pourquoi est-ce que le moteur de recherche d’Algolia est aussi rapide aujourd’hui ?</strong></p>
<p><strong>Vincent Voyer</strong> : C’est parce qu'au début c'était un SDK pour développeurs
d’applications mobiles, pour les aider à mettre un champ de recherche dans leur
application mobile sur les données des utilisateurs. Et comme il fallait que ce
SDK tourne sur tous les types de mobiles, que ça soit un vieux smartphone Nokia
ou le dernier iPhone, du coup tout le code était optimisé pour que ce soit
vraiment rapide à tout moment, donc optimisé à la milliseconde près.</p>
<p>Ce produit-là n'a pas fonctionné et quand ils ont fait Ycombinator, un
incubateur de startups aux États-Unis, ils leur ont dit : "C’est pas ça qu'il
faut faire en fait, c'est une API Web". Ils ont fait ça et ça a fonctionné et
aujourd’hui c'est notre principal produit, l’API et le moteur de recherche
d’Algolia.</p>
<p>Frank : <strong>Google ne s'est jamais trop positionné sur les moteurs de recherche internes, d’ailleurs ils ont annoncé arrêter leur produit Google Site Search. Votre positionnement c'est de vous cantonner à la recherche sur site ?</strong></p>
<p><strong>Vincent Voyer</strong> : Oui c'est ça. Généralement votre site de e-commerce, il a
une base de données avec des produits structurés. Beaucoup de gens passent par
Google pour rechercher des produits. Ils vont chercher par exemple "FNAC +
iPhone" sur Google. Le truc c'est qu'ils vont arriver sur le site de la FNAC et
si ça se trouve, ils vont vouloir faire une nouvelle recherche. Donc là sur le
site de la FNAC il y a un champ recherche et ce champ de recherche là il faut
qu'il soit beau. Ce que propose Google c'est une recherche dans l’Internet au
global, mais ils ne proposent pas d’implémenter un bon moteur de recherche pour
son site web. En fait ils le proposaient, ça s'appelait
<a href="https://enterprise.google.com/search/products/gss.html" target="_blank" rel="noopener noreferrer">Google Site Search</a>,
ils proposaient aux éditeurs de site web de mettre un champ de recherche avec
des pages de résultats dédiées au site en cours, c'est pas les résultats dans le
monde. Mais c'est pas suffisant.</p>
<p>Nous ce qu'on a voulu proposer c'est qu'on sait qu'un site de e-commerce il a
une base de données avec des objets structurés et on s'est rendu compte que pour
fournir une bonne expérience de recherche, avec de la tolérance à la faute de
frappe, avec du filtrage par type d’objet, il fallait pouvoir connaître la
structure d’un objet. Il faut savoir qu'une page produit, elle a une catégorie,
pour pouvoir proposer aux gens de filtrer sur cette catégorie-là. Du coup Google
aujourd’hui il ne sait pas faire ça, il essaie de déterminer en fonction de ses
algorithmes ce dont parle une page, mais dans les résultats Google on va pas
pouvoir dire : "je veux tous les iPhone", on va pouvoir l’exprimer avec une
requête, mais s'il le faut, cette requête elle va nous renvoyer des iPhone, des
coques d’iPhone, des livres sur l’iPhone, etc. Nous ce qu'on propose c'est des
objets structurés qui permettent ensuite aux développeurs de site web de créer
des menus, des sliders pour filtrer, etc.</p>
<p>Frank : <strong>La recherche à facettes ça existe déjà sur les sites de e-commerce, même s'il est vrai que l’expérience de recherche n'est pas toujours géniale. Je me rappelle que quand j'ai découvert <a href="https://community.algolia.com/magento/" target="_blank" rel="noopener noreferrer">Algolia pour Magento</a>, j'étais content de voir arriver un nouvel acteur qui décide de proposer une expérience de recherche au top</strong></p>
<p><strong>Vincent Voyer</strong> : Les gens s'attendent aujourd’hui à avoir la meilleure
expérience de recherche, qui est celle de Google. Et Google ils ont fait un truc
qui s'appelle <em>Instant Search</em>, qui affiche des propositions au fur et à mesure
qu'on tape notre requête. Donc déjà tous les moteurs de recherche ont dû se
baser sur ça et proposer le <em>Search as you Type</em>. C’est super important car ça
permet à la personne de ne pas avoir à taper "iPhone" puis de taper sur la
touche Entrée, se rendre compte qu'elle veut changer, revenir en arrière, etc.
Là c'est au fur et à mesure, c'est naturel. Ça, ça vient aussi du fait que ça a
été développé sur mobile à la base, car c'est encore plus compliqué de faire des
workflows de navigation sur mobile, du coup sur mobile il faut que ce soit le
plus simple possible. L'autre paramètre qui a été pris en compte c'est la
tolérance aux fautes de frappe.</p>
<p>Toutes ces choses-là, il y a des moteurs de recherche qui les font, mais chez
Algolia on s'est rendu compte qu'il n'y en avait aucun qui remplissait tous les
critères : une bonne performance, la gestion des fautes de frappe, du faceting
avancé et des résultats personnalisés, à savoir la capacité à dire : j'ai une
liste d’iPhone, mais je veux en faire remonter certains dans les résultats. Par
exemple sur la requête "iPhone" je vais vouloir favoriser certains résultats.
Par exemple je ne veux pas que ce soit les coques d’iPhone qui apparaissent en
premier, je veux que ce soit les iPhone. Nous on a par exemple un <em>custom
ranking</em> qui fait que ce sont les produits les plus consultés qui vont remonter
de façon naturelle. Au fur et à mesure que les gens vont sur la page iPhone, on
va mettre l’index de recherche à jour et l’affichage des résultats va être
naturel par rapport à ce que les gens demandent.</p>
<p>Algolia propose donc toutes ces fonctionnalités, performance, typo tolérance,
faceting et recherche avancés, que ne proposent pas forcément les autres moteurs
de recherche.</p>
<p>Frank : <strong>OK, revenons un peu au statique. Vous êtes présents sur beaucoup de sites de documentation ou de <a href="https://yarnpkg.com/lang/fr/" target="_blank" rel="noopener noreferrer">recherche de paquets comme yarn</a>. Ces sites sont générés à l’aide de différents outils. Comment avez-vous implémenté tout ça ?</strong></p>
<p><strong>Vincent Voyer</strong> : La stratégie d’Algolia c'est d’avoir une super bonne
<em>Developer Experience</em> et d’essayer de capter un maximum de développeurs sur
notre plate-forme forcément, pour que tout le monde soit au courant qu'on est un
super moteur de recherche. Sachant que nous-mêmes nous sommes développeurs, et
que nos outils c'est <code>npm</code>, c'est <code>yarn</code>, ça va être Hacker News, les différents
frameworks PHP comme Laravel ou Symfony, React pour le monde JavaScript. Tout ça
ce sont nos outils de tous les jours.</p>
<p>Et on a fait un jour le constat que sur tous ces sites-là, c'est souvent
difficile de trouver l’information qu'on cherche. Donc on a fait un projet qui
s'appelle <a href="https://community.algolia.com/docsearch/" target="_blank" rel="noopener noreferrer">DocSearch</a>, qui est en gros
un crawler de documentation pour les développeurs. DocSearch fournit aujourd’hui
de la recherche pour les développeurs sur plus de 300 sites web. Donc quand vous
cherchez sur le site web de React, de Flow, de Symfony, de Laravel, en fait
c'est Algolia qui est derrière. On l’affiche, c'est pour ça qu'on rend ce
service gratuitement, c'est parce que derrière ça va faire parler de nous et ça
nous permet à nous en interne d’être plus efficaces.</p>
<p>En faisant cela, nous avons pollinisé d’autres communautés et plein d’autres
sites ce sont dit : "Hé, je veux la même recherche, c'est super !"</p>
<p>Donc on a construit au fur et à mesure ces différents projets.</p>
<p>Frank: <strong>Comment ça fonctionne ?</strong></p>
<p><strong>Vincent Voyer</strong> : DocSearch c'est un crawler de site web avec des
configurations par site. Pour chaque site on va dire : le nom d’une méthode dans
l’API, c'est dans cette balise HTML, etc. Ça ressemble un petit peu à ce que
fait Google, mais là c'est fait de façon contrôlée, c'est pas un algorithme
générique, on cible exactement ce à quoi on veut que ressemble la documentation.</p>
<p>Donc on a fait ça, c'était un des premiers gros trucs pour la communauté des
développeurs, et à côté de ça on a aussi tout ce qui est outillage qu'on fournit
aux développeurs qui utilisent Algolia.</p>
<p>Ce dont on a envie c'est qu'à aucun moment ils se disent "Algolia ça a l’air
d’être un super moteur de recherche, mais c'est juste une API, y’a rien
derrière, du coup je suis obligé de tout construire. Si j'utilise Symfony, il va
falloir que je développe mon propre connecteur Symfony-Algolia, si j'utilise
Jekyll pareil, si j'utilise React il va falloir que je fasse mes propres
composants qui vont faire la liaison entre des composants React et la recherche
sur Algolia."</p>
<p>Du coup c'est ce qu'on a fait au fur et à mesure, pour tous les langages de
programmation les plus utilisés. On a 14 clients d’API je crois (JavaScript,
Ruby, Python, Go, etc.).</p>
<p>Ce qui permet à chaque développeur de ne pas avoir à implémenter son propre
client d’API qui se base sur notre API. On les a vraiment créés pour eux. La
plupart des boîtes s'arrêtent à trois et ils se disent pour les autres "c'est la
communauté qui les fera à un certain moment". Ça marche ou ça ne marche pas, la
qualité est là ou pas. Nous on les a faits pour tout le monde, tout est open
source donc les gens participent.</p>
<p>Et après on est allé encore plus loin, car les clients d’API c'est assez bas
niveau, sachant que sur un site web ce que je veux c'est une page de recherche
avec un champ recherche, avec une navigation, un slider sur le prix, etc.</p>
<p>Donc on a créé des bibliothèques plus haut niveau qui permettent d’exprimer ça
pour le développeur. Il est capable à l’aide de widgets de dire "là je mets un
champ de recherche, là je mets un sélecteur sur les genres de films, là je mets
un slider sur l’année de sortie du film", ce genre de choses.</p>
<p>Tout ça il peut l’exprimer à l’aide
d’<a href="https://community.algolia.com/instantsearch.js/v2/" target="_blank" rel="noopener noreferrer">InstantSearch</a>, qui est
un projet qui englobe des sous-projets. InstantSearch c'est un ensemble de
bibliothèques haut-niveau pour implémenter de la recherche avec Algolia.</p>
<p>Frank: <strong>C’est complémentaire avec DocSearch dont tu parlais tout à l’heure ?</strong></p>
<p><strong>Vincent Voyer</strong> : Non, ce sont deux produits différents, on a plein de
produits qu'on essaie de regrouper au fur et à mesure. InstantSearch c'est
vraiment pour les clients d’Algolia généralement qui vont vouloir implémenter de
la recherche sur leur application React ou VueJS par exemple, de façon plus
simple que de devoir faire des appels à l’API d’Algolia qui est très puissante,
certes, mais elle est puissante parce qu'elle est modulaire, donc nous on cache
un peu cette modularité pour répondre à la plupart des cas d’utilisation qu'ont
les gens, tout en laissant la porte ouverte pour des trucs un petit peu plus
avancés qui seront faits avec des paramètres différents.</p>
<p>InstantSearch c'est disponible pour du Vanilla JavaScript, C’est-à-dire sans
framework, pour React, pour VueJS bientôt, pour iOS, etc.</p>
<p>Pour React c'est intéressant, on est capable de construire notre recherche
complète, web et application native, et même bientôt server-side rendering avec
une seule bibliothèque, sans avoir à mettre beaucoup de code entre les
différents éléments de connexion. Pour l’utilisateur c'est super simplifié.</p>
<p>Frank: <strong>Ça inclut React Native ?</strong></p>
<p><strong>Vincent Voyer</strong> : Oui, même si on n'a pas encore des widgets tout faits pour
React Native, on ne les a que pour le web, par contre on est capable de
facilement abstraire la création d’un menu de genre de films par exemple, si
c'est un site de recherche de films.</p>
<p>Tout ça pour en venir où ? Tu parlais des générateurs de site statique. Tous ces
sites-là, InstantSearch pour React, InstantSearch Vanilla, InstantSearch VueJS,
etc. ils ont des sites dédiés et ces sites-là on essaie de les faire du mieux
possible, donc y’a une documentation complète, des guides, des tutoriels, un
petit peu comme le site de documentation de React ou de Laravel, c'est vraiment
un site complet où la personne va pouvoir y passer du temps et progresser au fur
et à mesure dans sa connaissance du produit.</p>
<p>Ces sites-là au fil du temps on les a fait évoluer. On est parti de
<a href="https://jekyllrb.com/" target="_blank" rel="noopener noreferrer">Jekyll</a>, parce que le site d’Algolia c'est une appli
Ruby on Rails, du coup il y avait déjà des connaissances en Ruby. Au bout d’un
moment, on est arrivé aux limites de Jekyll et beaucoup de nos développeurs
JavaScript voulaient travailler avec un outil en JavaScript. On a aussi essayé
<a href="https://middlemanapp.com" target="_blank" rel="noopener noreferrer">Middleman</a>. Donc on a des sites en Jekyll, en
Middleman, aujourd’hui on utilise <a href="http://www.metalsmith.io/" target="_blank" rel="noopener noreferrer">MetalSmith</a>, qui
est un générateur assez bas niveau, très modulaire, qui nous a permis de créer
différents petits modules pour sites statiques, par exemple extraire
automatiquement depuis le code la <a href="http://usejsdoc.org/" target="_blank" rel="noopener noreferrer">JSDoc</a>, des pages
descriptives. Pour chaque widget InstantSearch par exemple c'est le cas : on
prend la JSDoc avec des exemples, on transforme tout ça en une page Markdown et
HTML qui décrit le widget. Idem pour d’autres parties de l’API, idem pour les
guides.</p>
<p>Donc aujourd’hui on utilise MetalSmith, et ça nous va bien, le seul truc c'est
qu'on a pas encore fait un module qui abstrait tous les sites web, parce qu'en
fait chaque site web est peu du copier-coller aujourd’hui, donc on aimerait bien
faire un truc qui nous permette de partager plus de code entre les différents
sites web. Ça nous plait bien parce qu'à chaque fois qu'on a un besoin pour
l’écrire c'est assez simple. Le flow de MetalSmith, il prend un répertoire
<code>source</code> et ensuite il applique des middleware qui font transformer les fichiers
de ce répertoire <code>source</code>, donc on peut faire vraiment ce qu'on veut à partir du
moment où on sait écrire du JavaScript, c'est assez cool.</p>
<p>Frank : <strong>Aujourd’hui dans l’écosystème JS, beaucoup utilisent Hexo comme générateur, on voit aussi apparaître des générateurs qui embarquent React comme Gatsby ou Phenomic. Du coup on pourrait imaginer pouvoir avoir des composants Algolia. Vous seriez intéressés par des solutions aussi packagées ?</strong></p>
<p><strong>Vincent Voyer</strong> : Nous on fait des sites super custom où on modifie vraiment
beaucoup la source, on n'a pas pris ces solutions là, on a peut-être loupé
quelque chose, mais avec MetalSmith on est bien aujourd’hui. Mais les gens
poussent pour essayer d’autres choses, donc on essaiera peut-être autre chose. À
partir du moment où l’outil nous facilite la vie. Il y a clairement des choses
avec MetalSmith qui sont difficiles à faire, par exemple faire du bon
live-reload, c'est à nous de le faire. Ce qui est possible aussi c'est qu'avec
notre expérience avec MetalSmith, on se fasse nous-mêmes notre propre générateur
de site statique pour Algolia.</p>
<p>Frank : <strong>Dans le monde des sites statiques, on a beaucoup parlé de <a href="/2017/03/17/smashing-mag-va-dix-fois-plus-vite/">la refonte de Smashing Magazine</a> dernièrement dont la recherche a été développée avec Algolia, ça marche très bien. Le site est généré avec Hugo, un générateur écrit en Go, or, il n'y a pas de plugin natif Algolia pour Hugo, même si vous fournissez un client pour votre API en Go</strong></p>
<p><strong>Vincent Voyer</strong> : Oui, on a des gens spécialisés en Go ici. En fait c'est
vraiment en fonction des besoins, certaines personnes vont parfois venir sur
notre GitHub pour nous dire "j'aimerais bien avoir un client d’API pour Rust",
et là on va se dire OK on va le faire. On fait vraiment comme ça, Après parfois
on propose, mais seulement si nous en avons l’utilité en interne, parce que
sinon on ne saura pas si le plugin est vraiment bien fait vu qu'on l’utilisera
pas.</p>
<p>Le plugin Jekyll on l’a fait parce qu'on l’utilisait, le premier site
InstantSearchJS, on utilisait le plugin
<a href="https://github.com/algolia/algoliasearch-jekyll" target="_blank" rel="noopener noreferrer">algoliasearch-jekyll</a> pour
transférer les données de Jekyll vers Algolia. Après on a fait DocSearch, car
tous ces sites web-là, comme ils ont plein de générateurs de site statique
différents, nous on propose un niveau d’abstraction supplémentaire et on crawle
à partir du HTML. Ce qui fonctionne aussi très bien et nous permet de ne pas
avoir à faire de plugins pour chaque générateur. Maintenant s'il y a quelqu'un
qui veut un plugin Hugo ou Phenomic, du coup il faudra venir nous en parler et
on pourra faire.</p>
<p>Frank : <strong>Pour info Hugo et Gatsby sont les deux générateurs qui ont la cote en ce moment, ce sont deux projets très actifs</strong></p>
<p><strong>Vincent Voyer</strong> : OK, je me le note.</p>
<p>Frank : <strong>Algolia a levé 53 millions de dollars récemment, vous recrutez pas mal de gens, c'est quoi les prochaines priorités pour vous ?</strong></p>
<p><strong>Vincent Voyer</strong> : Notre ambition est d’être un acteur principal du monde de la
recherche, aujourd’hui il y a ElasticSearch qui est aussi un énorme acteur. Et
le scope d’Algolia n'est pas terminé. Beaucoup de clients nous challengent sur
des choses qu'Algolia ne sait pas encore faire. Tous nos clients e-commerce
veulent généralement avoir un contrôle plus précis sur la gestion des résultats,
des promotions, etc.</p>
<p>Aujourd’hui on n'a pas vraiment beaucoup d’outils pour faire ça, Algolia c'est
vraiment un outil orienté pour les développeurs, donc c'est quelque chose sur
lequel on travaille à fond, le fait de répondre encore plus finement aux besoins
des sites e-commerce et aux gens qui veulent faire du business sur les sites de
e-commerce, qui veulent avoir des outils de contrôle des résultats de recherche.</p>
<p>Donc on fait des évolutions sur les APIs pour permettre aux développeurs de
construire des outils plus orientés business et e-commerce.</p>
<p>Sur tout ce qui est bibliothèque, InstantSearch c'est vraiment notre
sous-produit phare de création d’interface de recherche, on en fait une vraie
marque. Le mot InstantSearch regroupe plein de sous-projets et ces projets on
les fait évoluer et on en créé des nouveaux.</p>
<p>Je t'ai parlé de VueJS, parce que c'est un framework qui est vraiment beaucoup
utilisé, on va avoir potentiellement Angular s'il y a un énorme besoin Angular.
Toujours dans l’optique de dire qu'il faut que l’expérience de développement
soit la meilleure.</p>
<p>Sur une roadmap plus précise des évolutions d’Algolia, c'est assez compliqué, je
suis pas forcément le plus à même de t'en parler. Moi je travaille beaucoup sur
InstantSearch par exemple, mais oui le moteur de recherche d’Algolia évolue
énormément et va continuer d’évoluer énormément en fonction des besoins de nos
clients qui nous challengent beaucoup dessus.</p>
<p>On travaille aussi beaucoup sur les datasets. Aujourd’hui Algolia sait bien
répondre à des jeux de plusieurs millions de données, qu'est-ce qui se passe
quand on arrive à des centaines de millions ou à des milliards de données ? Ça
c'est un gros challenge pour la plupart des moteurs de recherche et les bases de
données donc on est en train de travailler sur ça. C’est important pour pouvoir
passer de 100 millions d’objets — ce qui est déjà énorme — à 800 millions ou 1
milliard.</p>
<p>Frank : <strong>Toujours sur des données structurées ? Si j'ai des données non structurées, est-ce qu'Algolia peut faire quelque chose ?</strong></p>
<p><strong>Vincent Voyer</strong> : On travaille sur des outils qui vous nous permettre d’aller
chercher des clients qui n'ont pas forcément des données structurées, mais dans
tous les cas, ce qu'on fait c'est que ces outils-là vont permettre de
transformer ce qui n'est pas structuré en données structurées. Ça peut être un
outillage en PHP pour pouvoir extraire, depuis une page HTML ou un article de
blog sous WordPress par exemple, de la donnée structurée pour la faire rentrer
dans le moteur d’Algolia, donc scinder en paragraphes, en titres, etc. Donc ça
ce sont des choses qu'on fait parce qu'effectivement, tout le monde n'a pas
forcément une base de données structurée et pourtant ils ont quand même besoin
d’un bon moteur de recherche. Mais au final on est convaincu qu'on n'ira pas
vers ce que fait Google où on a des mots-clés en surbrillance dans les pages au
complet. On essaiera de toujours extraire de la donnée structurée d’un site web,
car il y a toujours de la structure. Un article de blog, une page qui liste des
fonctionnalités, tout ça a une structure, c'est simplement qu'il faut la
trouver.</p>
<p>Frank : <strong>Et pour les données enfermées dans des tableurs, des fichiers PDF ?</strong></p>
<p><strong>Vincent Voyer</strong> : Même chose, ce sont des outils qui vont être capables
d’aller chercher de la donnée et de l’indexer pour Algolia.</p>
<p>Dans les sujets qui nous intéressent, il y a aussi la recherche vocale. Moi-même
je fais beaucoup de choses avec Siri sur mon téléphone, j'envoie des textos, je
lance des recherches, etc. Et ça c'est quelque chose qu'on va devoir supporter,
prévoir des fonctionnalités qui permettront aux gens de faire des recherches
avec la voix.</p>
<p>On a des projets qui tournent avec Alexa (la voix d’Amazon Echo). On peut
développer des <em>skillsets</em> qui vont permettre de dire à un client dans son
application Alexa de faire une recherche sur son site plus facilement. Nous,
notre responsabilité là-dedans, ça va être de se connecter de la bonne façon
dans le moteur Alexa, définir comment on va scinder la phrase de l’utilisateur
et comment ça, ça va être appliqué à la recherche.</p>
<p><strong>Frank</strong> : Merci Vincent, merci pour toutes ces informations sur Algolia. C’est
vraiment un chouette produit qui on l’a compris permet de fournir une recherche
vraiment pertinente pour l’utilisateur. Pour en savoir plus rendez-vous sur
<a href="https://algolia.com" target="_blank" rel="noopener noreferrer">Algolia.com</a>.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2017/06/07/migration-de-jekyll-a-hugo/</id>
    <title>Passer de Jekyll+Github Pages à Hugo+Netlify</title>
    <published>2017-06-07T00:00:00+00:00</published>
    <link href="https://jamstatic.fr/2017/06/07/migration-de-jekyll-a-hugo/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Si vous faites du développement front-end, du CSS, du SVG et
autres joyeusetés, vous connaissez sans doute déjà la talentueuse
<a href="https://www.sarasoueidan.com/" target="_blank" rel="noopener noreferrer">Sara Soueidan</a>. Il se trouve que Sara a travaillé
récemment sur la refonte de Smashing Magazine et <a href="/2017/03/17/smashing-mag-va-dix-fois-plus-vite/">la migration de WordPress à
Hugo</a>. Cette mission lui
a permis de se familiariser avec Hugo et de découvrir au passage le service
offert par <a href="https://www.netlify.com/" target="_blank" rel="noopener noreferrer">Netlify</a>, la nouvelle référence en termes
d’hébergement d’applications statiques. Fatiguée des faibles temps de
compilation proposés par Jekyll (dus en grande partie à la lenteur de Kramdown,
le parseur Markdown utilisé, et à ses traitements d’expressions régulières),
Sara en a profité pour s'attaquer à la migration de son site perso. Comme
beaucoup d’autres, elle a été immédiatement séduite par les performances
proposées par Hugo, le générateur statique ultra-rapide et ultra-souple écrit en
Go. Elle nous livre ici en détails le récit de cette migration qu'elle est bien
contente d’avoir menée à bien. Puisse le partage de son périple vous épargner de
subir les mêmes écueils et vous aider à commencer à vous familiariser avec les
concepts d’Hugo.</p></aside>
<p>Ces derniers mois, travailler sur mon site web s'est révélé être de plus en plus
pénible, que ce soit pour continuer à le développer, itérer sur son design,
écrire un article de blog ou mettre à jour mes pages conférences et ateliers.
C'était dû en partie à <a href="https://jekyllrb.com/" target="_blank" rel="noopener noreferrer">Jekyll</a>, le générateur de site
statique que j'utilisais alors. Le vent du changement commençait à souffler.</p>
<p>Jekyll était devenu incroyablement lent et chaque changement entraîne une
recompilation… C'était devenu tellement lent qu'<strong>attendre que la compilation du
site soit terminée est devenue une vraie torture, tellement chronophage qu'il
fallait que je m'en débarrasse à tout prix</strong>.</p>
<p>On pourrait croire que j'exagère, mais je vous promets que non. Jekyll est
devenu beaucoup trop lent. "Trop lent" est en réalité un euphémisme.
Dernièrement chaque fois que je modifiais une propriété CSS ou que j'effectuais
une modification du code HTML <strong>je devais attendre jusqu'à cinq minutes pour que
le changement soit pris en compte et compilé par Jekyll</strong>. Encore une fois je
n'exagère <em>pas</em>. Jekyll se figeait littéralement. Il fallait que je l’arrête en
faisant CTRL-C pour débloquer la situation et que je le relance pour que les
changements soient pris en compte et qu'il finisse par compiler. Et si
j'effectuais beaucoup de changements d’affilée, le ventilateur de mon Macbook
commençait à s'emballer comme un fou, l’ordinateur chauffait et faisait le bruit
d’un avion sur le point de décoller. <sup id="fnref1:1"><a href="#fn:1" class="footnote-ref">1</a></sup></p>
<p>Je dirais que mon site est de taille modeste. J'ai moins d’une centaine de
billets de blog, même moins de 60 à l’heure où j'écris cet article, et seulement
quelques pages statiques. Je ne me repose pas beaucoup sur JavaScript. En fait,
j'ai à peine besoin d’utiliser la moindre ligne de JavaScript. Et pourtant,
Jekyll avait du mal à chaque fois qu'il devait compiler.</p>
<p>Oui, j'ai utilisé des options comme <code>--incremental</code> et toutes celles que l’on
m'a recommandées pour accélérer le processus de compilation. Sans le moindre
résultat.</p>
<p>J'ai même du mal à souligner à quel point cela a empiré ces douze derniers mois.
Je ressentais littéralement une montée d’hormones de stress dans mon flux
sanguin à chaque fois que j'imaginais que je devais apporter un changement à mon
site Web. Je savais que j'allais vivre un enfer en faisant cela.</p>
<p>Mais je savais que cela ne pourrait durer éternellement. Je savais que je
devrais laisser tomber Jekyll et migrer à un moment donné vers un nouveau
générateur. Je n'ai simplement jamais eu le temps de le faire. Pour être
honnête, je n'ai jamais vraiment <em>pris</em> le temps de le faire, car à chaque fois
que j'avais du temps de libre je voulais profiter au maximum de ce temps en
restant <em>éloignée</em> de mon ordinateur. Mon site n'était simplement pas une
priorité, surtout que j'étais encore indécise sur l’alternative à utiliser. Du
coup j'ai continué de le laisser en l’état.</p>
<p>Mais dernièrement, sachant que j'avais quelques semaines de libres pour faire ce
que je voulais et comme j'ai commencé à avoir plein d’idées pour mon blog et que
je voulais vraiment qu'elles voient le jour, comme mettre en place une lettre
d’information, modifier le design, améliorer le code (un travail toujours en
cours), ajouter un nouveau type de contenu (c'est pour bientôt) et quelques
autres idées, j'ai finalement réussi à m'y mettre et à le faire, car je
<em>voulais</em> que mes idées prennent forme et écrire quelques billets de blog.
<strong>Mais d’abord j'avais besoin de pouvoir de nouveau prendre du plaisir à
travailler sur mon site web</strong>. Du coup je me suis dit : <em>"Ça suffit, je vais
devoir m'y mettre pour de bon pendant quelques jours cette semaine et dédier du
temps à passer à un nouveau générateur de site statique"</em>. Je savais que c'était
un investissement personnel nécessaire et extrêmement utile que je devais me
résoudre à faire. Je m'y suis investie et je l’ai fait. (C’est vraiment la
manière la plus efficace d’être productif : le faire.)</p>
<h3 id="choisir-un-generateur-de-site-statique">Choisir un générateur de site statique</h3>
<p>Comme je l’ai dit plus haut, une des raisons pour laquelle je n'ai pas changé de
générateur plus tôt c'est parce que je ne savais pas lequel je voulais utiliser.
Plusieurs personnes sur Twitter m'ont gentiment suggéré quelques-unes des
nombreuses options disponibles. Mais aucune de ces options ne m'allait.
Voyez-vous, chaque personne a son propre mode de fonctionnement et ses
préférences lorsqu'il s'agit d’organiser ses fichiers, ses dossiers et son
travail. Aucun des générateurs statiques que j'ai regardés n'avait ce que je
recherchais pour mon site. Jusqu'à ce que quelqu'un me suggère de jeter un œil à
<a href="https://gohugo.io" target="_blank" rel="noopener noreferrer">Hugo</a>.</p>
<p>J'ai passé la documentation en revue quelques minutes, simplement pour me faire
une idée de ce à quoi je pouvais m'attendre et de ce que Hugo avait à offrir –
histoire d’avoir une première impression, à proprement parler. Après avoir lu la
partie sur la structuration des contenus et leur organisation et appris comment
Hugo offre la possibilité de créer plein de sections et de catégories de
contenus différents, en plus de la souplesse générale qu'il procure, je me suis
dit que c'était le générateur de site statique dont j'avais toujours rêvé et
celui dont j'avais besoin. L'organisation et la structure ressemblait exactement
à ce que j'avais pu imaginer pour mon propre site.</p>
<p>Mais ce qui m'a fait adopté Hugo plus que toute autre option, c'est de voir
<a href="https://novelist.xyz/tech/hugo-vs-jekyll-static-site-generator/" target="_blank" rel="noopener noreferrer">à quel point il est rapide</a>
comparé à Jekyll. Non seulement chaque billet de blog que j'ai pu lire y est
allé d’une comparaison qui atteste ce fait, mais j'ai aussi pu faire
l’expérience de cette vitesse pour la première fois lorsque j'ai travaillé sur
la <a href="/2017/03/17/smashing-mag-va-dix-fois-plus-vite/">refonte de Smashing Magazine</a>.</p>
<p>La nouvelle version de Smashing Magazine (actuellement accessible via
<a href="https://next.smashingmagazine.com" target="_blank" rel="noopener noreferrer">next.smashingmagazine.com</a>) utilise Hugo
comme générateur de site statique. La configuration que j'ai utilisée lorsque je
montais le front-end du magazine s'est montrée tellement rapide que je n'avais
aucun doute quant à la véracité des résultats que je pouvais lire. Et comme mon
site est bien plus petit que Smashing Magazine, je savais que je n'avais aucun
souci à me faire. Si Smashing Magazine pouvait être compilé aussi rapidement,
pourquoi pas mon blog ?</p>
<aside class="note note-info"><p>Veuillez prendre note que cet article n'est en aucun cas
destiné à constituer un guide exhaustif sur Hugo. Il me reste encore beaucoup de
choses à comprendre, je suis donc mal placée pour écrire un tel guide. Vous
verrez que vous devrez vous reporter à la documentation d’Hugo pour en savoir
plus sur les sujets que je vais aborder. Prenez cet article comme un guide qui
peut vous aider à savoir par où commencer (et parfois savoir quoi faire) sur
certaines thématiques particulières propres à Hugo. Et ce n'est en fin de compte
pas une comparaison entre Hugo et Jekyll. C’est davantage une introduction à
Hugo qui comporte quelques astuces. Si vous envisagez d’adopter Hugo comme
nouveau générateur de site statique, j'espère que vous trouverez quelques trucs
utiles pour avoir un système fonctionnel.</p></aside>
<h3 id="configurer-hugo">Configurer Hugo</h3>
<p>Configurer Hugo n'est pas compliqué. Il y a deux guides dans la documentation :
un pour
<a href="https://gohugo.io/tutorials/installing-on-mac/" target="_blank" rel="noopener noreferrer">installer Hugo sur un Mac</a> et
un pour
<a href="https://gohugo.io/tutorials/installing-on-windows/" target="_blank" rel="noopener noreferrer">l’installer sur Windows</a>.
Dans cet article je ferai toujours référence à la configuration pour un Mac,
puisque c'est ma principale machine de travail.</p>
<p>J'ai utilisé <code>brew</code> pour installer Hugo :</p>
<pre><code class="language-sh hljs bash">brew install hugo</code></pre>
<p>J'ai suivi les instructions présentes sur la page d’installation, mis à jour
<code>brew</code> et lancé quelques commandes pour m'assurer que tout était bien installé
et fonctionnait correctement. C’est tout ce dont vous avez besoin pour qu'Hugo
tourne sur votre machine. Difficile de faire plus simple. Avec Jekyll, ce
n'était pas aussi indolore, je me rappelle avoir passé pas mal de temps à le
configurer pour le faire tourner à l’époque.</p>
<p>J'ai parfois tendance à être une développeuse paresseuse. Mais ça a du bon, car
cela me pousse à trouver la manière la plus rapide et la plus simple de mener à
bien une tâche. Et donc la première des choses que j'ai voulu faire a été de
migrer automatiquement tous mes articles de blog dans Hugo sans avoir à repasser
sur chacun des billets pour modifier le <a href="https://gohugo.io/content/front-matter/" target="_blank" rel="noopener noreferrer">front matter</a>. (J'aurais
vraisemblablement abandonné si j'avais dû faire cela 😅)</p>
<p>Heureusement, depuis la version 0.15, Hugo offre
<a href="https://gohugo.io/commands/hugo_import_jekyll/" target="_blank" rel="noopener noreferrer">une commande pour migrer depuis Jekyll</a>.
Vous n'avez qu'à taper la ligne suivante dans le terminal – en remplaçant
<code>chemin_site_jekyll</code> et <code>repertoire_destination</code> par les chemins vers le
répertoire utilisé actuellement pour votre site sous Jekyll et celui dans lequel
vous voulez configurer votre nouveau site – et Hugo se chargera d’importer les
fichiers de votre installation actuelle de Jekyll dans le répertoire qui
contiendra votre site Hugo :</p>
<pre><code class="language-sh hljs bash">hugo import jekyll chemin_site_jekyll repertoire_destination</code></pre>
<p>Si vous n'importez pas un site depuis Jekyll, vous pouvez toujours aller lire la
documentation qui détaille ce qu'il faut savoir sur la structure des répertoires
de Hugo, où ranger les assets, le contenu, les modèles de mise en page et bien
plus.</p>
<p>L'étape suivante consiste à convertir vos modèles Jekyll en modèles Hugo et
c'est là où réside la plus grande partie du travail et où je me suis arrachée
les cheveux pas mal de fois. Mais croyez-moi, le résultat final prouve que ça
valait <strong>vraiment</strong> le coup. Au passage, j'ai beaucoup appris. C’est ce que je
vais partager avec vous dans la prochaine section.</p>
<aside class="note note-tip"><p><strong>Astuce</strong> : Vous appartenez peut-être à une autre catégorie
de développeur fainéant, vous préférez peut-être partir d’un modèle standard qui
vous fournit la configuration dont vous avez besoin et qui est prêt pour que
vous puissiez ajouter du contenu sur le champ, surtout si vous démarrez un
nouveau blog. Dans ce cas je vous recommande chaudement le
<a href="https://github.com/netlify/victor-hugo" target="_blank" rel="noopener noreferrer">modèle Victor Hugo</a> de Netlify, qui
contient tout ce qu'il faut, il y a même Webpack et Gulp de correctement
configurés pour pouvoir faire tourner votre site. La structure de ce thème
standard est légèrement différente de ce que je vais vous montrer, mais pas tant
que ça.</p></aside>
<h3 id="se-plonger-dans-hugo-quelques-details-techniques">Se plonger dans Hugo : quelques détails techniques</h3>
<p>Laissez-moi commencer en vous disant qu'à un moment donné pendant la migration,
je ne faisais que modifier des trucs, changer des valeurs, des noms de fichiers,
la structure, etc. dans l’espoir que les choses allaient marcher comme par magie
et, quand ce n'était pas le cas, je me disais alors : "Je n'ai aucune idée de
comment ou pourquoi ce truc marche". Et comme l’a dit quelqu'un sur Twitter,
apparemment je ne suis pas la seule à avoir subi ce genre de choses avec Hugo.
J'espère donc que cet (assez long) article aidera certains d’entre vous à passer
à Hugo, et vous évitera au passage quelques maux de têtes.</p>
<p><strong>Avertissement :</strong> Il y a encore beaucoup de choses que je ne sais <strong>pas
encore</strong> faire et où je me retrouve parfois à devoir chercher sur Internet. Mais
j'ai acquis toutes les connaissances de base et de tout ce dont j'ai besoin
<strong>pour le moment</strong> pour avoir un système fonctionnel, et oui, je sais comment et
pourquoi tout ce qui marche maintenant marche de cette manière. Donc laissez-moi
vous dévoiler tout ça. Je vous partagerai aussi les articles super utiles que
j'ai trouvé et qui m'ont également bien aidé. Prenez cet article comme un
pense-bête, un ensemble de rappels, une note à mon futur moi à laquelle je
devrai revenir si jamais j'ai besoin de revoir les bases.</p>
<aside class="note note-info"><p>Notez bien que vous finirez sûrement par ne pas utiliser le
même processus ou la même arborescence de fichiers que moi. Il est en effet peu
probable que vous ayez exactement les mêmes types de contenus que moi. Il se
peut aussi que vous trouviez une meilleure façon de faire que celle que
j'utilise actuellement, et c'est tant mieux. Et si vous êtes déjà un pro de Hugo
et que vous repérez des choses qui pourraient être réalisées d’une meilleure
façon, ne vous gênez pas pour partager vos manières de faire avec le reste
d’entre nous pour que nous puissions tous apprendre de vous.</p></aside>
<h4 id="la-structure-des-dossiers-d-hugo">La structure des dossiers d’Hugo</h4>
<p>La structure du répertoire de mon site en local ressemble actuellement à ça :</p>
<figure>
<picture title="Structure de dossiers pour Hugo.">
<source type="image/webp" srcset="/d33wubrfki0l68.cloudfront.net/4aa07c8129bdae37f8c6510453f274a32ac664c0/09ca5/images/article-assets/hugo-netlify/hugo-folder-structure.092af9f3ad903086345d112024559dd4.webp" width="494" height="682">
<source type="image/avif" srcset="/d33wubrfki0l68.cloudfront.net/4aa07c8129bdae37f8c6510453f274a32ac664c0/09ca5/images/article-assets/hugo-netlify/hugo-folder-structure.092af9f3ad903086345d112024559dd4.avif" width="494" height="682">
<img src="/d33wubrfki0l68.cloudfront.net/4aa07c8129bdae37f8c6510453f274a32ac664c0/09ca5/images/article-assets/hugo-netlify/hugo-folder-structure.092af9f3ad903086345d112024559dd4.png" alt="Structure de dossiers pour Hugo" loading="lazy" decoding="async" class="dark:brightness-90" width="494" height="682" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAADDElEQVR4nO2c65LbIAyFz2H6CJ1p3/89F/UHN4HxOk1t2UU5WaJkQzI7fBYSgix//f4p+GcRDEQgwRCSZQBZfp8fs/UDCYIAAQggEEgUiAhijKp9IcYIEenaqgrnfIyUH0AEkge4viSSn+d++WH3JFupL9ZOrvTjvI8SQJiGkFKv+qoIxAAERKTrQCBgfWuB0RpqA+DCO4ATgUi54lkMERFBdQsxIBJgiKAQoIBgAicYgESIRDSv8aFzPaQadlMWmFpkBAPBrxRPioMQDWiUFktmbXWdCARqwPqBS5NTvsUWzJttQDxC0DoVCKCmrs0LAAhInqZ6IGmeq8nADIoTLqcDqaF8ykRAYYIy2BbYi3VCYNAFQJCu8L9+T7obpy4vnlF00jpkJpX6vmB7GNf9VU/XNR5SVVKsN943QqmxZui5Nz/+p7rQQ7LGBeJOp3eHlOp+BV0PRJdQ6rD3VlTfujLXU9rhp6/jIRdPWVmi0l0RCAkqq7rhHSgrycBDgOYlJSXuraDUFreB3RMMwAwIdmG0CuIIw2e2ZQfkSLUcP1hnMgTCmhH1di5/KJJu8hCqTLXHMoZ4b7LJsrRqyX0PhG8Ze0guwX+7jvMZzIvsgEwg7IPxS8Qw7T3uoPfQverGtFcmhUHnNGAOpJ0oKc8BfDxD6SYPUaURpwvAPT1npf4RgDuArLN1cYnMF4YEvst33ct2HaJAlEcfNr0eE0M+YJJM90NeyW29gzH3kLoppbQtyPulYj9lyfGxBL847owhDk8lviJbIPPD8duD1zuH4jzoxiyrDXz5rsjUOpP9jiGQB1sPuN60yl/BUvJ0Ev4GIOxaA5G+d6jVc/ABxRZI9YzWtt6hpY48+OBhCYST1qDUXkyl+TF+eCnTmwX10TO2IHTfbTD3Et+NgPRe0aoow4p9GPTy3x88LRVNgDSP0Or30hOkbXaVpi9AA11ZBkD2B7KvN+oAvj34MIe6ni4HcjyI/cG4AsPT2kPrYiDnXtItpqyrx2xQFa0+4Ed6BJD97MqfHgGk6FUIK8P6A6x26BK7+40RAAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Structure de dossiers pour Hugo.</figcaption>
</figure>
<p>Les dossiers que vous pouvez voir sur l’image ci-dessus, à l’exception du
dossier <code>node_modules</code> sont ceux générés pour vous par Hugo lorsque vous
importez votre site depuis Jekyll, ce sont ceux que vous devriez normalement
créer pour un site géré avec Hugo.</p>
<p>Les fichiers du bas sont ceux qui sont nécessaires et utilisés par Git et Gulp.
Le seul fichier qui est utilisé par Hugo est le fichier <code>config.toml</code>.</p>
<p><code>config.toml</code> contient la configuration de variables du site comme <code>baseURL</code>
parmi beaucoup d’autres variables que vous allez décider d’utiliser ou pas. Ce
fichier est similaire au fichier de configuration YAML de Jekyll. La
documentation d’Hugo liste
<a href="https://gohugo.io/overview/configuration/" target="_blank" rel="noopener noreferrer">toutes les variables disponibles</a> et
ce que vous devez savoir pour pouvoir utiliser celles dont vous avez besoin. Mon
fichier de configuration ne contient pas beaucoup de variables pour le moment.</p>
<p>Votre site est compilé dans le répertoire <code>/public/</code>. Il correspond au dossier
<code>dist</code> qu'on retrouve dans beaucoup d’arborescences d’applications. C’est dans
tous les autres dossiers que va se dérouler le développement.</p>
<p>Le dossier <code>static</code> est destiné à héberger les contenus statiques comme les
images, les fichiers CSS et JS mais aussi les fichiers audio, vidéo, les slides
de présentations, etc. Je passe pas mal de temps à travailler dans ce dossier.</p>
<aside class="note note-info"><p>Après être intervenue sur le redesign de Smashing Magazine,
j'ai appris que votre structure peut-être différente de celle présentée plus
haut. C’est à peu près la même chose mais si vous utilisez un modèle comme
Victor Hugo de Netlify, votre configuration sera légèrement différente, mais
c'est du pareil au même pour ce qui est compilé et vers où. Notez que l’adoption
du modèle Victor Hugo est un bon moyen de commencer à intégrer Webpack et Gulp
dans votre workflow. En ce qui me concerne je n'ai pas vraiment besoin de
Webpack sur mon site vu le peu de JS que j'utilise, mais si vous en avez
l’utilité, je vous recommande d’utiliser leur template pour Hugo. Et perso, je
préfère commencer de zéro pour apprendre et comprendre comment tout ça marche.
Faites comme bon vous semble.</p></aside>
<h4 id="creer-et-mettre-en-page-du-contenu">Créer et mettre en page du contenu</h4>
<p>Pour chaque type de contenu dont vous avez besoin, que ce soit une page, un
billet de blog, un index de vos articles, de vos études de cas, etc. vous allez
devoir créer un fichier Markdown (<code>.md</code>) dans le dossier <code>/content/</code>. C’est là
où sont stockés <em>tous</em> les contenus. Après avoir créé le contenu dans son
répertoire spécifique, vous allez créer ou réutiliser un modèle de mise en page
stocké dans le dossier <code>/layouts/</code>.</p>
<p>Chaque fichier <code>.md</code> du dossier <code>/content/</code> correspond à une page qui commence
avec une entête <a href="https://gohugo.io/content/front-matter/" target="_blank" rel="noopener noreferrer">front matter</a>, écrite au format <code>yaml</code> ou <code>toml</code>.
Puisque je voulais m'imprégner d’un nouvel environnement et que la plupart de la
documentation et des ressources dédiées à Hugo utilisent le format <code>toml</code>, c'est
le format que j'ai utilisé. Jekyll utilise <code>yaml</code>.<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref">2</a></sup></p>
<aside class="note note-info"><p>Je ne rentrerai pas ici sur les différences entre les deux
formats, la documentation d’Hugo et Google sont vos amis. Personnellement ça m'a
pris un peu de temps pour apprendre à utiliser toutes ces nouvelles syntaxes
(TOML, les modèles de template en Go, etc.) avant de me sentir à l’aise.
Néanmoins la courbe d’apprentissage est assez rapide, ne vous laissez donc pas
intimider par ces nouvelles syntaxes si tout cela est nouveau pour vous.</p></aside>
<h5 id="definir-ou-declarer-les-types-de-contenu">Définir (ou déclarer) les types de contenu</h5>
<p>Le <a href="https://gohugo.io/content/front-matter/" target="_blank" rel="noopener noreferrer">front matter</a> de chaque page définit le type de page ou de
contenu qui à son tour définit le type de modèle qui sera utilisé pour le rendu.
Le type de page est défini par la variable <code>type</code>. Par exemple le front matter
d’un article dans la section blog de mon site ressemble à ça:</p>
<pre><code class="language-toml hljs ini">+++
<span class="hljs-attr">type</span> = <span class="hljs-string">"blog"</span>
<span class="hljs-attr">description</span> = <span class="hljs-string">"…"</span>
<span class="hljs-attr">title</span> = <span class="hljs-string">"…"</span>
<span class="hljs-attr">date</span> = …
…
+++</code></pre>
<p>La valeur <code>type</code> peut prendre pratiquement n'importe quelle
valeur, et c'est là où on peut se rendre compte du pouvoir
d’Hugo. Vous pouvez définir autant de types de contenus que vous voulez. Par
exemple, j'utilise actuellement cinq types de contenus pour mon site :
<em>statique</em> (pour les pages comme "À propos" et "Travailler avec moi"), <em>blog</em>
(pour les articles comme celui que vous êtes en train de lire), <em>ateliers</em>,
<em>études de cas</em> et <em>bureau</em> (un nouveau type d’articles à paraître bientôt). Je
peux créer autant de types de contenu que je veux.</p>
<aside class="note note-update"><p>Il est possible de créer des sous-sections de contenu
depuis la version 0.24 d’Hugo ! Cela vous permet par exemple de créer des
sous-sections <em>design</em> et <em>développement</em> dans la section <em>articles</em> et bien
plus. C’est une fonctionnalité intéressante.</p></aside>
<p>C’est une des choses que j'aime chez Hugo comparativement à Jekyll qui, <em>à ma
connaissance</em>, n'offre pas de fonctionnalité similaire.<sup id="fnref1:3"><a href="#fn:3" class="footnote-ref">3</a></sup></p>
<p>La capture d’écran ci-contre montre à quoi ressemble mon dossier <code>/content/</code> en
ce moment :</p>
<figure>
<picture title="Le contenu du dossier content de mon site.">
<source type="image/webp" srcset="/d33wubrfki0l68.cloudfront.net/32450b106a26b69980db6e73094c9411c5734a61/ff4f7/images/article-assets/hugo-netlify/content-types.f527c820f14db9af68380b0e37e511da.webp" width="442" height="726">
<source type="image/avif" srcset="/d33wubrfki0l68.cloudfront.net/32450b106a26b69980db6e73094c9411c5734a61/ff4f7/images/article-assets/hugo-netlify/content-types.f527c820f14db9af68380b0e37e511da.avif" width="442" height="726">
<img src="/d33wubrfki0l68.cloudfront.net/32450b106a26b69980db6e73094c9411c5734a61/ff4f7/images/article-assets/hugo-netlify/content-types.f527c820f14db9af68380b0e37e511da.png" alt="Le contenu du dossier `content` de mon site" loading="lazy" decoding="async" class="dark:brightness-90" width="442" height="726" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAD50lEQVR4nOVc23bjIAyc8dlP2HN2//9DrX0AgcTFSbdOwTCtg7nYaZkMSAKHf/7+FtwEkiCJgwdAgOEFMQOCIHM7xLxCRCAiOM8znJ+SykQEAgFw2587JY5b7yaalJ0m8Td2qYRDywHN53vk9vlndTIA4NedNxMIKPkjLxRA89Q6iaoBIIxtYlZVEMlIHKzPQ8KthABGHTHREQvKTSJMUnm61g5NhUJ2YeV+QqTfcaH/JZ4R6dReK14pm/CQcDshgCdFIGFyr9oApK/T66xS6vlobXyEEMDMARctAEZiYomY4W5DdQAfJMTiahiz41ZSyIbKUPwIIYprYt5vszLu9UPehJ0rbBpEEdRC40wCuXx1/KhCXkId++ifUAihoSJNS+uqaBJCsgpos1dQ53ExDCYkDk8u7qU1LVbUk/RpCsMsgKGE0Lz6+cI1qIrEnWSsQMxYhdCow5QlSE1aMpILL18veLqRNsTKAnLEV8q5wDqEZIqy6ME0z2joPqf5eC6GERJget8EE1NVPGemoehvbxKTcOsrT8TAIUsgNvIbQUST14xPlYJ6d0zrLM/FYCtLw+wdEG59JVzhFQWoY/lwJiIGD1k5CHmVCvxwliLBdnlX1iBmEsfwTVSqaIRfHo7hCnEwDmLpKAJmuV3a6QqYSCFq11ozdj/MoRBmk9X6Fq2yfroGJlAIq6NWyEJj0gsMV4hXQV33NTyfuMGE+B4vCVhpsn4XEwxZvuMzKVLUy0X6fP9DMQUhAZYAP3+8ImQlTERIVgppyXnlCK5FyPBJvYYYh28vMoBJCKnXNfbFBIR438OawCQv0xXJG0qI98bzQzx25W83UgZO6j0PPWBHHwQYqJA6VpVqUv2OGKaQUgGlQ7irQsYv4dpc4RCGsr1M3+GOoQ+b1OGSq1XC1cgAJiAkQDu62IVi92yt1/dNTOCHBNi4lN2D1Tr3Ju9as/9whfRW/3RflpjnEP3uUbu5t7HRt0AeGueW2gS73/O3OwD20w+nCn1IFKA7z+h3dH6Osb05byayhhHiPfSGJ17sOrFDWjvsfk2ItvHmtFySNYKoQYSoMugIuQqLaKcB3yEEZqdkHeIvI82k1tHUfZacIYRYAkjiONghpLoSwDcJqepqkv3z8qHcrkp+clPeAELYJaNFSut/t2p5C5Xl3PB3zHmPEJET51nf4078OCHZmiqJOWI9nUL6Ma2vmbsiqGw1AcAOIWo4ZFIA4AB5xraLEALUIXeripKQeMUN76kzB6HPnhAwX4ijc4a+n+1wAjgjSXTzyt0YMmSVDl2PiNb+3v+FWlBEkIoSU+bD3+MjB0EpjKkl5X78AzxJcrxMI8uhAAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Le contenu du dossier <code>content</code> de mon site.</figcaption>
</figure>
<p>Les pages statiques sont créées dans des fichiers individuels au format Markdown
à la racine du dossier <code>/content/</code>. Les autres types de contenus qui auraient
besoin d’un index (comme des articles, des ateliers, des études de cas, etc.)
sont créés dans des dossiers nommés d’après le type de contenu. Par exemple on
stockera les contenus de type <em>ateliers</em> dans un dossier <code>/content/ateliers/</code>.
Mes articles se trouvent dans le répertoire <code>/content/blog/</code>. {{&lt; marker &gt;}}Les
dossiers de ce type sont également appelés des <code>sections</code>.{{&lt; /marker &gt;}}</p>
<p>Pour chaque contenu, il vous faut définir son type. Vous pouvez faire ça de deux
manières.</p>
<p>Le type pour les pages statiques est défini à l’aide de la variable <code>type</code> dans
l’entête <a href="https://gohugo.io/content/front-matter/" target="_blank" rel="noopener noreferrer">front matter</a> de la page. Le type des sections (blog,
ateliers, études de cas et bureau) est quant à lui défini à l’aide de
l’arborescence de dossiers. Vous n'avez pas besoin de spécifier le type dans le
front matter lorsque vous vous reposez sur l’arborescence de fichiers. Par
exemple un billet de blog qui se trouve dans le dossier <code>/content/blog/</code> sera
automatiquement traité comme un type de contenu <code>blog</code>. Inutile de le préciser
dans le front matter de chaque article.</p>
<p>Vous pouvez choisir de définir le type de contenu à l’aide du front matter ou de
l’arborescence de fichier. Généralement vous utiliserez la variable <code>type</code> pour
les pages statiques et vous vous reposerez sur l’arborescence de fichiers pour
les contenus qui auront besoin d’un index, par exemple des billets de blog.</p>
<p>Une chose importante à savoir est que {{&lt; marker &gt;}}si vous définissez le type
de page à l’aide de la variable <code>type</code>, la page peut se trouver n'importe où
dans le dossier <code>/content/</code>, l’arborescence n'aura alors aucune importance.{{%
/marker %}}</p>
<p>Vous pourriez donc attribuer le type <code>static</code> à une page et la placer dans le
dossier <code>blog</code> et Hugo la considérera comme une page statique et ne tiendra pas
compte de sa place dans l’arborescence.</p>
<p>Mais… pourquoi donc ? Réponse : pour choisir le type de modèle à utiliser.</p>
<p>Voyez-vous, chaque type de contenu est associé avec un certain type de mise en
page. Vous pouvez également utiliser un même modèle pour plusieurs types de
contenu. Nous verrons cela dans la partie suivante. Mais d’abord, créons
quelques pages de contenus : deux pages statiques (<em>Accueil</em> et <em>À propos</em> par
exemple) et une page d’index pour les articles de blog.</p>
<p>Avant de faire cela, j'aimerais préciser quelque chose quant à la création de
pages d’index pour différentes sections ou types de contenu.</p>
<p>La section blog nécessite la présence d’un fichier <code>_index.md</code> dans le dossier
<code>/content/blog/</code>. C’est le fichier d’index pour cette section (celui grâce
auquel nous afficherons la liste de tous les articles). Le dossier
<code>/content/blog/</code> hébergera également tous les billets de blog. La capture
d’écran suivante montre cela de façon plus visuelle :</p>
<figure>
<picture title="Le contenu du dossier /content/blog/.">
<source type="image/webp" srcset="/d33wubrfki0l68.cloudfront.net/37bc25dc5366c0b251c5b2c50edd8ca246b85f4f/36428/images/article-assets/hugo-netlify/section-type.7f63ffb725507e545a3ad5d7a55893c6.webp" width="472" height="484">
<source type="image/avif" srcset="/d33wubrfki0l68.cloudfront.net/37bc25dc5366c0b251c5b2c50edd8ca246b85f4f/36428/images/article-assets/hugo-netlify/section-type.7f63ffb725507e545a3ad5d7a55893c6.avif" width="472" height="484">
<img src="/d33wubrfki0l68.cloudfront.net/37bc25dc5366c0b251c5b2c50edd8ca246b85f4f/36428/images/article-assets/hugo-netlify/section-type.7f63ffb725507e545a3ad5d7a55893c6.png" alt="Le contenu du dossier `/content/blog/`" loading="lazy" decoding="async" class="dark:brightness-90" width="472" height="484" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAECUlEQVR4nO1aa87cIAwcr3qESu39L+r+CCYGDIGEDf4qRlolmxfgYfwgoT9/fzM23OCzugMbKTYhzrAJcYZNiDNsQpxhE+IMv1Y0SqC4z9hZt8brCiEQQIg/Tc7GQpcViQj8bBxYRgiDNxEGXieEwZCwcUaPTY1giUIiKTueF1jqshgAM3ZwV1hchyiJuCRFpYMvYUkdYoNAMm5eVZ+EKWHaXx38Yv/cECKGoFcGTkmbzfMGWKfqk/vohBBKycgt8XDQ1efmV3R6pkgXA0x83D2JmOWEnB46NZoZT7oHXTPwM2UU3akUtcz3iVlMCKU85C6r6jPsjDk39LUy1FU34jYBANMxSQigsE+g22pZSkicj3pdi/JrOllRVr1Uh3m+cl219f80qJfDNyhoWKfgRO10qa3yxGFz//wYopVx/i9n99WMzZVypYSMcGJEl1/1g2WTx+ZcAzL3b2AZIbmZUwNqv943tecoxTBly7Zi/Imp7zqFZMoAAUTHDgkpGSGMmUq5gMkwpqrBwiJCDGXIfzqIoFi2j1myTynXZkyu4OTEV1cRfMQQovj7fMLMbhAyRSmjNn1pOWd5liUQQvJ9dUVxjxBjbc07b9QaR0PvraytLwxRElFTR8FRAQYzgcioHO+S8TLcKMSCRYAY3N4CQkpJgNe3YWkx65oQZmSZlhhVFifybThnKQQeKMmSGaPeckGIGF4vyqUxJH37XjfsSVBYaMoamtLdYdh1kLXMQx4ICUsVDEjtIbyIKxp9lk3lGJIFQiLQjcBuxcLmSoQXl3UKg5VazvNFLBGPJEaK24mdksVOSXeJQJ2pb0xSBpQhWESILrpS91J/lZCxUpAxgw3K1KWX1QEQg1qKlQL0hjLk2DKFRPuphEgrxQIZM2oqonWC6wvGZ0JwWZXYFPt3XxkOXFYecM/BXqe2CuLfcz/XDeuNy0kEwIerCu3o89nlj5QRhrI2hiT2s0qHFugIu1DGuk1KabXjcPhoLCpEfxaTdOW5MpykvVaxcMEMAyBZJGknwXX0fQEmBAgZhUKUgXN3OqoMwfIsq5jQPXzEvQcwG0lDOskqu1Zg1na+5mYa3DiWHl+eZWnYhjXjR8h6itqkM9Nqq+I8G6sZUs8kRIJKhVjHrI8nrlXigBDbjmZQh2Q+HL1V/xvw/iAVlWHeb7/nzZONYaWEPReE5INs1iK2tSaipYxwwLil+HNTKU4Iua5BUvR+S3Xv8+1+ZRjtPVSKG0IOXNUg2n19+Vv5HmUY9xR/BpXijBBPGFFGee+4Ug64JUQGdP2WUN80qe1RZTT70aOUE+4I0V+cmBXwK52Y96C2Ukq4I0RQkPEyJ6MwJ01TKTbcECLKKL82gRqLY1aaXTMWRSvX/QNt0ndIcCFCGAAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Le contenu du dossier <code>/content/blog/</code>.</figcaption>
</figure>
<p>Chaque type de contenu qui utilise cette arborescence de dossiers (ou chaque
<em>section</em> de contenu) comporte une page d’index qui commence par un tiret bas
(<code>_</code>) en plus des fichiers de cette section. De la même manière, tout autre type
de contenu (ou section) comportera aussi un index et des fichiers pour cette
section.</p>
<p>OK, créons maintenant quelques pages.</p>
<h6 id="la-page-d-accueil">La page d’accueil</h6>
<p>La page d’accueil se crée en plaçant un fichier nommé <code>_index.md</code> dans le
dossier <code>/content/</code> comme vous pouvez le voir dans la capture d’écran un peu
plus haut.</p>
<p>La page d’accueil est un peu spéciale, c'est la seule de toutes les autres pages
qui nécessite d’avoir son propre modèle de mise en page dans le dossier
<code>/layouts/</code> – nous parlerons de ces modèles plus en détail dans la prochaine
section – et ce modèle de mise en page se nomme aussi <code>index.html</code>.</p>
<p>Vous définissez le type page dans le <a href="https://gohugo.io/content/front-matter/" target="_blank" rel="noopener noreferrer">front matter</a> du fichier
<code>/content/_index.md</code> et vous lui attribuez un titre ainsi qu'une description.</p>
<p>Le front matter de ma page d’accueil ressemble à ça :</p>
<pre><code class="language-go-html-template hljs go">+++
<span class="hljs-keyword">type</span> = <span class="hljs-string">"page"</span>
title = <span class="hljs-string">"Accueil"</span>
description = <span class="hljs-string">"Sara Soueidan — Développeuse Web Front-end, auteure et conférencière"</span>
+++</code></pre>
<p>La description est utilisée dans le fichier partiel d’entête du site en tant que
valeur de l’attribut <code>&lt;title&gt;</code> ainsi :</p>
<pre><code class="language-go-html-template hljs go">&lt;title&gt; {{ .Page.Description }} &lt;/title&gt;</code></pre>
<p>La raison pour laquelle je n'utilise pas la valeur du <code>title</code> dans le front
matter pour la balise HTML <code>&lt;title&gt;</code> est que dans les autres pages, le <code>title</code>
de la page est aussi utilisé comme intitulé de lien dans le menu principal de
navigation. Mais nous verrons tout ça plus tard.</p>
<p>Les fichiers Markdown (<code>.md</code>) peuvent contenir du Markdown et du HTML et, comme
pour la page d’accueil, je n'ai aucune entrée dynamique (comme une liste
d’articles), elle contient juste le code HTML de la page. Mais comment ce code
Markdown et HTML sont-ils mis en forme ? Et comment fait-on pour inclure un
entête et un pied de page ? Tout cela se passe dans le modèle de mise en page.</p>
<p>Le fichier <code>/layouts/index.html</code> est la mise en page utilisée pour l’accueil et
voici à quoi il ressemble :</p>
<pre><code class="language-go-html-template hljs go">{{ partial <span class="hljs-string">"homepage-header.html"</span> . }}

{{ .Content }}

{{ partial <span class="hljs-string">"footer.html"</span> . }}</code></pre>
<p><code>{{ .Content }}</code> récupère le contenu de la page correspondante
dans le dossier <code>/content/</code>. Donc ici ça récupère le contenu de
la page d’accueil à partir du fichier <code>/contents/_index.md</code>.</p>
<p>En outre, j'appelle l’entête ainsi que le pied de page à l’aide de fichiers
partiels.</p>
<p>Par défaut, quand vous demandez <code>partial "footer.html ."</code>, Hugo va
regarder s'il existe un fichier partiel dans le dossier <code>partials</code> situé dans le
répertoire <code>layouts</code>.</p>
<p>Reportez-vous à
<a href="https://gohugo.io/templates/partials/" target="_blank" rel="noopener noreferrer">la documentation d’Hugo sur les fichiers partiels</a>
pour savoir ce que veut dire le point à la fin, ce qu'il fait et comment on peut
personnaliser les appels à des fichiers partiels.</p>
<p>Et voilà comment on crée une page d’accueil pour son site : un fichier
<code>/content/_index.md</code> qui contient le contenu de la page d’accueil, lui-même mis
en page à l’aide du fichier <code>/layouts/index.html</code>.</p>
<h6 id="ajouter-une-page-statique">Ajouter une page statique</h6>
<p>Une fois la page d’accueil terminée, j'ai voulu m'occuper du reste des pages
statiques avant de passer à des contenus plus dynamiques. Je me suis donc mise à
bâtir la page <em>À propos</em>.</p>
<p>J'ai dû faire pas mal de recherches et lire quelques fils de discussions d’aide
sur le forum d’Hugo et ailleurs pour y parvenir. J'espère donc que ce billet
vous sera bénéfique si vous cherchez à créer des pages statiques, ce qui s'avère
étonnement simple en fait.</p>
<p>Les pages statiques sont créées à la racine du répertoire <code>/content/</code>, tout
comme la page d’accueil. Toutefois, contrairement à la page d’accueil, les noms
de fichiers ne commencent pas par un tiret bas.</p>
<p>Et contrairement à la page d’accueil, vous allez devoir définir le type de page
et dire à Hugo de l’inclure dans le menu principal du site, en lui attribuant un
titre et une description.</p>
<p>Pour la page <em>À propos</em> de mon site, j’ai créé un fichier <code>/content/about.md</code>.
Le front matter de la page est le suivant :</p>
<pre><code class="language-toml hljs ini">+++
<span class="hljs-attr">type</span> = <span class="hljs-string">"static"</span>
<span class="hljs-attr">page</span> = <span class="hljs-string">"static/single.html"</span>
<span class="hljs-attr">title</span> = <span class="hljs-string">"À propos"</span>
<span class="hljs-attr">description</span> = <span class="hljs-string">"À propos de Sara Soueidan — Développeuse Web front-end, auteure et conférencière"</span>
<span class="hljs-attr">menu</span> = <span class="hljs-string">"main"</span>
<span class="hljs-attr">weight</span> = <span class="hljs-string">"1"</span>
+++</code></pre>
<p>Notez la valeur de <code>type</code>. Comme dit plus haut, vous pouvez attribuer ici la
valeur de votre choix. J'ai choisi <code>static</code>, car ça décrit littéralement le type
de la page. Et aussi parce qu'on trouve beaucoup de ressources en ligne qui
utilisent ce type pour les pages statiques.</p>
<p>La variable <code>page</code> indique à Hugo quel modèle de mise en page présent dans le
répertoire <code>/layouts/</code> utiliser.</p>
<aside class="note note-info"><p>Il est bon de noter également que Hugo utilisera automatiquement ce modèle même
si je ne lui dis pas. Je me rappelle tout de même avoir eu quelques prises de
tête au début quand j'essayais de comprendre comment utiliser les modèles pour
les différentes pages. Je ne savais pas quel modèle allait être utilisé. Même en
ayant lu la documentation, je me suis retrouvée à faire et défaire pas mal de
choses pour m'apercevoir que les choses marchaient comme par magie, ou pas du
tout. Au début, Hugo ressemblait à une boîte noire pour moi et il m'a fallu
quelques jours pour en comprendre assez et pour oser écrire à son sujet. Quand
ça a fini par fonctionner, j'ai décidé de ne plus toucher au front matter, car
j'avais peur de casser une fois de plus ma mise en page. Mais maintenant que
j'en sais davantage, il est bon de signaler que vous n'avez pas vraiment besoin
de la variable <code>page</code> ici.</p></aside>
<p>Le <code>title</code> est utilisé comme intitulé de lien dans le menu. (Sur mon site le
menu situé en haut de page contient une entrée "About &amp; Interviews").</p>
<p>Je vous ai déjà dit que la <code>description</code> est utilisée dans le fichier partiel
qui gère l’entête de page, cette description apparait ensuite dans l’onglet de
votre navigateur.</p>
<p>La variable <code>menu</code> indique à Hugo que cette page doit avoir une
entrée dans le menu principal.</p>
<p>La variable <code>weight</code> est très utile pour vous aider à définir
l’ordre d’affichage des liens dans le menu. Si vous ne l’utilisez
pas, Hugo utilisera son propre ordre par défaut – qui n'était pas celui que je
souhaitais pour mon site. Vous pouvez également définir des valeurs négatives
pour cette variable.</p>
<aside class="note note-info"><p>Pour faire court, je vous renvoie une fois de plus à la
documentation d’Hugo pour ce qui est de l’utilisation et de la configuration du
menu principal. J'ajoute que certains aspects sont encore assez confus pour moi,
mais comme je suis arrivée à faire ce que je voulais maintenant : je ne touche
plus à rien, j'ai trop peur de casser un truc. Une fois de plus. 😂</p></aside>
<p>Toutes les autres pages statiques sont créées de la même manière. La seule chose
qui change c'est le titre, la description et leur ordre dans le menu. Elles
utilisent toutes le même modèle de mise en page.</p>
<p>Je me note quelque chose ici pour plus tard :</p>
<p><strong>Hugo respecte un ordre spécifique pour décider du modèle de mise en page à
utiliser pour chaque page créée dans le dossier <code>/content/</code>. Nous en reparlerons
dans la section dédiée aux modèles juste après. Donc si nous n'avions pas défini
le fichier <code>/layouts/static/single.html</code> comme étant le modèle à utiliser, Hugo
aurait utilisé un modèle par défaut stocké dans <code>/layouts/</code>. Nous y
reviendrons</strong>.</p>
<p>Enfin, comme pour la page d’accueil, le contenu HTML de la page <em>À propos</em> se
trouve dans le fichier <code>about.md</code> puis il est ensuite inséré dans le modèle
<code>/layouts/static/single.html</code> à l’aide de <code>{{ .Content }}</code>. Nous faisons aussi
appel aux fichiers partiels d’entête et de bas de page. Notez la correspondance
entre le type <code>static</code> et le dossier <code>static</code> situé dans <code>layouts</code> qui contient
le modèle de mise en page.</p>
<aside class="note note-info"><p>Vous n'avez pas à écrire tout le HTML dans le fichier
Markdown. Vous pouvez mettre toute la structure du HTML, comme les conteneurs,
etc. dans le modèle de mise en page et n'avoir que le texte dans le fichier
Markdown. Si j'ai procédé de la sorte, c'est juste que ça me convient bien comme
ça.</p></aside>
<h5 id="les-archetypes-de-contenu">Les archétypes de contenu</h5>
<p>Vous avez peut-être remarqué sur la capture d’écran plus haut que j'ai aussi un
dossier nommé <code>/archetypes/</code> à la racine de mon site. Ce dossier est lui aussi
lié aux types de contenu que vous créez. Mais il a un but bien précis.</p>
<p>Pour expliquer à quoi sert ce répertoire, je vais commencer par citer
<a href="https://hugodocs.info/content-management/archetypes/" target="_blank" rel="noopener noreferrer">la page correspondante de la documentation d’Hugo</a> :</p>
<blockquote>
<p>Les archétypes vous permettent de créer de nouvelles instances de types de
contenu et de définir des paramètres par défaut à partir de la ligne de
commande.</p>
<p>Les archétypes sont des fichiers de contenu stockés dans le répertoire
<code>archetypes</code> de votre projet, qui contiennent un front matter pré-configuré
pour les types de contenu de votre site web. Les archétypes facilitent la
consistance des métadonnées des contenus à travers tout votre site et
permettent aux auteurs de générer rapidement de nouvelles instances de type de
contenu à l’aide de la commande <code>hugo new</code></p>
<p>Hugo est capable de déduire l’archétype approprié à l’aide de la section de
contenu passée en argument de la commande <code>new</code> :</p>
<p><code>hugo new &lt;section-de-contenu&gt;/&lt;nom-de-fichier.md&gt;</code></p>
</blockquote>
<p>En d’autres mots, définir un archétype vous permet de créer de nouveaux contenus
plus rapidement, puisqu'il va remplir le front matter de notre nouvelle page
avec les variables de votre choix.</p>
<p>Par exemple, supposons que je veuille créer une nouvelle étude de cas (qui irait
dans <code>/content/etudes-de-cas/</code>). Au lieu de créer un nouveau fichier Markdown
dans le répertoire, je peux taper cette commande dans le terminal et Hugo va
créer le nouveau fichier pour moi :</p>
<pre><code class="language-sh hljs bash">hugo new etudes-de-cas/ma-nouvelle-etude-de-cas.md</code></pre>
<p>Et les variables de cette nouvelle étude de cas (<code>ma-nouvelle-etude-de-cas.md</code>)
seront automatiquement ajoutées : nom du client, logo du client (chemin vers
l’image), description du client, description du projet, date du projet, etc. Par
défaut les valeurs de ces variables seront vierges, prêtes à être renseignées.</p>
<p>La capture d’écran suivante montre les variables front matter que j'ai définis
pour l’archétype <code>etudes-de-cas</code> :</p>
<figure>
<picture title="Les variables définies pour l’archétype des études de cas. À chaque fois que je demande à Hugo de créer une nouvelle étude de cas pour moi, il va automatiquement ajouter ces variables front matter. Ces variables sont ensuite utilisées par le modèle HTML de la page d’études de cas.">
<source type="image/webp" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346514/archetype-hugo.55dc72733c09de2b5b4cb1ac27ac5e81.webp" width="711" height="594">
<source type="image/avif" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346514/archetype-hugo.55dc72733c09de2b5b4cb1ac27ac5e81.avif" width="711" height="594">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346514/archetype-hugo.55dc72733c09de2b5b4cb1ac27ac5e81.png" alt="" loading="lazy" decoding="async" class="dark:brightness-90" width="711" height="594" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAEiklEQVR4nO1cUbLCIAzcOB7o3f9w8j4EuoQkRW1p67gzDBQobbMuCUWVv7+/BAciUlM5Lkgp1ZzT4/FQdXgmLKmOofJHLre55HQDIABuOeWy6LwkWXJIzpHL+klTrgvySbgXw2qICLiNyfDgjdX1G6hLTWn92uFg+vQUtB2Mu9eQUupI0AbX6ujre3X4hu+PvXNikhK1p7YaAoj3odF61fkcDCvEqrdJWH8Ab9qyym3NKx/nBCRpjytHPG2dg4iCIYVow3PZT7Y6ose3+sRmYSUEeUpPH1Ly0JMdQ0TBywop8Jx6ISOCR0w0te2Dc5EBrChk5LhVRKnLfYIEpzyXkPOhEjIcISn/8cyWKep51ObmOPDJ2AwlKDHzaJo7DncgJsPyH50SHCKiT/9o23vIaw8gr0fKWkT3ORcZgDFljfkNtDnG8m68oO0jCBYiONXGtaseR8zdi6AYnxKx5ie2m7IEpjqYEAFqONxc7Hh1AC85dZXj9dx65E1VUvlQ01TliEhZxTEEmU59TzI8NXxGjKBTR6nmeuEG8htJM3TglLXWYQ8ymvHH79WH5iNSCCvFvQtx6vfHPamLtkp5b9A1Mix1vIeOCcQKMQhZDbDmknLf8nqekUfKY6OXRL7ATdxHR1q5gYXgvnSci+c6xDANq2N0unqnvC+UYoCWlPJgHHmZmHfH962uN4cIFRIkL5BG7ya8/Zz60vHjm9sEjVM3w9IVK55LEXQ1mtmcigUiAzc55ynaKCu45qfT1LaIVJIhyudUPkofsZ25d9OTFGSGvaOGPIciNApRID7YIQYhbQJiy+//ZPY6ZINpag4slWQItXU7h+q47KQB/ViTfUu4MIzUfB5FaDARWMpR/8T5sVhdqRes+Y5jwUrh4weA24uE5HOFytPD3hVEDv18IAMmIibR4pDXH+UUpP78Az5+wwq5BrRPyXWNXbUPQausJhKb/xH8MkIYFiEl9uU1SaSG+XPBlxJSlPLC7qCpjPm4HXblKUiUGc47nUMVjC8n5Hq4OCES5/wKnjendPcT4Qt8CFvXSN3XgHgr93wB/EUJ0buDeuPJSe53s3T0dRxRFyWEESiEf7QjTIYyumn/Y0i5GCHe/rm1pUtE3EgdzY4hj2sR8FuHvAFHIbyXjkJK6e548+YVSvCafkdciBBLGaVJVLMmBgsRHiFJV1ur+f1xIUI0NDGKLCaHf/ipCSmqqLOWFwv/Xi6OwYx2NTkcadGJfFgF0UlFddoX1yfEc/Q8Ten+VQ28wyiD9t6XlGsT4qwFO4V4vofJs17LH4ALE+KysTQDhpHNeepF7KeS6xIS8aGWJ6ZCBHkXMb+mN5cinuH3U9JFCPEs7rVRZOVNU6X41luTn0JarKmjcx0c+oJyD9E3UPZdm1yAkNHpwVKI7jJKCGF4GtsGFyAEWJeC0b0Umje+qEopfLXmdYztLhi3J+cihBBWpyvlI2r5SYr+Q5369yHWtZq3J7+V+guwHHc/fS2RcNv/+b8u2ebs2KuzN+WEoPJtnJqQxYxkWDPBTt2AkeOQ/rD5+tAc/APTYfbi1DIRMAAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Les variables définies pour l’archétype des études de cas. À chaque fois que je demande à Hugo de créer une nouvelle étude de cas pour moi, il va automatiquement ajouter ces variables front matter. Ces variables sont ensuite utilisées par le modèle HTML de la page d’études de cas.</figcaption>
</figure>
<p>Notez aussi que les autres archétypes que j'ai définis dans le répertoire
<code>archetypes</code> qui correspondent aux quatre autres types de section qui figurent
sur mon site. C’est à peu près tout ce qu'il faut savoir sur les archétypes. Si
vous souhaitez en savoir plus, reportez-vous à la page dédiée dans la
documentation d’Hugo. C’est bien expliqué. Vous n'êtes pas obligés de définir
des archétypes, mais je pense que vous en aurez envie.</p>
<h5 id="presenter-le-contenu-avec-les-modeles-de-page-et-creer-une-page-d-index-pour-les-billets">Présenter le contenu avec les modèles de page et créer une page d’index pour les billets</h5>
<p>C’est la partie avec laquelle j'ai eu le plus de mal au début. Comment est-ce
que je sais que tel modèle est utilisé pour telle section ? Comment est-ce que
je sais de combien de modèles j'ai besoin ? Et est-ce qu'il y a vraiment besoin
de modèle ?</p>
<p>J'ai pas mal trifouillé et cherché sur le net, puis j'ai passé le plus clair de
mon temps à faire des essais, jusqu'à avoir des modèles qui fonctionnent bien.
Puis j'ai tout cassé et refait les choses pour comprendre quand et comment ça
fonctionnait. Je peux maintenant affirmer avec assurance que j'ai bien compris
tout ça.</p>
<p>En général, pour un blog très simple, vous n'aurez besoin que de deux modèles
par défaut : <code>list.html</code> et <code>single.html</code>.</p>
<p>Le modèle <code>list.html</code> aura pour mission d’afficher des listes d’éléments, comme
sur la page d’index où sont affichées la liste de vos billets de blog.</p>
<p>Quant au modèle <code>single.html</code>, comme vous l’aurez deviné, il servira pour mettre
en forme les pages uniques comme celle d’un billet de blog.</p>
<p>Ces deux modèles doivent se trouver dans le répertoire <code>/layouts/_defaults/</code>.</p>
<p>Ainsi, si vous créez un blog avec quelques articles et ne donnez aucune
instruction particulière à Hugo à propos de leur mise en page, il ira voir dans
le dossier <code>/layouts/_defaults/</code> quels modèles utiliser.</p>
<p>J'ai mis en place ces modèles comme solution par défaut sur mon blog, mais je
les <em>surcharge</em>.</p>
<p>Vous pouvez surcharger les modèles par défaut en fournissant des modèles qui
porteront le même nom que votre section ou votre type de contenu.</p>
<p>En d’autres termes, vous pouvez créer dans le répertoire <code>/layouts/</code> une
structure de dossiers similaire à celle que vous avez dans le répertoire
<code>/content/</code> et Hugo se basera sur cette structure pour déterminer le modèle à
utiliser.</p>
<p>Ou alors vous pouvez créer un répertoire du même nom que le <code>type</code> que vous avez
défini, comme <code>static</code> par exemple que j'utilise pour les pages statiques.
Plutôt que d’utiliser le modèle par défaut, Hugo utilisera alors le modèle situé
dans le répertoire <code>/layouts/static/</code> pour toutes les pages qui auront le
<code>type = static</code>.</p>
<p>J'ai pour ma part créé le fichier <code>/layouts/static/single.html</code> que Hugo va
utiliser pour surcharger la mise en page des pages statiques
<code>/layouts/_default/single.html</code> .</p>
<p>Encore une fois la page <code>/layouts/static/single.html</code> est simplement un modèle
avec le contenu suivant :</p>
<pre><code class="language-go-html-template hljs go">{{ partial <span class="hljs-string">"header.html"</span> . }}

{{ .Content }}

{{ partial <span class="hljs-string">"footer.html"</span> . }}</code></pre>
<p>dans lequel le contenu est récupéré à partir des fichiers Markdown respectifs.
Donc la page <code>about.html</code> est générée à l’aide du modèle de page
<code>/layouts/static/single.html</code> et <code>{{ .Content }}</code> est remplacé par le contenu du
fichier <code>/content/about.md</code>.</p>
<p>Maintenant pour créer une page d’index pour une liste d’éléments, comme la page
de blog et les articles listés ou la page d’ateliers et les pages de détails des
ateliers, on procède de manière très similaire.</p>
<p>De la même manière que nous avons créé un répertoire pour le type de contenu qui
porte le même nom que le <code>type</code> lui-même, nous créons un répertoire pour chaque
autre type de contenu que nous avons défini à l’aide de notre arborescence de
dossiers et nous donnons à ce répertoire le même nom que celui du dossier
présent dans le dossier <code>content</code>.</p>
<p>Ou si vous préférez : de la même manière que nous avons créé un dossier dans le
répertoire <code>layouts/</code> du même nom que le <code>type</code> de contenu, nous créons un
dossier pour chaque section de contenu (<code>blog</code>, <code>ateliers</code>, <code>etudes-de-cas</code>,
etc.) de manière à obtenir une structure de dossiers similaire dans <code>layouts</code> à
celle que nous avons dans <code>/content/</code>.</p>
<p>C’est toujours pas clair ? Alors regardez ce que ça donne pour mon site :</p>
<figure>
<picture title="La structuration des répertoires pour le contenu et les modèles de mon site.">
<source type="image/webp" srcset="/d33wubrfki0l68.cloudfront.net/1e4417080932df239c9a7eae7ded8f0ad59eb2ea/7ae87/images/article-assets/hugo-netlify/layouts.befc688570e542aa30c368ba7bf2a4c6.webp" width="474" height="1270">
<source type="image/avif" srcset="/d33wubrfki0l68.cloudfront.net/1e4417080932df239c9a7eae7ded8f0ad59eb2ea/7ae87/images/article-assets/hugo-netlify/layouts.befc688570e542aa30c368ba7bf2a4c6.avif" width="474" height="1270">
<img src="/d33wubrfki0l68.cloudfront.net/1e4417080932df239c9a7eae7ded8f0ad59eb2ea/7ae87/images/article-assets/hugo-netlify/layouts.befc688570e542aa30c368ba7bf2a4c6.png" alt="" loading="lazy" decoding="async" class="dark:brightness-90" width="474" height="1270" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAADI0lEQVR4nO2b63LjIAyFj8g+ws5s3/9B0f7AspEAx51pLWFyOg7EQJvqixA307+vv4wbRURI6YWUEl6vtOdTIhAlEAEAbbUZbD8dMxgMMMDM4K3Cu3z3wq3/+iX98fmzYqzaYLTdp71cN6nMt7U7igb5Nz8R5QKEd4OWq/5GD1poTzEeIKm9Bz65gup2IMwMomK8nIt1UmIwZwBpKyfbqPKOLX8BiHRLM3iGyMlDtGfkXGAQZRBR4y17J9Yx+JV0Bs8QOcUQoADJyBlIiZBzCfjiHdZLToFIkD9Jo3uGyBGISOKD7rJs19UFUht+L+5DmUVuQX1QMm5TNRyCMPlaBAITg7ikUSG5DnubuwzlHXoIu71eAKGhEEASS7Y8b/cDUnHrsnpeUnqodi7CXAHBVRDb7wTAjfEJFDSquAb1vkrc0MCug5C8xJ++2WPCAJyD+iiWEJkC1ia8NEtnBsEMCmQAMYgzEeQ8yhrFEhrWuwoDACoeu+eMgn4UuQ97Wy8pN+xk3Y5evwUGCA9C5A6kqO7/a8kk0cKo8+9hzKTk/QFEtksZfaM1jDo/PwwgEJDv6gqYGRUGiGxM6fS9RmBmVRggQIEiC4zHVdfg/f2RSgWbzil3IBbCde+Y2/AjuQOxOmCQWYJnk39A/9SR67DXdk9anQMOqJbeeb+x19fpnAoxDxEjt1B0eW8nsLfyO7MCrGWZjaiOW7RASju7xvUEBVjLamE0eyEnnvE0BemyALVkPjjS81QItUIAEU859YpF5DrsHZ4wWRQG4OQhFoQN6nE3WH9ftwOpZ+O9YS4RTXd05yd1K5AejFF3BUQ+ivB7ChLUtdQBhbV4xFvLAlAdRFhPt3pIOQh3PAtyTAA761iLErnZQ/QWbZkAlvdyLTjSVXKIISOjH4+xrSzHGDI60DCeo6ygUIuLpF90zUX6shCjLGoy29t93tLbwHqmQgAZgegUPV7uQHpP4Pa7J1KHp58qdyC1NAiuXktuhblJiKWTkUeoM7/PZwEgCBCgB0VGYPEfIfhJhQEi6oNZR6FiSKu1YADhgaynD5Bg+gAJpg+QYPoPq5mwblwh9+MAAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>La structuration des répertoires pour le contenu et les modèles de mon site.</figcaption>
</figure>
<p>Attardons-nous à nouveau quelques instants sur la section blog. Au répertoire
<code>/content/blog/</code> correspond le répertoire <code>/layouts/blog/</code>.</p>
<p>À l’intérieur du répertoire <code>/content/blog/</code> se trouve la page d’index
<code>_index.md</code> et les articles de blog.</p>
<p>Dans <code>/layouts/blog/</code> nous avons le modèle <code>list.html</code> ainsi que celui de la
page <code>single.html</code>.</p>
<p>Hugo utilisera le modèle <code>list.html</code> pour la page <code>_index.md</code> et le modèle
<code>single.html</code> pour chacun des articles de blog.</p>
<p>De la même manière, toutes les autres sections possèdent leur propre répertoire
de modèles, qui contient les modèles <code>list.html</code> et <code>single.html</code>.</p>
<p>Encore une fois vous n'avez pas réellement besoin de tous ces modèles. Et vous
aurez peut-être remarqué que quelques-unes des pages sont en tout point
similaires à l’exception de leur nom. Si je fais ça, c'est uniquement pour des
raisons de flexibilité future. Si jamais je veux changer le modèle de l’un des
types de section, j'aurai simplement à modifier son modèle correspondant. Si
votre site est plus simple et n'utilise pas autant de types de contenus, vous
n'avez surement pas besoin de créer autant de modèles que moi.</p>
<p>La seule exception à la structuration des répertoires de modèles c'est la page
d’accueil, dont le modèle de mise en page est placé à la racine du répertoire
<code>/layouts/</code> et se nomme <code>index.html</code>.</p>
<p>Il est important de vérifier l’ordre dans lequel Hugo va choisir le modèle à
utiliser pour chaque page. Je vous le recommande vivement.</p>
<p>Pour citer la documentation :</p>
<blockquote>
<p>Hugo obéit à plusieurs règles pour savoir quel modèle utiliser pour effectuer
le rendu d’une page spécifique. Hugo va utiliser la liste priorisée suivante.
Si un fichier n'est pas présent, alors on utilisera le suivant dans la liste.
Cela vous permet de concevoir des modèles particuliers quand vous le souhaitez
sans devoir créer plus de modèles que nécessaire. Pour la plupart des sites,
seul le fichier <code>_default</code> en fin de liste sera nécessaire. Les utilisateurs
peuvent spécifier le type et le modèle dans le front matter. La section est
déterminée en fonction de l’endroit où se trouve le fichier de contenu. Si le
type est fourni, il sera utilisé à la place de la section.</p>
</blockquote>
<p>Vous en apprendrez davantage sur cet ordre de priorisation dans
<a href="https://hugodocs.info/content-management/organization/" target="_blank" rel="noopener noreferrer">la page qui documente l’organisation des contenus</a>.</p>
<h4 id="boucler-sur-les-listes-de-section">Boucler sur les listes de section</h4>
<p>Le dernier point technique sur Hugo que je veux aborder concerne le listing des
articles d’une section sur la page d’index de cette section.</p>
<p>Une fois de plus, basons-nous sur l’exemple de la section blog située dans
<code>/content/blog/</code>.</p>
<p>Les fichiers Markdown ne contiennent bien entendu aucune logique de modèle. Donc
pour lister tous les billets de blog, nous allons devoir faire cela dans le
modèle correspondant à cette page d’index, situé dans <code>/layouts/blog/list.html</code>.
La boucle et toute la logique de modèle est écrite à l’aide du
<a href="https://hugodocs.info/templates/introduction/" target="_blank" rel="noopener noreferrer">templating HTML du langage Go</a>.</p>
<p>La boucle en elle-même pourra et sera probablement différente pour la majorité
d’entre vous. Après avoir pas mal cherché, je suis arrivée à écrire la boucle
suivante qui affiche les cinq derniers articles, suivi d’un appel à un fichier
partiel pour la gestion de la pagination.</p>
<pre><code class="language-go-html-template hljs go">&lt;ul class=<span class="hljs-string">"articles-list"</span>&gt;
    &lt;!-- Boucle à travers les fichiers situés dans content/blog<span class="hljs-comment">/*.md --&gt;
    {{ range (.Paginator 5).Pages }}
    &lt;li class="post"&gt;
        &lt;a class="post-title" href="{{.RelPermalink}}"&gt;{{ .Title }}&lt;/a&gt;
        &lt;span class="post-meta"&gt;&lt;time&gt;{{ .Date.Format "January 2, 2006" }}&lt;/time&gt; {{ if .Params.External }} — &lt;span class="post-host"&gt;for {{.Params.External.Host}}&lt;/span&gt; {{ end }}&lt;/span&gt;

        &lt;div class="post-summary"&gt;
            {{ .Summary }} &lt;!-- extrait automatiquement le premier paragraphe du fichier Markdown de l’article --&gt;
        &lt;/div&gt;

        &lt;p&gt;&lt;small&gt;&lt;a href="{{.RelPermalink}}" class="read-more-link"&gt;En savoir plus ››&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
    &lt;/li&gt;

    {{ end }}
&lt;/ul&gt;

{{ partial "pagination.html" . }}</span></code></pre>
<aside class="note note-info"><p>Ne faites pas attention au code HTML de cette boucle, ça fait
un moment que je n'ai pas travaillé sur mon site, il aurait bien besoin de
quelques améliorations. Le balisage sera bientôt mis à jour.</p></aside>
<p>C’est la partie <code>{{ range .Paginator.Pages }}</code> qui est vraiment importante ici.
{{&lt; marker &gt;}}Chaque <code>.Paginator</code> que vous utilisez dans une page d’index de
section va boucler et afficher les articles <strong>de cette section</strong>.{{&lt; /marker &gt;}}
<code>(.Paginator 5).Pages</code> indique à Hugo de ne lister que cinq éléments. Cette
boucle va parcourir tous les articles de la section <code>blog</code> et ne lister que les
cinq plus récents. Une boucle similaire dans le fichier
<code>layouts/workshops/index.html</code> bouclerait sur les ateliers stockés dans le
dossier <code>/content/workshops/</code> et afficherait la liste des ateliers dans l’index.</p>
<aside class="note note-info"><p>Je confonds encore quelques variables globales du site et des
variables de page dans Hugo. Ce que j'ai pour le moment me suffit, et si jamais
j'avais besoin de plus de flexibilité, d’options ou de fonctionnalités, il
faudra que je me replonge de nouveau dans la documentation pour arriver à tirer
de la logique d’Hugo plus qu'une simple boucle. Vous devriez en faire de
même.</p></aside>
<p>Et pour ce qui est du fichier partiel <code>pagination.html</code>, le mien ressemble pour
le moment à ça :</p>
<pre><code class="language-go-html-template hljs go">{{ $baseurl := .Site.BaseURL }}
{{ $pag := .Paginator }}

{{ <span class="hljs-keyword">if</span> gt $pag.TotalPages <span class="hljs-number">1</span> }}

&lt;nav class=<span class="hljs-string">"center pagination"</span>&gt;
    {{ <span class="hljs-keyword">range</span> $pag.Pagers }}{{ <span class="hljs-keyword">if</span> eq . $pag }}&lt;span class=<span class="hljs-string">"pagination__button button--disabled"</span>&gt;{{ .PageNumber }}&lt;/span&gt;{{ <span class="hljs-keyword">else</span> }}&lt;a class=<span class="hljs-string">"pagination__button"</span> href=<span class="hljs-string">'{{ $baseurl }}{{ .URL }}'</span>&gt;{{ .PageNumber }}&lt;/a&gt;{{ end }}{{ end }}

    &lt;div class=<span class="hljs-string">"clearfix"</span>&gt;
        {{ <span class="hljs-keyword">if</span> .Paginator.HasPrev }}
        &lt;a class=<span class="hljs-string">"pagination__button pagination__button--previous"</span> title=<span class="hljs-string">"Page précédente"</span> href=<span class="hljs-string">"{{ .Paginator.Prev.URL }}"</span>&gt;
            Articles plus récents
        &lt;/a&gt;
        {{ <span class="hljs-keyword">else</span> }}
        &lt;span class=<span class="hljs-string">"pagination__button pagination__button--previous button--disabled"</span>&gt;Articles plus récents&lt;/span&gt;
        {{ end }}

        {{ <span class="hljs-keyword">if</span> .Paginator.HasNext }}
        &lt;a class=<span class="hljs-string">"pagination__button pagination__button--next"</span> title=<span class="hljs-string">"Next Page"</span> href=<span class="hljs-string">"{{ .Paginator.Next.URL }}"</span>&gt;
            Articles plus anciens
        &lt;/a&gt;
        {{ <span class="hljs-keyword">else</span> }}
        &lt;span class=<span class="hljs-string">"pagination__button pagination__button--next button--disabled"</span>&gt;Articles plus anciens&lt;/span&gt;
        {{ end }}
    &lt;/div&gt;

    &lt;a href=<span class="hljs-string">"../article-archives/"</span> class=<span class="hljs-string">"button button--full"</span>&gt;Voir la liste de tous les articles&lt;/a&gt;

&lt;/nav&gt;

{{ end }}</code></pre>
<p>Libre à vous d’aller en apprendre plus sur les variables. Je trouve que le code
ci-dessus est compréhensible tel quel, mais encore une fois, si vous avez besoin
de plus de fonctionnalités, la documentation et le forum vous seront
probablement d’une plus grande aide.</p>
<h4 id="creer-une-page-d-archive">Créer une page d’archive</h4>
<p>En plus de la page de blog par défaut, je voulais ajouter une page d’archive qui
liste la totalité de mes articles sur une seule et unique page. Ce n'était pas
aussi évident que je l’aurais cru. La documentation ne m'a pas beaucoup aidée.
Et j'ai dû à nouveau faire des recherches. Je suis tombée sur
<a href="https://parsiya.net/blog/2016-02-14-archive-page-in-hugo/" target="_blank" rel="noopener noreferrer">cet article extrêmement utile</a>
et j'ai eu recours à la même technique que celle exposée par l’auteur.</p>
<p>Pour la page d’archive, j'ai créé une page statique dans <code>/content/</code> et je lui
ai donné un nouveau <code>type</code>: <code>archive</code>. La page utilise le modèle situé dans
<code>/layouts/archive/single.html</code>.</p>
<p>Dans le modèle de page, je boucle sur les articles comme pour la page d’index du
blog, mais avec une différence importante :</p>
<pre><code class="language-go-html-template hljs go">&lt;!-- /layouts.archive/single.html --&gt;

{{ <span class="hljs-keyword">range</span> where .Site.Pages <span class="hljs-string">"Type"</span> <span class="hljs-string">"blog"</span> }}
&lt;li class=<span class="hljs-string">"post"</span>&gt;
    &lt;a class=<span class="hljs-string">"post-title"</span> href=<span class="hljs-string">"{{.RelPermalink}}"</span>&gt;{{ .Title }}&lt;/a&gt;
    &lt;span class=<span class="hljs-string">"post-meta"</span>&gt;&lt;time&gt;{{ .Date.Format <span class="hljs-string">"January 2, 2006"</span> }}&lt;/time&gt; {{ <span class="hljs-keyword">if</span> .Params.External }} — &lt;span class=<span class="hljs-string">"post-host"</span>&gt;<span class="hljs-keyword">for</span> {{ .Params.External.Host }}&lt;/span&gt; {{ end }}&lt;/span&gt;

    &lt;div class=<span class="hljs-string">"post-summary"</span>&gt;
        {{ .Summary }} &lt;!-- récupère automatiquement le premier paragraphe de l’article --&gt;
    &lt;/div&gt;

    &lt;p&gt;&lt;small&gt;&lt;a href=<span class="hljs-string">"{{.RelPermalink}}"</span> class=<span class="hljs-string">"read-more-link"</span>&gt;Lire la suite ››&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
&lt;/li&gt;
{{ end }}</code></pre>
<p>En résumé : <code>.Site.Pages</code> boucle sur toutes les pages de votre
site. En d’autres termes, cela va lister tous les fichiers Markdown contenus
dans le dossier <code>/content/</code>. Pour indiquer à Hugo de n'afficher
que les fichiers situés dans la section <code>/content/blog/</code>, on “filtre” les pages
en précisant le <code>"Type" "blog"</code>. On procédera également de la sorte pour une
page d’archive d’une autre section, en utilisant le nom de la section comme
filtre. Et c'est tout.</p>
<h3 id="heberger-chez-netlify">Héberger chez Netlify</h3>
<p>J'avais choisi d’héberger mon site avec GitHub Pages depuis quelques années.
Puis est arrivé un moment où ça commençait à faire un peu juste. Il semble qu'il
y ait eu aussi régulièrement de curieux problèmes de cache et je devais pousser
deux fois les changements sur le dépôt pour que ces derniers soient pris en
compte (j'imagine que le cache n'était pas invalidé quand il devait l’être).
J'ai donc commencé à devoir créer des enregistrements vides juste pour vider le
cache et être capable de voir les changements que j'avais faits en production.</p>
<p>Maintenant, je ne suis pas certaine que c'était vraiment un problème de cache,
bien que ça y ressemblait beaucoup. Je ne sais pas non plus si quelqu'un d’autre
est capable de reproduire ce problème. Et non, je n'ai pas contacté le support
de GitHub à ce sujet. Je détestais tellement mon site Web que je me suis dit
"j'ai déjà assez bien de problèmes en local pour me soucier de ce problème en
production", j'en ai donc fait totalement abstraction.</p>
<p>J'ai pu aussi me rendre compte de l’ultra-rapidité de
<a href="https://www.netlify.com/" target="_blank" rel="noopener noreferrer">Netlify</a> quand j'ai travaillé sur Smashing Magazine.
De plus, Netlify permet de "rendre votre site ou votre application web bien plus
rapide en la servant au plus près des utilisateurs. Au lieu d’un serveur unique,
vous déployez sur un réseau global de nœuds CDN intelligents, qui gère aussi
l’unicité des assets, la mise en cache automatique des entêtes, les redirections
et les réécritures intelligentes."</p>
<p>Et en plus de tout ça, si vous êtes un développeur et que vous travaillez en
open source, Netlify vous offre un abonnement Pro à vie. Tout ce qu'ils
demandent en retour est un lien vers Netlify sur votre site ou votre
application. Pour moi ce ne fut pas un problème vu que je mentionne toujours où
mon site est hébergé dans le bas de page. J'ai donc signé pour la formule Pro.
Un hébergement gratuit et rapide ! Woohoo !</p>
<p>La configuration de votre site se fait en quelques clics :</p>
<ul>
<li>Créer un compte sur <a href="https://netlify.com" target="_blank" rel="noopener noreferrer">netlify.com</a></li>
<li>Relier son compte Netlify à son dépôt de code. Le mien est hébergé sur GitHub,
j'ai pu le connecter depuis l’interface de Netlify.</li>
<li>Spécifier le dossier de destination ainsi que la commande de build,
respectivement <code>public</code> et <code>hugo</code> dans mon cas. (Voir les captures d’écrans
ci-dessous)</li>
<li>Configuration de votre nom de domaine. Cela demande de faire quelques
changements de DNS.</li>
<li>Cela m'a demandé seulement 3 clics pour bénéficier d’un certificat SSL
renouvelé automatiquement et d’une connexion HTTPS pour mon site.</li>
<li>Et… c'est tout.</li>
</ul>
<p>Je devrais probablement mentionner le fait que j'ai rencontré quelques
difficultés lorsque j'ai fait la bascule, mais ce n'était pas de la faute de
Netlify. L'équipe de Netlify a même été super et m'a aidée à déboguer les
problèmes que je rencontrais. Après avoir effectué les changements dans la
console du registrar de mon domaine, cela a pris quelques heures pour que mon
site soit en ligne avec mon nom de domaine personnalisé.</p>
<p>Quelques bons trucs à savoir :</p>
<ul>
<li>Ajouter votre dossier <code>/public/</code> à votre fichier <code>.gitignore</code>. Netlify va
lancer la génération de votre site sur ses serveurs. Pour éviter de possibles
conflits, ne versionnez pas votre dossier de destination dans votre dépôt. Le
mien n'est présent que sur ma machine. Je rencontrais des problèmes de rendus
avec certains templates quand je le versionnais auparavant.</li>
<li>Vérifiez bien la version d’Hugo que vous utilisez (<code>hugo version</code>) et celle
utilisée par Netlify. Au début j'ai eu droit à des erreurs de build qui
empêchaient le déploiement, car ma version était plus récente que celle de
Netlify. Si c'est le cas
<a href="https://www.netlify.com/blog/2017/04/11/netlify-plus-hugo-0.20-and-beyond/" target="_blank" rel="noopener noreferrer">ajoutez une variable d’environnement à votre site</a>
qui correspond à la version d’Hugo que vous utilisez localement.</li>
</ul>
<p>Voici en partie à quoi ressemble mon tableau de bord Netlify :</p>
<figure>
<picture title="Paramètres de déploiement et variables environnement dans le tableau de bord de Netlify.">
<source type="image/webp" srcset="/thumbnails/768x/d33wubrfki0l68.cloudfront.net/9827bd9472d1606e4262dc9207669478e50a48c2/76bd7/images/article-assets/hugo-netlify/netlify-dashboard.6f9519dd4edb1e0432e42b6fb267c8c4.webp 768w, /thumbnails/1024x/d33wubrfki0l68.cloudfront.net/9827bd9472d1606e4262dc9207669478e50a48c2/76bd7/images/article-assets/hugo-netlify/netlify-dashboard.6f9519dd4edb1e0432e42b6fb267c8c4.webp 1024w" width="1024" height="586" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/d33wubrfki0l68.cloudfront.net/9827bd9472d1606e4262dc9207669478e50a48c2/76bd7/images/article-assets/hugo-netlify/netlify-dashboard.6f9519dd4edb1e0432e42b6fb267c8c4.avif 768w, /thumbnails/1024x/d33wubrfki0l68.cloudfront.net/9827bd9472d1606e4262dc9207669478e50a48c2/76bd7/images/article-assets/hugo-netlify/netlify-dashboard.6f9519dd4edb1e0432e42b6fb267c8c4.avif 1024w" width="1024" height="586" sizes="100vw">
<img src="/d33wubrfki0l68.cloudfront.net/9827bd9472d1606e4262dc9207669478e50a48c2/76bd7/images/article-assets/hugo-netlify/netlify-dashboard.6f9519dd4edb1e0432e42b6fb267c8c4.png" alt="" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="586" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAE90lEQVR4nOVaWZbcIAwsEe5/vLycxsoHiEUITLtpT5upxOONVUUJQZv+/vvHzIzjYDAzDj7A6TqcywMAOB4JHP5UzwAQABCBiOCI4JzDH+fgXLiWZ6TPkm/xOTcsXxdPq3uKafT7KiXV71Me6uRS762zlyKJgkmJQy3caYz5PPTYTE8lIUQgKlP2u7sSeqCAOdUs71JLxJjF4MsoSWD53xDAzH1STuBzPgqGivcOwKFTx9HWI6tJjkyIfcR6z0bWCjDXKrlWCMAEpvP+izcBkJUxUYMPBgk3DqEyIcIhuyem0KdTMlTlmgTXOVMke+huXjTok5Qh8HUBBDjAMXAwMjMEgCm6tRNQ7ZC0IkbkUEiwlJSnKEOQCAmuyIHBOMBwOMAuqoIJTHlkmNAGRDHaNSEw1CEubAERa5RB6XSHMgTeBSskg4OjQo7CYVE92WuYI7poeEUIWqVQbINJSLyuzjNYogyIOD6uDIEnFzMzwCAwDhwAXFJHrZAmTDRCt57LSRGWUkqe3GM3kgEmLFHgPWWQSlBcJmXo4tcpQ+AdUe4IATgcqI2vqgiseacipsrwzRmJpEoZUoua0uwqLatFvKiMUZAyE01mZeTGvkORr1wDczaeUmBv7qiJCPNClwh1XRKhSm3vB1arBlTsx5kymDm1zVzQFvlbztcrQ+BTw4sKiBFcFIJBuehgTqTcjz40EVW+Frp/WilTaKxvJyHjupemWw7HVAOxXoGX4V84rjCJscQWndGQCNFhLIrw9ZyIptCX7jvglps0MatJusehrYw1ccIIXuZR5kIhYMitKKR4OVZGpY6sklzEeCXTdPhNA/QUMbvb0JSnMq7mJ+5lcWUIBiF6rFYhhjLqNQaK8LUgmSb8SVnJ8P5eXCXvCrxMyqUKCDAVQmQoA6jWFSmdjpi00s7wsxwAaBUl+GTTfK4lbxyW6w2tEEr/EMnQCzzUisqlXnDAP8/KneoA0qSuRm7loWqFWHtSFWHqHmiLfxpudVn6Aamq7ShL9FE8Mcgpy5ROPZybj8OFk9BA2dbxsEZ/jGq7Kklp7+nDEN/QhldQhL3RxXN8EDGrkG/Dd7euj8ZlAZicQ+5o3hzMlf/trVgDkxC1RDzdt6nC4hshtX3T4HgXtkIEapVNU9sfAeb+1yJoIjbiY0xIXMNPb/R9WikNETtJI6JLiMzv1jLlFDFCWBXiSuAhkeCOyhD0FSIr9ws7nKOFFKl0I9SKoK2VITAWhgFpK/CKQiZh7ROVz6m40WTsSkmrENXhVb8BjJTR3dsdBRGbqiQR0h2tKxRCxm3nk6IpMvpFPx7Vbi9gjOQ3FKKzsXp5Os/MbL9sphTfU8Y1pDgo+n9q33L7lXxbAubIsPI9HH6oDP1iFlRf1so4N/QlUjZRynil/io6NqE4Ec2osCRjYRMeA2/9mETIP+G+C5ZCC8ysI6oUv0gpaxUC9dtzJ2ZOWyyjcsieh2bxVFqMdciagvNXP6+rQ+M3zSmJkJWL8eGnmYU6zDp7Rv8lSvGWUVaRM1r4Xflw4DcopXJZn/oAofxEzrpG8ewUmyvlIy6rB03GVSPtrBQP3P9pjv4k6JKpNlXK8rB3Fis+PttRKT9GyDJsppTnE4K9lLIFIQC2Uco+hGAPpXw3IVe+D364Ur6WEML1rfgnK+U7CbF+aXyzjOlsl3Ktw9cRUimjebm/Uv4D7I5/+Oja+a4AAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/d33wubrfki0l68.cloudfront.net/9827bd9472d1606e4262dc9207669478e50a48c2/76bd7/images/article-assets/hugo-netlify/netlify-dashboard.6f9519dd4edb1e0432e42b6fb267c8c4.png 768w, /thumbnails/1024x/d33wubrfki0l68.cloudfront.net/9827bd9472d1606e4262dc9207669478e50a48c2/76bd7/images/article-assets/hugo-netlify/netlify-dashboard.6f9519dd4edb1e0432e42b6fb267c8c4.png 1024w" sizes="100vw">
</picture>
<figcaption>Paramètres de déploiement et variables environnement dans le tableau de bord de Netlify.</figcaption>
</figure>
<p>J'aime aussi le fait que Netlify propose des options pour optimiser et assembler
les assets pour vous, afin d’améliorer les performances globales de votre site.</p>
<figure>
<picture title="Options d’optimisation des assets dans le tableau de bord de Netlify.">
<source type="image/webp" srcset="/thumbnails/768x/d33wubrfki0l68.cloudfront.net/341e3023bff0c722f41c37b91c18c9d04fa612c5/35119/images/article-assets/hugo-netlify/netlify-dashboard-2.c93793deba949acdb3050462f5be9088.webp 768w, /thumbnails/1024x/d33wubrfki0l68.cloudfront.net/341e3023bff0c722f41c37b91c18c9d04fa612c5/35119/images/article-assets/hugo-netlify/netlify-dashboard-2.c93793deba949acdb3050462f5be9088.webp 1024w" width="1024" height="366" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/d33wubrfki0l68.cloudfront.net/341e3023bff0c722f41c37b91c18c9d04fa612c5/35119/images/article-assets/hugo-netlify/netlify-dashboard-2.c93793deba949acdb3050462f5be9088.avif 768w, /thumbnails/1024x/d33wubrfki0l68.cloudfront.net/341e3023bff0c722f41c37b91c18c9d04fa612c5/35119/images/article-assets/hugo-netlify/netlify-dashboard-2.c93793deba949acdb3050462f5be9088.avif 1024w" width="1024" height="366" sizes="100vw">
<img src="/d33wubrfki0l68.cloudfront.net/341e3023bff0c722f41c37b91c18c9d04fa612c5/35119/images/article-assets/hugo-netlify/netlify-dashboard-2.c93793deba949acdb3050462f5be9088.png" alt="" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="366" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAADc0lEQVR4nO2bW7LjIAxEpZT3v8e5G+n5AGHAiLeTQHyqMnZysQVqmhjI8L+/P5ACE5sja0dbquk4F6kLsdS2L5bWRiK/ref72utLx5hDU4OJCARiYoJajRZAUKWvJ26IqxvgZa0+lpaY4HaXiMEd6gJVcmg1B5nKmgZLs+Pj+4GtbyqRehJ7YxExw55rzpgXj6jokMcZYcTgDnWBGtEdEozJn2c/Z6TznnXIaMDHGe2kHXLH41AnuzpD60BJh7TEvFbwccYIR9yg3h4w21T7OeOMlePQek6J+DLMsAX9rjOEdkFurt+uzqhFFeTyKSufk2/DUZecSx6/5gyhKIisQ0WfunImQXOGK5dwZmIvc7/gDOF4FSKLHmz+obDnyHx+ljA2higA2J6+vzOE4ndI7BS+iGIaiykuARFsUgFCIMqEu3+xM4SiQwStmOu5cF4ZqI7nELvODM+F+fqt7Qyh67HXdlwH6BRjzCcgBhkRGMHi8ohLVnCGUCHI9TviMp4jLDMmChNz+OWOuAf4pTdxhnDk9DA50IcMwMwbcBZ2JXtEsV/f9uJzA0CGrtZevJIzhCPXQ1xP9Ug6JRLFl6PPLbJPqV+/mzME1SGlx0z5O84T9cIWUZxLmIggw5d/LN9jRWcIikNqUxg74vLIVX1Hf2BkKwYYTpTSHubqzhACh2Q6+e34KTdvQAwmeEqVevXKzhBeJg3ymkurrn55iMkKvUP/M6LXGrxyOuRykZqxz+Jcq0Qwx1HLO1vBPvldX6vwar2AuTxJG0kAUufAJbm7OUOoEkRrvBGH6NbhLnIJ3EIjtnKG0OwQn7jRsxKQcwlFYoR7Mes6QzhaCueHifm4h1xZPnGVuM5PYr79aUqj0yG4xRl6tDhQetha2RlCk0OE8pZtosf2BAqCkpmTyAnLB+FkcVVnCF2CCFdnpDfeR3MUXB/oYX+dP3ET69MMCXJyjxDavZI7yZswJAgzn9vfVFptGiO+py/Kue+/Pl2CfGJ0KAqyCZ0Ouee/p+UjJt7/uiApEd4lTFKQDWl0yPudcUbOv9+FKkFqRLg7QbsKEFPpkDAdOYF+JXF3kRVkk7nWUhQcUviZ6SPYdJKCPIn+HIpD2hR5BJxHIMiT2M8TOWRQEU6ePjRgBHmy9zVMWn5PwduswL6ToR85PMznRocIj1Na+A+H4gaghecu9gAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/d33wubrfki0l68.cloudfront.net/341e3023bff0c722f41c37b91c18c9d04fa612c5/35119/images/article-assets/hugo-netlify/netlify-dashboard-2.c93793deba949acdb3050462f5be9088.png 768w, /thumbnails/1024x/d33wubrfki0l68.cloudfront.net/341e3023bff0c722f41c37b91c18c9d04fa612c5/35119/images/article-assets/hugo-netlify/netlify-dashboard-2.c93793deba949acdb3050462f5be9088.png 1024w" sizes="100vw">
</picture>
<figcaption>Options d’optimisation des assets dans le tableau de bord de Netlify.</figcaption>
</figure>
<p>J'ai constaté quelques améliorations et plus de A verts sur la page de résultats
sur <a href="https://webpagetest.org" target="_blank" rel="noopener noreferrer">webpagetest.org</a> alors qu'ils étaient rouges
auparavant. J'ai encore du travail de ce côté-là.</p>
<h3 id="resume-de-ma-configuration-actuelle">Résumé de ma configuration actuelle</h3>
<ul>
<li>Le code source du site web est hébergé sur GitHub,</li>
<li>J'utilise Hugo comme générateur de site statique,</li>
<li>Déploiement automatiquement à chaque <code>push</code> sur le dépôt grâce à Netlify,</li>
<li>Hébergée gratuitement chez Netlify avec le plan Open Source.</li>
</ul>
<p>Il est également utile de mentionner que désormais la compilation complète de
mon site après chaque changement, sans avoir à filtrer de vieux contenus, prend
à Hugo moins de 40 millisecondes. {{&lt; marker &gt;}}Hugo met 39ms à compiler mon
site pour être plus précis{{&lt; /marker &gt;}}, là où Jekyll, même avec des options
comme <code>--incremental</code> mettait plusieurs <strong>minutes</strong>.</p>
<h3 id="objectifs-futurs">Objectifs futurs</h3>
<p>On retrouve ici quelques-unes des choses qui figurent sur ma TODO liste depuis
quelques années et que j'avais jusqu'ici remis à plus tard, en partie à cause de
la situation dans laquelle je me trouvais précédemment avec Jekyll :</p>
<ul>
<li><strong>Lancer une mailing-list.</strong> C’est prévu d’ici la fin du mois.</li>
<li>Une nouvelle section pour les articles qui ne rentrent pas dans la section des
articles techniques.</li>
<li>Améliorer la qualité du code du site pour ne plus être embarrassée et rendre
le dépôt public sur Github.</li>
<li><strong>Rendre le site disponible en mode offline.</strong> Et le rendre encore plus
<em>rapide</em>.</li>
<li>Il y aura une <strong>FAQ</strong> mais pas au format des AMA (Ask Me Anything) qu'on
trouve sur GitHub. Il y a des aspects que je n'aime pas dans ce format. Plus
d’informations et de détails dès que la lettre d’information paraîtra.</li>
<li><strong>Écrire plus régulièrement.</strong> Je laisse beaucoup trop d’idées de côté que je
devrais transformer en articles de blog. Je me suis promise d’écrire plus
souvent, même si ces idées d’articles ne sont pas aussi poussées que
d’habitude. Cet article est un début.</li>
</ul>
<h3 id="quelques-mots-de-conclusion">Quelques mots de conclusion ?</h3>
<p>Je laisserai à Agnès le soin d’exprimer ce que je ressens vis-à-vis de cette
nouvelle configuration, même si je sais que je peux et que j'améliorerai encore
quelques trucs dans le futur :</p>
<figure>
<iframe src="https://giphy.com/embed/uHSbNh58qwIwM" width="480" height="264" frameborder="0" class="giphy-embed" allowfullscreen></iframe>
</figure>
<p>Au moins maintenant je dispose d’un système qui m'évitera des maux de tête à
chaque changement que je voudrais apporter à mon site Web. Je prends de nouveau
plaisir à écrire des articles de blog, ce qui veut dire que vous pouvez vous
attendre à de prochaines publications dans les semaines à venir.</p>
<p>Merci de m'avoir lue jusqu'ici.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn:1">
<p>NdT: Il est vrai que le temps de compilation de Jekyll peut excéder plusieurs minutes quand vous compilez des centaines de pages, cela dépend des plugins que vous utilisez et de l’optimisation de vos templates Liquid. À titre de comparaison, pour ce blog, il n'excède pas les 10 secondes par défaut et à peine plus d’une seconde avec l’option <code>incremental</code> activée.&#160;<a href="#fnref1:1" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
<li id="fn:2">
<p>NdT: Pour la petite histoire c'est Tom Preston-Werner, le créateur de Jekyll qui est à l’origine de <a href="https://github.com/toml-lang/toml" target="_blank" rel="noopener noreferrer">TOML</a> (d’où son nom). Vous pouvez <a href="https://learnxinyminutes.com/docs/toml/" target="_blank" rel="noopener noreferrer">apprendre TOML en quelques minutes</a>, <a href="https://learnxinyminutes.com/docs/fr-fr/yaml-fr/" target="_blank" rel="noopener noreferrer">même chose pour YAML</a>&#160;<a href="#fnref1:2" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
<li id="fn:3">
<p>NdT: C’est inexact, Jekyll offre la possibilité de créer ses propres types de contenu avec les <a href="https://jekyllrb.com/docs/collections/" target="_blank" rel="noopener noreferrer">collections</a>.&#160;<a href="#fnref1:3" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
</ol>
</div>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2017/05/29/configurer-netlify-cms-pour-jekyll/</id>
    <title>Configurer Netlify CMS pour Jekyll</title>
    <published>2017-05-29T00:00:00+00:00</published>
    <link href="https://jamstatic.fr/2017/05/29/configurer-netlify-cms-pour-jekyll/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Les outils de gestion de contenus connectés aux générateurs de site statique continuent d’évoluer. Lors de <a href="/2017/03/17/smashing-mag-va-dix-fois-plus-vite/">la refonte de Smashing Magazine</a>, Netlify la startup basée à San Francisco spécialisée dans l’hébergement et le déploiement de sites statiques a développé un <a href="https://www.netlifycms.org/" target="_blank" rel="noopener noreferrer">CMS headless</a> pour faciliter la contribution des rédacteurs. Ce <a href="https://www.netlify.com/blog/2017/03/17/an-open-source-cms-with-a-git-centric-workflow/" target="_blank" rel="noopener noreferrer">CMS open source</a> est simple à configurer, cela ne vous prendra que quelques minutes. Dans cet article, nous utiliserons <a href="https://jekyllrb.com/" target="_blank" rel="noopener noreferrer">Jekyll</a>, le générateur le plus populaire, sachez que le principe est similaire pour <a href="https://gohugo.io/" target="_blank" rel="noopener noreferrer">Hugo</a> ou d’autres générateurs.</p></aside>
<p>Nous partirons du principe que vous avez une installation de Jekyll déjà fonctionnelle, dans le cas contraire, reportez-vous à la <a href="https://jekyllrb.com/docs/installation/" target="_blank" rel="noopener noreferrer">documentation officielle</a>. Nous présupposons également que vous versionnez votre projet avec Git et vous poussez votre code sur <a href="https://github.com" target="_blank" rel="noopener noreferrer">GitHub</a>, <a href="https://gitlab.com" target="_blank" rel="noopener noreferrer">GitLab</a> ou <a href="https://bitbucket.org/" target="_blank" rel="noopener noreferrer">BitBucket</a>.</p>
<h2 id="configurer-netlify">Configurer Netlify</h2>
<p>La première étape est de <a href="https://app.netlify.com/signup" target="_blank" rel="noopener noreferrer">se connecter chez Netlify</a> afin de pouvoir relier votre dépôt Git à ce service d’hébergement et de déploiement continu. C’est <strong>gratuit</strong> et si vous travaillez sur un <a href="https://www.netlify.com/open-source/" target="_blank" rel="noopener noreferrer">projet open source</a>, vous pouvez utiliser la formule pro.</p>
<figure>
<picture title="Ajout de site, étape 2 : choix du dépôt.">
<source type="image/webp" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346664/new-site-netlify.bfded9f2af15897dd2e4ee5c10707253.webp" width="724" height="433">
<source type="image/avif" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346664/new-site-netlify.bfded9f2af15897dd2e4ee5c10707253.avif" width="724" height="433">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346664/new-site-netlify.bfded9f2af15897dd2e4ee5c10707253.png" alt="Ajout de site, étape 2 : choix du dépôt" loading="lazy" decoding="async" class="dark:brightness-90" width="724" height="433" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Ajout de site, étape 2 : choix du dépôt.</figcaption>
</figure>
<p>L'ajout de site se fait en quelques clics, il n'y a qu'à sélectionner le service utilisé (GitHub pour nous), définir la branche (<code>master</code> dans notre cas) et la commande de build utilisée (<code>jekyll build</code> pour Jekyll) ainsi que le dossier de publication(<code>_site</code> par défaut avec Jekyll). Une fois le site configuré, nous allons pouvoir nous occuper d’autoriser l’édition de contenu via Netlify CMS.</p>
<figure>
<picture title="Ajout de site, étape 3 : configuration du déploiement.">
<source type="image/webp" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346618/deploy-settings-netlify.6de60ca2d5f9dba4734495a7ed9e8283.webp" width="697" height="478">
<source type="image/avif" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346618/deploy-settings-netlify.6de60ca2d5f9dba4734495a7ed9e8283.avif" width="697" height="478">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346618/deploy-settings-netlify.6de60ca2d5f9dba4734495a7ed9e8283.png" alt="Ajout de site, étape 3 : configuration du déploiement" loading="lazy" decoding="async" class="dark:brightness-90" width="697" height="478" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAEIklEQVR4nO2b65arIAyFd7p8/+ecp2jmBwRCuIgIba3d68zYURHM5iOCPfT398dYICKqHYE7ZLexTL1svY7aZ6nz1eq8hUwb81w/JBjM3B3YUek62vUt6XM7Grv3bXIrEqVmU6XXMGzj9zrJntGx/PvIGO3o0wj5kSE6d+/TCNH9/EfGuM4TIomYGUy0NBTfTIboPCG+ozO8KeHAj4wRnSOECACDmKIpU5qV6g5kiM4RwgwQgcEgHwt+Bxmhm+43eZZmkyE6TQgFU+bT0U3G5KD0ac3weJoQFlNAlTa+gIxSlYu0igzRBEIwvbPckQzRduaW3KMuACYQMTjbut4uQV1ORlag77QerSZDdHIeQnApvX7cRqUV9DuTITo9MWyPWOX8YQM9nYxSMwb1KjJEzpCRuggAGGACisOVe/zVw1XNiPVkvH7COKrt2TSkEQCW+YbMQWrvOZxBdmvfX5SMkF6Z7e+9u90SdaPiutzhyk5p42erxoZZPmkDLpOAcmPcP2MQkdvnzoghcTg5c5CGyi5W9ltSCfgHA+MIqaphiNpP5M7zy4xNQ8j9Sn/gw6wXKsO1bfS48KnR/kx9C6DveGwAdpN6X36JpCAhJTGkQIb9eUBV51eOczpU26oqhPyDqdAKhpRv71jClzwvxsDPQ8gn+kjFo+jGE3nIAz+Sr9SxuuzRta8FZmqqIckieHhSdXkhpYNzPzi9jqwAsD+/r4tT888raKohqlRQGQy2cOD5VOaFFWRSpsgFW1E2k9ALOpLkkJmUiELgyZsDwsOcZcuEuYtbzC/Wk++l1sHLaNM9zk/NkG7o0A0WTzU7Y4/n5O9jz/y2XX7OcmU3AGyrvyESn4DTPJKI4WgwC5HZFpEaNo/aF/chaJkh+qrJPAQomwIoVBCR0ab4vMK0/rXxuzTVEHulMEEMn+O8JEwiC+XsFSnf9bU6ZUipJKlflO7ITAlnUc82XiB8B3i45Z+rQ4bUzqTkYNsIkCoTjIllugzpbvH1tGtIm4K4R+/TgU+DHT/oZ7uyYT9DmqLsgw28/lsFTucLO1VIApzTkxmSteZdS4DrtD0yP3YMIhvAfLiJp6ZDVkqD7NKsZJVUDOls6wVVJ6Rxr+UebAwC0uCXhpviEGRJ6WnU95CyHe9j+fuOahKGyQu1HFCk5FibvkXJ0kmPsqcjvSWCHaYSE4aTcm+J65MyQAh091dbSugoGrH8Ken6pBwmJJd95FWBn0JHrKetuXS8y9oT3+0dHHho1Xrs9ekAgO10n9VkvGSpz15/bd5YvBieaen/wpXV2qKmkfIdZIiGDMnJMGoZcVGtfm8kWktIj5bllGtq2JAiGTfQalLeT4joRwqAAUPuSobVKlI+hxDRzUk5ZMidA1XTbFKmE2KXucYvdE9SPm/IuqhmkfIP4XmsdosCVCcAAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Ajout de site, étape 3 : configuration du déploiement.</figcaption>
</figure>
<h2 id="authentification-via-github">Authentification via GitHub</h2>
<p>Maintenant il nous faut <a href="https://github.com/settings/applications/new" target="_blank" rel="noopener noreferrer">créer une nouvelle application Oauth sur GitHub</a> (ou le service que vous utilisez) et de mentionner <code>https://api.netlify.com/auth/done</code> comme URL de callback d’authentification. Vous donnez ainsi l’autorisation à Netlify CMS d’accéder aux fichiers du dépôt.</p>
<figure>
<picture title="Configuration de l’application Oauth dans GitHub.">
<source type="image/webp" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346686/edit-oauth-app-github.6f3a30f7840e90c1e5578d0d38a69ccd.webp" width="551" height="602">
<source type="image/avif" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346686/edit-oauth-app-github.6f3a30f7840e90c1e5578d0d38a69ccd.avif" width="551" height="602">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346686/edit-oauth-app-github.6f3a30f7840e90c1e5578d0d38a69ccd.png" alt="Configuration de l’application Oauth dans GitHub" loading="lazy" decoding="async" class="dark:brightness-90" width="551" height="602" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAEEklEQVR4nO1c7XasIAyc9Oz7v2DPfZXm/oCE8KlWYMHunG5ZBBEZh2iCS9/f/xh3QAAR+Y/93vjA1YNJ3wPpg/9OACHpl2w7lcbtwp9rK03xun1OrP9gu1SpqB39oIz7hCAa6uoWvRrTmnxPoNdhr/xeYP93v+0uhMQqAVrdYVPvvdPVmuhDCMqaSLemGumvjgOCu6oC6KkMwas2lJfBhLytuHuc5D4KyfHqcpESQOBwpWRgqRbt1E8h1MxWNt1Af2UIutkQJoAyWyIg5CUfhZTQzYaAAVBLJU5FoToBtxVC0dfx9I5ThqAfIVBOUDfxlNaeNJD7oCshZ1Ti6nkyfn1D8TxlCPoSgmOVAAATgZh96u1P745siu6EyExUpeOW3ZipDNZkhjIE/QmBexonJs0h+5Zi3gmvjiGEuNvgxOG4mTJcd+dfKGMIEajqrRFvpR5/WCnjCBGV3HrUmKwM7a6Nk8zFcIXwZUb+tlK+phxFiLmUwlyxAzsGBrN8Rh/vGHMI+eA0xk5Zgktx6did0n/Kss8XoYOr+DmHEyILCM6mSkZiP86NVzseswPGEqKDfUSEDef2cTiWHP6pKla8aRhKCAF+aRD80pecAFWG7FFRiG0zx/7KEAyesgg2kh6IQbAXSEmJy07DOim5drP9vueLsxhMiB8W+oIlRgY8DEyDjKZSnqMMwVBC1CHCbCRiyQhqCaAkORjkQ2Xs5bgc/6RuxzdxWYUCIFdJbZs0VGxkezQJ6RLx9l5etopID6Js+b2USA57lJ6iTyhjZXtRwgtITrSj7yBaqsUAxesWQeTzbJXgw0F+m7jxy+O62WifgFPIAQm/jnybK12vcCJHjDbq8mpMtJ5XR3iwNg0fKGMTe1FCNGX1dqyF+LrLMQD8MH4onkpEA7pMiBDkFdt4vwM9Ng4/3HWSr3f4gd60siXGWPzI+DsrEiuEkYda9laGYIpzMYgkxA7JsxFSsReVG1175/XglSpzvL1IQurMfukpZ0uC6qMsBW4aCwpZx1PbAy/ATRsz3pspL9e6dmCiZytEA1Q08QSjaKCmfJi6hM1OB9iQsa+15M7FlEqjT3laDXZtpKY+74d0QmGMC3ETn/eZOI3JWOtiOwc16u8M8OcDH16xRqXsiJTI1b8RFlJIeI8daJBwkpQdyQCsQt5wcFFG/uMCUlonxj6XlEjRaW+zeeu1hEAqhNgAVunXFvyuCFv2VIXFtAdDi6AM6E9toKQQO/gZSUYhmeHPadlFKW8hJEM090s+IcLX001h1yTdY+BrmE5Ipg5LBoQISojQfwWFVI5TIWZ1wtZQCABEiiiXUUEhT8NUQorqMCohpC75RkP1bFy2mVIWUkgMIabsAH6uUqYR0lRHKLjeaD0bl22ilP/hNUpUnZm8lwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Configuration de l’application Oauth dans GitHub.</figcaption>
</figure>
<h2 id="ajout-des-fichiers-de-l-admin">Ajout des fichiers de l’admin</h2>
<p>Maintenant que l’authentification est configurée nous pouvons ajouter un dossier <code>admin</code> à la racine de notre dépôt qui contiendra deux fichiers : <code>index.html</code> et <code>config.yml</code>.</p>
<p><script src="https://gist.github.com/DirtyF/efa029c00cccf7c45300d5f10b0afd7c.js"></p>
<p>Comme vous pouvez le voir ce fichier <code>index.html</code> se contente d’appeler des fichiers JS et CSS distants, le fait d’utiliser <code>@latest</code> vous permet de bénéficier automatiquement de la dernière version, il n'y aura donc aucune mise à jour à faire 😃.</p>
<p>Le fichier <code>config.yml</code> contient le chemin vers votre dépôt GitHub (à adapter donc à votre cas de figure), ici l’option <code>editorial_workflow</code> est activée mais vous pouvez commenter la ligne si vous n'en avez pas l’utilité.</p>
<figure>
<picture title="Aperçu du workflow de publication de Netlify CMS.">
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346696/editorial-workflow-netlify-cms.21dc53ad2ff8769f0406fcf43c4aa614.webp 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346696/editorial-workflow-netlify-cms.21dc53ad2ff8769f0406fcf43c4aa614.webp 828w" width="828" height="519" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346696/editorial-workflow-netlify-cms.21dc53ad2ff8769f0406fcf43c4aa614.avif 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346696/editorial-workflow-netlify-cms.21dc53ad2ff8769f0406fcf43c4aa614.avif 828w" width="828" height="519" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346696/editorial-workflow-netlify-cms.21dc53ad2ff8769f0406fcf43c4aa614.png" alt="Aperçu du workflow de publication de Netlify CMS" loading="lazy" decoding="async" class="dark:brightness-90" width="828" height="519" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346696/editorial-workflow-netlify-cms.21dc53ad2ff8769f0406fcf43c4aa614.png 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346696/editorial-workflow-netlify-cms.21dc53ad2ff8769f0406fcf43c4aa614.png 828w" sizes="100vw">
</picture>
<figcaption>Aperçu du workflow de publication de Netlify CMS.</figcaption>
</figure>
<p>Vous pouvez préciser le dossier dans lequel vous sauvegardez vos images, ici elles vont dans le dossier <code>assets/images/</code>.</p>
<p>La dernière section <code>collections</code> recense les champs habituellement utilisés dans les variables Front Matter des <a href="https://jekyllrb.com/docs/collections/" target="_blank" rel="noopener noreferrer">collections</a> que vous souhaitez pouvoir éditer dans l’interface du CMS. Vous pouvez <a href="https://github.com/netlify/netlify-cms/blob/master/docs/quick-start.md#collections" target="_blank" rel="noopener noreferrer">personnaliser cette section</a> en fonction de vos besoins et ajouter les widgets dont vous avez besoin.</p>
<figure>
<picture title="Édition des champs personnalisés d’un article.">
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346656/netlify-cms-edit.e6c3ec27e116e8a01f5d6cde4c660682.webp 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346656/netlify-cms-edit.e6c3ec27e116e8a01f5d6cde4c660682.webp 1024w" width="1024" height="640" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346656/netlify-cms-edit.e6c3ec27e116e8a01f5d6cde4c660682.avif 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346656/netlify-cms-edit.e6c3ec27e116e8a01f5d6cde4c660682.avif 1024w" width="1024" height="640" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346656/netlify-cms-edit.e6c3ec27e116e8a01f5d6cde4c660682.png" alt="Édition des champs personnalisés d’un article" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="640" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346656/netlify-cms-edit.e6c3ec27e116e8a01f5d6cde4c660682.png 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346656/netlify-cms-edit.e6c3ec27e116e8a01f5d6cde4c660682.png 1024w" sizes="100vw">
</picture>
<figcaption>Édition des champs personnalisés d’un article.</figcaption>
</figure>
<p>Une fois les champs personnalisés ajoutés, il ne vous reste plus qu'à les enregistrer dans votre projet et à pousser le tout. Grosso modo ça revient à taper quelque chose comme :</p>
<pre><code class="language-sh hljs bash">git add admin
git commit -m <span class="hljs-string">"Admin de Netlify CMS"</span>
git push</code></pre>
<h2 id="acceder-a-l-administration">Accéder à l’administration</h2>
<p>Ce commit va déclencher un build et un déploiement sur Netlify et vous devriez maintenant pouvoir accéder à <code>https://votredomaine.com/admin/</code>. (Notez que ça ne marchera pas en local à l’instar de plugin comme <a href="https://github.com/jekyll/jekyll-admin" target="_blank" rel="noopener noreferrer"><code>jekyll-admin</code></a>).</p>
<p>Après avoir été authentifié via GitHub, vous avez maintenant accès à l’interface d’édition des contenus. L'UI est encore très sommaire, mais c'est fonctionnel et ça fait le job, cet article a été en partie rédigé via le CMS.</p>
<p>Netlify est en train de travailler sur son <a href="https://styleguide.netlify.com/" target="_blank" rel="noopener noreferrer">Styleguide</a> et à n'en pas douter son CMS devrait en bénéficier quand il sera plus abouti.</p>
<figure>
<picture title="La liste des articles dans Netlify CMS.">
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346629/netlify-cms.930180b71ea02f3a446c2b73bb2bbe89.webp 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346629/netlify-cms.930180b71ea02f3a446c2b73bb2bbe89.webp 1024w" width="1024" height="640" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346629/netlify-cms.930180b71ea02f3a446c2b73bb2bbe89.avif 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346629/netlify-cms.930180b71ea02f3a446c2b73bb2bbe89.avif 1024w" width="1024" height="640" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346629/netlify-cms.930180b71ea02f3a446c2b73bb2bbe89.png" alt="La liste des articles dans Netlify CMS" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="640" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346629/netlify-cms.930180b71ea02f3a446c2b73bb2bbe89.png 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346629/netlify-cms.930180b71ea02f3a446c2b73bb2bbe89.png 1024w" sizes="100vw">
</picture>
<figcaption>La liste des articles dans Netlify CMS.</figcaption>
</figure>
<p>Dans notre exemple, nous avons un site Jekyll tout ce qu'il y a de plus simple, avec la collection par défaut, celle des posts (renommés Articles dans notre interface via le fichier de configuration). Si vous avez défini d’autres collections dans votre fichier de configuration, vous pourrez également les gérer depuis le CMS.</p>
<p>Si vous souhaitez donner l’accès à plusieurs collaborateurs, rendez-vous sur <a href="https://app.netlify.com/" target="_blank" rel="noopener noreferrer">app.netlify.com</a> dans l’onglet access de votre site et ajoutez autant de collaborateurs que vous le souhaitez.</p>
<figure>
<picture title="Configuration de l’accès au site Netlify.">
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346691/access-netlify.093173f47e307d3fa49b4c5fa51e03ac.webp 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346691/access-netlify.093173f47e307d3fa49b4c5fa51e03ac.webp 843w" width="843" height="202" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346691/access-netlify.093173f47e307d3fa49b4c5fa51e03ac.avif 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346691/access-netlify.093173f47e307d3fa49b4c5fa51e03ac.avif 843w" width="843" height="202" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346691/access-netlify.093173f47e307d3fa49b4c5fa51e03ac.png" alt="Configuration de l’accès au site Netlify" loading="lazy" decoding="async" class="dark:brightness-90" width="843" height="202" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAADk0lEQVR4nO2a23asIAyGky7f/2F375t9AcEAQRBQgzP/qoNVIE7Cx8nBf7+/BEci8slxtjcJEdX/S+lMbfBBjm4VN76aw6c2Um9rO1vlm0m5osWf1ZcQoSfJYIunCWG9iRQLZLC+hIANMljdhLBWJsUSGayPJsQSGaxhQlgrkWKRDNZHEmKRDNY0QliWSbFMBuujCLFMBms6ISxLpKxABusjCFmBDNY0QkqGnyRlJTJY0wixyNlKZLCGCWk1fCcpK5LBGibkS4bXIBmsbkJ6DWtO6HIMYvYMK5PB6ibkTKmSozgQQy1VBGZlMlhDY8jI+EFEUUDInTQYLThAIWZFDY0htZItXQgHhqC95Wq1ojNYtTVNk8lgbX+DFWDhHKDchYQgyANqpCD/KbdQ5lpa2xXTUY0MtduSRwMpea1KN6VEZAUyWNusZ0UAIN+CjwZX53goH0Ahz94pYtIbYZT4muFaV92jIUIQnMtCMMj/n3ZRSlkSaUwKHyQCKy1C5vMwy0qfaSEyWMNdlgxGdVANKYXPcZEPgLNNRE0DvFV1BwT9R+QMSFoo5+u1UXVqsJDfmdYX39sRdgUkBIO804iAEAHdBb6xpyzRDVVtHHpgH1tk3VywHCb72noLBl8nwSAiwCQtqeg07gd1yxDQlNfSwJ/4LvozPDNF+Bkq7Qfx9BfypS2RJieh7wIRskOZs4Va5fR5ZXUTkqo0fhyuKbTmRwiE5IKiFN2LlEkZ3RsrPdodmhaQcSUrcQIgpDRHlO5ZfT7am8Kik6xrAxI33qNWi4BIYjEj0ooBQgIkzCbRyrB/rIfJYG1PPUC0EK/kzLIdlFNnWEac3aJLCJEDPa8lgg8FKWFl7mcHalqqn88ovh46LTHEHAbCWLC2q/tanv5m191NzhTNkPY05Gy3B8miVMqY8zUNbS6mMyWNjFSSlGia2vGyqkRKk4wGZzv/TXa5rgH9GBwvzkpkyLK1uk8MNOCmaHtg9o0CZbEK9gLBGtt+dxtVIPbdH/mhQVga8laOaZcfaxvZZAh0AAER+lY5LyinKHnLO/WuX+CED0HHRa2yKR7qtsqaOkVI8oKOdxfTO18NqGn7vTR9vEPFPa+XqhqQyBc3D9ionL1dhjYXCzoVi/UDZzYgXXSE7WBsSw3KZEB4DZ1uv7eVzUNZSi3qsYBkr3mTd/L+rUZHxTwRwGBHS9NzKzJDSPa2UX9h2FSPTFfTf3+cN00vBQYfAAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346691/access-netlify.093173f47e307d3fa49b4c5fa51e03ac.png 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346691/access-netlify.093173f47e307d3fa49b4c5fa51e03ac.png 843w" sizes="100vw">
</picture>
<figcaption>Configuration de l’accès au site Netlify.</figcaption>
</figure>
<h2 id="et-voila">Et voilà !</h2>
<p>Félicitations, vous venez d’ajouter une interface d’administration pour la gestion de vos contenus gérés à l’aide d’un générateur de site statique. Vos collaborateurs peuvent se focaliser sur la rédaction et l’édition de contenus, sans avoir à se préoccuper des commandes Git ou du déploiement, tout est automatisé ! Vous bénéficiez d’un workflow de publication de type Kanban si vous le désirez et Netlify va jusqu'à générer une <a href="https://www.youtube.com/watch?v=s_4UL9oAcVE" target="_blank" rel="noopener noreferrer">URL unique de prévisualisation</a>
accessible depuis GitHub pour chaque pull-request créée. Elle est pas belle la vie ?</p>
<figure>
<picture title="Lors de la sauvegarde d’un nouvel article, une pull request est créée sur GitHub avec un lien vers une URL de prévisualisation.">
<source type="image/webp" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346656/pull-request-netlify-cms.dde0a3ea3a265f85b7d54ef7c4bf9e1f.webp" width="712" height="777">
<source type="image/avif" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346656/pull-request-netlify-cms.dde0a3ea3a265f85b7d54ef7c4bf9e1f.avif" width="712" height="777">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346656/pull-request-netlify-cms.dde0a3ea3a265f85b7d54ef7c4bf9e1f.png" alt="Lors de la sauvegarde d’un nouvel article, une pull request est créée sur GitHub avec un lien vers une URL de prévisualisation" loading="lazy" decoding="async" class="dark:brightness-90" width="712" height="777" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Lors de la sauvegarde d’un nouvel article, une pull request est créée sur GitHub avec un lien vers une URL de prévisualisation.</figcaption>
</figure>
<p>Et si vous êtes développeur, sachez que Netlify CMS utilise des composants React que vous pouvez étendre pour ajouter vos propres widgets. Vous trouverez plus d’informations à ce sujet dans <a href="https://www.netlifycms.org/docs/" target="_blank" rel="noopener noreferrer">la documentation du projet</a>.</p>
<p>Ce projet de Netlify est encore jeune et le développement assez actif, on peut lui faire confiance vu qu'il est utilisé en production par Smashing Magazine — et Jamstatic à un bien plus modeste niveau. Ce n'est pas le seul service qui permette d’éditer des contenus dans une interface visuelle, mais contrairement à <a href="https://siteleaf.com" target="_blank" rel="noopener noreferrer">Siteleaf</a> ou <a href="https://forestry.io" target="_blank" rel="noopener noreferrer">Forestry</a>, il est auto-hébergé.</p>
<p>Vous pouvez consulter <a href="https://github.com/netlify/netlify-cms/projects/3" target="_blank" rel="noopener noreferrer">la roadmap</a> pour suivre l’avancée du projet.</p>
<p>Il est important de noter que le fait d’ajouter ce type de CMS en parallèle de votre générateur de site statique ne change en rien vos habitudes. Les fichiers sont toujours versionnés avec Git puis partagés sur un service de type Github. Contrairement à Drupal ou WordPress — utilisés en mode monolithique — ici <strong>les développeurs et les rédacteurs partagent un worflow commun</strong>. C’est un gain de sérénité et l’assurance de pouvoir travailler indépendamment des modifications effectuées par un autre profil, tout en gardant une totale autonomie pour publier en production.</p>
<p>Nous sommes convaincus que ce type de workflow va continuer de se répandre de plus en plus dans les équipes, pas seulement avec Netlify CMS mais avec toutes les solutions de CMS headless qui sont désormais disponibles.</p>
<p>La solution présentée ici a l’avantage d’être totalement open source, gageons que le projet gagnera en maturité avec l’aide de la communauté, c'est tout le mal qu'on lui souhaite.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2017/03/17/smashing-mag-va-dix-fois-plus-vite/</id>
    <title>Smashing Magazine est maintenant 10 fois plus rapide</title>
    <published>2017-03-17T11:44:01+00:00</published>
    <link href="https://jamstatic.fr/2017/03/17/smashing-mag-va-dix-fois-plus-vite/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>La refonte de Smashing Magazine à l’aide d’un générateur de site
statique et d’APIs tierces comme Algolia constitue un petit évènement pour le
développement Web moderne. Smashing est en effet un des sites les plus consultés
et le fait de faire évoluer leur stack technique, de se reposer sur des outils
actuels montre à quel point cet écosystème est mature et robuste. C’est la
société Netlify, basée à San Francisco et spécialisée dans l’hébergement de
sites statiques, qui a accompagné Smashing dans cette aventure. En traduisant
<a href="https://www.netlify.com/blog/2017/03/16/smashing-magazine-just-got-10x-faster/" target="_blank" rel="noopener noreferrer">l’article paru sur leur blog</a>,
nous continuons à promouvoir ces solutions dans l’espoir que cela vous incite à
adopter à votre tour ce type d’architectures dans certains de vos projets.</p></aside>
<p><div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;">
<iframe src="https://www.youtube-nocookie.com/embed/rB4Cl5LSe2c" loading="lazy" width="640" height="360" frameborder="0" allow="accelerometer;autoplay;encrypted-media;gyroscope;picture-in-picture;fullscreen;web-share;" allowfullscreen="" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background-color:#d8d8d8;"></iframe>
</div></p>
<h2 id="ecouter-l-epopee-de-smashing-magazine">Écouter l’épopée de Smashing Magazine</h2>
<p>Smashing Magazine a toujours été une plateforme de confiance pour les
développeurs. C’est un endroit où on peut trouver les meilleures pratiques liées
au développement Web. Cela fait longtemps que nous leur faisons confiance,
depuis que Matt Biilmann, co-fondateur et CEO de Netlify, a commencé à utiliser
Smashing Magazine pour apprendre à programmer par lui-même il y a quelques
années de cela.</p>
<p>En novembre 2015, Matt a écrit
<a href="https://www.smashingmagazine.com/2015/11/modern-static-website-generators-next-big-thing/" target="_blank" rel="noopener noreferrer">"Pourquoi les générateurs de site web statique sont le prochain gros truc"</a>,
qui est devenu un des articles les plus lus sur Smashing Magazine. Dans cet
article, Netlify a mené des expériences intéressantes. Nous avons effectué une
comparaison pour voir à quel point Smashing Magazine serait plus rapide s'il
était hébergé sur Netlify, qui tire parti de la rapidité d’un CDN global… les
premiers tests ont montré que ça allait <strong>6 fois plus vite</strong> !</p>
<p>En avril 2016, Matt a fait une présentation lors de la SmashingConf à San
Francisco où il a parlé de la <a href="https://jamstack.org/" target="_blank" rel="noopener noreferrer">Jamstack</a>, une
architecture de développement moderne pour le Web basée sur le Javascript côté
client, des APIs réutilisables et du Markup pré-généré.</p>
<p>La Jamstack intègre des pratiques qui la rendent idéale pour le développement
Web moderne :</p>
<ul>
<li>Les sites basés sur la Jamstack tirent partie de la puissance des CDN pour
bénéficier d’une vitesse et d’une performance impossibles à battre.</li>
<li>Tout est versionné dans Git, pas besoin de bases de données à répliquer, pas
d’installation compliquée.</li>
<li>Le code est pré-généré avec la Jamstack, les régénérations sont automatisées,
les modifications ne seront pas en production avant la prochaine régénération…
pour ne citer que quelques exemples.</li>
</ul>
<p>C’est une nouvelle manière de bâtir des sites et des applications qui offrent de
meilleures performances, une sécurité plus élevée, un moindre coût de
redimensionnement et une meilleure expérience de développement.</p>
<p>Une renaissance du Web moderne est en train d’avoir lieu et Smashing comptait
bien en faire partie. Mais d’abord, ils voulaient l’implémenter pour eux-mêmes.
En faisant cela Smashing consolide un peu plus le fait "qu'ils appliquent ce
qu'ils recommandent" et qu'ils font partie des pionniers du développement Web
moderne. Le fait d’avoir choisi la Jamstack et Netlify pour les aider à
concrétiser cette vision ne pouvait que nous réjouir.</p>
<h2 id="un-travail-titanesque">Un travail titanesque</h2>
<p>Smashing Magazine a décidé l’année dernière de redesigner leur site, car ils
faisaient face à pas mal de problèmes avec leur configuration précédente. Ils
utilisaient différents outils et plate-formes pour tout gérer, des abonnements
en passant par les contenus, ce qui pouvait s'avérer frustrant quand on ne
savait pas où aller pour faire quelque chose.</p>
<p>WordPress leur a causé de forts maux de têtes et Smashing n'était plus satisfait
de ce qu'il pouvait proposer. Même en utilisant la plupart des plugins de cache
disponibles, il était clair que WordPress ne fonctionnait pas comme il fallait
puisqu'il y avait des problèmes avec <strong>chacun des plugins de cache</strong>.</p>
<figure>
<img src="/cdn.netlify.com/3e5d615c43e682e4601dcfcd7eb8ee15357be6d9/63741/img/blog/gif_1.a2bf10291749375a5cc78d22571fb3df.gif" alt="Vitaly Friedman : nous avions des problèmes de cache avec chacun des plugins de cache WordPress existants" title="Vitaly Friedman : nous avions des problèmes de cache avec chacun des plugins de cache WordPress existants." loading="lazy" decoding="async" class="dark:brightness-90" width="500" height="281" style="">
<figcaption>Vitaly Friedman : nous avions des problèmes de cache avec chacun des plugins de cache WordPress existants.</figcaption>
</figure>
<p><strong>Si</strong> Netlify voulait relever le défi, il fallait bien comprendre les priorités
cruciales pour Smashing :</p>
<ul>
<li>L'accès à une <strong>plate-forme unifiée</strong> — un endroit qui regroupe les différents
outils techniques utilisés pour la gestion du site,</li>
<li>La liberté de produire <strong>un design</strong> qu'ils aiment sans avoir à subir les
contraintes imposées par WordPress et les autres outils,</li>
<li>Le site le <strong>plus performant</strong> possible en se focalisant sur la fiabilité et
la rapidité.</li>
</ul>
<p>…le tout en l’espace de <strong>quelques mois</strong><sup id="fnref1:1"><a href="#fn:1" class="footnote-ref">1</a></sup>.</p>
<p>Rien que ça ? Après quelques échanges et une réunion avec toute l’équipe,
Netlify a décidé qu'ils ne pouvaient laisser passer une aussi belle occasion.
Smashing magazine serait la parfaite étude de cas pour montrer que l’utilisation
de la Jamstack constitue <strong>la</strong> manière de développer des sites.</p>
<figure>
<img src="/cdn.netlify.com/8847cc537164cc098d3f77f8db225c41f14430a5/e553b/img/blog/gif_4.57842079d55365acf9d0b8e5cf17c689.gif" alt="Mathias Biilmann : Smashing avait besoin d’un générateur, d’un CMS, de commentaires, d’une plate-forme de E-commerce, d’une gestion des abonnements et des paiements" title="Mathias Biilmann : Smashing avait besoin d’un générateur, d’un CMS, de commentaires, d’une plate-forme de E-commerce, d’une gestion des abonnements et des paiements." loading="lazy" decoding="async" class="dark:brightness-90" width="500" height="278" style="">
<figcaption>Mathias Biilmann : Smashing avait besoin d’un générateur, d’un CMS, de commentaires, d’une plate-forme de E-commerce, d’une gestion des abonnements et des paiements.</figcaption>
</figure>
<p>Cela signifie que Smashing avait beaucoup de besoins. Il fallait créer un outil
de build pour le magazine, un gestionnaire de contenus pour les milliers
d’articles, un moteur de commentaires (avec plus de 200 000 commentaires
!), une plate-forme de e-commerce pour les ventes, un service d’abonnement et un
compte utilisateur.</p>
<p>Nous ne sommes généralement pas pour réinventer la roue, nous sommes donc partis
à la recherche d’outils actuels susceptibles de répondre aux solides besoins de
Smashing. Malheureusement nous nous sommes vite aperçus qu'il allait falloir
repartir de zéro.</p>
<h2 id="ainsi-naquirent-nos-reves-d-open-source">Ainsi naquirent nos rêves d’open source</h2>
<p>Nous voulions fournir à Smashing tous les outils et les fonctionnalités dont il
avait besoin. Mais il était également important pour nous que nos efforts en
vaillent la peine et puissent bénéficier plus largement à l’ensemble de la
communauté. Nous avions l’occasion de montrer que la Jamstack représente le
futur du développement Web tout en construisant des outils qui allaient pouvoir
être utilisés par la communauté grandissante qui gravite autour. C’est pour cela
que tout ce que nous avons développé pour Smashing est <strong>open source</strong>.</p>
<p>En partant des besoins de Smashing, le début de notre périple technique a
consisté à développer les APIS open source suivantes :</p>
<ul>
<li><strong>GoTell</strong> — une API et un outil de build pour la gestion d’un grand nombre de
commentaires,</li>
<li><strong>GoCommerce</strong> — une petite API en Go pour les sites e-commerce pour la
gestion des commandes et des paiements,</li>
<li><strong>GoJoin</strong> — une API qui intègre les abonnements Stripe pour les Single Page
Apps et les sites,</li>
<li><strong>GoTrue</strong> — une petite API open-source écrite en Go qui peut agir comme une
API de service indépendante pour la gestion des inscriptions et des
authentifications. Elle est basée sur OAuth2 et JWT et peut gérer les
inscriptions, les connexions et les données personnelles des utilisateurs.</li>
</ul>
<p>Vous pouvez retrouver toutes ces APIs sur notre
<a href="https://www.netlify.com/open-source/" target="_blank" rel="noopener noreferrer">page open source</a>. Il y a tout un
historique technique derrière ces décisions et nous dévoilerons plus de détails
à ce propos dans un prochain article.</p>
<h2 id="notre-cms-open-source">Notre CMS open source</h2>
<p>Une des tâches les plus importantes liées au travail pour Smashing a été sans
conteste le projet de gestionnaire de contenus open source
<strong><a href="https://www.netlifycms.org/" target="_blank" rel="noopener noreferrer">Netlify CMS</a></strong>.</p>
<figure>
<img src="/cdn.netlify.com/f938aee2d1bd841ea4fe599a3af0f4e9bbba6b3c/024d0/img/blog/netlifycms.c159587dcbedc7c024dfd53395b25040.svg" alt="Un CMS qui unifie le travail des auteurs, des éditeurs et des développeurs" title="Un CMS qui unifie le travail des auteurs, des éditeurs et des développeurs." loading="lazy" decoding="async" class="dark:brightness-90" width="300" height="265">
<figcaption>Un CMS qui unifie le travail des auteurs, des éditeurs et des développeurs.</figcaption>
</figure>
<p>Loin de ses cousins bouffis et monolithiques comme WordPress, nous voulions que
le CMS de Netlify permette aux éditeurs de contenus de bénéficier du processus
de versionnement basé sur Git. Qui plus est, Netlify CMS est compatible avec
toutes les plate-formes et fonctionne avec (presque) tous les générateurs de
site statique compatibles avec GitHub. C’est rapide, simple, souple et nous
sommes très fiers du résultat.</p>
<p><em>Spéciale dédicace à toutes les technologies utilisées</em> :</p>
<p><a href="https://www.algolia.com/" target="_blank" rel="noopener noreferrer">Algolia</a>, un service de recherche super rapide en
temps réel, <a href="https://gohugo.io/" target="_blank" rel="noopener noreferrer">Hugo</a>, combiné à une gestion moderne des assets
avec Gulp et Webpack, le tout basé sur le modèle open-source
<a href="https://github.com/netlify/victor-hugo" target="_blank" rel="noopener noreferrer">Victor Hugo</a>.</p>
<h2 id="putain-on-l-a-fait">Putain, on l’a fait !</h2>
<p>Quelques mois plus tard, nous y voici… Smashing a annoncé aujourd’hui la sortie
du <a href="https://next.smashingmagazine.com/" target="_blank" rel="noopener noreferrer">site en version bêta</a> et même s'il reste
encore du travail à faire, nous pouvons célébrer quelques victoires :</p>
<p>SmashingMagazine.com est aujourd’hui bien plus rapide, ils sont passés d’un
temps de début de chargement de 800ms à 80ms. Les visiteurs de Smashing vont
maintenant bénéficier d’une expérience plus fluide grâce à de meilleures
intégrations, une vitesse accrue et une meilleure performance.</p>
<figure>
<img src="/cdn.netlify.com/f1e06365a29be3cfcf08b8f1a82fc902cb9a75cf/ec035/img/blog/gif_2.4a2fe45f9827ac399590330a8c978161.gif" alt="Le temps de début de chargement est bien plus rapide qu&#039;auparavant" title="Le temps de début de chargement est bien plus rapide qu&#039;auparavant." loading="lazy" decoding="async" class="dark:brightness-90" width="500" height="281" style="">
<figcaption>Le temps de début de chargement est bien plus rapide qu'auparavant.</figcaption>
</figure>
<p>Smashing possède désormais la plate-forme unifiée dont ils rêvaient. Ils gèrent
maintenant à partir <strong>d’un seul et même endroit</strong> tous les solides besoins de
leur site. Avec Netlify, les projets open source et une manière plus efficace de
gérer les contenus, fini d’hésiter pour savoir sur quel bouton appuyer ou quel
outil utiliser pour réaliser une action particulière.</p>
<p>Ils ont maintenant pleinement le contrôle sur le design de leur choix et pour
résultat <a href="https://next.smashingmagazine.com/" target="_blank" rel="noopener noreferrer">un joli site</a>.</p>
<p>…et la JAMStack a prouvé qu'elle est une figure de proue de la renaissance du
Web moderne et nous avons été capable d’achever tout ce travail tout en
fournissant une boîte à outil open source pour la communauté.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn:1">
<p>NdT. Le projet s'est étendu de juin 2016 à mars 2017.&#160;<a href="#fnref1:1" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
</ol>
</div>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2017/03/16/5-raisons-de-tester-la-jamstack/</id>
    <title>Passer au statique : 5 raisons de tester la Jamstack sur votre prochain projet</title>
    <published>2017-03-16T00:00:00+00:00</published>
    <link href="https://jamstatic.fr/2017/03/16/5-raisons-de-tester-la-jamstack/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Adopter une stack de développement Web moderne pour pouvoir générer des sites statiques présente bien des avantages et Tom Bennet en a listé cinq des principaux. Si vous n'aviez encore jamais entendu parlé de la Jamstack, cet article donne un aperçu global du processus et de l’écosystème actuel ainsi que des gains engendrés. Vous trouverez également des lectures pour approfondir votre connaissance sur le sujet.</p></aside>
<p>Que ce soit pour mettre en place un blog, configurer un site de e-commerce ou développer une single page application en JavaScript, le temps où on se rabattait par défaut sur WordPress pour tout ou presque est révolu. Les générateurs de site statique et les réseaux de CDN ultra-rapides propulsent une nouvelle génération de sites Web et c'est le bon moment pour embrasser le mouvement.</p>
<p>Avant d’expliquer plus en détail <em>pourquoi</em>, commençons déjà par regarder de <em>quoi</em> on parle.</p>
<h2 id="jamstack-et-developpement-statique">Jamstack et développement statique</h2>
<p>Le terme <strong>Jamstack</strong> désigne la stack JavaScript, APIs et Markup et une manière
de construire des sites Web statiques sans base de données. Ce concept est
<em>vraiment tout bête</em> — le mot "statique" sous-entend de la simplicité ou un
manque d’interactivité, mais c'est loin d’être le cas. Quand on parle de sites
statiques, on fait surtout référence aux technologies utilisées pour les
générer, les mettre en ligne et les héberger.</p>
<p>Le truc vraiment puissant c'est que le développement avec la Jamstack est bien
plus simple à appréhender que le développement de sites dynamiques à base de
CMS. Il est facile d’oublier le nombre d’étapes nécessaires pour satisfaire une
simple requête de page et la complexité des opérations qui sont menées
constamment côté serveur pour générer le HTML final affiché par le navigateur de
l’utilisateur. Prenons l’exemple (très schématique) d’un site typique qui
fonctionne sous WordPress.</p>
<figure>
<picture title="Génération de page HTML à l’aide de PHP et d’un serveur MySQL.">
<source type="image/webp" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346597/diagram.27761d260df1135a6f7e5d65e9e85b97.webp" width="740" height="340">
<source type="image/avif" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346597/diagram.27761d260df1135a6f7e5d65e9e85b97.avif" width="740" height="340">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346597/diagram.27761d260df1135a6f7e5d65e9e85b97.png" alt="Génération de page HTML à l’aide de PHP et d’un serveur MySQL" loading="lazy" decoding="async" class="dark:brightness-90" width="740" height="340" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Génération de page HTML à l’aide de PHP et d’un serveur MySQL.</figcaption>
</figure>
<p>Quand l’utilisateur demande à afficher une page, votre serveur fait une requête
dans une base de données MySQL et utilise un interpréteur PHP, assemble les
données avec le thème et les plugins pour en faire un document HTML qui pourra
ensuite être affiché dans le navigateur de l’utilisateur. Ce qui fait que cette
opération est extraordinairement complexe c'est surtout le <strong>templating</strong>. Vu
qu'on ne va pas coder toutes les pages d’un blog, aussi modeste soit-il, à la
main : séparer les contenus en composants réutilisables et automatiser leur
assemblage fait sens.</p>
<p>Mais pourquoi donc cette opération de templating a besoin de se passer côté
serveur ? Avons-nous vraiment besoin de bases de données et de logiciels côté
serveur (qui nous exposent par la même occasion à des douzaines de failles de
sécurité) pour créer un simple blog ? Maintenant que les navigateurs sont
devenus des systèmes d’exploitation, capable d’interagir avec un nombre
incalculable d’APIs et de faire tourner des applications complexes côté client,
et que le développement front-end est dominé par JavaScript et les
automatisations à l’aide de <code>npm</code>, n'avons-nous pas déjà dépassé ce modèle ?</p>
<figure>
<picture title="Un site Web statique développé à l’aide d’un processus basé sur la Jamstack.">
<source type="image/webp" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346591/diagram-ssg.2a4158deb3bd0142cf8826b0b0eef765.webp 768w" width="768" height="314" sizes="100vw">
<source type="image/avif" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346591/diagram-ssg.2a4158deb3bd0142cf8826b0b0eef765.avif 768w" width="768" height="314" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346591/diagram-ssg.2a4158deb3bd0142cf8826b0b0eef765.png" alt="Un site Web statique développé à l’aide d’un processus basé sur la Jamstack" loading="lazy" decoding="async" class="dark:brightness-90" width="768" height="314" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346591/diagram-ssg.2a4158deb3bd0142cf8826b0b0eef765.png 768w" sizes="100vw">
</picture>
<figcaption>Un site Web statique développé à l’aide d’un processus basé sur la Jamstack.</figcaption>
</figure>
<p>Les générateurs de site statique comme Jekyll et Hugo permettent de rendre cela
possible. Ils nous servent essentiellement de système de templating à la place
du PHP mais au lieu de tourner sur un serveur et de générer du contenu à la
volée, ils tournent en local en tant que partie intégrante du processus de
développement. Votre HTML est généré en amont et votre site Web — désormais un
ensemble de fichiers statiques faciles à mettre en cache — peut être distribué
par un CDN (réseau de distribution de contenu) rapide comme l’éclair.</p>
<p>Mais leurs bienfaits ne s'arrêtent pas là.</p>
<h2 id="1-referencement">1. Référencement</h2>
<p>Assurément, je vais commencer avec le référencement. Le développement statique
présente une multitude de bénéfices pour la visibilité de votre site sur les
moteurs de recherche et tous ne sont pas forcément toujours appréciés à leur
juste valeur.</p>
<p>Premièrement, la simplification des URLs et de l’architecture du site est
souvent plus simple avec la Jamstack qu'avec un site dynamique et un CMS. Plutôt
que de se reposer sur des règles de réécritures complexes d’URLs côté serveur
pour que votre contenu soit accessible via des URLS lisibles
(<code>example.com/?p=12345</code> → <code>example.com/clair-et-net/</code>), vos URLs sont ce que
vous voulez qu'elles soient : elles reflètent simplement l’emplacement des
fichiers de votre site<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref">1</a></sup>.</p>
<p>Le risque de duplication de contenus est également très réduit. Beaucoup de CMS
génèrent automatiquement des pages pour les catégories, les tags et les archives
par date alors que vous n'en avez peut-être pas besoin. Généralement des
directives <code>noindex</code> et des URLS canoniques sont ajoutées à l’aide de plugins
supplémentaires pour gérer tout ça. Les générateurs de site statique à l’inverse
vous permettent de créer des pages finement et de mettre en place la taxonomie
qui correspond à <em>votre</em> contenu. Si besoin, beaucoup de générateurs embarquent
des fonctions et une bonne logique pour créer, filtrer et paginer des pages
d’archives.</p>
<p>Enfin, il y a beaucoup d’avantages potentiels pour le SEO pour les sites qui
utilisent beaucoup le rendu côté client et les frameworks JavaScript. Étant
donné le caractère statique de votre code source, le mécanisme qui consiste à
servir des versions pré-rendues de votre HTML aux moteurs s'en retrouve
nettement simplifié. Certains hébergeurs spécialisés dans les sites statiques
comme Netlify
<a href="https://www.netlify.com/docs/prerendering/" target="_blank" rel="noopener noreferrer">offrent même le pré-rendu</a> de base
grâce à l’utilisation d’<code>_escaped_fragment_</code>, ça s'installe <em>littéralement</em> en
un clic. Les personnes intéressées par ce sujet feraient bien d’aller lire
l’étude de cas de Phil Hawksworth sur le
<a href="https://www.hawksworx.com/blog/isomorphic-rendering-on-the-jam-stack/" target="_blank" rel="noopener noreferrer">rendu isomorphique avec les sites statiques</a>.</p>
<h2 id="2-performance">2. Performance</h2>
<p>La performance est étroitement liée au référencement, car elle joue un rôle
prépondérant en termes d’expérience utilisateur.</p>
<p>Les avantages des sites statiques en termes de performance peuvent être
phénoménaux. Avec la génération en amont du HTML et l’élimination des requêtes
vers les bases de données, vos contenus peuvent être délivrés instantanément
depuis un CDN comme Amazon Cloudfront. Les tests effectués par Mathias Biilmann
avec
<a href="https://www.smashingmagazine.com/2015/11/modern-static-website-generators-next-big-thing/#dynamic-websites-and-caching" target="_blank" rel="noopener noreferrer">Smashing Magazine</a>
ont montré que même avec un site dynamique très optimisé (et une solide
stratégie de cache), le temps de début de chargement était en moyenne <strong>six fois
plus rapide</strong> avec une version statique distribuée via CDN. Smashing Magazine
ont d’ailleurs <a href="/2017/03/17/smashing-mag-va-dix-fois-plus-vite/">migré vers la Jamstack et Netlify</a> à l’occasion de leur refonte.</p>
<p>La mise en cache s'en retrouve grandement simplifiée. Avec WordPress (ou
n'importe quel CMS dynamique) les URLs peuvent retourner différents contenus en
fonction des requêtes passées en paramètre et de facteurs comme si l’utilisateur
à l’origine de la demande est authentifié ou non. Toute modification sur les
tags, les catégories, les commentaires, les pages des auteurs, etc. peut avoir
une incidence sur le fait qu'une page en cache est à jour ou pas. Avec un site
Web statique, toutes les URLs retournent le <em>même</em> fichier HTML à tous les
utilisateurs et les mises à jour sont propagées dans le monde entier presque
instantanément. Tout contenu "dynamique" est alors géré côté client grâce à
l’utilisation de JavaScript et d’APIs comme <a href="https://disqus.com/" target="_blank" rel="noopener noreferrer">Disqus</a> pour
les commentaires ou <a href="https://formkeep.com/" target="_blank" rel="noopener noreferrer">FormKeep</a> pour les formulaires.</p>
<h2 id="3-securite">3. Sécurité</h2>
<p>On va pouvoir passer rapidement sur ce point, car les sites Web statiques sont
de véritables <strong>forteresses</strong>.</p>
<p>Sans bases de données, sans plugins, sans logiciel dynamique qui tourne sur
votre serveur, la possibilité d’injection de code et de hacks est fortement
réduite. Quand votre site Web consiste en un ensemble de fichiers statiques,
toutes les fonctionnalités dynamiques sont alors prises en charge par les APIs
et le JavaScript côté client, on élimine ainsi le besoin de se reposer sur des
plugins de CMS. Bien qu'il soit tout à fait possible qu'une API externe chargée
de traiter des données persistantes expose une vulnérabilité, le fait d’éliminer
votre CMS entraîne la suppression de nombreux points de défaillance et de
multiples vecteurs d’attaque. Pour les blogs statiques, il n'est pas exagéré
d’affirmer que la sécurité devient essentiellement un <strong>faux problème</strong>, du
moins comparé à une installation typique de WordPress.</p>
<p>Les certificats SSL sont également faciles à installer et sont disponibles
gratuitement grâce à des autorités de certification automatisées comme
<a href="https://letsencrypt.org/" target="_blank" rel="noopener noreferrer">LetsEncrypt</a>.</p>
<h2 id="4-deploiement-workflow">4. Déploiement &amp; Workflow</h2>
<p>Une fois que vous avez travaillé sur un site Web avec la Jamstack — que vous
avez déployé des mises à jour et publié des contenus régulièrement — vous
commencez à ressentir le potentiel disruptif de cette manière de développer. Ça
évolue rapidement et on peut parfois se sentir un peu comme dans le Far West,
avec tous ces nouveaux outils et ces nouveaux services qui arrivent tous les
jours, mais ne vous y trompez pas : c'est puissant, flexible, on a atteint le
stade de la maturité.</p>
<p>Un des principes de base du développement avec la Jamstack c'est que tout vit
dans un dépôt Git, que ce soit les composants de notre site statique, les
fichiers de configuration de notre générateur, nos fichiers CSS et JS, nos
contenus écrits (sauvegardés sous forme de documents Markdown en texte brut).
Avec votre service de déploiement et d’hébergement configuré pour refléter en
permanence l’état de la branche de votre dépôt, appliquer une modification est
aussi simple que de pousser un commit sur un dépôt GitHub. L'ensemble de votre
site Web — le code et le contenu — vit dans un endroit centralisé, protégé par
un versionnement robuste et peut être configuré pour être déployé en continu.</p>
<p><strong>Oui mais et les clients dans tout ça ?</strong> Quid des utilisateurs non techniques
? Les experts de la production de contenu qui sont à l’aise avec des éditeurs
comme celui de WordPress mais qui ne connaissent pas Markdown et GitHub ?</p>
<p>Le problème a été identifié il y a maintenant plusieurs années et beaucoup de
solutions réjouissantes commencent à apparaître. Certaines sont
extraordinairement simples. Si les éditeurs de vos contenus sont déjà familiers
avec
<a href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet" target="_blank" rel="noopener noreferrer">les bases de Markdown</a> -
C’est-à-dire <code># titre</code>, <code>**gras**</code>, <code>*italique*</code> — alors il n'y a aucune raison
qu'ils ne puissent éditer le dépôt sous-jacent. Des outils comme
<a href="https://prose.io/" target="_blank" rel="noopener noreferrer">Prose.io</a> s'intègrent à GitHub pour proposer une interface
utilisateur plus adaptée pour les auteurs non techniques. Créez une branche pour
les éditeurs de contenu puis fusionnez simplement leurs modifications pour
publier de nouveaux contenus.</p>
<p>Sinon il y a aussi des dizaines de
<a href="https://css-tricks.com/what-is-a-headless-cms/" target="_blank" rel="noopener noreferrer">CMS "headless"</a> qui sont
parfaits pour les sites Web statiques. Ce sont avant tout des gestionnaires de
contenus reposant sur une API qui permettent de dissocier vos contenus du côté
client chargé de l’affichage de votre site.
<a href="https://www.siteleaf.com/" target="_blank" rel="noopener noreferrer">Siteleaf</a>, par exemple qui fonctionne avec Jekyll et
GitHub propose une édition dans le cloud compatible avec vos outils existants —
comme c'est écrit sur leur site <strong>les sites Web devraient survivre à leurs
CMS</strong>.</p>
<p>Comme je le disais, c'est un secteur qui se développe rapidement, vous <em>serez</em>
confrontés à des challenges pour vous adapter à un workflow statique,
particulièrement pour les projets ambitieux. Quiconque s'intéresse à
l’utilisation de sites statiques pour un projet commercial ou à grande échelle
devrait aller lire
<a href="https://www.smashingmagazine.com/2016/08/using-a-static-site-generator-at-scale-lessons-learned/" target="_blank" rel="noopener noreferrer">l’article de Stefan Baumgartner</a>
sur Smashing Magazine.</p>
<h2 id="5-une-communaute-en-pleine-expansion">5. Une communauté en pleine expansion</h2>
<p>La popularité grandissante du développement de site statique a donné naissance à
quelques nouveaux services assez incroyables.</p>
<p>Prenez le e-commerce par exemple. Pour les petits vendeurs — ceux qui se
reposeraient typiquement sur WordPress et WooCommerce — un site statique est
désormais une option parfaitement valable. <a href="https://snipcart.com/" target="_blank" rel="noopener noreferrer">Snipcart</a> est
un système de panier et de paiement basé sur JavaScript qui permet aux
développeurs d’ajouter des fonctionnalités de e-commerce sur n'importe quel
site Web. L'inventaire des produits et des ventes est géré via le tableau de
bord de Snipcart et son API permet l’intégration de systèmes de gestion
d’inventaires, de livreurs, etc. Il existe d’autres solutions comme
<a href="https://www.foxy.io/" target="_blank" rel="noopener noreferrer">Foxy</a> et le
<a href="https://www.shopify.co.uk/buy-button" target="_blank" rel="noopener noreferrer">bouton d’achat Shopify</a>.</p>
<p>Pour les <strong>blogueurs</strong> qui voudraient utiliser un thème tout fait pour leur
site, il en existe maintenant des centaines — allez faire un tour sur les
annuaires de thèmes pour <a href="https://themes.gohugo.io/" target="_blank" rel="noopener noreferrer">Hugo</a> et
<a href="https://hexo.io/themes/" target="_blank" rel="noopener noreferrer">Hexo</a>. Il convient de mentionner que certaines
connaissances techniques sont requises pour être opérationnel avec ces
générateurs statiques.</p>
<p>Ces annuaires ne sont pas encore près de devenir des places de marché de thèmes
prospères en tant que telles, mais en un sens c'est une bonne chose : les thèmes
bourrés de plugins inutiles et d’outils de construction de pages ne sont pas un
problème ici !</p>
<p>Il y a aussi des <strong>fournisseurs d’hébergement</strong> spécialisés dans les sites Web
statique. <a href="https://www.netlify.com/features/" target="_blank" rel="noopener noreferrer">Netlify</a> est le plus connu et à
juste titre. Ils offrent un CDN mondial, des domaines personnalisés et des
certificats SSL gratuits ainsi qu'une intégration avec GitHub pour permettre de
générer et de faire des déploiements atomiques depuis la ligne de commande. Le
service se vante aussi d’une bonne interface pour faire des choses gérées
typiquement sur votre serveur, comme les redirections, les pages d’erreur
personnalisées, la protection par mot de passe, la proxyfication, etc. (oui ça
veut dire <strong>fini les htaccess</strong>).</p>
<h2 id="est-ce-que-c-est-fait-pour-moi">Est-ce que c'est fait pour moi ?</h2>
<p>Bien entendu, il y a plein de sites Web pour lesquels le développement avec la
Jamstack n'est pas approprié. Il y a également des problèmes légitimes et
quelques obstacles — auxquels vous ferez face même sur de tous petits projets —
qui doivent être surmontés. Et plus particulièrement les services destinés aux
utilisateurs non techniques et aux éditeurs de contenu doivent être encore plus
soignés. La vitesse à laquelle la plupart de ces outils évoluent peut s'avérer
déconcertante pour les nouveaux arrivants.</p>
<p>Toutefois je crois que l’écosystème autour du développement de site statique a
maintenant atteint un <strong>point critique</strong>. Dans bien des cas les avantages du
développement statique surpassent maintenant les inconvénients. Une utilisation
plus répandue de ces outils, de ces plate-formes et de ces services va les
pousser à s'étoffer.</p>
<p><strong>Alors est-ce que c'est fait pour vous ?</strong> Si vous êtes vaguement familier avec
le développement Web et que vous n'avez pas encore testé un générateur de site
statique moderne, c'est le moment idéal pour le faire et de répondre vous-même à
cette question. Prenez connaissance des ressources ci-dessous, regardez
<div style="position:relative;padding-bottom:56.25%;height:0;overflow:hidden;">
<iframe src="https://player.vimeo.com/video/163522126" loading="lazy" width="640" height="360" frameborder="0" allow="accelerometer;autoplay;encrypted-media;gyroscope;picture-in-picture;fullscreen;web-share;" allowfullscreen="" style="position:absolute;top:0;left:0;width:100%;height:100%;border:0;background-color:#d8d8d8;"></iframe>
</div> lors de la
SmashingConf, puis essayez de remonter votre site perso à l’aide des outils de
développement statique modernes, laissez tomber les base de données et passez
sur un CDN rapide.</p>
<p>Vous aurez du mal à faire machine arrière et de nouvelles possibilités feront
leur apparition un peu plus chaque jour.</p>
<h3 id="ressources">Ressources</h3>
<ul>
<li>Critiques de générateurs de site statique : Jekyll, Middleman, Hugo – <a href="https://www.smashingmagazine.com/2015/11/static-website-generators-jekyll-middleman-roots-hugo-review/" target="_blank" rel="noopener noreferrer">Smashing Magazine</a></li>
<li>Utilisation d’un générateur de site statique à grande échelle : leçons apprises – <a href="https://www.smashingmagazine.com/2016/08/using-a-static-site-generator-at-scale-lessons-learned/" target="_blank" rel="noopener noreferrer">Smashing Magazine</a></li>
<li>Jamstack pour les clients : bénéfices, CMS pour site statique et limitations – <a href="https://snipcart.com/blog/jamstack-clients-static-site-cms" target="_blank" rel="noopener noreferrer">Snipcart</a></li>
<li>Passez au statique sans perdre votre serveur – <a href="https://www.netlify.com/blog/2016/03/10/go-static-without-losing-your-server/" target="_blank" rel="noopener noreferrer">Netlify</a></li>
<li>C’est quoi un CMS headless ? – <a href="https://css-tricks.com/what-is-a-headless-cms/" target="_blank" rel="noopener noreferrer">CSS-Tricks</a></li>
<li>Gestionnaires de contenus pour sites statiques – <a href="https://headlesscms.org/" target="_blank" rel="noopener noreferrer">headlesscms.org</a></li>
<li>Jamstack | JavaScript, APIs et Markup – <a href="https://jamstack.org/" target="_blank" rel="noopener noreferrer">jamstack.org</a></li>
<li>Générateurs de site statique open-source – <a href="https://www.staticgen.com/" target="_blank" rel="noopener noreferrer">staticgen.com</a></li>
<li>{static is} The New Dynamic – <a href="https://www.thenewdynamic.org/" target="_blank" rel="noopener noreferrer">thenewdynamic.org</a></li>
</ul>
<div class="footnotes">
<hr>
<ol>
<li id="fn:1">
<p>NdT. Les URLs peuvent être également définies dans les métadonnées des fichiers de contenu.&#160;<a href="#fnref1:1" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
</ol>
</div>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2017/03/11/mode-offline-avec-hugo/</id>
    <title>Passez en mode hors-connexion avec un Service Worker et Hugo !</title>
    <published>2017-03-11T15:04:00+00:00</published>
    <link href="https://jamstatic.fr/2017/03/11/mode-offline-avec-hugo/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>La majorité des articles publiés jusqu'ici se référaient à Jekyll,
cette fois place à <a href="https://gohugo.io/" target="_blank" rel="noopener noreferrer">Hugo</a>. Hugo est un générateur de site
statique populaire très performant et beaucoup plus performant pour vos
visiteurs si vous lui adjoignez les services d’un Service Worker pour gérer le
mode déconnecté de votre site web. Notez que les explications fournies ici sont
valables et facilement adaptables pour tout autre générateur statique.</p></aside>
<p>Après le <em>mobile first</em>, place maintenant au <em>offline first</em> et
<a href="https://frank.taillandier.me/2016/06/28/que-sont-les-progressive-web-apps/" target="_blank" rel="noopener noreferrer"><em>aux progressive web apps (PWA)</em></a>
tous deux très tendances en ce moment. Les Service Workers jouent un rôle majeur
dans tous les cas de figure. Un Service Worker en gros c'est un script qui va
jouer le rôle d’un proxy entre le navigateur web et le réseau Internet. Vous
trouverez dans cet article un exemple simple qui vous permettra d’installer un
Service Worker sur un site statique généré avec <a href="https://gohugo.io/" target="_blank" rel="noopener noreferrer">Hugo</a> afin
de le rendre ultra-performant.</p>
<h2 id="de-quoi-parle-t-on">De quoi parle-t-on ?</h2>
<p>Si vous n'avez pas encore entendu parler des Service Workers et que vous voulez
en savoir plus sur le sujet, merci de consulter les liens suivants :</p>
<ul>
<li><strong><a href="https://developers.google.com/web/fundamentals/getting-started/codelabs/your-first-pwapp/" target="_blank" rel="noopener noreferrer">Votre première Progressive Web App</a></strong> publié sur Google Developers</li>
<li><strong><a href="https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API" target="_blank" rel="noopener noreferrer">L'API Service Worker</a></strong> publié sur MDN Mozilla Developer Network</li>
<li><strong><a href="https://ponyfoo.com/articles/serviceworker-revolution" target="_blank" rel="noopener noreferrer">Service Worker Revolution</a></strong> publié chez Ponyfoo</li>
<li><strong><a href="https://github.com/pazguille/offline-first" target="_blank" rel="noopener noreferrer">Tout ce que vous devez savoir pour créer vos premières applications hors-ligne</a></strong> sur Github</li>
</ul>
<p>Maintenant que vous avez lu tout ça — ou du moins que vous avez compris de quoi
il en retourne — voici ce que nous allons faire :</p>
<ul>
<li><strong>Installer un Service Worker</strong> à partir d’un exemple dans Hugo.</li>
<li><strong>Afficher une page hors-connexion personnalisée</strong> en cas de panne de réseau ou si la page n'est pas en cache</li>
<li><strong>Afficher une page d’erreur 404 personnalisée</strong> en cas de requêtes HHTP retournant une erreur client de type 4xx</li>
<li><strong>Ajouter un fichier <code>manifest.json</code></strong> pour définir l’apparence de l’application Web sur mobile.</li>
</ul>
<h2 id="prerequis">Prérequis</h2>
<h3 id="creer-une-page-hors-connexion">Créer une page <code>hors-connexion</code></h3>
<p>Assurez-vous de créer une page hors-connexion personnalisée pour afficher à vos
visiteurs quand ils sont déconnectés du réseau.</p>
<p>Par exemple vous pouvez créer les fichiers suivants :</p>
<pre><code class="language-sh hljs bash">├── content
│   ├── offline.md
├── layouts
│   ├── offline/single.html</code></pre>
<p>Contenu du fichier <strong>content/offline.md</strong> :</p>
<pre><code class="language-md hljs markdown">+++
date = "2016-10-16T19:28:41+02:00"
draft = false
title = "Oops, vous êtes déconnecté du réseau."
type = "offline"
+++

Essayez de vous connecter à Internet pour naviguer sur le site.</code></pre>
<p>Le fichier <strong>layouts/offline/single.html</strong> :</p>
<pre><code class="language-twig hljs twig"><span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span></span><span class="hljs-template-variable">{{ .Title }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
 <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
 <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span></span><span class="hljs-template-variable">{{ .Title }}</span><span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    </span><span class="hljs-template-variable">{{ .Content }}</span><span class="xml">
 <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></span></code></pre>
<p>C’est <em>vraiment un exemple minimaliste</em>, vous pouvez bien entendu créer une page
hors-connexion avec le contenu de votre choix.</p>
<p>Mais déjà grâce à notre exemple, nous avons généré une page
<code>offline/index.html</code>. OK, ça c'est fait.</p>
<h3 id="creer-une-page-404-personnalisee">Créer une page 404 personnalisée</h3>
<p>Si votre projet ne possède pas encore de page 404 personnalisée, vous pouvez
vous référer à
<a href="https://gohugo.io/templates/404/" target="_blank" rel="noopener noreferrer">la documentation d’Hugo pour créer une page 404</a>
ou vous contenter de suivre les quelques instructions de base ci-dessous.</p>
<p>Pour cela, vous aurez besoin des fichiers suivants :</p>
<pre><code class="language-sh hljs bash">├──content
│   ├── 404.md
├── layouts
│   ├── 404.html</code></pre>
<p>Le fichier <strong>content/404.md</strong> :</p>
<pre><code class="language-md hljs markdown">+++
date = "2016-10-16T19:28:41+02:00"
draft = false
title = "Zut… Page non trouvée."
+++

Vous devriez aller voir ailleurs.</code></pre>
<p>Le fichier <strong>layouts/404.html</strong> :</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">html</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>{{ .Title }}<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>{{ .Title }}<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
    {{ .Content }}
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span></code></pre>
<h3 id="creer-les-icones-de-l-application-web">Créer les icônes de l’application Web</h3>
<p>Les icônes des applications sont juste des favicons qu'on affiche sur un écran
de démarrage au chargement du site depuis l’écran d’accueil.</p>
<p>Les tailles suivantes sont recommandées :</p>
<ul>
<li>128px × 128px</li>
<li>144px × 144px</li>
<li>152px × 152px</li>
<li>192px × 192px</li>
<li>256px × 256px</li>
</ul>
<p>Pour les générer rapidement, vous pouvez utiliser un service comme
<a href="http://www.favicomatic.com/" target="_blank" rel="noopener noreferrer">favicomatic.com</a>.</p>
<p>Ensuite placez les fichiers PNG dans votre dossier <code>/static</code> folder. Par
exemple :</p>
<pre><code class="language-sh hljs bash">├── static
│   ├── favicons
│   │   ├── icon-128x128.png
│   │   ├── icon-144x144.png
│   │   ├── icon-152x152.png
│   │   ├── icon-192x192.png
│   │   ├── icon-256x256.png</code></pre>
<h3 id="installation-du-fichier-manifest-json">Installation du fichier <code>manifest.json</code></h3>
<p>Le vrai travail commence maintenant avec la création et la configuration du
fichier <code>manifest.json</code>.</p>
<p>Nous allons utiliser pour cela un
<a href="https://github.com/wildhaber/offline-first-sw/blob/master/manifest.json" target="_blank" rel="noopener noreferrer">exemple de fichier manifest</a>
existant tiré du dépôt <code>offline-first-sw</code>.</p>
<p>Placez ce fichier également dans le dossier <code>static/</code>, il doit obligatoirement
se trouver à la racine comme ceci :</p>
<pre><code class="language-sh hljs bash">├── static
│   ├── manifest.json</code></pre>
<p>Vous pouvez recopier ce fichier à la main ou utiliser la commande suivante si
vous travaillez dans un environnement GNU Linux ou macOS :</p>
<pre><code class="language-sh hljs bash"><span class="hljs-comment"># à partir du dossier racine d’Hugo</span>
<span class="hljs-built_in">cd</span> static
wget https://raw.githubusercontent.com/wildhaber/offline-first-sw/master/manifest.js</code></pre>
<p>Vous devriez maintenant avoir un fichier qui ressemble à cela dans votre dossier
<code>static</code> :</p>
<pre><code class="language-json hljs json">{
  <span class="hljs-attr">"name"</span>: <span class="hljs-string">"&lt;nom-de-votre-application&gt;"</span>,
  <span class="hljs-attr">"short_name"</span>: <span class="hljs-string">"&lt;nom-abrégé&gt;"</span>,
  <span class="hljs-attr">"icons"</span>: [
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"/img/icons/logo-128x128.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"128x128"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"/img/icons/logo-144x144.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"144x144"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"/img/icons/logo-152x152.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"152x152"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"/img/icons/logo-192x192.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"192x192"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    },
    {
      <span class="hljs-attr">"src"</span>: <span class="hljs-string">"/img/icons/logo-256x256.png"</span>,
      <span class="hljs-attr">"sizes"</span>: <span class="hljs-string">"256x256"</span>,
      <span class="hljs-attr">"type"</span>: <span class="hljs-string">"image/png"</span>
    }
  ],
  <span class="hljs-attr">"start_url"</span>: <span class="hljs-string">"/index.html"</span>,
  <span class="hljs-attr">"display"</span>: <span class="hljs-string">"standalone"</span>,
  <span class="hljs-attr">"orientation"</span>: <span class="hljs-string">"portrait"</span>,
  <span class="hljs-attr">"background_color"</span>: <span class="hljs-string">"#000000"</span>,
  <span class="hljs-attr">"theme_color"</span>: <span class="hljs-string">"#000000"</span>
}</code></pre>
<p>Ajustez les valeurs à votre guise.</p>
<h3 id="ajoutez-un-lien-vers-manifest-json-dans-votre-modele">Ajoutez un lien vers <code>manifest.json</code> dans votre modèle</h3>
<p>Pour que le navigateur soit en mesure de détecter votre <code>manifest.json</code>, ajoutez
le bout du code suivant dans le <code>&lt;head&gt;</code> de vos modèles :</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">link</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"manifest"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/manifest.json"</span> /&gt;</span></code></pre>
<h3 id="installation-du-service-worker">Installation du Service Worker</h3>
<p>Pour cela nous allons aussi utiliser l’exemple de
<a href="https://github.com/wildhaber/offline-first-sw/blob/master/sw.js" target="_blank" rel="noopener noreferrer">Service Worker</a>
fourni dans le dépôt
<a href="https://github.com/wildhaber/offline-first-sw" target="_blank" rel="noopener noreferrer"><code>offline-first-sw</code></a>.</p>
<p>Le fichier <code>sw.js</code> doit également se trouver à la racine du dossier <code>static</code>
comme ceci :</p>
<pre><code class="language-sh hljs bash">├── static
│   ├── sw.js</code></pre>
<p>Là encore soit vous recopiez le fichier à la main, soit vous utilisez la
commande suivante dans un environnement GNU Linux ou macOS :</p>
<pre><code class="language-sh hljs bash"><span class="hljs-comment"># à partir du dossier racine d’Hugo</span>
<span class="hljs-built_in">cd</span> static
wget https://raw.githubusercontent.com/wildhaber/offline-first-sw/master/sw.js</code></pre>
<p>Vous devez vous retrouver avec le fichier suivant à la racine :</p>
<pre><code class="language-js hljs javascript"><span class="hljs-keyword">const</span> CACHE_VERSION = <span class="hljs-number">1</span>;

<span class="hljs-keyword">const</span> BASE_CACHE_FILES = [
  <span class="hljs-string">'/style.css'</span>,
  <span class="hljs-string">'/script.js'</span>,
  <span class="hljs-string">'/search.json'</span>,
  <span class="hljs-string">'/manifest.json'</span>,
  <span class="hljs-string">'/favicon.png'</span>,
];

<span class="hljs-keyword">const</span> OFFLINE_CACHE_FILES = [
  <span class="hljs-string">'/style.css'</span>,
  <span class="hljs-string">'/script.js'</span>,
  <span class="hljs-string">'/offline/index.html'</span>,
];

<span class="hljs-keyword">const</span> NOT_FOUND_CACHE_FILES = [
  <span class="hljs-string">'/style.css'</span>,
  <span class="hljs-string">'/script.js'</span>,
  <span class="hljs-string">'/404.html'</span>,
];

<span class="hljs-keyword">const</span> OFFLINE_PAGE = <span class="hljs-string">'/offline/index.html'</span>;
<span class="hljs-keyword">const</span> NOT_FOUND_PAGE = <span class="hljs-string">'/404.html'</span>;

<span class="hljs-keyword">const</span> CACHE_VERSIONS = {
  <span class="hljs-attr">assets</span>: <span class="hljs-string">'assets-v'</span> + CACHE_VERSION,
  <span class="hljs-attr">content</span>: <span class="hljs-string">'content-v'</span> + CACHE_VERSION,
  <span class="hljs-attr">offline</span>: <span class="hljs-string">'offline-v'</span> + CACHE_VERSION,
  <span class="hljs-attr">notFound</span>: <span class="hljs-string">'404-v'</span> + CACHE_VERSION,
};

<span class="hljs-comment">// Durée de mise en cache en SECONDES en fonction des différentes extensions</span>
<span class="hljs-keyword">const</span> MAX_TTL = {
  <span class="hljs-string">'/'</span>: <span class="hljs-number">3600</span>,
  <span class="hljs-attr">html</span>: <span class="hljs-number">3600</span>,
  <span class="hljs-attr">json</span>: <span class="hljs-number">86400</span>,
  <span class="hljs-attr">js</span>: <span class="hljs-number">86400</span>,
  <span class="hljs-attr">css</span>: <span class="hljs-number">86400</span>,
};

<span class="hljs-keyword">const</span> CACHE_BLACKLIST = [
  <span class="hljs-comment">//(str) =&gt; {</span>
  <span class="hljs-comment">//    return !str.startsWith('http://localhost') &amp;&amp; !str.startsWith('https://jamstatic.fr');</span>
  <span class="hljs-comment">//},</span>
];

<span class="hljs-keyword">const</span> SUPPORTED_METHODS = [
  <span class="hljs-string">'GET'</span>,
];

<span class="hljs-comment">/**
 * isBlackListed
 * @param {string} url
 * @returns {boolean}
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">isBlacklisted</span>(<span class="hljs-params">url</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-function">(<span class="hljs-params">CACHE_BLACKLIST.length &gt; <span class="hljs-number">0</span></span>) ? !<span class="hljs-params">CACHE_BLACKLIST</span>.<span class="hljs-params">filter</span>(<span class="hljs-params">(rule</span>) =&gt;</span> {
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> rule === <span class="hljs-string">'function'</span>) {
      <span class="hljs-keyword">return</span> !rule(url);
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
    }
  }).length : <span class="hljs-literal">false</span>
}

<span class="hljs-comment">/**
 * getFileExtension
 * <span class="hljs-doctag">@param <span class="hljs-type">{string}</span> <span class="hljs-variable">url</span></span>
 * <span class="hljs-doctag">@returns <span class="hljs-type">{string}</span></span>
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getFileExtension</span>(<span class="hljs-params">url</span>) </span>{
  <span class="hljs-keyword">let</span> extension = url.split(<span class="hljs-string">'.'</span>).reverse()[<span class="hljs-number">0</span>].split(<span class="hljs-string">'?'</span>)[<span class="hljs-number">0</span>];
  <span class="hljs-keyword">return</span> (extension.endsWith(<span class="hljs-string">'/'</span>)) ? <span class="hljs-string">'/'</span> : extension;
}

<span class="hljs-comment">/**
 * getTTL
 * <span class="hljs-doctag">@param <span class="hljs-type">{string}</span> <span class="hljs-variable">url</span></span>
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getTTL</span>(<span class="hljs-params">url</span>) </span>{
  <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> url === <span class="hljs-string">'string'</span>) {
    <span class="hljs-keyword">let</span> extension = getFileExtension(url);
    <span class="hljs-keyword">if</span> (<span class="hljs-keyword">typeof</span> MAX_TTL[extension] === <span class="hljs-string">'number'</span>) {
      <span class="hljs-keyword">return</span> MAX_TTL[extension];
    } <span class="hljs-keyword">else</span> {
      <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
    }
  } <span class="hljs-keyword">else</span> {
    <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
  }
}

<span class="hljs-comment">/**
 * installServiceWorker
 * <span class="hljs-doctag">@returns <span class="hljs-type">{Promise}</span></span>
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">installServiceWorker</span>(<span class="hljs-params"></span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">Promise</span>.all(
    [
      caches.open(CACHE_VERSIONS.assets)
      .then(
        <span class="hljs-function">(<span class="hljs-params">cache</span>) =&gt;</span> {
          <span class="hljs-keyword">return</span> cache.addAll(BASE_CACHE_FILES);
        }
      ),
      caches.open(CACHE_VERSIONS.offline)
      .then(
        <span class="hljs-function">(<span class="hljs-params">cache</span>) =&gt;</span> {
          <span class="hljs-keyword">return</span> cache.addAll(OFFLINE_CACHE_FILES);
        }
      ),
      caches.open(CACHE_VERSIONS.notFound)
      .then(
        <span class="hljs-function">(<span class="hljs-params">cache</span>) =&gt;</span> {
          <span class="hljs-keyword">return</span> cache.addAll(NOT_FOUND_CACHE_FILES);
        }
      )
    ]
  );
}

<span class="hljs-comment">/**
 * cleanupLegacyCache
 * <span class="hljs-doctag">@returns <span class="hljs-type">{Promise}</span></span>
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">cleanupLegacyCache</span>(<span class="hljs-params"></span>) </span>{

  <span class="hljs-keyword">let</span> currentCaches = <span class="hljs-built_in">Object</span>.keys(CACHE_VERSIONS)
    .map(
      <span class="hljs-function">(<span class="hljs-params">key</span>) =&gt;</span> {
        <span class="hljs-keyword">return</span> CACHE_VERSIONS[key];
      }
    );

  <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(
    <span class="hljs-function">(<span class="hljs-params">resolve, reject</span>) =&gt;</span> {

      caches.keys()
        .then(
          <span class="hljs-function">(<span class="hljs-params">keys</span>) =&gt;</span> {
            <span class="hljs-keyword">return</span> legacyKeys = keys.filter(
              <span class="hljs-function">(<span class="hljs-params">key</span>) =&gt;</span> {
                <span class="hljs-keyword">return</span> !~currentCaches.indexOf(key);
              }
            );
          }
        )
        .then(
          <span class="hljs-function">(<span class="hljs-params">legacy</span>) =&gt;</span> {
            <span class="hljs-keyword">if</span> (legacy.length) {
              <span class="hljs-built_in">Promise</span>.all(
                  legacy.map(
                    <span class="hljs-function">(<span class="hljs-params">legacyKey</span>) =&gt;</span> {
                      <span class="hljs-keyword">return</span> caches.delete(legacyKey)
                    }
                  )
                )
                .then(
                  <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
                    resolve()
                  }
                )
                .catch(
                  <span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
                    reject(err);
                  }
                );
            } <span class="hljs-keyword">else</span> {
              resolve();
            }
          }
        )
        .catch(
          <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
            reject();
          }
        );

    }
  );
}


self.addEventListener(
  <span class="hljs-string">'install'</span>, event =&gt; {
    event.waitUntil(installServiceWorker());
  }
);

<span class="hljs-comment">// La méthode activate est chargée de supprimer les vieux caches</span>
self.addEventListener(
  <span class="hljs-string">'activate'</span>, event =&gt; {
    event.waitUntil(
      <span class="hljs-built_in">Promise</span>.all(
        [
          cleanupLegacyCache(),
        ]
      )
      .catch(
        <span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
          event.skipWaiting();
        }
      )
    );
  }
);

self.addEventListener(
  <span class="hljs-string">'fetch'</span>, event =&gt; {

    event.respondWith(
      caches.open(CACHE_VERSIONS.content)
      .then(
        <span class="hljs-function">(<span class="hljs-params">cache</span>) =&gt;</span> {

          <span class="hljs-keyword">return</span> cache.match(event.request)
            .then(
              <span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {

                <span class="hljs-keyword">if</span> (response) {

                  <span class="hljs-keyword">let</span> headers = response.headers.entries();
                  <span class="hljs-keyword">let</span> date = <span class="hljs-literal">null</span>;

                  <span class="hljs-keyword">for</span> (<span class="hljs-keyword">let</span> pair <span class="hljs-keyword">of</span> headers) {
                    <span class="hljs-keyword">if</span> (pair[<span class="hljs-number">0</span>] === <span class="hljs-string">'date'</span>) {
                      date = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>(pair[<span class="hljs-number">1</span>]);
                    }
                  }

                  <span class="hljs-keyword">if</span> (date) {
                    <span class="hljs-keyword">let</span> age = <span class="hljs-built_in">parseInt</span>((<span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().getTime() - date.getTime()) / <span class="hljs-number">1000</span>);
                    <span class="hljs-keyword">let</span> ttl = getTTL(event.request.url);

                    <span class="hljs-keyword">if</span> (ttl &amp; amp; &amp; amp; age &gt; ttl) {

                      <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Promise</span>(
                          <span class="hljs-function">(<span class="hljs-params">resolve</span>) =&gt;</span> {

                            <span class="hljs-keyword">return</span> fetch(event.request)
                              .then(
                                <span class="hljs-function">(<span class="hljs-params">updatedResponse</span>) =&gt;</span> {
                                  <span class="hljs-keyword">if</span> (updatedResponse) {
                                    cache.put(event.request, updatedResponse.clone());
                                    resolve(updatedResponse);
                                  } <span class="hljs-keyword">else</span> {
                                    resolve(response)
                                  }
                                }
                              )
                              .catch(
                                <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {
                                  resolve(response);
                                }
                              );

                          }
                        )
                        .catch(
                          <span class="hljs-function">(<span class="hljs-params">err</span>) =&gt;</span> {
                            <span class="hljs-keyword">return</span> response;
                          }
                        );
                    } <span class="hljs-keyword">else</span> {
                      <span class="hljs-keyword">return</span> response;
                    }

                  } <span class="hljs-keyword">else</span> {
                    <span class="hljs-keyword">return</span> response;
                  }

                } <span class="hljs-keyword">else</span> {
                  <span class="hljs-keyword">return</span> <span class="hljs-literal">null</span>;
                }
              }
            )
            .then(
              <span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
                <span class="hljs-keyword">if</span> (response) {
                  <span class="hljs-keyword">return</span> response;
                } <span class="hljs-keyword">else</span> {
                  <span class="hljs-keyword">return</span> fetch(event.request)
                    .then(
                      <span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {

                        <span class="hljs-keyword">if</span> (response.status &lt; <span class="hljs-number">400</span>) {
                          <span class="hljs-keyword">if</span> (~SUPPORTED_METHODS.indexOf(event.request.method) &amp; amp; &amp; amp; !isBlacklisted(event.request.url)) {
                            cache.put(event.request, response.clone());
                          }
                          <span class="hljs-keyword">return</span> response;
                        } <span class="hljs-keyword">else</span> {
                          <span class="hljs-keyword">return</span> caches.open(CACHE_VERSIONS.notFound).then(<span class="hljs-function">(<span class="hljs-params">cache</span>) =&gt;</span> {
                            <span class="hljs-keyword">return</span> cache.match(NOT_FOUND_PAGE);
                          })
                        }
                      }
                    )
                    .then(<span class="hljs-function">(<span class="hljs-params">response</span>) =&gt;</span> {
                      <span class="hljs-keyword">if</span> (response) {
                        <span class="hljs-keyword">return</span> response;
                      }
                    })
                    .catch(
                      <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> {

                        <span class="hljs-keyword">return</span> caches.open(CACHE_VERSIONS.offline)
                          .then(
                            <span class="hljs-function">(<span class="hljs-params">offlineCache</span>) =&gt;</span> {
                              <span class="hljs-keyword">return</span> offlineCache.match(OFFLINE_PAGE)
                            }
                          )

                      }
                    );
                }
              }
            )
            .catch(
              <span class="hljs-function">(<span class="hljs-params">error</span>) =&gt;</span> {
                <span class="hljs-built_in">console</span>.error(<span class="hljs-string">'Error in fetch handler:'</span>, error);
                <span class="hljs-keyword">throw</span> error;
              }
            );
        }
      )
    );

  }
);</code></pre>
<p>Maintenant vous pouvez définir le comportement souhaité pour votre Service
Worker :</p>
<h4 id="fichiers-a-mettre-en-cache-par-defaut">Fichiers à mettre en cache par défaut</h4>
<pre><code class="language-js hljs javascript"><span class="hljs-keyword">const</span> BASE_CACHE_FILES = [
  <span class="hljs-string">"/style.css"</span>,
  <span class="hljs-string">"/script.js"</span>,
  <span class="hljs-string">"/search.json"</span>,
  <span class="hljs-string">"/manifest.json"</span>,
  <span class="hljs-string">"/favicon.png"</span>,
];</code></pre>
<p>Listez dans ce tableau tous les fichiers qui devraient être mis en cache par
défaut</p>
<h4 id="fichiers-en-mode-hors-connexion">Fichiers en mode hors-connexion</h4>
<pre><code class="language-js hljs javascript"><span class="hljs-keyword">const</span> OFFLINE_CACHE_FILES = [<span class="hljs-string">"/style.css"</span>, <span class="hljs-string">"/script.js"</span>, <span class="hljs-string">"/offline/index.html"</span>];</code></pre>
<p>Listez dans ce tableau les fichiers nécessaires pour l’affichage de votre page
<code>offline</code>.</p>
<h4 id="fichiers-en-cas-d-erreur-4xx">Fichiers en cas d’erreur 4xx</h4>
<pre><code class="language-js hljs javascript"><span class="hljs-keyword">const</span> NOT_FOUND_CACHE_FILES = [<span class="hljs-string">"/style.css"</span>, <span class="hljs-string">"/script.js"</span>, <span class="hljs-string">"/404.html"</span>];</code></pre>
<p>Listez dans ce tableau les fichiers nécessaires pour l’affichage de votre page
d’erreur 404.</p>
<h4 id="page-hors-connexion">Page hors-connexion</h4>
<pre><code class="language-js hljs javascript"><span class="hljs-keyword">const</span> OFFLINE_PAGE = <span class="hljs-string">"/offline/index.html"</span>;</code></pre>
<p>C’est la page qui sera affichée quand le visiteur sera déconnecté du réseau ou
que la page n'est pas déjà en cache.</p>
<h4 id="page-d-erreur">Page d’erreur</h4>
<pre><code class="language-js hljs javascript"><span class="hljs-keyword">const</span> NOT_FOUND_PAGE = <span class="hljs-string">"/404.html"</span>;</code></pre>
<p>Le chemin de la page qui sera affichée en cas d’erreur de type 4xx.</p>
<h4 id="duree-de-mise-en-cache">Durée de mise en cache</h4>
<pre><code class="language-js hljs javascript"><span class="hljs-keyword">const</span> MAX_TTL = {
  <span class="hljs-string">"/"</span>: <span class="hljs-number">3600</span>,
  <span class="hljs-attr">html</span>: <span class="hljs-number">3600</span>,
  <span class="hljs-attr">json</span>: <span class="hljs-number">86400</span>,
  <span class="hljs-attr">js</span>: <span class="hljs-number">86400</span>,
  <span class="hljs-attr">css</span>: <span class="hljs-number">86400</span>,
};</code></pre>
<p>Ce tableau clé-valeur indique pour chaque type d’extension de fichier la durée
maximum de mise en cache appelée <em>Time To Live</em> (définit <strong>en secondes</strong> et pas
en millisecondes). C’est le temps qui s'écoulera avant qu'un fichier ne soit mis
à jour à partir du réseau.</p>
<p>Les extensions non présentes resteront en cache jusqu'à la prochaine la mise à
jour du cache par le Service Worker.</p>
<pre><code class="language-js hljs javascript"><span class="hljs-comment">// 60 = 1 minute</span>
<span class="hljs-comment">// 3600 = 1 heure</span>
<span class="hljs-comment">// 86400 = 1 jour</span>
<span class="hljs-comment">// 604800 = 1 semaine</span>
<span class="hljs-comment">// 2592000 = 30 jours (~ 1 mois)</span>
<span class="hljs-comment">// 31536000 = 1 an</span></code></pre>
<h4 id="fichiers-a-exclure-de-la-mise-en-cache">Fichiers à exclure de la mise en cache</h4>
<pre><code class="language-js hljs javascript"><span class="hljs-keyword">const</span> CACHE_BLACKLIST = [
  <span class="hljs-function">(<span class="hljs-params">str</span>) =&gt;</span> {
    <span class="hljs-comment">// str = URL de la ressource</span>
    <span class="hljs-comment">// Appliquez cette règle lorsque vous ne voulez pas mettre des fichiers externes en cache</span>
    <span class="hljs-keyword">return</span> !str.startsWith(<span class="hljs-string">"https://votresiteweb.tld"</span>);
  },
];</code></pre>
<p>Ajustez ces paramètres au contexte de votre site ou de votre application.</p>
<h3 id="enregistrement-du-service-worker">Enregistrement du Service Worker</h3>
<p>Ajoutez le script suivant avant la fermeture de la balise <code>&lt;body&gt;</code> ou placez-le
dans votre fichier JavaScript généré :</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
  <span class="hljs-keyword">if</span> (<span class="hljs-string">"serviceWorker"</span> <span class="hljs-keyword">in</span> navigator) {
    navigator.serviceWorker
      .register(<span class="hljs-string">"/sw.js"</span>, { <span class="hljs-attr">scope</span>: <span class="hljs-string">"/"</span> })
      .then(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">registration</span>) </span>{
        <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Service Worker enregistré"</span>);
      });

    navigator.serviceWorker.ready.then(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">registration</span>) </span>{
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">"Service Worker prêt"</span>);
    });
  }
</span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></code></pre>
<p>Ce code JS va enregistrer, installer et activer votre Service Worker.</p>
<p>Vous en avez à présent terminé avec toutes les étapes nécessaires. Vous disposez
maintenant d’un site Hugo ultra-rapide. :)</p>
<h3 id="deboguer-votre-service-worker">Déboguer votre Service Worker</h3>
<p>Pour déboguer un Service Worker avec Google Chrome, il vous suffit d’ouvrir la
console et d’aller dans l’onglet <code>Application</code>. C’est là que vous trouverez
votre Service Worker et vos caches.</p>
<p>Vous en apprendrez davantage sur le
<a href="https://developers.google.com/web/fundamentals/getting-started/codelabs/debugging-service-workers/" target="_blank" rel="noopener noreferrer">débogage de Service Workers</a>
sur le site pour les développeurs de Google.</p>
<p>Si votre navigateur préféré est Firefox vous en saurez plus sur
<a href="https://hacks.mozilla.org/2016/03/debugging-service-workers-and-push-with-firefox-devtools/" target="_blank" rel="noopener noreferrer">le débogage des Service Workers et Push à l’aide des outils de développement pour Firefox</a>
sur hacks.mozilla.org.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2017/02/23/creer-un-environnement-de-preproduction-pour-jekyll/</id>
    <title>Créer un environnement de préproduction pour Jekyll</title>
    <published>2017-02-23T00:00:00+00:00</published>
    <link href="https://jamstatic.fr/2017/02/23/creer-un-environnement-de-preproduction-pour-jekyll/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Dans son article, <a href="https://eduardoboucas.com/" target="_blank" rel="noopener noreferrer">Eduardo Boucas</a> révèle comment il
a mis simplement en place <a href="https://eduardoboucas.com/blog/2017/02/22/jekyll-staging-environment.html" target="_blank" rel="noopener noreferrer">un site de préproduction pour Jekyll grâce à Netlify</a>.
Ce blog est également déployé et hébergé grâce à Netlify. La nouvelle version de
leur interface d’administration propose nativement des fonctionnalités comme le
fait d’associer un sous-domaine à une branche ou le fait de pouvoir bloquer le
site de production à un certain commit. Vous n'avez donc pas besoin de créer
deux sites dans Netlify pour bénéficier d’une prévisualisation sur une URL
dédiée. L'article d’Eduardo aborde néanmoins l’utilisation des variables
d’environnements et utilise Jekyll comme exemple, la technique reste bien
entendu valable pour d’autres générateurs comme Hugo, Hexo et les autres.</p></aside>
<p>Un environnement de préproduction ou de <em>staging</em> est une infrastructure de test
qui s'approche autant que possible de la configuration d’un site de production.
Dans le cas d’un site statique, il peut servir à partager un nouvel article ou
une nouvelle fonctionnalité avec un nombre de personnes restreintes avant de le
rendre disponible publiquement. Dans cet article, je vais vous montrer comment
j'ai fait pour en créer un et comment je m'en sers.</p>
<h2 id="le-workflow-git">Le workflow Git</h2>
<p>Mon site est hébergé sur GitHub et servi grâce à
<a href="https://pages.github.com/" target="_blank" rel="noopener noreferrer">GitHub Pages</a>, ce qui signifie que tout ce que je
pousse sur la branche <code>master</code> déclenche une régénération du site et est publié
presque immédiatement. Si je visite l’URL de mon site quelques secondes plus
tard, je peux voir les contenus mis à jour.</p>
<p>Pour notre environnement de préproduction, ce que nous voulons c'est faire une
copie basique de cette infrastructure de manière à faire passer notre contenu
par un site distinct, avec une URL distincte. La démarche ressemblerait à
quelque chose comme :</p>
<ol>
<li>Pousser les changements sur GitHub</li>
<li>Le site de préproduction est régénéré, l’URL de préproduction peut être utilisée pour prévisualiser le nouvel état</li>
<li>Créer une pull request de la préproduction vers la production pour répercuter les changements</li>
<li>Le site de production est régénéré, l’URL de production reflète le nouvel état du site</li>
</ol>
<p>Pour que notre système fonctionne uniquement avec GitHub Pages nous allons avoir
besoin de deux dépôts, puisqu'on ne peut pas servir deux sites à partir d’un
seul dépôt. Mais à moins qu'un dépôt ne soit le fork d’un autre, ce qui signifie
avoir deux comptes GitHub ou utiliser un compte <em>organisation</em>, vous ne pouvez
pas créer de pull request entre eux.</p>
<p>J'utilise donc <a href="https://netlify.com" target="_blank" rel="noopener noreferrer">Netlify</a> à la place pour servir mon site de
préproduction à partir de la branche <code>dev</code> de mon dépôt existant - GitHub Pages
sert <code>eduardoboucas.com</code> à partir de la branche <code>master</code> et Netlify sert
<code>dev.eduardoboucas.com</code> à partir de la branche <code>dev</code>.</p>
<p>Pour créer cette branche, vous pouvez utiliser la commande suivante :</p>
<pre><code class="language-sh hljs bash"><span class="hljs-comment"># Partir de la branche actuelle et en créer une nouvelle appelée dev</span>
git checkout -b dev

<span class="hljs-comment"># Pousser la nouvelle branche sur le dépôt distant</span>
git push origin dev</code></pre>
<h2 id="utilisation-de-netlify">Utilisation de Netlify</h2>
<p>Pour commencer à utiliser Netlify, rendez-vous sur
<a href="https://netlify.com" target="_blank" rel="noopener noreferrer">leur site</a> et connectez-vous avec votre compte GitHub
(c'est <a href="https://netlify.com/pricing/" target="_blank" rel="noopener noreferrer">gratuit pour les projets open-source</a>).
Cliquez sur <code>Ajoutez un nouveau projet</code>, sélectionnez GitHub et sélectionnez le
dépôt qui dans lequel se trouve votre site.</p>
<figure>
<picture title="Netlify : Configuration du dépôt">
<source type="image/webp" srcset="/thumbnails/768x/images/2017-02-23_creer-un-environnement-de-preproduction-pour-jekyll/netlify1-1666827229390-61.c1ad32726dfa1343307a62650ee7cf0a.webp 768w, /thumbnails/1024x/images/2017-02-23_creer-un-environnement-de-preproduction-pour-jekyll/netlify1-1666827229390-61.c1ad32726dfa1343307a62650ee7cf0a.webp 1024w" width="1024" height="616" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/2017-02-23_creer-un-environnement-de-preproduction-pour-jekyll/netlify1-1666827229390-61.c1ad32726dfa1343307a62650ee7cf0a.avif 768w, /thumbnails/1024x/images/2017-02-23_creer-un-environnement-de-preproduction-pour-jekyll/netlify1-1666827229390-61.c1ad32726dfa1343307a62650ee7cf0a.avif 1024w" width="1024" height="616" sizes="100vw">
<img src="/images/2017-02-23_creer-un-environnement-de-preproduction-pour-jekyll/netlify1-1666827229390-61.c1ad32726dfa1343307a62650ee7cf0a.png" alt="Netlify : Configuration du dépôt" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="616" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/2017-02-23_creer-un-environnement-de-preproduction-pour-jekyll/netlify1-1666827229390-61.c1ad32726dfa1343307a62650ee7cf0a.png 768w, /thumbnails/1024x/images/2017-02-23_creer-un-environnement-de-preproduction-pour-jekyll/netlify1-1666827229390-61.c1ad32726dfa1343307a62650ee7cf0a.png 1024w" sizes="100vw">
</picture>
<figcaption>Netlify : Configuration du dépôt</figcaption>
</figure>
<p>Dans l’onglet <code>Paramètres de base</code>, sélectionnez votre branche de préproduction
(par exemple <code>dev</code>). Pour un site avec Jekyll, le dossier de publication par
défaut est <code>_site</code> et la commande de build <code>jekyll build</code>. Dans l’onglet
<code>Paramètres avancés</code>, ajouter une variable d’environnement nommée <code>JEKYLL_ENV</code>
avec la valeur <code>stage</code> — cela va servir à dire à Jekyll dans quel environnement
tourne le site.</p>
<p>Ensuite, cliquez sur <code>Générer votre site</code> et attendez quelques secondes. Lorsque
la génération du site est terminée, cliquez sur <code>Voir votre site</code> pour voir le
résultat.</p>
<p>Un nom aléatoire vous sera attribué, comme <code>cartoonist-foreground-47121</code>, que
vous pourrez ensuite modifier dans les paramètres. Vous pouvez également définir
un nom de domaine personnalisé pour ce site, pour cela vous devrez configurer
votre DNS. Si vous avez choisi <code>dev-example-com</code> comme nom pour votre site, il
vous faudra un CNAME qui pointe vers <code>dev-example-com.netlify.com</code>.</p>
<figure>
<picture title="Netlify : le panneau de configuration">
<source type="image/webp" srcset="/thumbnails/768x/images/2017-02-23_creer-un-environnement-de-preproduction-pour-jekyll/netlify2.a9c28797b4e0daa084c9f58244778be2.webp 768w, /thumbnails/1024x/images/2017-02-23_creer-un-environnement-de-preproduction-pour-jekyll/netlify2.a9c28797b4e0daa084c9f58244778be2.webp 1024w" width="1024" height="616" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/2017-02-23_creer-un-environnement-de-preproduction-pour-jekyll/netlify2.a9c28797b4e0daa084c9f58244778be2.avif 768w, /thumbnails/1024x/images/2017-02-23_creer-un-environnement-de-preproduction-pour-jekyll/netlify2.a9c28797b4e0daa084c9f58244778be2.avif 1024w" width="1024" height="616" sizes="100vw">
<img src="/images/2017-02-23_creer-un-environnement-de-preproduction-pour-jekyll/netlify2.a9c28797b4e0daa084c9f58244778be2.png" alt="Netlify : le panneau de configuration" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="616" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/2017-02-23_creer-un-environnement-de-preproduction-pour-jekyll/netlify2.a9c28797b4e0daa084c9f58244778be2.png 768w, /thumbnails/1024x/images/2017-02-23_creer-un-environnement-de-preproduction-pour-jekyll/netlify2.a9c28797b4e0daa084c9f58244778be2.png 1024w" sizes="100vw">
</picture>
<figcaption>Netlify : le panneau de configuration</figcaption>
</figure>
<h2 id="configuration-de-jekyll">Configuration de Jekyll</h2>
<p>On peut accéder à la variable d’environnement que nous avons créé plus haut dans
Jekyll, en utilisant la variable <code>jekyll.environment</code> dans n'importe quel modèle
Liquid. Grâce à ça, nous pouvons effectuer quelques ajustements au site en
fonction de l’environnement dans lequel il tourne.</p>
<p>Par exemple, nous ne voulons pas que le site de préproduction soit indexé par
les moteurs de recherche.</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> jekyll.environment == 'stage' %}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"robots"</span> <span class="hljs-attr">content</span>=<span class="hljs-string">"noindex"</span>&gt;</span>
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span></code></pre>
<p>Vous pouvez même ajouter une bannière en haut de chaque page, pour avertir les
visiteurs qu'ils consultent la version de développement de votre site.</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">if</span></span> jekyll.environment == 'stage' %}</span><span class="xml">
  <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"banner"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://eduardoboucas.com"</span>&gt;</span>
      Ceci est la version de développement du site. Cliquez ici pour voir la version de production.
    <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endif</span></span> %}</span></code></pre>
<p>Et voilà mon <a href="https://dev.eduardoboucas.com" target="_blank" rel="noopener noreferrer">site de préproduction</a> est
configuré. Lorsque je veux demander à quelqu'un de relire un article avant qu'il
ne soit publié, je le pousse sur la branche <code>dev</code> pour le publier sur le site de
préproduction, pour le publier en production, j'ai juste à le fusionner dans la
branche <code>master</code>.</p>
<p>Vous pourriez même vous passer complètement de GitHub Pages (NdT: et de ses
limitations) et vous reposer entièrement sur Netlify pour servir vos
environnements de production et de préproduction (ce que je risque de faire
bientôt pour des raisons que je dévoilerai dans un prochain article)</p>
<p>Et voilà, vous avez maintenant un environnement de préproduction simple avec
intégration continue pour un site statique et tout ça gratuitement. Pas mal,
non ?</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2017/02/10/creer-des-mises-en-page-dynamiques-avec-jekyll/</id>
    <title>Créer des mises en page dynamiques avec Jekyll</title>
    <published>2017-02-10T00:00:00+00:00</published>
    <link href="https://jamstatic.fr/2017/02/10/creer-des-mises-en-page-dynamiques-avec-jekyll/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Dans <a href="https://medium.com/tmw-interactive/creating-dynamic-layouts-with-jekyll-3bbb7fc57d1f#.iac16fjec" target="_blank" rel="noopener noreferrer">son article publié sur Medium</a>, Zander Martineau partage les secrets de fabrication du portfolio client de son agence. Zander a dû faire preuve d’ingéniosité et de créativité afin de pouvoir varier les mises en pages des différentes études de cas à l’aide de Jekyll.</p></aside>
<blockquote>
<p>Voici comment nous avons tiré profit du YAML front matter pour pouvoir
effectuer de nombreux changements au sein d’un même modèle pour différents
articles sur le nouveau site de notre agence.</p>
</blockquote>
<figure>
<picture title="Extrait de la campagne Canon #unleashprint">
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346708/jamstatic/canon-unleashprint-full.4f4ec6a0cd4d8c65ea0440106dcd24d0.webp 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346708/jamstatic/canon-unleashprint-full.4f4ec6a0cd4d8c65ea0440106dcd24d0.webp 1024w" width="1024" height="683" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346708/jamstatic/canon-unleashprint-full.4f4ec6a0cd4d8c65ea0440106dcd24d0.avif 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346708/jamstatic/canon-unleashprint-full.4f4ec6a0cd4d8c65ea0440106dcd24d0.avif 1024w" width="1024" height="683" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346708/jamstatic/canon-unleashprint-full.4f4ec6a0cd4d8c65ea0440106dcd24d0.jpg" alt="Extrait de la campagne Canon #unleashprint" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="683" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8AqXU4CHmubvbgFjzU13fEqeaw5pi7GqwuDcdWFfMed2RIZMmpI25qmpNTI+K9LksjbD12zUhlAFMupwVNVBKQKq3E5wa4qq5Xc9zDzuVbpssapE4apZXzVZutRGomFaOpehbirsPJFZcL81owP0rqhLQdE1IjxUwNVY24qwgzUy1O1KxJminCM4oqLDuZMshaoghNP2kmp4o8mvSm1FaH5lQg5S1IViPpThEfStOK13DpUps8DpXBOurn0mGoaGOUOKrTRmt1rX2qrLa8dKylJTPXowsc5KhBqAiti4tsdqz5IiD0rlcXHU63C5CnBq9A3SqoWp4eDV0q2tio07GtCeBV2IiqMHQVaU4r0IJM1voXg4xRVUMaK05ERYqbRVmBORVON8mtG37VNV6Hw1GEb6GlAgxU5UYqKI8VLmvKnue9h1oQsgqtMq4qeeTaKy57jk81vSpSlqelBEU6Kc1nSwAmrLTZPWnqu8VdWi0jtpJdTLaDBoRCGrSkhqFYvmrjp0nzGsmkiS3BwKvxRFqjgi6VqQRACvVp6I5myAW3HSitER8UVpdiuclD2rUt+1FFRXPh8PuacXSph0oory5bn0FDYpXfQ1izdTRRXp4fY9SlsVT96rtv0FFFXX+E6Yk0lQL96iiuKG45bF6DtWnD0oorriYMsCiiiqEf/9k=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346708/jamstatic/canon-unleashprint-full.4f4ec6a0cd4d8c65ea0440106dcd24d0.jpg 768w, /thumbnails/1024x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346708/jamstatic/canon-unleashprint-full.4f4ec6a0cd4d8c65ea0440106dcd24d0.jpg 1024w" sizes="100vw">
</picture>
<figcaption>Extrait de la campagne Canon <a href="https://www.tmwunlimited.com/work/canon-unleashprint" target="_blank" rel="noopener noreferrer">#unleashprint</a></figcaption>
</figure>
<p>Les sites web créés avec <a href="https://jekyllrb.com/" target="_blank" rel="noopener noreferrer">Jekyll</a> sont généralement simples et leurs mises en page prévisible. J'aimerais vous montrer comme j’ai créé une mise en page pseudo-dynamique pour des études de cas sur <a href="https://www.tmwunlimited.com" target="_blank" rel="noopener noreferrer">le nouveau site de TMW</a>, en utilisant du YAML front matter et un peu de magie<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref">1</a></sup>…</p>
<p>Chez TMW, nous travaillons sur des projets de toutes sortes et de toutes tailles. Des modèles de page classiques ne suffiraient pas, car nous faisons un travail extrêmement varié et les mises en page doivent refléter cela. Nous avons décidé, au tout début de la refonte de notre site, que chaque étude de cas devrait changer en fonction du projet et des types de contenu spécifiques. Cela constituait un défi intéressant à relever car Jekyll n'offre pas cette fonctionnalité nativement, j'ai dû donc faire preuve d’un peu d’imagination…</p>
<h2 id="du-yaml-front-matter-une-boucle-for-et-pas-mal-de-modules">Du YAML front matter, une boucle <code>for</code> et pas mal de modules</h2>
<p>Après quelques itérations, je me suis arrêté à une solution robuste et peu orthodoxe, qui faisait appel à des variables définies dans le YAML front matter (puis utilisées dans le Markdown de chaque étude de cas), une simple boucle for (dans le modèle de page des études de cas) et à beaucoup de modules pour chaque section.</p>
<h3 id="le-yaml-front-matter">Le YAML front matter</h3>
<p>Nous avons ajouté un tableau YAML <code>partials</code> dans les entêtes YAML front matter de chaque étude de cas (comme vous pouvez le voir dans le code inséré ci-dessous). Chaque élément du tableau possède une propriété <code>name</code> qui correspond au <code>name</code> du partial/module qui sera utilisé.</p>
<p><script src="https://gist.github.com/mrmartineau/ee7cd73fcfdef19b45afd01c4d6b3b9f.js"></p>
<h3 id="la-boucle-for">La boucle <code>for</code></h3>
<p>La boucle en question a été ajoutée dans le modèle <code>work</code> du dossier <code>_layouts</code>. Ça a l’air un peu timbré — et ça l’est — mais s'il vous plaît, restez avec moi. La façon intrinsèque dont Jekyll compile les fichiers fait que j'ai du listé toutes les propriétés possibles pour chaque module utilisé dans le YAML.</p>
<p>La boucle parcourt le tableau <code>partials</code> et utilise la propriété <code>name</code> pour inclure un module <em>différent</em>, comme ceci: <code>include {{item.name}}.html</code>. Ceux-ci ont ensuite été transmis au module inclus en utilisant les paramètres suivants de la balise <code>include</code>. Même si la propriété n'était pas nécessaire dans ce module, elle devait néanmoins être transmise.</p>
<p>Très vite, j'ai compris que les propriétés du tableau <code>partials</code> devaient partager les mêmes propriétés pour que ma boucle <em>for</em> ne parte pas en sucette.</p>
<p><script src="https://gist.github.com/mrmartineau/e0ad7ae56552c9571e285e30e3469476.js"></p>
<h3 id="les-modules">Les modules</h3>
<p>Créer des modules pour cette page n'avait vraiment rien d’ordinaire, comme vous pouvez le voir dans celui montré un peu plus bas. Vous pouvez voir que certaines des valeurs sont optionnelles (comme <code>{% if include.spaced %}</code>), ce qui veut dire que j'ai poussé la personnalisation encore plus loin en ajoutant ou en retirant des classes et du contenu pour donner à la page un caractère encore plus singulier. Avec par exemple l’ajout optionnel d’images dans le module <code>section-image</code> à une colonne égale à la largeur de l’élément <code>.l-container</code> ou à une version un peu plus large via l’utilisation de la classe <code>.l-container--wide</code>. Cette technique a été utilisé avec le plus bel effet sur <a href="https://www.tmwunlimited.com/work/unilever-lynx-bigger-issues/" target="_blank" rel="noopener noreferrer">l’étude de case de Lynx Bigger Issues</a>.</p>
<p><script src="https://gist.github.com/mrmartineau/8919159d58818c8530d516d118c3b838.js"></p>
<h3 id="les-palettes-de-couleur">Les palettes de couleur</h3>
<p>Une autre caractéristique des études de cas, était qu'ils avaient chacun leur propre palette chromatique. Elle est génèralement influencée par les visuels ou la marque dans ce cas particulier et défini encore une fois dans des entêtes YAML front matter.</p>
<p>Les couleurs primaires, secondaires ainsi que celles du texte sont définies dans du YAML et ajoutées ensuite dans un petit bloc <code>&lt;style&gt;</code> embarqué dans la page et qui modifie certains aspects de la présentation de la page.</p>
<p><script src="https://gist.github.com/mrmartineau/01bde36c11a6b799112148a8dc83cc16.js"></p>
<p>Grâce à tout cela, nous avons pu ajouter pas mal de personnalisation à notre nouveau site web, ci-dessous une sélection d’études de cas :</p>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346747/jamstatic/canon-unleashprint-fullpage.c779f41e4e939db155f0cb5b7396c034.webp 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346747/jamstatic/canon-unleashprint-fullpage.c779f41e4e939db155f0cb5b7396c034.webp 800w" width="800" height="5271" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346747/jamstatic/canon-unleashprint-fullpage.c779f41e4e939db155f0cb5b7396c034.avif 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346747/jamstatic/canon-unleashprint-fullpage.c779f41e4e939db155f0cb5b7396c034.avif 800w" width="800" height="5271" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346747/jamstatic/canon-unleashprint-fullpage.c779f41e4e939db155f0cb5b7396c034.jpg" alt="" loading="lazy" decoding="async" class="dark:brightness-90" width="800" height="5271" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A9LnuAg61Sa/GetJdq5Xisowybu9YTm0dUYJo1xejHWg3yjvWX5bgd6gkEg9awjVk2ZzikjXOoL6006ko71hP5me9QOZR61309Ucbk72N99UUd6rvq6jvXPSNJ71Ucyk9TW3KjSNzqf7XXPWnDVl9a5RA/qakJcDqalxVjto01Lc6uPVlLda1rS8EmOa8+ikcOOa6jSXYgZrhnJpnTUoxjG6OrEnFFQJ9wUVV2cV0SSWgYdKgOnjPStnbRsHpWjjcz52Yh08elRtpoPat7yx6Unlj0qVSQnJs5w6UPSoJdKGOldSYh6VDLEMdK1WhCWpx8ulAZ4rKubIIeldhd4UGsG6G8mjnsdCptmEIsHpSmAntWlHa7m6VpQaZuHSjnuN3gc9Fand0rf02Erjir8elAfw1egsdmOKlxTIlVkx6D5RRVoQYHSiixnc0NtG2pNtG2qER4oxUm2kIoAjIqGUZU1YNRsM0AYd3AWzWcbAseRXTPCGPSm/Zl9KlxN41bKxhwafg9K1YLUKOlWlhA7VKExTSInO5GsI9KeIwKkApwFMzI9tFSbaKAJqKKKACmGiigBjUw0UUANooopiHCloopDFFOFFFADqKKKAP/9k=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346747/jamstatic/canon-unleashprint-fullpage.c779f41e4e939db155f0cb5b7396c034.jpg 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346747/jamstatic/canon-unleashprint-fullpage.c779f41e4e939db155f0cb5b7396c034.jpg 800w" sizes="100vw">
</picture>
<figure>
<picture title="https://www.tmwunlimited.com/work/">
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346723/jamstatic/dogs-trust-fullpage.a7ce883804708000ec62759aeb093d26.webp 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346723/jamstatic/dogs-trust-fullpage.a7ce883804708000ec62759aeb093d26.webp 800w" width="800" height="3082" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346723/jamstatic/dogs-trust-fullpage.a7ce883804708000ec62759aeb093d26.avif 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346723/jamstatic/dogs-trust-fullpage.a7ce883804708000ec62759aeb093d26.avif 800w" width="800" height="3082" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346723/jamstatic/dogs-trust-fullpage.a7ce883804708000ec62759aeb093d26.jpg" alt="https://www.tmwunlimited.com/work/" loading="lazy" decoding="async" class="dark:brightness-90" width="800" height="3082" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A9oJppkApW6VUlYigCx5wo84VQ3mjeaAL/nCl80Vn7zTlkI60AaIbNPBqik+KkE4oAt5ozVUTU7zaALGaM1CHzTgc0ASZopKKAI36VTmqdpgR1qu7A0rgQ4owadketKMGi4DMGjBqcIDTggouBXCml2mrIQUbRRcCAbqUEg1KQKT5aLgPQmp16VXDAVIJQO9FwLFFQ+cPWii4HGnxCn98Uw+Io/7wryZNbkYfeNRyazLn7xrkVVk3PXB4jjz94VctdZSUjDV4outS7hljXT6FqTyOuWodYEz1qK6DDOam+0D1rnbOcmMc1c881yTxdmdMad0a/wBoHrSG5HrWT55ppmOOtTHF3Y3TL098EHWqLauoON1ZOpXTKp5rl5dQfzSNxrr9vpcwa1sd+NWX+9TW1hR/FXFR3zFetQz3z44NZLFNsuVNpXO4/ttf71FedHUJc/eNFX9YMbnBw9KWSiipRK2IR98V1/hz760UVMtio7npVl/qhVwUUV5Nfc76ewUHpRRU0dxyMXVPuGuQl/15+tFFeovhOT7Raj+7UM1FFc63Oip8JTb71FFFaHCf/9k=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346723/jamstatic/dogs-trust-fullpage.a7ce883804708000ec62759aeb093d26.jpg 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346723/jamstatic/dogs-trust-fullpage.a7ce883804708000ec62759aeb093d26.jpg 800w" sizes="100vw">
</picture>
<figcaption><a href="https://www.tmwunlimited.com/work/" target="_blank" rel="noopener noreferrer">https://www.tmwunlimited.com/work/</a></figcaption>
</figure>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346706/jamstatic/lynx-fullpage.a6f6e8b0403746198501a944a052487d.webp 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346706/jamstatic/lynx-fullpage.a6f6e8b0403746198501a944a052487d.webp 800w" width="800" height="3121" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346706/jamstatic/lynx-fullpage.a6f6e8b0403746198501a944a052487d.avif 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346706/jamstatic/lynx-fullpage.a6f6e8b0403746198501a944a052487d.avif 800w" width="800" height="3121" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346706/jamstatic/lynx-fullpage.a6f6e8b0403746198501a944a052487d.jpg" alt="" loading="lazy" decoding="async" class="dark:brightness-90" width="800" height="3121" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A8V020fzQcV2dnZt5Q4rTtPCjxuDs/St+DQ2RMbaBHB39o3pUVlAwbpXdXWgPJ/BTLfw04P3KBnD6lAzIeKw1tX3dK9WuvDTOuNlZn/CKuGzs/SgDz6W1bb0p1jbMJhxXeyeF3b+A/lUlr4VdXB2fpQIxI4mEI4psYIfpXajw6wjxsqo3h2QNwtAzmZ1JQ8VzOpxNzxXpb6DIVxtNZl14WeQk7D+VAHlDQvuPBor0g+DpM/cP5UUAe3ixth0Ap4tYB6V52PGgP8f604eMh/f/AFoA9C+x259Kctrbr0Argk8Xbv4/1ofxZj+P9aAO9a3t27CozY2x7CuB/wCEw5+/+tOHi0kff/WgDvPsFr6Cnx2FsDwBXn//AAl/P3/1q9beKd5Hz/rQB3f2KAjoKhewt/QVz8XiHcPvVHceINoJ3UAbrWVuOwphsbY9hXF3PiwRk/PVM+NQD/rP1oA9A+wWvotFcB/wmy/89P1ooA8nE8w7mpI55iRyasfZ/atCw08SSDigBbNJ3A61ZnhmCZ5rrNM0MGMHbVi+0hUhJ20AebPJKsmCTVuOVinWptTtxDIeKzxNtGKACW4dX4JrQsbp+OTWO53PWjZDpQB01veOB1pt3eOUPNVouFqK6b5DQBh6ldPk4Y1zk9/MrH5jW1qHOa5y5GWNADv7Rm/vGiqmKKAO3HWtnSv9YtFFAHoul/6kfSl1P/Ut9KKKAPM9c/1jVz5oooAQdRWpZdqKKANmP7tV7r7poooA5y/71z9x1NFFAFaiiigD/9k=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346706/jamstatic/lynx-fullpage.a6f6e8b0403746198501a944a052487d.jpg 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346706/jamstatic/lynx-fullpage.a6f6e8b0403746198501a944a052487d.jpg 800w" sizes="100vw">
</picture>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346792/jamstatic/lynx-calm-fullpage.441130555fd3ecb2bdf545a6dbe37e25.webp 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346792/jamstatic/lynx-calm-fullpage.441130555fd3ecb2bdf545a6dbe37e25.webp 800w" width="800" height="5473" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346792/jamstatic/lynx-calm-fullpage.441130555fd3ecb2bdf545a6dbe37e25.avif 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346792/jamstatic/lynx-calm-fullpage.441130555fd3ecb2bdf545a6dbe37e25.avif 800w" width="800" height="5473" sizes="100vw">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346792/jamstatic/lynx-calm-fullpage.441130555fd3ecb2bdf545a6dbe37e25.jpg" alt="" loading="lazy" decoding="async" class="dark:brightness-90" width="800" height="5473" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A8etdMct0rYh0xlHStC0WMHtWkTHt4xXS4pExu2YRtygqrL8tbFxjBxWHeNjNZaHRZ2InlGKpyHc1QyTHdinRZdhUyMy5bpnFaCwllptnbFgOK1Utio5FRcZizWRbtUCWZV+ldE0I9KiMHtWkZNCJdLk8ojNdXbXq4HNcaAYzxVmK8ZO9XzjO4W+UDrRXHjUWx1oqAsjIgWUHoa0Y1kI6Gu6XwhtP3P0qdfC2P4P0qZSbLptI89kgdh0NZtzYO2flNer/APCMcfcph8KA/wAFSrmsqkWeNtpLk/dNS22kyeYPlNeu/wDCIj+5+lPj8JhWzs/Srvoc7aOK0/SyEGVq7NZFB92u7g8P7F4Wkm0Dd/DWdguebtbHOMVIlgzD7td3/wAI1z9yrEfh7H8NVck82uNPZR92s57WQN9016zL4c3D7lVG8Kgn7n6U0wPMPs8n900V6b/wig/ufpRTuFz0c2aAdBULW6L2FabjiqMxwakRX8pPQUeWvoKQvzRvoAXy19KURp6Cm76N9AE6xL6U77OnpUCy4qUT0ASfZk9KUWyegqPz6cJ6AH/ZkPaj7InpTklzVlDkUAVfsaelFXcUUAMfpWfcd6KKAKR60lFFABRRRQAop1FFAAacKKKALENaEf3aKKAJaKKKAP/Z);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346792/jamstatic/lynx-calm-fullpage.441130555fd3ecb2bdf545a6dbe37e25.jpg 768w, /res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346792/jamstatic/lynx-calm-fullpage.441130555fd3ecb2bdf545a6dbe37e25.jpg 800w" sizes="100vw">
</picture>
<h3 id="YoYaSL">👋</h3>
<p>Comme vous pouvez le voir, on peut faire des trucs incroyables avec Jekyll. Si vous avez des retours ou des commentaires à faire, n'hésitez pas.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn:1">
<p>Réalisé sans trucages&#160;<a href="#fnref1:1" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
</ol>
</div>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2017/02/09/y-a-quoi-dans-un-generateur-de-site-statique/</id>
    <title>Qu’y a-t-il dans un générateur de site statique ?</title>
    <published>2017-02-09T15:21:52+00:00</published>
    <link href="https://jamstatic.fr/2017/02/09/y-a-quoi-dans-un-generateur-de-site-statique/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Après être aller regarder <a href="/2017/01/17/comment-fonctionne-jekyll/">sous le capot de Jekyll</a> et toujours dans l’idée de continuer à nous
familiariser avec différents générateurs de site statique, voici la traduction
d’un <a href="https://css-tricks.com/really-makes-static-site-generator/" target="_blank" rel="noopener noreferrer">article de Brian Rinaldi paru chez
CSS-tricks</a>, qui
nous entraîne cette fois-ci dans les entrailles de <a href="https://harpjs.com/" target="_blank" rel="noopener noreferrer">Harp</a>,
un générateur de fichiers statiques développé en JavaScript, qui résume bien le
périmètre fonctionnel de ces outils.</p></aside>
<p>Je parle beaucoup des générateurs de site statique, mais je parle toujours de
comment <em>utiliser</em> des générateurs de site statique. Ils sont souvent perçus
comme une boîte noire. Je crée un modèle, j'écris un peu de Markdown et hop
j'obtiens une page entièrement formatée en HTML. C’est magique !</p>
<p>Mais qu'est-ce vraiment un générateur de site statique ? Qu’y a-t-il dans cette
boîte noire ? De quelle magie Vaudou s'agit-il ?</p>
<p>Dans cet article, nous allons examiner les différentes parties qui constituent
un générateur de site statique. Premièrement, nous parlerons des généralités et
ensuite nous regarderons de plus près un vrai bout de code source en plongeant
au plus profond d’<a href="https://harpjs.com/" target="_blank" rel="noopener noreferrer">HarpJS</a>. Alors, enfilez votre casquette
d’aventurier et partons en exploration.</p>
<p>Pourquoi Harp ? Pour deux raisons. La première est qu'HarpJS est, de par sa
conception, un générateur de site statique très simple. Il ne possède pas
beaucoup de fonctionnalités qui feraient que nous nous perdrions en route en
voulant explorer un générateur de site statique plus complet (comme
<a href="https://jekyllrb.com/" target="_blank" rel="noopener noreferrer">Jekyll</a> par exemple). La deuxième raison à cela, bien
plus pragmatique, est que je connais bien JavaScript alors que je ne connais pas
très bien Ruby.</p>
<h2 id="les-rudiments-d-un-generateur-de-site-statique">Les rudiments d’un générateur de site statique</h2>
<p>En vérité, un générateur de site statique c'est un concept très simple. Les
ingrédients clefs d’un générateur de site statique sont typiquement :</p>
<ul>
<li>Un (ou des) langage(s) de gabarit pour créer les modèles de pages/articles,</li>
<li>Un langage de balisage léger (en général Markdown) pour rédiger le contenu,</li>
<li>Un langage de balisage structurel (souvent YAML) pour définir la configuration
et les métadonnées (par exemple
"<a href="https://jekyllrb.com/docs/frontmatter/" target="_blank" rel="noopener noreferrer">front matter</a>"),</li>
<li>Un ensemble de règles et de structure pour organiser et nommer les fichiers
qui seront exportés/compilés, les fichiers qui ne le seront pas et comment ces
fichiers seront traités (par exemple préfixer un fichier ou un fichier avec un
tiret bas(<code>_</code>) signifie qu'il ne sera pas recopié avec les fichiers du site
final ou encore tous les articles vont dans un dossier <code>posts</code>),</li>
<li>Un moyen de compiler les modèles et le balisage en HTML (le support pour des
préprocesseurs CSS ou JavaScript est également fréquemment inclus),</li>
<li>Un serveur local pour tester.</li>
</ul>
<p>C’est tout. Si vous vous dites "Hé… mais je pourrais en développer un !" vous
avez sûrement raison. Les choses se compliquent quand vous commencez à ajouter
des fonctionnalités, comme la plupart des générateurs de site statique le font.</p>
<p>Regardons donc maintenant comment Harp gère tout cela.</p>
<h2 id="allons-au-c艙ur-du-probl猫me">Allons au cœur du problème</h2>
<p>Voyons comment Harp fait pour gérer les ingrédients clefs décris ci-dessus. Harp
offre plus que cet ensemble de fonctionnalités, mais pour l’examen qui nous
intéresse, nous nous limiterons à ces éléments.</p>
<p>Premièrement, parlons des rudiments de Harp.</p>
<h2 id="les-rudiments-de-harp">Les rudiments de Harp</h2>
<p>Harp supporte <a href="https://pugjs.org" target="_blank" rel="noopener noreferrer">Jade</a> et <a href="http://www.embeddedjs.com/" target="_blank" rel="noopener noreferrer">EJS</a>
(pour les modèles) et Markdown comme langage de balisage léger (pour le
contenu). Remarquez que bien que Jade s’appelle maintenant Pug, Harp n'a pas
officiellement mis à jour sa documentation ou son code, donc nous parlerons
encore de Jade dans cet article. Harp offre aussi le support pour d’autres
préprocesseurs comme Less, Sass et Stylus pour CSS et CoffeeScript pour
JavaScript.</p>
<p>Par défaut, Harp n'a pas trop besoin de configuration ou de métadonnées. Il tend
à favoriser
<a href="https://harpjs.com/docs/development/rules" target="_blank" rel="noopener noreferrer">une convention plutôt que de la configuration</a>.
Toutefois, il permet de configurer ou d’ajouter des métadonnées à l’aide de
fichiers JSON. Il se distingue de beaucoup d’autres générateurs de site statique,
car le fichier de métadonnées est stocké en dehors du fichier le concernant dans
un fichier <code>_data.json</code>.</p>
<p>Bien qu'il soit configurable jusqu'à un certain point, Harp a établi des règles
strictes sur la manière de structurer les fichiers. Typiquement, comme dans
beaucoup d’applications, les fichiers qui sont servis sont stockés dans un
dossier <code>public</code>. Et tout fichier ou dossier préfixé d’un tiret bas(<code>_</code>) ne sera
pas servi.</p>
<p>Enfin, Harp intègre un serveur web local basique de test qui offre quelques
options de configuration. Et bien entendu, Harp va compresser les fichiers HTML,
CSS et JavaScript finaux pour le déploiement.</p>
<h3 id="regardons-donc-le-code-source-de-harp">Regardons donc le code source de Harp</h3>
<p>Comme l’essentiel de ce qui fait un générateur de site statique ce sont les
règles et les conventions, le code s'occupe principalement de servir et de
compiler (en grande partie). Fouillons tout ça.</p>
<h3 id="la-fonction-serveur">La fonction serveur</h3>
<p>Dans Harp, servir votre projet se fait généralement en lançant la commande
<code>harp server</code> depuis votre terminal. Regardons le
<a href="https://github.com/sintaxi/harp/blob/master/lib/index.js" target="_blank" rel="noopener noreferrer">code for cette fonction</a>:</p>
<pre><code class="language-js hljs javascript">exports.server = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">dirPath, options, callback</span>) </span>{
  <span class="hljs-keyword">var</span> app = connect();
  app.use(middleware.regProjectFinder(dirPath));
  app.use(middleware.setup);
  app.use(middleware.basicAuth);
  app.use(middleware.underscore);
  app.use(middleware.mwl);
  app.use(middleware.static);
  app.use(middleware.poly);
  app.use(middleware.process);
  app.use(middleware.fallback);

  <span class="hljs-keyword">return</span> app.listen(options.port || <span class="hljs-number">9966</span>, options.ip, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    app.projectPath = dirPath;
    callback.apply(app, <span class="hljs-built_in">arguments</span>);
  });
};</code></pre>
<p>Bien que la fonction ait l’air simple, il y a manifestement un paquet de choses
qui se passent dans
<a href="https://github.com/sintaxi/harp/blob/master/lib/middleware.js" target="_blank" rel="noopener noreferrer">middleware</a> et
qui n'est pas visible ici.</p>
<p>Le reste de cette fonction lance un serveur avec les options spécifiées (s'il y
en a). Ces options comprennent un port, une adresse IP à laquelle se connecter
et un répertoire. Par défaut le port est le 9000 (et pas le 9966 comme pourrait
le laisser penser le code), le répertoire est le répertoire courant (celui dans
lequel Harp est lancé) et l’adresse IP est <code>0.0.0.0</code>.</p>
<p>Ces options par défaut sont détaillées dans le
<a href="https://github.com/sintaxi/harp/blob/master/bin/harp" target="_blank" rel="noopener noreferrer">code source de l’exécutable en ligne de commande</a>.</p>
<h3 id="la-fonction-de-compilation">La fonction de compilation</h3>
<p>Restons dans le fichier
<a href="https://github.com/sintaxi/harp/blob/master/lib/index.js" target="_blank" rel="noopener noreferrer">index.js</a> et jetons
maintenant un coup d’œil à la fonction suivante <code>compile</code>.</p>
<pre><code class="language-js hljs javascript">exports.compile = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">projectPath, outputPath, callback</span>) </span>{
  <span class="hljs-comment">/**
   * Both projectPath and outputPath are optional
   */</span>

  <span class="hljs-keyword">if</span> (!callback) {
    callback = outputPath;
    outputPath = <span class="hljs-string">"www"</span>;
  }

  <span class="hljs-keyword">if</span> (!outputPath) {
    outputPath = <span class="hljs-string">"www"</span>;
  }

  <span class="hljs-comment">/**
   * Setup all the paths and collect all the data
   */</span>

  <span class="hljs-keyword">try</span> {
    outputPath = path.resolve(projectPath, outputPath);
    <span class="hljs-keyword">var</span> setup = helpers.setup(projectPath, <span class="hljs-string">"production"</span>);
    <span class="hljs-keyword">var</span> terra = terraform.root(setup.publicPath, setup.config.globals);
  } <span class="hljs-keyword">catch</span> (err) {
    <span class="hljs-keyword">return</span> callback(err);
  }

  <span class="hljs-comment">/**
   * Protect the user (as much as possible) from compiling up the tree
   * resulting in the project deleting its own source code.
   */</span>

  <span class="hljs-keyword">if</span> (!helpers.willAllow(projectPath, outputPath)) {
    <span class="hljs-keyword">return</span> callback({
      <span class="hljs-attr">type</span>: <span class="hljs-string">"Invalid Output Path"</span>,
      <span class="hljs-attr">message</span>:
        <span class="hljs-string">"Output path cannot be greater then one level up from project path and must be in directory starting with `_` (underscore)."</span>,
      <span class="hljs-attr">projectPath</span>: projectPath,
      <span class="hljs-attr">outputPath</span>: outputPath,
    });
  }

  <span class="hljs-comment">/**
   * Compile and save file
   */</span>

  <span class="hljs-keyword">var</span> compileFile = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">file, done</span>) </span>{
    process.nextTick(<span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
      terra.render(file, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">error, body</span>) </span>{
        <span class="hljs-keyword">if</span> (error) {
          done(error);
        } <span class="hljs-keyword">else</span> {
          <span class="hljs-keyword">if</span> (body) {
            <span class="hljs-keyword">var</span> dest = path.resolve(
              outputPath,
              terraform.helpers.outputPath(file)
            );
            fs.mkdirp(path.dirname(dest), <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">err</span>) </span>{
              fs.writeFile(dest, body, done);
            });
          } <span class="hljs-keyword">else</span> {
            done();
          }
        }
      });
    });
  };

  <span class="hljs-comment">/**
   * Copy File
   *
   * <span class="hljs-doctag">TODO:</span> reference ignore extensions from a terraform helper.
   */</span>
  <span class="hljs-keyword">var</span> copyFile = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">file, done</span>) </span>{
    <span class="hljs-keyword">var</span> ext = path.extname(file);
    <span class="hljs-keyword">if</span> (
      !terraform.helpers.shouldIgnore(file) &amp;&amp;
      [
        <span class="hljs-string">".jade"</span>,
        <span class="hljs-string">".ejs"</span>,
        <span class="hljs-string">".md"</span>,
        <span class="hljs-string">".styl"</span>,
        <span class="hljs-string">".less"</span>,
        <span class="hljs-string">".scss"</span>,
        <span class="hljs-string">".sass"</span>,
        <span class="hljs-string">".coffee"</span>,
      ].indexOf(ext) === <span class="hljs-number">-1</span>
    ) {
      <span class="hljs-keyword">var</span> localPath = path.resolve(outputPath, file);
      fs.mkdirp(path.dirname(localPath), <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">err</span>) </span>{
        fs.copy(path.resolve(setup.publicPath, file), localPath, done);
      });
    } <span class="hljs-keyword">else</span> {
      done();
    }
  };

  <span class="hljs-comment">/**
   * Scan dir, Compile Less and Jade, Copy the others
   */</span>

  helpers.prime(outputPath, { <span class="hljs-attr">ignore</span>: projectPath }, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">err</span>) </span>{
    <span class="hljs-keyword">if</span> (err) <span class="hljs-built_in">console</span>.log(err);

    helpers.ls(setup.publicPath, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">err, results</span>) </span>{
      <span class="hljs-keyword">async</span>.each(results, compileFile, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">err</span>) </span>{
        <span class="hljs-keyword">if</span> (err) {
          callback(err);
        } <span class="hljs-keyword">else</span> {
          <span class="hljs-keyword">async</span>.each(results, copyFile, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">err</span>) </span>{
            setup.config[<span class="hljs-string">"harp_version"</span>] = pkg.version;
            <span class="hljs-keyword">delete</span> setup.config.globals;
            callback(<span class="hljs-literal">null</span>, setup.config);
          });
        }
      });
    });
  });
};</code></pre>
<p>La première portion de code définit le chemin de destination passé en argument
de l’appel à <code>harp compile</code> via la ligne de commande
(<a href="https://github.com/sintaxi/harp/blob/master/bin/harp" target="_blank" rel="noopener noreferrer">le source est ici</a>). Par
défaut, comme vous pouvez le voir c'est <code>www</code>. <code>callback</code> est une fonction de
callback passée en argument de la ligne de commande qui n'est pas configurable.</p>
<p>La portion suivante commence par appeler la fonction <code>setup</code> du
<a href="https://github.com/sintaxi/harp/blob/master/lib/helpers.js" target="_blank" rel="noopener noreferrer">module helpers</a>.
Pour faire court, nous n'irons pas examiner le code de la fonction (libre à vous
d’aller regarder par vous-même), mais en gros ça lit la configuration du site
(C’est-à-dire le fichier <code>harp.json</code>).</p>
<p>Vous aurez peut-être remarqué un appel à un truc qui s'appelle <code>terraform</code>. Cela
revient ensuite de nouveau au sein de cette fonction.
<a href="https://github.com/sintaxi/terraform" target="_blank" rel="noopener noreferrer">Terraform</a> est en fait projet séparé dont
Harp dépend pour la gestion du
<a href="https://launchschool.com/blog/rails-asset-pipeline-best-practices" target="_blank" rel="noopener noreferrer">traitement des assets</a>.
C’est lors du traitement des assets qu'est fait tout le difficile travail de
compilation et de génération du site final (nous irons jeter un œil au code de
Terraform sous peu).</p>
<p>La portion de code suivante, comme elle l’indique, essaie de vous empêcher de
définir un dossier de destination qui écraserait votre code source (ce qui
serait malheureux puisque vous perdriez tout votre travail depuis votre dernier
commit).</p>
<p>Les fonctions <code>compileFile</code> et <code>copyFile</code> parlent d’elles-mêmes. La fonction
<code>compileFile</code> se repose sur Terraform pour effectuer la compilation. Ces deux
fonctions permettent à la fonction <code>prime</code> qui s'aide de la fonction (<code>fs</code>) pour
parcourir les dossiers, compiler ou copier les fichiers nécessaires pendant le
processus.</p>
<h3 id="terraform">Terraform</h3>
<p>Comme je l’ai dit, Terraform fait le sale travail pour compiler les fichiers
Jade, Markdown, Sass et CoffeeScript en HTML, CSS et JavaScript (et pour
assembler ces pièces comme voulu par Harp). Terraform est constitué d’un nombre
de fichiers qui définissent ces processeurs pour JavaScript, CSS/feuilles de
style et modèles (qui ici comprennent Markdown).</p>
<picture>
<source type="image/webp" srcset="/images/2017-02-09_y-a-quoi-dans-un-generateur-de-site-statique/terraform-1.5faca6422c352f3d635eaf5aee273d94.webp" width="250" height="369">
<source type="image/avif" srcset="/images/2017-02-09_y-a-quoi-dans-un-generateur-de-site-statique/terraform-1.5faca6422c352f3d635eaf5aee273d94.avif" width="250" height="369">
<img src="/images/2017-02-09_y-a-quoi-dans-un-generateur-de-site-statique/terraform-1.5faca6422c352f3d635eaf5aee273d94.jpg" alt="img" loading="lazy" decoding="async" class="dark:brightness-90" width="250" height="369" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A9jZwKZ561BcOQKzWuGD4q1G5m5G0JgaXzB61kpOSKd9oIo5R8xqeaPWjzB61l/aDR9oNHIHMavmL60eYvrWV9oNJ9pNHIHMa3mA96eDmsyKYsa0IjkVLVhp3JxTxUYqRaQyQUUDpRQBkzR7hVJrTLZxWoxFRFlq02S0Ult8U/wCz+1Wty0oK0XYWKn2f2o+ze1XQVo3LSuwsUvs3tR9m9qu7lpQQaOZhZFWODBq7GMCgAU4DFJu5SRIKeKYKeKQyQdKKBRQIx53Kis5rkhq05o9wqg1rlulaxsQ7gk5xT/tBoW3wKcYKNA1G/aDSfaTS/Z6Ps/tRoLUT7SakinLGmi29qljt8HpSdhq5diORUwFRRLtFWBUMtCAVIKTFKBSGPooooAzn6VCetFFWiWAoNFFAgooopgPFSLRRUgSrUq0UVJaHinUUUALRRRQB/9k=);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<p>Dans chacun de ces dossiers se trouve un dossier <code>processors</code> qui renferme le
code pour chacun des processeurs spécifiques que Terraform (C’est-à-dire Harp)
supporte. Par exemple, dans le dossier des modèles se trouvent les fichiers qui
permettent de compiler les fichiers EJS, Jade, and Markdown.</p>
<picture>
<source type="image/webp" srcset="/images/2017-02-09_y-a-quoi-dans-un-generateur-de-site-statique/terraform_processors.671fde3b94b09e94e737389dd94d1ca1.webp" width="142" height="218">
<source type="image/avif" srcset="/images/2017-02-09_y-a-quoi-dans-un-generateur-de-site-statique/terraform_processors.671fde3b94b09e94e737389dd94d1ca1.avif" width="142" height="218">
<img src="/images/2017-02-09_y-a-quoi-dans-un-generateur-de-site-statique/terraform_processors.671fde3b94b09e94e737389dd94d1ca1.png" alt="img" loading="lazy" decoding="async" class="dark:brightness-90" width="142" height="218" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAABYlAAAWJQFJUiTwAAAJAklEQVR4nO2aaZIjOwiEMynf/6xzAYv3Q4BAkstLu5d4YUV4anFZQnwkSNXDf//+Kf5YI3l6/X9uF9W/wSM7XVWXa3tq88NvNuyHmk/j8qtWpLaDsCpjEzx/I56+3BQAyN9XyEcZqekfUMhHGQDIkbJ+SyEfZaSmgBIg9PcU8lEGijK8/bhCPspILSkDSih+oYZ8lIGtMnyCP6aQjzJS2yjDH/oxhXyUgVNl+Om3K+SjjNROlPFjO/WPMvCQMvzq2xTyUUZqDyjD27cp5KMMPKUMb29XyDuVkW+900rv9zvYv6oMb29XyDuV8V1i+U4RvqoM99FbFeKdvqNm7G6/w9If6fcFZXQfvfX1O7slIMgxgFtAt3Sx5IaB6VenDz7Zar/vC8avKsMfuFyv19csSIP3Tg3AeACkW8Dzqf/CyurdSGidvaoM53Jprb1uBNnBqNoRyMBHNN6ZtprB8dszQl9x4cCgaQxCvwimZgLFFIIPKMM7eFkh3iHJ+CgJgqASSx2/IwONf5KBnKfxNccxTTw7TLN5rwwQFEwi+T72M5+V4e3LCqkfAeiyxZQ+dzPlcjkXx5it1j6e8dsutQ4xdq1ojQhEaV1CO52arGmK673rcLKu+eGWMrx9SSHzB6z3w8Mbyd4fIBmtfqYxDeh9tbjb3QecIraWvhw9WoCUcTxo7LdMRyqitjKNl8e8pQxvLyvkHIhUh455pjspdja1I6cXTT9WaO3nhAot5Y30Wh1Vj9lOjaMqBpQEg3bBdN9KKkhAZoeXFdiqDG8vKWQHIwOJo8WKO07rtDdGudOnhJ4U4qlFkQBPULKTuiPUbLSjpZXZqTG+A2l9vG2ypUd67VsMCGRSyh1leHsayC0YMxCzqjvPYaTJrfuMGAGjZqRmNWQGEX2mh3Pkd0d159Psk1CyLqmld9U7baaQquyc8tT6VVB6v42ASH9WCEhS55kyvD0F5AzGACIAG0AB0KDgcNwCRjdgcm1geFtTUV/60p2z8ocQB5OASAFVLej9KrTVsbJpHaYpo3UQDiOMEUA8S0w1bNeeqiGPAUGHwQZNCmnmuLYFs24cFSxLnaIKbKBUX5V6QWqCoN1xpOV6FniRXpNCwubNiktsDBHgUEC0H6GAeiGR8SrpTsZ6JxABqD23mkI0KaS1PrE9GN2A2RXZ5LAZygwEKI4WS1FCQIQ4MhgZSvGRXSFu95weFblvQBrQBDiOCq6vvoimnsAx6tYOSF9J7MrWM0AkQPTS4elK+qQUaI0GRPdwFiBTRE5pY1bHfSAaOV1ELdf3c1FPab1+abJrF0QBBAPIIRkY19R5v3ysQO6BOYPRgQAUBVuDukIsOq4G4doc0DphV0pZZSWTFnU8BYQGRHEIezRbihGFpbSsytXGohRzsNgnvs9jS78nCqhaLT1RB5D+HuJgzqCcwegrDQBNodI912xi19YndzUgV5us3y+TxVjl3GwTjIb18RlKVwhxiKlUiaaKQ4lDFSppSZohnEABEhBLVWXMBjQSmtVzp10yiHtQbsIQ7Wt7BSR5S8GYxFUV1zaAXBOQ6wxF53GHk1+FEo4TdAcd4weKoRJfdRcYFkwzFNhv8soqQAhLqhNsDNwBifk9AGWBESA0QPTopk3U8nHrn9aANgHJn9Y2QDza/NzBpIi7CUXH8+E4Tf06NTtqAqIzCD9vFUijraoMxiHEVfu9pogs8SCPtYacQQkY0pUhqn1lpQql2stODe3mfDuWkBqq2AJJ636HQY7cK5PDd+dRU7p5sWMH+l6kWZA0j2b0jom+IlLU4LiVtkb/avWCReWqw65HYAA3/qZ+CwpFTAkJQtcB/I2n+XFEL5A2dDpBWlNVTHa2aSqGEd0pOonx27Vwarmrdh1HHdc9IFhsyc7d9RxRYJ88/2fapU+mGzQf15F1N9O9gZMx6iHvcNInN05ez/lf0kbM09H8qj82d2kQ32P4vsPfNy1T2Sgu14aoSdXE6a0Ayj7jAXeVdskQ+sD9KCKLSur7qv7HKJtuHP28xiOHoWUpmvK2LRPzaiQ/53UkHOO5KykiNl4YLzSz08Ry/CF930BBgpO8mORoiW7UIr+XvvfNZg6cbPczYC4ZRGttAbHUERF7BTEtgcEENTj0lRctX5NQi9RjPIJGjKVhGq68/kiAIkpjlixqyzAykLwfOcQV499xpDyTmbDXFwH70Qs+at9C4DiIy8F4n5VV+AyUS/z5VfUuDPdSAJEBRNI5bHIN3UBtPSrdcb4qEYNRcvU02ez8MUGOCeYUhfWdWOlnkwIH5N6nEn0TR4fTrzmByP2KdOVd7OMqLEp5kMhFpGt/B+J8PyJbIA5MSYjrWmiRRVwIsI09wc0Xd3msOB9H5hup6TJrXYDkFDb6ZCrgjD2O5OtsF2o/h4M4+lHiU8e91wJIN+T+5tA9UvckGUi/VgjU16teXEm0Zhkhr642juR8DJkkxaB8ecvYqR9d4OTnfIVVX83Yd+nJGYgkIEMltBTJh6FcjuMwQ9b9yGlbgEgoBHAoHQhN8jQo4jBwWxmRRHL+TdE8+XE8f7O3tDDB5JxcGzKQKVh8obJPgT0ou0JSjUoKeaRtFeLnp20u6gam77D6UcnYaFGxgCgrItTo2eplycWbP4lyRlN7Z7q/JjdPn1UhxRpWIFEnhDgcigzF5NXXIyq5HMexhfAQED/S/x5CMIB0hagpxYuiRNTd6HZy2/rliPDyxc2JVjg5RTHu2jFt6rJCSmJLNaguyxkqmZfBXgcfqiGxMgqjHtxbpuUyYsk7jmpwQgUJxDrCJtI3z+wuOU5yb3Y9L90Ggng8PRIAdDof0y2LACLDQNRSIWzBY88lZdytIb7UDZPvemaxbEQ1bWPoa3pzjprDyz4hdXcv++8vz0D4KNNvNb/cqY9k55fXJTgHQtI2rPk/T6Q3AjHEnVxlbVHI/cZ6ZLrOzuUofwMOpndSnPz7YDAsj3L9KuxKGwi/p1a5pkeUKP8b9DYQxtH/o8QMI6e0p1LWA8/kadZjdmJRihltk3FQm5gdy9m7486Xq0IqGJ3uKYKB/7N55Gb5WhND/e1kTlHiNIGz+P8PMIqKDxFV7r0AAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<p>Je ne vais pas aller creuser le code de chacun d’entre eux, mais pour la
plupart, ils se reposent sur des modules npm externes qui gèrent le processeur
supporté. Par exemple, le support de Markdown dépend de
<a href="https://www.npmjs.com/package/marked" target="_blank" rel="noopener noreferrer">Marked</a>.</p>
<p>Le cœur de la logique de Terraform se trouve dans sa fonction <code>render</code>.</p>
<pre><code class="language-js hljs javascript"><span class="hljs-comment">/**
        * Render
        *
        * This is the main method to to render a view. This function is
        * responsible to for figuring out the layout to use and sets the
        * `current` object.
        *
        */</span>

    <span class="hljs-attr">render</span>: <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">filePath, locals, callback</span>)</span>{

        <span class="hljs-comment">// get rid of leading slash (windows)</span>
        filePath = filePath.replace(<span class="hljs-regexp">/^\\/g</span>, <span class="hljs-string">''</span>)

        <span class="hljs-comment">// locals are optional</span>
        <span class="hljs-keyword">if</span>(!callback){
        callback = locals
        locals   = {}
        }


        <span class="hljs-comment">/**
        * We ignore files that start with underscore
        */</span>

        <span class="hljs-keyword">if</span>(helpers.shouldIgnore(filePath)) <span class="hljs-keyword">return</span> callback(<span class="hljs-literal">null</span>, <span class="hljs-literal">null</span>)


        <span class="hljs-comment">/**
        * If template file we need to set current and other locals
        */</span>

        <span class="hljs-keyword">if</span>(helpers.isTemplate(filePath)) {

        <span class="hljs-comment">/**
            * Current
            */</span>
        locals._ = lodash
        locals.current = helpers.getCurrent(filePath)


        <span class="hljs-comment">/**
            * Layout Priority:
            *
            *    1. passed into partial() function.
            *    2. in `_data.json` file.
            *    3. default layout.
            *    4. no layout
            */</span>

        <span class="hljs-comment">// 1. check for layout passed in</span>
        <span class="hljs-keyword">if</span>(!locals.hasOwnProperty(<span class="hljs-string">'layout'</span>)){

            <span class="hljs-comment">// 2. _data.json layout</span>
            <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Change this lookup relative to path.</span>
            <span class="hljs-keyword">var</span> templateLocals = helpers.walkData(locals.current.path, data)

            <span class="hljs-keyword">if</span>(templateLocals &amp;&amp; templateLocals.hasOwnProperty(<span class="hljs-string">'layout'</span>)){
            <span class="hljs-keyword">if</span>(templateLocals[<span class="hljs-string">'layout'</span>] === <span class="hljs-literal">false</span>){
                locals[<span class="hljs-string">'layout'</span>] = <span class="hljs-literal">null</span>
            } <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(templateLocals[<span class="hljs-string">'layout'</span>] !== <span class="hljs-literal">true</span>){

                <span class="hljs-comment">// relative path</span>
                <span class="hljs-keyword">var</span> dirname = path.dirname(filePath)
                <span class="hljs-keyword">var</span> layoutPriorityList = helpers.buildPriorityList(path.join(dirname, templateLocals[<span class="hljs-string">'layout'</span>] || <span class="hljs-string">""</span>))

                <span class="hljs-comment">// absolute path (fallback)</span>
                layoutPriorityList.push(templateLocals[<span class="hljs-string">'layout'</span>])

                <span class="hljs-comment">// return first existing file</span>
                <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Throw error if null</span>
                locals[<span class="hljs-string">'layout'</span>] = helpers.findFirstFile(root, layoutPriorityList)

            }
            }

            <span class="hljs-comment">// 3. default _layout file</span>
            <span class="hljs-keyword">if</span>(!locals.hasOwnProperty(<span class="hljs-string">'layout'</span>)){
            locals[<span class="hljs-string">'layout'</span>] = helpers.findDefaultLayout(root, filePath)
            }

            <span class="hljs-comment">// 4. no layout (do nothing)</span>
        }

        <span class="hljs-comment">/**
            * <span class="hljs-doctag">TODO:</span> understand again why we are doing this.
            */</span>

        <span class="hljs-keyword">try</span>{
            <span class="hljs-keyword">var</span> error  = <span class="hljs-literal">null</span>
            <span class="hljs-keyword">var</span> output = template(root, templateObject).partial(filePath, locals)
        }<span class="hljs-keyword">catch</span>(e){
            <span class="hljs-keyword">var</span> error  = e
            <span class="hljs-keyword">var</span> output = <span class="hljs-literal">null</span>
        }<span class="hljs-keyword">finally</span>{
            callback(error, output)
        }

        }<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(helpers.isStylesheet(filePath)){
        stylesheet(root, filePath, callback)
        }<span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(helpers.isJavaScript(filePath)){
        javascript(root, filePath, callback)
        }<span class="hljs-keyword">else</span>{
        callback(<span class="hljs-literal">null</span>, <span class="hljs-literal">null</span>)
        }

    }</code></pre>
<p>(Si vous avez bien lu l’intégralité de ce code, vous aurez peut-être relevé
quelques TODO et un commentaire assez drôle "comprendre à nouveau pourquoi nous
faisons cela". C’est ça le code dans la vraie vie!)</p>
<p>La grande partie du code dans la fonction <code>render</code> s'occupe de la gestion des
modèles. Des trucs comme CoffeeScript et Sass génèrent fondamentalement un seul
fichier. Par exemple, <code>style.scss</code> donnera un fichier <code>style.css</code>. Même s'il y a
des includes c'est géré par le préprocesseur. La toute fin de la fonction
<code>render</code> s'occupe de ces types de fichiers.</p>
<p><a href="https://harpjs.com/docs/development/layout" target="_blank" rel="noopener noreferrer">Les modèles dans Harp</a>, à l’opposé,
sont imbriqués les uns dans les autres de différentes manières qui peuvent aussi
dépendre de la configuration. Par exemple, le fichier <code>about.md</code> peut être rendu
par le modèle <code>_layout.jade</code> (ce qui, pour être précis, est défini par
l’utilisation de <code>!= yield</code> à l’intérieur du modèle). Toutefois, <code>_layout.jade</code>
pourrait également lui-même faire appel à plusieurs autres modèles en son sein
grâce au support des fichiers
<a href="https://harpjs.com/docs/development/partial" target="_blank" rel="noopener noreferrer">partiels</a> dans Harp.</p>
<p>Les fichiers partiels sont une façon de découper un modèle en plusieurs
fichiers. Ils sont particulièrement utiles pour réutiliser du code. Par exemple,
j'aurais pu mettre l’entête du site dans un fichier partiel. Les fichiers
partiels sont importants pour rendre la gestion des modèles plus maintenable,
mais ils ajoutent aussi un bon degré de complexité dans la compilation des
modèles. Cette complexité est gérée à l’intérieur de la fonction <code>partial</code> du
<a href="https://github.com/sintaxi/terraform/blob/master/lib/template/index.js" target="_blank" rel="noopener noreferrer">traitement des modèles</a>.</p>
<p>Enfin, nous pourrions écraser le modèle par défaut en définissant un modèle
spécifique ou pas de modèle du tout pour un fichier particulier à l’intérieur du
fichier de configuration <code>_data.json</code>. Tous ces scénarios sont gérés (et même
numérotés) dans la logique de la fonction <code>render</code>.</p>
<h2 id="c-est-pas-si-complique-non">C’est pas si compliqué, non ?</h2>
<p>Pour rendre tout cela digeste, j'ai fait l’impasse sur pas mal de détails. À la
base, chaque générateur de site statique que j'ai utilisé (et j'en ai utilisé
<a href="https://github.com/remotesynth/Static-Site-Samples" target="_blank" rel="noopener noreferrer">un paquet</a>) fonctionne de
manière similaire : un ensemble de règles, de conventions et de configuration
qui sont traitées à travers des compilateurs de langages variés. C’est
peut-être pour ça qu'il y a un
<a href="https://staticsitegenerators.net/" target="_blank" rel="noopener noreferrer">nombre ridicule</a> de générateurs de site
statique dans la nature.</p>
<p>Ceci étant dit, je ne me lancerais pas dans le développement du mien!</p>
<h2 id="mon-rapport-et-mon-livre">Mon rapport et mon livre</h2>
<p>Si vous voulez apprendre comment faire des sites à l’aide d’un générateur de
site statique, J’ai écrit un rapport et coécrit un livre pour O'Reilly qui
pourrait vous intéresser. Mon rapport, simplement intitulé
<a href="http://www.oreilly.com/web-platform/free/static-site-generators.csp" target="_blank" rel="noopener noreferrer">Les générateurs de site statique</a>
est gratuit et essaie d’aborder l’historique, le paysage actuel et les
fondamentaux des générateurs de site statique.</p>
<picture>
<source type="image/webp" srcset="/thumbnails/768x/images/2017-02-09_y-a-quoi-dans-un-generateur-de-site-statique/books-1.511b6ca5097f037b9f2c3ab26cf49b35.webp 768w, /thumbnails/1024x/images/2017-02-09_y-a-quoi-dans-un-generateur-de-site-statique/books-1.511b6ca5097f037b9f2c3ab26cf49b35.webp 1024w" width="1024" height="622" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/2017-02-09_y-a-quoi-dans-un-generateur-de-site-statique/books-1.511b6ca5097f037b9f2c3ab26cf49b35.avif 768w, /thumbnails/1024x/images/2017-02-09_y-a-quoi-dans-un-generateur-de-site-statique/books-1.511b6ca5097f037b9f2c3ab26cf49b35.avif 1024w" width="1024" height="622" sizes="100vw">
<img src="/images/2017-02-09_y-a-quoi-dans-un-generateur-de-site-statique/books-1.511b6ca5097f037b9f2c3ab26cf49b35.jpg" alt="img" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="622" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A665uABWZJdgHrUl4Tg1iTOwJrsjE+XxFaSZpfbB60n2wetYxmOaQymr5UcvtZm/DeAsOa6TTZgwFcDbysXFdjozHArOpHQ7sDWk5am7dH90a4vVj8xrsbk4gP0riNZmCu3NTR3OnMleJjOeajNQvdLnrUZul9a67nzjiyVxUWaY1wD3pvnCncycXclzRUPmiincOVnpF5ZEqeKwLmzIJ4rvLiJSp4rAvYVBPFecqtj7KpgFNnJPbkHpUflGteWIbjxUSw5PSj2wLLlsR2VoWccV2OmW+xRxWPYQgMOK6i0UBBSdXmNKeBVN3I79tlufpXlviTUPLkbmvTdYOLVvpXiniuRjM+PWlGViq+HVRWMx9Wyx+alGpE9654K7P3q2kbY71r7VnC8uia51PHemHVgP4qyJUYDvWVcySITgmn7ZiWWxOr/thf71FcMbuQHqaKXtmV/ZcT7AuJMLWJdPuJrbniJFZc1qea43c+ihymK65NLHHntVp4MGrFvb5PSlZsfNFCWkTBhxXQWqkKKrW1uBjitOKMBaqKM5zTMvWBm2Ye1eOeJLfdO3HevaNUTMLCvL9etd0rHFapXOOdRR3OEjs/m6Vfjscr0q4tttbpVtFCiq5GYPFQMG4ssDpWDe2nXiu0uVBBrFubbdnin7Nk/W4HFvaHeeKK6VtPBPSij2bH9cgfT8nSqco+U0UVzs9NGXL96p7fqKKKaEzVg7VeTpRRTM2UdR/1TfSvN9b/wBY1FFawODFbHP96O1FFdCPImV5ulZ70UUzIiwKKKKYH//Z);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/2017-02-09_y-a-quoi-dans-un-generateur-de-site-statique/books-1.511b6ca5097f037b9f2c3ab26cf49b35.jpg 768w, /thumbnails/1024x/images/2017-02-09_y-a-quoi-dans-un-generateur-de-site-statique/books-1.511b6ca5097f037b9f2c3ab26cf49b35.jpg 1024w" sizes="100vw">
</picture>
<p>Le livre que j'ai coécrit avec <a href="https://twitter.com/raymondcamden" target="_blank" rel="noopener noreferrer">Raymond Camden</a> s'appelle <a href="http://shop.oreilly.com/product/0636920051879.do" target="_blank" rel="noopener noreferrer">Travailler avec les sites statiques</a> et est disponible en prépublication, mais devrait bientôt être disponible en version papier.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2017/01/23/produire-des-livres-avec-le-statique/</id>
    <title>Publier des livres avec un générateur de site statique</title>
    <published>2017-01-23T15:37:00+00:00</published>
    <link href="https://jamstatic.fr/2017/01/23/produire-des-livres-avec-le-statique/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Suite à la parution du <a href="http://blogs.getty.edu/irisan-editors-view-of-digital-publishing/" target="_blank" rel="noopener noreferrer">procédé de publication numérique basé sur Git et Middleman</a> d’un éditeur, <a href="https://www.quaternum.net/" target="_blank" rel="noopener noreferrer">Antoine Fauchié</a> est allé poser quelques questions à <a href="https://egardner.github.io/" target="_blank" rel="noopener noreferrer">Eric Gardner</a>, développeur et designer au sein de l’équipe d’édition numérique de <a href="https://getty.edu/" target="_blank" rel="noopener noreferrer">The Getty</a>, un campus culturel et de recherche situé à Los Angeles.</p></aside>
<h3 id="comment-et-pourquoi-en-etes-vous-arrives-a-choisir-un-generateur-de-site-statique-comme-cle-de-voute-de-votre-processus-de-publication-aux-editions-the-getty-pourquoi-ne-pas-avoir-opte-pour-un-developpement-natif">Comment et pourquoi en êtes-vous arrivés à choisir un générateur de site statique comme clé de voûte de votre processus de publication aux éditions The Getty ? Pourquoi ne pas avoir opté pour un développement natif ?</h3>
<p>Lorsque j'ai commencé à travailler chez The Getty, le programme de publication numérique était assez récent. Il y avait eu précédemment plusieurs expérimentations qui adoptaient toutes une approche CMS plus traditionnelle ainsi qu'une application mobile développée par un prestataire externe, mais au bout de quelques années ces projets sont devenus difficile à maintenir.</p>
<p>Je voulais expérimenter une chaîne de production statique pour plusieurs raisons. Premièrement, je voulais m'assurer que notre travail demeure accessible aussi longtemps que possible. Les évolutions technologiques sont rapides, mais l’édition universitaire est un processus lent – particulièrement dans les domaines comme l’histoire classique ou de l’histoire de l’art, où la recherche peut se faire sur des décennies. Personne ne voudrait publier chez nous s'ils avaient peur que leurs travaux disparaissent deux ans plus tard. Un logiciel complexe comme un CMS ou une application mobile nécessitent une maintenance constante pour rester viable ; vous ne pouvez pas continuer de publier de nouveaux livres si la contrainte de la maintenance augmente à chaque projet.<br>
C’est vraiment de ça dont il est question avec un générateur de site statique, on espère que le produit final fonctionne pendant longtemps. Et même si ce n'est pas le cas, les contenus sous-jacents subsisteront pour toujours sous la forme de fichiers texte lisibles par des humains dans un dépôt Git.</p>
<p>Notre deuxième préoccupation était la dépendance à des logiciels propriétaires.<br>
Des sociétés comme Adobe ou Apple ont créé des outils très bien faits, mais qui possède vraiment vos contenus à la fin de la journée ? Si un produit cesse d’être maintenu, vous ne pouvez pas faire grand-chose en tant qu'éditeur – vos travaux seront perdus. Une plate-forme open source semble être l’unique solution pour assurer à vos auteurs et à vos éditeurs le contrôle de leurs documents.</p>
<p>Enfin, comme toute personne ayant une formation en design, je me soucie beaucoup du design et de l’expérience que nous proposons à nos utilisateurs. Les livres imprimés que The Getty publie sont vraiment beaux, et je veux faire perdurer autant que possible cette tradition sur le web. Cela tombe bien, un site "statique" n'a pas besoin d’être ennuyeux – les applications statiques HTML, CSS et JavaScript peuvent proposer tout un tas de fonctionnalités interactives ainsi qu’un design et une typographie de grande qualité.</p>
<h3 id="comment-votre-equipe-s-est-appropriee-ce-nouveau-workflow-l-appropriation-a-t-elle-ete-facile">Comment votre équipe s'est appropriée ce nouveau workflow ? L'appropriation a-t-elle été facile ?</h3>
<p>Il est vrai que publier de cette façon signifie que pas mal de choses vont changer. Heureusement j'ai de super collègues qui étaient prêts à se familiariser avec le nouveau procédé. Notre équipe en charge du numérique a bénéficié de quelques formations "Démarrer avec GitHub" qui ont été bien reçues, et écrire en Markdown n'est pas si différent qu'écrire dans n'importe quel autre traitement de texte. J'espère cependant toujours que nous pourrons rendre le procédé moins pénible – configurer l’environnement de développement pour prévisualiser son travail peut encore intimider. J'aimerais développer des outils pour aider à simplifier ce procédé pour les gens.</p>
<figure>
<picture title="Digital Publishing at the Getty">
<source type="image/webp" srcset="/thumbnails/768x/images/post/2017-01-23_produire-des-livres-avec-le-statique/eric_ruth_greg_1009_1200.4c47196c05b05e731095c2e1d1f5e675.webp 768w, /thumbnails/1024x/images/post/2017-01-23_produire-des-livres-avec-le-statique/eric_ruth_greg_1009_1200.4c47196c05b05e731095c2e1d1f5e675.webp 1024w" width="1024" height="685" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/post/2017-01-23_produire-des-livres-avec-le-statique/eric_ruth_greg_1009_1200.4c47196c05b05e731095c2e1d1f5e675.avif 768w, /thumbnails/1024x/images/post/2017-01-23_produire-des-livres-avec-le-statique/eric_ruth_greg_1009_1200.4c47196c05b05e731095c2e1d1f5e675.avif 1024w" width="1024" height="685" sizes="100vw">
<img src="/images/post/2017-01-23_produire-des-livres-avec-le-statique/eric_ruth_greg_1009_1200.4c47196c05b05e731095c2e1d1f5e675.jpg" alt="Digital Publishing at the Getty" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="685" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8AwwaKgWUGpA4NJjiSdqhdqkJ4qrK+DTQMlVuasxHmqMbirUbgGgjU0FGVqtNkGpo5Rio5SGp2Qrsfavg1daT5az4cA1YZsrTsO7GSS81GrZNMk60kZyaLISuWR0op6rxRSLOTS496tR3A9awhPjvSm8I71ajcXNY6L7Qu3rVSWYE9aylvS3eniUtTcLD57mjFIc1aWU1mwtzV1CCKxtqWrWLInI704XGe9VmBxUHm4NWiZJGrHNU3n8daykmpkt5sHWjcSNKSYetPgfLVgf2gC2M1ftLsZBzQ1YrQ6NB8ooqit8Ao5oqbhY4h4GA6VSlR93Su0k0rjpVRtGy33aIYiKE6TZz9rA7HkVqJaHb0rZt9JC4+Wro0/A6UpYlMqNFnN+WUqSOXBxW1Lp2e1UpNOKnIFRGqmynBob5gKVTk+9VwWzdKetiW7Vo6isZ8rKKk4qle7tpxXQrYEdqhuNM3L0qI1lcv2bscaryCTvWxZs5Aq3/Y3z/drTtdK2gcVpKorEqLuUsvjvRW0NO9qKx50Xys0XHFQEDd0oorjRu9y1CBjpUlFFSzREbAZNU5gOaKKuG5EyqetWIRRRW0tjJbk4ApJAMdKKKxjuadCvgbulXrcDFFFbPYzW5MetFFFSaH/9k=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/post/2017-01-23_produire-des-livres-avec-le-statique/eric_ruth_greg_1009_1200.4c47196c05b05e731095c2e1d1f5e675.jpg 768w, /thumbnails/1024x/images/post/2017-01-23_produire-des-livres-avec-le-statique/eric_ruth_greg_1009_1200.4c47196c05b05e731095c2e1d1f5e675.jpg 1024w" sizes="100vw">
</picture>
<figcaption>Digital Publishing at the Getty</figcaption>
</figure>
<h3 id="est-ce-que-tu-penses-qu-un-process-a-base-de-technologies-web-peut-remplacer-le-couple-demoniaque-word-et-indesign-particulierement-en-ce-qui-concerne-la-facilite-d-ecriture-et-de-structuration-des-contenus-wysiwyg-ainsi-que-la-mise-en-page-indesign">Est-ce que tu penses qu'un process à base de technologies web peut remplacer le couple démoniaque Word et InDesign ? Particulièrement en ce qui concerne la facilité d’écriture et de structuration des contenus (WYSIWYG) ainsi que la mise en page (InDesign) ?</h3>
<p>Je pense qu'on s'en rapproche (et je frémis à l’idée d’essayer d’obtenir un texte propre à partir d’un jeu de fichiers InDesign…). Mais nos outils ont encore du chemin à parcourir ; J’ai écrit <a href="http://blogs.getty.edu/iris/digital-publishing-needs-new-tools/" target="_blank" rel="noopener noreferrer">un article là-dessus</a> sur le blog de The Getty il y a un petit moment. Les gens utilisent Word et InDesign parce qu'ils sont intuitifs et bien pensés, notre workflow est encore un peu rude en comparaison. Toutefois le monde du texte brut possède des outils vraiment puissants lui aussi : une véritable gestion des versions (bien mieux que le système de révisions), des éditeurs étonnamment puissants comme Vim ou Emacs<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref">1</a></sup>, la possibilité de spécifier ce qu'on veut dire vraiment avec des langages de balisage simples comme <a href="https://learnxinyminutes.com/docs/fr-fr/markdown/" target="_blank" rel="noopener noreferrer">Markdown</a> ou <a href="https://learnxinyminutes.com/docs/asciidoc/" target="_blank" rel="noopener noreferrer">Asciidoc</a>, etc. Je pense que l’équivalence visuelle exacte n'est plus l’objectif à atteindre ici. À contrario, des outils intuitifs et fiables, qui essaient avant tout d’aider les gens à exprimer du <em>sens</em> et ce dans plus d’un contexte de présentation, c'est plutôt ça la voie à suivre.</p>
<h3 id="est-ce-que-ce-nouveau-process-augmente-la-qualite-des-publications-de-the-getty">Est-ce que ce nouveau process augmente la qualité des publications de The Getty ?</h3>
<p>Haha, j'ai l’impression que la barre est déjà assez haute donc je veux déjà produire un travail de qualité équivalente à nos livres papier. Mais l’édition numérique peut vous permettre de faire des choses qui ne seraient pas possibles dans l’édition papier et j'ai espoir que ces nouvelles affordances du médium aideront à faire progresser la recherche de façon singulière. Nous travaillons actuellement sur un livre numérique dans lequel figure par exemple beaucoup de partitions musicales, pouvoir avoir des annotations, des discussions ainsi que de la lecture audio et vidéo dans un même document, c'est quelque chose de véritablement passionnant.</p>
<h3 id="est-ce-que-ce-nouveau-workflow-vous-fait-gagner-du-temps-et-de-l-argent-y-a-t-il-une-difference-de-prix-de-revient-pour-les-livres-y-compris-pour-les-livres-papier">Est-ce que ce nouveau workflow vous fait gagner du temps et de l’argent ? Y’a t-il une différence de prix de revient pour les livres, y compris pour les livres papier ?</h3>
<p>Cela varie en fonction des maisons d’édition, mais globalement je dirais que non. Des publications de qualité, cela demande beaucoup de travail de la part de gens spécialisés et le format numérique ne change rien à cela. Cependant il nous permet de rendre disponibles des choses qu'il ne serait autrement pas viable de publier. Depuis que je travaille chez The Getty, nous avons travaillé sur plusieurs catalogues pour des collections d’œuvres d’art. Ce sont principalement des outils de recherche pour une audience assez spécifique. En publiant d’abord un catalogue en ligne, dans différents formats, nous pouvons rendre cette information (y compris des images interactives de grand qualité) disponible pour les spécialistes. Et la possibilité de générer un fichier PDF à partir de la version web signifie que les gens qui veulent une copie imprimée pourront toujours en acheter une, grâce à notre offre d’impression à la demande. Donc dans ce genre de situation, je pense que la publication numérique peut aider à certains projets à devenir plus rentables.</p>
<h3 id="quelles-sont-les-contraintes-auxquelles-vous-vous-heurtez-encore-aujourd-hui-quel-serait-votre-workflow-ideal">Quelles sont les contraintes auxquelles vous vous heurtez encore aujourd’hui ? Quel serait votre workflow idéal ?</h3>
<p>Tout à l’heure j'ai dit que le fait de configurer un environnement de développement, de travailler avec les outils de programmation plus bas-niveau (comme Git) peut s'avérer être frustrant et déroutant pour les non-programmeurs et que ces outils pourraient être améliorés. J'aimerais une application avec une interface simple qui facilite l’écriture en Markdown, branchée sur Git et qui permette de faire des choses comme choisir un modèle de livre, sans forcer l’utilisateur final à se soucier de la gestion de dépendances ou de l’installation de librairies<sup id="fnref1:2"><a href="#fn:2" class="footnote-ref">2</a></sup>.</p>
<h3 id="est-ce-que-tu-penses-que-d-autres-maisons-d-edition-vont-adopter-a-leur-tour-cette-strategie-avec-des-generateurs-de-site-statique-pas-de-wysiwyg-un-balisage-leger-pas-de-base-de-donnees-des-metadonnees-yaml-versionnement-avec-git-etc-est-ce-qu-un-courant-pourrait-se-former-autour-de-ce-concept">Est-ce que tu penses que d’autres maisons d’édition vont adopter à leur tour cette stratégie avec des générateurs de site statique (pas de WYSIWYG, un balisage léger, pas de base de données, des métadonnées YAML, versionnement avec Git, etc.) ? Est-ce qu'un courant pourrait se former autour de ce concept ?</h3>
<p>Des collègues qui travaillent pour d’autres musées m'ont fait part de leur intérêt. Pour le moment, je ne pense pas que quelqu'un ait publié un livre à l’aide de ce procédé, mais j'espère bien que ça changera bientôt. Une fois que nous aurons un peu affiné notre procédé, je prévois de faire un peu plus d’"évangélisation" 😉</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn:1">
<p>NdT: Des éditeurs comme Sublime ou Atom sont aussi puissants et encore plus accessibles.&#160;<a href="#fnref1:1" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
<li id="fn:2">
<p>NdT: Le projet <a href="https://www.gitbook.com" target="_blank" rel="noopener noreferrer">GitBook</a> adopte cette démarche.&#160;<a href="#fnref1:2" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
</ol>
</div>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2017/01/17/comment-fonctionne-jekyll/</id>
    <title>Comment marche Jekyll ?</title>
    <published>2017-01-17T00:00:00+00:00</published>
    <link href="https://jamstatic.fr/2017/01/17/comment-fonctionne-jekyll/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Si vous suivez ce blog, vous savez déjà que Jekyll est un
générateur de site statique développé en Ruby. Jack Phelan a décidé d’aller
jeter un œil dans le moteur de Jekyll histoire de mieux comprendre comment sont
traités les différents types de fichiers qui sont passés en entrée. Nous
traduisons
<a href="https://www.bytesandwich.com/jekyll/software/blogging/2016/09/14/how-does-jekyll-work.html" target="_blank" rel="noopener noreferrer">son article</a>
afin de vous inciter à plonger un peu dans le code de Jekyll et prendre
connaissance des concepts fondamentaux de ce générateur. Nous espérons que cela
vous permettra de mieux appréhender la philosophie de Jekyll ou que cela vous
sera utile si vous songez à développer un plugin.</p></aside>
<style type="text/css">

.tabs {
    position: relative;
    margin: 25px 0;
}
.tab {
    display: inline-block;
    padding: 8px 18px;
    border: 1px solid #4A21CC;
    margin: 0;
    margin-left: -1px;
    position: relative;
    left: 4px;
    bottom: -1px;
    border-top-left-radius: 3px;
    border-top-right-radius: 3px;
    cursor: pointer;
}
.tab.tab-selected {
    background-color: #4A21CC;
    color: white;
    border-bottom: 1px solid #4A21CC;
}

table {
  font-size: 0.775em;
  margin: 2em calc(50% - 50vw);
}

table tr,
table td,
table th {
  border: 1px solid rgba(35,27,48,.2);
  border-collapse: collapse;
  padding: .5em;
}

th, tr td:first-child {
  font-weight: bold;
  background-color: rgba(74,33,294,.75);
  color: white;
}

.copied {
  background-color: rgba(125,172,255,.2);
}
.transformed {
  background-color: rgba(204,204,32,.2);
}
.post-transformed {
  background-color: rgba(64,204,32,.2);
}
</style>
<script src="https://code.jquery.com/jquery-3.1.1.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous"></script>
<script type="text/javascript">
    function activateTab(tab) {
        var $tabs = $(tab).closest('.tabs')
        var index = $(tab).index()
        var $tabAreas = $tabs.find('.tab-content')
        var $toShow = $tabAreas.eq(index);

        $tabs.find('.tab-selected').removeClass('tab-selected');
        $(tab).addClass('tab-selected');

        $tabAreas.hide()
        $toShow.show()
    }
    $(() => {
      $(".tab").click((e) => activateTab(e.target))
      $('.tabs').each((i, tabs) => activateTab($(tabs).find('.tab').first().get()));
    })
</script>
<p><a href="https://jekyllrb.com" target="_blank" rel="noopener noreferrer">Jekyll</a> peut paraître un peu déroutant au début. En effet
Jekyll ne fait pas grand-chose à vos fichiers, si ce n'est qu'il les classifie
de différentes façons.</p>
<p>Jekyll va soit copier, soit omettre, soit transformer les fichiers du répertoire
source dans le répertoire de destination[^1]. Lorsque Jekyll transforme vos
fichiers, c'est toujours de cette manière, si ce n'est que la deuxième étape
peut être potentiellement sautée.[^2]</p>
<p>Si un fichier commence par une entête
<a href="https://jekyllrb.com/docs/frontmatter/" target="_blank" rel="noopener noreferrer">YAML Front Matter</a> Jekyll va appliquer
les transformations suivante au fichier:</p>
<ol>
<li><strong>Interprétation du code Liquid</strong> : Le contenu du fichier est d’abord
parcouru par le parser de <a href="https://shopify.github.io/liquid/" target="_blank" rel="noopener noreferrer">Liquid</a>, les
variables comme <code>site</code> ou <code>page</code> auxquelles le modèle Liquid veut accéder
sont alors interprétées.</li>
<li><strong>Conversion du contenu</strong> : en fonction de l’extension de fichier, Jekyll
fait appel à un convertisseur dédié, par example Kramdown pour les fichiers
<code>.md</code> ou <code>.markdown</code>, qui est chargé de convertir le résultat obtenu après
l’étape 1.</li>
<li><strong>Parsing du modèle</strong>: Le résultat de cette conversion est alors transmis
dans la variable <code>{{content}}</code>, soit au modèle de page par défaut, soit à
celui qui est spécifié dans l’entête YAML Front Matter.</li>
<li>Le résultat de cette dernière conversion du modèle de page, généralement un
fichier HTML, est écrit dans votre répertoire de destination.</li>
</ol>
<p>J'aimerais maintenant vous montrer un exemple où Jekyll applique cette
transformation. Ensuite, lors d’un
<a href="#test-exhaustif-dune-génération">test complet</a> de génération de site, nous
irons étudier la structure générale de l’algorithme au
<a href="#le-cœur-de-jekyll">cœur de Jekyll</a> pour voir quels traitements sont effectués
sur les différents types de fichiers.</p>
<h2 id="la-transformation-de-jekyll">La transformation de Jekyll</h2>
<p>Le mécanisme de transformation de Jekyll est situé dans
<a href="https://github.com/jekyll/jekyll/blob/2b15b0b3251d35c290dc96eb07e18fa31a58bcc6/lib/jekyll/renderer.rb#L32-L79" target="_blank" rel="noopener noreferrer">la méthode run du fichier renderer</a>,
qui fait essentiellement la chose suivante, en sautant potentiellement quelques
étapes :</p>
<pre><code class="language-ruby hljs ruby">after_liquid = render_with_liquid(file_content) <span class="hljs-comment"># line 62</span>
after_markdown = convert(after_liquid) <span class="hljs-comment"># line 66</span>
place_in_layout(after_markdown) <span class="hljs-comment"># line 71</span></code></pre>
<p>Donc si nous transformons l’article présent dans le thème par défaut de Jekyll :</p>
<pre><code class="language-markdown hljs markdown">---
layout: post
title: "Bienvenue dans Jekyll !"
date: 2016-08-17 13:50:36 +0100
<span class="hljs-section">categories: jekyll update
---</span>

You’ll find this post in your <span class="hljs-code">`_posts`</span> directory. Go ahead and edit it and
re-build the site to see your changes. You can rebuild the site in many
different ways, but the most common way is to run <span class="hljs-code">`jekyll serve`</span>, which launches
a web server and auto-regenerates your site when a file is updated.

To add new posts, simply add a file in the <span class="hljs-code">`_posts`</span> directory that follows the
convention <span class="hljs-code">`YYYY-MM-DD-name-of-post.ext`</span> and includes the necessary front
matter. Take a look at the source for this post to get an idea about how it
works.

Jekyll also offers powerful support for code snippets:

<span class="hljs-code">    def print_hi(name)</span>
<span class="hljs-code">      puts "Hi, #{name}"</span>
<span class="hljs-code">    end</span>
<span class="hljs-code">    print_hi('Tom')</span>
<span class="hljs-code">    #=&gt; prints 'Hi, Tom' to STDOUT.</span>

Check out the [<span class="hljs-string">Jekyll docs</span>][<span class="hljs-symbol">jekyll-docs</span>] for more info on how to get the most
out of Jekyll. File all bugs/feature requests at [Jekyll’s GitHub
repo][jekyll-gh]. If you have questions, you can ask them on [Jekyll
Talk][jekyll-talk].

[<span class="hljs-symbol">jekyll-docs</span>]: <span class="hljs-link">https://jekyllrb.com/docs/home</span>
[<span class="hljs-symbol">jekyll-gh</span>]: <span class="hljs-link">https://github.com/jekyll/jekyll</span>
[<span class="hljs-symbol">jekyll-talk</span>]: <span class="hljs-link">https://talk.jekyllrb.com/</span></code></pre>
<p>…vous pouvez voir le résultat des deux premières étapes de la transformation :</p>
<div class="tabs">
  <div class="tab">En entrée</div>
  <div class="tab">1. Liquidifié</div>
  <div class="tab">2. Converti</div>
  <div class="tab-content">
    <pre><code class="language-markdown">
You’ll find this post in your `_posts` directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run `jekyll serve`, which launches a web server and auto-regenerates your site when a file is updated.

To add new posts, simply add a file in the `_posts` directory that follows the
convention `YYYY-MM-DD-name-of-post.ext` and includes the necessary front
matter. Take a look at the source for this post to get an idea about how it
works.

Jekyll also offers powerful support for code snippets:

    def print_hi(name)
      puts "Hi, #{name}"
    end
    print_hi('Tom')
    #=&gt; prints 'Hi, Tom' to STDOUT.

Check out the [Jekyll docs][jekyll-docs] for more info on how to get the most
out of Jekyll. File all bugs/feature requests at [Jekyll’s GitHub
repo][jekyll-gh]. If you have questions, you can ask them on [Jekyll
Talk][jekyll-talk].

[jekyll-docs]: https://jekyllrb.com/docs/home
[jekyll-gh]: https://github.com/jekyll/jekyll
[jekyll-talk]: https://talk.jekyllrb.com/

</code></pre>

  </div>

  <div class="tab-content">
    <pre><code class="language-markdown">
You’ll find this post in your `_posts` directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run `jekyll serve`, which launches a web server and auto-regenerates your site when a file is updated.

To add new posts, simply add a file in the `_posts` directory that follows the
convention `YYYY-MM-DD-name-of-post.ext` and includes the necessary front
matter. Take a look at the source for this post to get an idea about how it
works.

Jekyll also offers powerful support for code snippets:

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">print_hi</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span>
  <span class="nb">puts</span> <span class="s2">"Hi, </span><span class="si">\#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="n">print_hi</span><span class="p">(</span><span class="s1">'Tom'</span><span class="p">)</span>
<span class="c1">
 #=&gt; prints 'Hi, Tom' to STDOUT.</span></code></pre></figure>

Check out the [Jekyll docs][jekyll-docs] for more info on how to get the most
out of Jekyll. File all bugs/feature requests at [Jekyll’s GitHub
repo][jekyll-gh]. If you have questions, you can ask them on [Jekyll
Talk][jekyll-talk].

[jekyll-docs]: https://jekyllrb.com/docs/home
[jekyll-gh]: https://github.com/jekyll/jekyll
[jekyll-talk]: https://talk.jekyllrb.com/

</code></pre>

  </div>
  <div class="tab-content">
  <pre><code class="language-markdown">
<p>You’ll find this post in your <code class="highlighter-rouge">_posts</code> directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run <code class="highlighter-rouge">jekyll serve</code>, which launches a web server and auto-regenerates your site when a file is updated.</p>
<p>To add new posts, simply add a file in the <code class="highlighter-rouge">_posts</code> directory that follows the convention <code class="highlighter-rouge">YYYY-MM-DD-name-of-post.ext</code> and includes the necessary front matter. Take a look at the source for this post to get an idea about how it works.</p>
<p>Jekyll also offers powerful support for code snippets:</p>
<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">print_hi</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span>
  <span class="nb">puts</span> <span class="s2">"Hi, </span><span class="si">\#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">"</span>
<span class="k">end</span>
<span class="n">print_hi</span><span class="p">(</span><span class="s1">'Tom'</span><span class="p">)</span>
<span class="c1">#=&gt; prints 'Hi, Tom' to STDOUT.</span></code></pre></figure>
<p>Check out the <a href="https://jekyllrb.com/docs/home">Jekyll docs</a> for more info on how to get the most out of Jekyll. File all bugs/feature requests at <a href="https://github.com/jekyll/jekyll">Jekyll’s GitHub repo</a>. If you have questions, you can ask them on <a href="https://talk.jekyllrb.com/">Jekyll Talk</a>.</p>
</code></pre>
</div>
</div>
<p>Puis vient la dernière étape où nous mettons tout cela dans la variable
<code>{{content}}</code> de notre modèle :</p>
<div class="tabs">
  <div class="tab">Modèle</div>
  <div class="tab">3. Résultat final</div>
  <div class="tab-content"><pre><code class="language-markdown">
<article class="post" itemscope itemtype="https://schema.org/BlogPosting">

  <header class="post-header">
    <h1 class="post-title" itemprop="name headline">{{ page.title | escape }}</h1>
    <p class="post-meta"><time datetime="{{ page.date | date_to_xmlschema }}" itemprop="datePublished">{{ page.date | date: "%b %-d, %Y" }}</time>{% if page.author %} • <span itemprop="author" itemscope itemtype="https://schema.org/Person"><span itemprop="name">{{ page.author }}</span></span>{% endif %}</p>
  </header>

  <div class="post-content" itemprop="articleBody">
    {{ content }}
  </div>

</article>
    </code></pre></div>
<div class="tab-content">
<pre><code class="language-markdown">
<article class="post" itemscope itemtype="https://schema.org/BlogPosting">

  <header class="post-header">
    <h1 class="post-title" itemprop="name headline">Welcome to Jekyll!</h1>
    <p class="post-meta"><time datetime="2016-08-17T23:50:36-04:00" itemprop="datePublished">Aug 17, 2016</time></p>
  </header>

  <div class="post-content" itemprop="articleBody">
    <p>You’ll find this post in your <code class="highlighter-rouge">_posts</code> directory. Go ahead and edit it and re-build the site to see your changes. You can rebuild the site in many different ways, but the most common way is to run <code class="highlighter-rouge">jekyll serve</code>, which launches a web server and auto-regenerates your site when a file is updated.</p>

    <p>To add new posts, simply add a file in the <code class="highlighter-rouge">_posts</code> directory that follows the convention <code class="highlighter-rouge">YYYY-MM-DD-name-of-post.ext</code> and includes the necessary front matter. Take a look at the source for this post to get an idea about how it works.</p>

    <p>Jekyll also offers powerful support for code snippets:</p>

    <figure class="highlight"><pre><code class="language-ruby" data-lang="ruby">
    <span class="k">def</span> <span class="nf">print_hi</span><span class="p">(</span><span class="nb">name</span><span class="p">)</span>
    <span class="nb">puts</span> <span class="s2">"Hi, </span><span class="si">\#{</span><span class="nb">name</span><span class="si">}</span><span class="s2">"</span>
    <span class="k">end</span>
    <span class="n">print_hi</span><span class="p">(</span><span class="s1">'Tom'</span><span class="p">)</span>
    <span class="c1">#=&gt; prints 'Hi, Tom' to STDOUT.</span></code></pre>
    </figure>

    <p>Check out the <a href="https://jekyllrb.com/docs/home">Jekyll docs</a> for more info on how to get the most out of Jekyll. File all bugs/feature requests at <a href="https://github.com/jekyll/jekyll">Jekyll’s GitHub repo</a>. If you have questions, you can ask them on <a href="https://talk.jekyllrb.com/">Jekyll Talk</a>.</p>

  </div>

</article>
    </code></pre>
  </div>
</div>
<p>Ceci est juste un exemple de conversion avec kramdown pour le markdown et
d’insertion du résultat dans un modèle. Jekyll intègre d’autres convertisseurs
comme
<a href="https://github.com/jekyll/jekyll/blob/2b15b0b3251d35c290dc96eb07e18fa31a58bcc6/lib/jekyll/converters/smartypants.rb" target="_blank" rel="noopener noreferrer">smartypants</a>.
Jekyll inclut aussi par défaut un
<a href="https://github.com/jekyll/jekyll-sass-converter" target="_blank" rel="noopener noreferrer">convertisseur pour Sass</a> dans
le
<a href="https://github.com/jekyll/jekyll/blob/499b83236c0289471118991bd5fe743effe9b348/jekyll.gemspec" target="_blank" rel="noopener noreferrer">fichier de spécification de sa gem Ruby</a>,
qui n'est pas un convertisseur intégré, mais vous comprendrez quand vous
lancerez la commande <code>jekyll new</code>. Vous pouvez installer d’autres convertisseurs
à l’aide de plugins. Les modèles sont des fichiers qui sont soit stockés dans
votre dossier <code>_layouts</code>, soit dans celui empaqueté dans la gem du thème utilisé
par votre fichier de configuration.</p>
<h2 id="le-c艙ur-de-jekyll">Le cœur de Jekyll</h2>
<p>Maintenant que vous comprenez l’étape de transformation de Jekyll, regardons
comme elle s'intègre dans un processus de génération de site plus global à
partir de fichiers en entrée.</p>
<p>Si nous pitons le code exécuté lors de l’invocation de la commande
<code>jekyll build</code>, nous nous apercevons que
<a href="https://github.com/jekyll/jekyll/blob/2b15b0b3251d35c290dc96eb07e18fa31a58bcc6/lib/jekyll/site.rb#L65" target="_blank" rel="noopener noreferrer"><code>site.process</code></a>
représente le cœur de Jekyll. Vous trouverez les parties importantes un peu plus
bas accompagnées de mes commentaires explicatifs. Reportez-vous à
<a href="#debug">la partie sur le debug</a> si vous souhaitez vous baladez à votre tour
dans l’appel de la méthode.</p>
<pre><code class="language-ruby hljs ruby"><span class="hljs-comment"># Public: Lit, processe et écrit le Site dans la destination.</span>
<span class="hljs-comment">#</span>
<span class="hljs-comment"># Ne retourne rien.</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">process</span></span>
  reset <span class="hljs-comment"># vide tous les layouts/pages/static_file/data/collections du site.</span>
  read <span class="hljs-comment"># lit les fichiers de votre répértoire et les stocke dans un objet Site, les classer dépend</span>
  <span class="hljs-comment"># d’où ils se trouvent, ce qu'ils contiennent et de votre fichier de configuration.</span>
  generate <span class="hljs-comment"># Vous donne la chance de lancer des générateurs pour ajouter plus de choses au site..</span>
  render <span class="hljs-comment"># c'est la méthode chargée de la conversion.</span>
  cleanup <span class="hljs-comment"># se débarasse de tous les fichiers qui auraient pu restés d’une génération précédente ou qui traitent des metadonnées.</span>
  write <span class="hljs-comment"># écrit les fichiers dans votre dossier de destination.</span>
  print_stats
<span class="hljs-keyword">end</span></code></pre>
<p><code>render</code> applique la transformation de Jekyll aux fichiers qui possèdent une
entête <a href="https://jekyllrb.com/docs/frontmatter/" target="_blank" rel="noopener noreferrer">YAML Front Matter</a>. Les
documents sont créés à partir des fichiers de collection qui possèdent des
entêtes YAML Front Matter et les pages sont d’autres documents avec des entêtes
YAML. Vous devez déclarer les collections dans votre fichier <code>_config.yml</code>,
comme ça vous saurez que vous en avez. Vous devez également savoir que
<a href="https://jekyllrb.com/docs/frontmatter/" target="_blank" rel="noopener noreferrer">les posts et les brouillons de posts sont simplement des collections spéciales</a>,
ce sont donc aussi des documents.</p>
<pre><code class="language-ruby hljs ruby"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">render</span></span>
    …
    render_docs(payload) <span class="hljs-comment"># cette fonction s'occupe du rendu des fichiers de collections qui possèdent des entêtes Front Matter, y compris le dossier _posts (les brouillons sont ajoutés aux posts avec l’option --drafts)</span>
    render_pages(payload) <span class="hljs-comment"># cette fonction s'occupe du rendu des fichiers qui n'appartiennent pas à des collections mais qui ont des entêtes Front Matter</span>
    <span class="hljs-comment"># cela peut être vos fichiers `feed.xml`, `index.html`, `main.scss`, etc.</span>
    …
<span class="hljs-keyword">end</span>
…

<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">render_docs</span><span class="hljs-params">(payload)</span></span>
  collections.each <span class="hljs-keyword">do</span> <span class="hljs-params">|_, collection|</span>
    collection.docs.each <span class="hljs-keyword">do</span> <span class="hljs-params">|document|</span>
      <span class="hljs-keyword">if</span> regenerator.regenerate?(document)
        document.output = Jekyll::Renderer.new(<span class="hljs-keyword">self</span>, document, payload).run
        document.trigger_hooks(<span class="hljs-symbol">:post_render</span>)
      <span class="hljs-keyword">end</span>
    <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span>
…
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">render_pages</span><span class="hljs-params">(payload)</span></span>
  pages.flatten.each <span class="hljs-keyword">do</span> <span class="hljs-params">|page|</span>
    <span class="hljs-keyword">if</span> regenerator.regenerate?(page)
      page.output = Jekyll::Renderer.new(<span class="hljs-keyword">self</span>, page, payload).run
      page.trigger_hooks(<span class="hljs-symbol">:post_render</span>)
    <span class="hljs-keyword">end</span>
  <span class="hljs-keyword">end</span>
<span class="hljs-keyword">end</span></code></pre>
<h2 id="test-exhaustif-d-une-generation">Test exhaustif d’une génération</h2>
<p>Regardons à présent ce que nous obtenons lors d’un exemple de génération à
partir de quelques types de fichiers dans différents répertoires, avec et sans
l’option <code>--drafts</code> (active ou non la génération des brouillons). Le conteneur
Docker de ce test est dispos sur
<a href="https://github.com/bytesandwich/jekyll-outcomes" target="_blank" rel="noopener noreferrer">bytesandwich/jekyll-outcomes</a>.</p>
<p>Il recopie ces fichiers :</p>
<ul>
<li><strong>2016-05-05-post-normal.md</strong> <em># un post normal avec une date passée</em></li>
<li><strong>2016-05-05-post-without-frontmatter.md</strong> <em># un post sans frontmatter, avec
une date passée</em></li>
<li><strong>2020-02-02-post-future.md</strong> <em># un post standard, daté dans le futur</em></li>
<li><strong>frontmatter-not-post.md</strong> <em># un fichier avec du frontmatter qui n'est pas un
post</em></li>
<li><strong>text.txt</strong> <em># un fichier texte normal</em></li>
<li><strong>yaml.yml</strong> <em># un fichier YAML normal</em></li>
</ul>
<p>… dans chacun de ces répertoires :</p>
<ul>
<li><strong>/</strong></li>
<li><strong>/_posts</strong></li>
<li><strong>/_drafts</strong></li>
<li><strong>/_data</strong></li>
<li><strong>/_my_output_collection</strong></li>
<li><strong>/_my_non_output_collection</strong></li>
<li><strong>/_underscore_dir</strong></li>
<li><strong>/regular_dir</strong></li>
</ul>
<p>Sauf que pour chaque association de fichier et de répertoire, le nom du
répertoire de destination est ajouté à la fin du fichier de manière à ce que
nous puissions mieux appréhender les corresponsances entre les fichiers d’entrée
et les fichiers de sortie. Vous retrouvez un aperçu du résultat de la commande
<code>tree</code> après les deux tableaux.</p>
<p>J'ai fait un tableau avec une ligne par répertoire et une colonne par fichier,
la cellule contient l’opération effectuée sur le fichier, qui peut être :</p>
<ul>
<li><em>copié</em> sans altération</li>
<li><em>omis</em></li>
<li><em>transformé</em> et placé dans le répertoire correspondant</li>
<li><em>post transformé</em>, qui est ensuite placé dans une arborescence de dossiers,
crées d’après la date du post.</li>
</ul>
<h2 id="generation-des-fichiers-sans-l-option-brouillons">Génération des fichiers sans l’option brouillons</h2>
<table>
  <thead>
    <tr>
      <th> </th>
      <th>text.txt</th>
      <th>frontmatter-not-post.md</th>
      <th>2016-05-05-post-without-frontmatter.md</th>
      <th>yaml.yml</th>
      <th>2020-02-02-post-future.md</th>
      <th>2016-05-05-post-normal.md</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th>/</th>
      <td class="copied">copié</td>
      <td class="transformed">transformé</td>
      <td class="copied">copié</td>
      <td class="copied">copié</td>
      <td class="transformed">transformé</td>
      <td class="transformed">transformé</td>
    </tr>
    <tr>
      <td>/posts</td>
      <td class="omitted">omis</td>
      <td class="omitted">omis</td>
      <td class="post-transformed">post transformé</td>
      <td class="omitted">omis</td>
      <td class="omitted">omis</td>
      <td class="post-transformed">post transformé</td>
    </tr>
    <tr>
      <td>/drafts</td>
      <td class="omitted">omis</td>
      <td class="omitted">omis</td>
      <td class="omitted">omis</td>
      <td class="omitted">omis</td>
      <td class="omitted">omis</td>
      <td class="omitted">omis</td>
    </tr>
    <tr>
      <td>/data</td>
      <td class="omitted">omis</td>
      <td class="omitted">omis</td>
      <td class="omitted">omis</td>
      <td class="omitted">omis</td>
      <td class="omitted">omis</td>
      <td class="omitted">omis</td>
    </tr>
    <tr>
      <td>/my_output_collection</td>
      <td class="copied">copié</td>
      <td class="transformed">transformé</td>
      <td class="copied">copié</td>
      <td class="copied">copié</td>
      <td class="omitted">omis</td>
      <td class="transformed">transformé</td>
    </tr>
    <tr>
      <td>/my_non_output_collection</td>
      <td class="copied">copié</td>
      <td class="omitted">omis</td>
      <td class="copied">copié</td>
      <td class="copied">copié</td>
      <td class="omitted">omis</td>
      <td class="omitted">omis</td>
    </tr>
    <tr>
      <td>/underscore_dir</td>
      <td class="omitted">omis</td>
      <td class="omitted">omis</td>
      <td class="omitted">omis</td>
      <td class="omitted">omis</td>
      <td class="omitted">omis</td>
      <td class="omitted">omis</td>
    </tr>
    <tr>
      <td>/regular_dir</td>
      <td class="copied">copié</td>
      <td class="transformed">transformé</td>
      <td class="copied">copié</td>
      <td class="copied">copié</td>
      <td class="transformed">transformé</td>
      <td class="transformed">transformé</td>
    </tr>
  </tbody>
</table>
<h2 id="generation-des-fichiers-avec-l-option-brouillon">Génération des fichiers avec l’option brouillon</h2>
<table>
  <thead>
    <tr>
      <th> </th>
      <th>text.txt</th>
      <th>frontmatter-not-post.md</th>
      <th>2016-05-05-post-without-frontmatter.md</th>
      <th>yaml.yml</th>
      <th>2020-02-02-post-future.md</th>
      <th>2016-05-05-post-normal.md</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>/</td>
      <td class="copied">copié</td>
      <td class="transformed">transformé</td>
      <td class="copied">copié</td>
      <td class="copied">copié</td>
      <td class="transformed">transformé</td>
      <td class="transformed">transformé</td>
    </tr>
    <tr>
      <td>/posts</td>
      <td>omis</td>
      <td>omis</td>
      <td class="post-transformed">post transformé</td>
      <td>omis</td>
      <td>omis</td>
      <td class="post-transformed">post transformé</td>
    </tr>
    <tr>
      <td>/drafts</td>
      <td>Aucune</td>
      <td class="post-transformed">post transformé</td>
      <td class="post-transformed">post transformé</td>
      <td class="post-transformed">post transformé</td>
      <td>omis</td>
      <td class="post-transformed">post transformé</td>
    </tr>
    <tr>
      <td>/data</td>
      <td>omis</td>
      <td>omis</td>
      <td>omis</td>
      <td>omis</td>
      <td>omis</td>
      <td>omis</td>
    </tr>
    <tr>
      <td>/my_output_collection</td>
      <td class="copied">copié</td>
      <td class="transformed">transformé</td>
      <td class="copied">copié</td>
      <td class="copied">copié</td>
      <td>omis</td>
      <td class="transformed">transformé</td>
    </tr>
    <tr>
      <td>/my_non_output_collection</td>
      <td class="copied">copié</td>
      <td>omis</td>
      <td class="copied">copié</td>
      <td class="copied">copié</td>
      <td>omis</td>
      <td>omis</td>
    </tr>
    <tr>
      <td>/underscore_dir</td>
      <td>omis</td>
      <td>omis</td>
      <td>omis</td>
      <td>omis</td>
      <td>omis</td>
      <td>omis</td>
    </tr>
    <tr>
      <td>/regular_dir</td>
      <td class="copied">copié</td>
      <td class="transformed">transformé</td>
      <td class="copied">copié</td>
      <td class="copied">copié</td>
      <td class="transformed">transformé</td>
      <td class="transformed">transformé</td>
    </tr>
  </tbody>
</table>
<div class="tabs">
  <div class="tab tab-selected">Source</div>
  <div class="tab">Site</div>
  <div class="tab">Site avec les brouillons</div>
  <div class="tab-content"><pre><code class="language-shell">
bash-4.3# tree .
.
├── 2016-05-05-post-normal.md
├── 2016-05-05-post-without-frontmatter.md
├── 2020-02-02-post-future.md
├── Gemfile
├── Gemfile.lock
├── _config.yml
├── _data
│   ├── 2016-05-05-post-normal-data.md
│   ├── 2016-05-05-post-without-frontmatter-data.md
│   ├── 2020-02-02-post-future-data.md
│   ├── frontmatter-not-post-data.md
│   ├── text-data.txt
│   └── yaml-data.yml
├── _drafts
│   ├── 2016-05-05-post-normal-drafts.md
│   ├── 2016-05-05-post-without-frontmatter-drafts.md
│   ├── 2020-02-02-post-future-drafts.md
│   ├── frontmatter-not-post-drafts.md
│   ├── text-drafts.txt
│   └── yaml-drafts.yml
├── _layouts
│   └── special.html
├── _my_non_output_collection
│   ├── 2016-05-05-post-normal-my_non_output_collection.md
│   ├── 2016-05-05-post-without-frontmatter-my_non_output_collection.md
│   ├── 2020-02-02-post-future-my_non_output_collection.md
│   ├── frontmatter-not-post-my_non_output_collection.md
│   ├── text-my_non_output_collection.txt
│   └── yaml-my_non_output_collection.yml
├── _my_output_collection
│   ├── 2016-05-05-post-normal-my_output_collection.md
│   ├── 2016-05-05-post-without-frontmatter-my_output_collection.md
│   ├── 2020-02-02-post-future-my_output_collection.md
│   ├── frontmatter-not-post-my_output_collection.md
│   ├── text-my_output_collection.txt
│   └── yaml-my_output_collection.yml
├── _posts
│   ├── 2016-05-05-post-normal-posts.md
│   ├── 2016-05-05-post-without-frontmatter-posts.md
│   ├── 2016-09-14-welcome-to-jekyll.markdown
│   ├── 2020-02-02-post-future-posts.md
│   ├── frontmatter-not-post-posts.md
│   ├── text-posts.txt
│   └── yaml-posts.yml
├── _underscore_dir
│   ├── 2016-05-05-post-normal-underscore_dir.md
│   ├── 2016-05-05-post-without-frontmatter-underscore_dir.md
│   ├── 2020-02-02-post-future-underscore_dir.md
│   ├── frontmatter-not-post-underscore_dir.md
│   ├── text-underscore_dir.txt
│   └── yaml-underscore_dir.yml
├── about.md
├── css
│   └── main.scss
├── feed.xml
├── frontmatter-not-post.md
├── index.html
├── regular_dir
│   ├── 2016-05-05-post-normal-regular_dir.md
│   ├── 2016-05-05-post-without-frontmatter-regular_dir.md
│   ├── 2020-02-02-post-future-regular_dir.md
│   ├── frontmatter-not-post-regular_dir.md
│   ├── text-regular_dir.txt
│   └── yaml-regular_dir.yml
├── text.txt
└── yaml.yml

9 directories, 57 files </code></pre>

  </div>
  <div class="tab-content"><pre><code class="language-shell">
bash-4.3# tree _site
_site
├── 2016
│   └── 05
│       └── 05
│           ├── post-normal-posts.html
│           └── post-without-frontmatter-posts.html
├── 2016-05-05-post-normal.html
├── 2016-05-05-post-without-frontmatter.md
├── 2020-02-02-post-future.html
├── Gemfile
├── Gemfile.lock
├── about
│   └── index.html
├── css
│   └── main.css
├── feed.xml
├── frontmatter-not-post.html
├── index.html
├── jekyll
│   └── update
│       └── 2016
│           └── 09
│               └── 14
│                   └── welcome-to-jekyll.html
├── my_non_output_collection
│   ├── 2016-05-05-post-without-frontmatter-my_non_output_collection.md
│   ├── text-my_non_output_collection.txt
│   └── yaml-my_non_output_collection.yml
├── my_output_collection
│   ├── 2016-05-05-post-normal-my_output_collection.html
│   ├── 2016-05-05-post-without-frontmatter-my_output_collection.md
│   ├── frontmatter-not-post-my_output_collection.html
│   ├── text-my_output_collection.txt
│   └── yaml-my_output_collection.yml
├── regular_dir
│   ├── 2016-05-05-post-normal-regular_dir.html
│   ├── 2016-05-05-post-without-frontmatter-regular_dir.md
│   ├── 2020-02-02-post-future-regular_dir.html
│   ├── frontmatter-not-post-regular_dir.html
│   ├── text-regular_dir.txt
│   └── yaml-regular_dir.yml
├── text.txt
└── yaml.yml

13 directories, 29 files</code></pre></div>

<div class="tab-content"><pre><code class="language-shell">
bash-4.3# tree _site
_site
├── 2016
│   ├── 05
│   │   └── 05
│   │       ├── post-normal-drafts.html
│   │       ├── post-normal-posts.html
│   │       ├── post-without-frontmatter-drafts.html
│   │       └── post-without-frontmatter-posts.html
│   └── 09
│       └── 14
│           ├── frontmatter-not-post-drafts.html
│           ├── text-drafts.txt
│           └── yaml-drafts.yml
├── 2016-05-05-post-normal.html
├── 2016-05-05-post-without-frontmatter.md
├── 2020-02-02-post-future.html
├── Gemfile
├── Gemfile.lock
├── about
│   └── index.html
├── css
│   └── main.css
├── feed.xml
├── frontmatter-not-post.html
├── index.html
├── jekyll
│   └── update
│       └── 2016
│           └── 09
│               └── 14
│                   └── welcome-to-jekyll.html
├── my_non_output_collection
│   ├── 2016-05-05-post-without-frontmatter-my_non_output_collection.md
│   ├── text-my_non_output_collection.txt
│   └── yaml-my_non_output_collection.yml
├── my_output_collection
│   ├── 2016-05-05-post-normal-my_output_collection.html
│   ├── 2016-05-05-post-without-frontmatter-my_output_collection.md
│   ├── frontmatter-not-post-my_output_collection.html
│   ├── text-my_output_collection.txt
│   └── yaml-my_output_collection.yml
├── regular_dir
│   ├── 2016-05-05-post-normal-regular_dir.html
│   ├── 2016-05-05-post-without-frontmatter-regular_dir.md
│   ├── 2020-02-02-post-future-regular_dir.html
│   ├── frontmatter-not-post-regular_dir.html
│   ├── text-regular_dir.txt
│   └── yaml-regular_dir.yml
├── text.txt
└── yaml.yml

15 directories, 34 files </code></pre></div></div>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2016/12/09/des-commentaires-statiques-avec-jekyll-et-staticman/</id>
    <title>Des commentaires statiques avec Jekyll et Staticman</title>
    <published>2016-12-09T00:00:00+00:00</published>
    <link href="https://jamstatic.fr/2016/12/09/des-commentaires-statiques-avec-jekyll-et-staticman/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p><a href="https://github.com/mmistakes" target="_blank" rel="noopener noreferrer">Michael Rose</a>, l’auteur du <a href="https://mademistakes.com/work/minimal-mistakes-jekyll-theme/" target="_blank" rel="noopener noreferrer">thème Jekyll Minimal Mistakes</a>, revient sur les détails de l’implémentation de commentaires statiques — les commentaires sont versionnés au format YAML dans le dépôt GitHub — à l’aide de <a href="https://eduardoboucas.com/blog/2016/08/10/staticman.html" target="_blank" rel="noopener noreferrer">Staticman</a>, un service open-source développé par <a href="https://eduardoboucas.com" target="_blank" rel="noopener noreferrer">Eduardo Bouças</a>, qui permet d’insérer des contenus générés par les utilisateurs sur un site plus si statique que ça, proposant ainsi une alternative à Disqus au même titre que <a href="https://github.com/ummels/jekyll-aws-comments" target="_blank" rel="noopener noreferrer">Jekyll AWS comment</a>.</p></aside>
<p>Depuis que j'ai quitté Disqus pour <a href="https://mademistakes.com/articles/jekyll-static-comments/" target="_blank" rel="noopener noreferrer">un système de commentaires statiques</a>, <a href="https://staticman.net" target="_blank" rel="noopener noreferrer"><strong>Staticman</strong></a> a muri avec des ajouts de fonctionnalités comme <em>les fils de commentaires</em> et <em>les notifications par mail</em>.</p>
<p>À l’aide des instructions fournies par Eduardo Bouças dans <a href="https://github.com/eduardoboucas/staticman/issues/42" title="Email notification upon replies" target="_blank" rel="noopener noreferrer">cette issue GitHub</a>, je me suis lancé dans l’amélioration de l’expérience relative aux commentaires sur <strong><a href="https://mademistakes.com" target="_blank" rel="noopener noreferrer">Made Mistakes</a></strong>. Voici comme j'ai procédé.</p>
<h2 id="passer-a-la-version-2-de-staticman">Passer à la version 2 de Staticman</h2>
<p>Pour tirer parti de ces nouvelles fonctionnalités, il était nécessaire de migrer les paramètres de Staticman du fichier <code>_config.yml</code> de Jekyll vers un nouveau fichier <code>staticman.yml</code><sup id="fnref1:staticman-yml"><a href="#fn:staticman-yml" class="footnote-ref">1</a></sup>. Comme il n'y a eu aucun changement dans les paramètres, la transition vers la version 2 était grandement simplifiée.</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">comments:</span>
  <span class="hljs-attr">allowedFields     :</span> <span class="hljs-string">['name',</span> <span class="hljs-string">'email'</span><span class="hljs-string">,</span> <span class="hljs-string">'url'</span><span class="hljs-string">,</span> <span class="hljs-string">'message'</span><span class="hljs-string">]</span>
  <span class="hljs-attr">branch            :</span> <span class="hljs-string">"master"</span>
  <span class="hljs-attr">commitMessage     :</span> <span class="hljs-string">"Nouveau commentaire."</span>
  <span class="hljs-attr">filename          :</span> <span class="hljs-string">"commentaire-{@timestamp}"</span>
  <span class="hljs-attr">format            :</span> <span class="hljs-string">"yaml"</span>
  <span class="hljs-attr">moderation        :</span> <span class="hljs-literal">true</span>
  <span class="hljs-attr">path              :</span> <span class="hljs-string">"src/_data/comments/{options.slug}"</span>
  <span class="hljs-attr">requiredFields    :</span> <span class="hljs-string">['name',</span> <span class="hljs-string">'email'</span><span class="hljs-string">,</span> <span class="hljs-string">'message'</span><span class="hljs-string">]</span>
  <span class="hljs-attr">transforms:</span>
    <span class="hljs-attr">email           :</span> <span class="hljs-string">md5</span>
  <span class="hljs-attr">generatedFields:</span>
    <span class="hljs-attr">date:</span>
      <span class="hljs-attr">type          :</span> <span class="hljs-string">"date"</span>
      <span class="hljs-attr">options:</span>
        <span class="hljs-attr">format      :</span> <span class="hljs-string">"iso8601"</span></code></pre>
<h3 id="nouvelles-options-de-configuration">Nouvelles options de configuration</h3>
<p>Assurez-vous de jeter un œil au <a href="https://github.com/eduardoboucas/staticman/blob/master/staticman.sample.yml" target="_blank" rel="noopener noreferrer">modèle de fichier de configuration</a> et à la <a href="https://staticman.net/docs/configuration" target="_blank" rel="noopener noreferrer">liste complète des paramètres</a> pour vous faire une idée des possibilités de configuration.</p>
<p>Par exemple, vous pouvez configurer plusieurs propriétés (commentaires, avis et autres types de contenus générés par les utilisateurs), modifier le message de commit et le corps de texte de la pull request, activer les notifications par mail et bien plus à partir du fichier <code>staticman.yml</code>.</p>
<h3 id="supprimer-ajouter-staticman-en-tant-que-collaborateur">Supprimer/Ajouter Staticman en tant que collaborateur</h3>
<p>Je ne suis pas vraiment certain que l’opération suivante soit nécessaire. Je me suis heurté à des erreurs lors de mes tests de commentaires et cela a eu l’air de régler le problème. Il est possible que je me sois trompé quelque part ailleurs dans la configuration et que l’origine du problème était ailleurs…</p>
<p>Quoi qu’il en soit, vous pouvez toujours partager votre expérience de la mise à jour de la version 1 à la version 2 de Staticman dans les commentaires de ce billet.</p>
<ol>
<li>Révoquez les droits de collaboration de Staticman <code>v1</code> dans les paramètres de votre dépôt GitHub.  <picture>
<source type="image/webp" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.webp 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.webp 1024w" width="1024" height="512" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.avif 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.avif 1024w" width="1024" height="512" sizes="100vw">
<img src="/images/no-image.158b054bca15d8e77f8ac75c945a532c.png" alt="Supprimer staticmanapp en tant que collaborator" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="512" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAD7ElEQVR4nO2bUZLbIAyGf3n61NP0VL3/CVAfECAJYTvebcN29c9kwDaObX0IQSLTr5+/Gf+VvvbjHO++gZRVAtlMCWQzJZDN9OPxmfSJd/GvtXHcfw0IxSUFbQgA+Nmz04dgE/xVGXYXs9reDM41EG18UsYiOaQBeEA8G+PWtfTmJZyzBmxrXGEQaskMoLxwf/9A50A0hFUJX5LpfdUQfOuZ155BF8dHG7+r7WVmKeVTpESt7wJlDaQZ/aiGaKUHQgSQPDkpo7RhofdMKU+v5zdfAGDaU7uTjgMAgZkFCIMJKAKCdbM3656HOCi9JAEhBiDpwq3nQXph2wdw356uo6tBUFqzGeMkuVJ/X+sgRbyjgEEM4ACIEd/XG3TqIaQ/hwYhEA4piQYgqGGBGFQA5nqcGb3U1/H1yPiBP8zneBg0DrS4cZQaNogBOqSDtHYbQImBLLpj9YIICjoUYPS4Ugh8AFRYYNRhI4wFJ9fUtXAoIxdjNJgeR8TyxGqoDS72ZigxkNZr/G6ej5GeTiKoq4CuA6tRYAg/PyCp1ZHPeQiz+QrzdezupaiA7u9jWw8B+hQRMuYCqOv6NlU8ABRGIQL1XkfqXB5DV+HzGKItGe0XHP3w1AuoMnJD4vBEuW67p8L9nvoMawMYwFVQF+PzUcu+70Cf0LMawvy5ZlbjZl1Gc0gwTgBw4EQ6eHCvsoJgJlmAAdKCe+gpb9Sph/SiiOGlB7ayBv4RE4jakNSCu+2Zl72QbBPNpJl8xJD5yzxEs6XXIQrE1/MQwMxEGhhAeYYYqI9YrkfafSfX86FhimO8nv66A2x6lL2H7q0NxCYwgDs/nShDNgO1YaoO37xcxHFfkOC1h6Z182lVQnHLcGgErKduBKLptR8XAzjAqA8PuZp63dDCFdRc65k2hKD1/Od3b2i/4Pvog29uuL+l50C8vqkBP1v5j+FmSiCbKYFspgSymRLIZkogmymBbKYEspkSyGZKIJspgWymBLKZEshmSiCbKYFsps/7P+Q7a87GeKwE8op8upJPg9V/bT9MwksgV3L5Aj6X2LTjRdYkj+NXYBLISibFCSr3bBw3baXaXgjSdZMkCJxCSSCBVi8o9WO9oUuOkZw1k2Osc790Wu4CSgLx0hAO5xkRFDmH9PmAMbgZxnx2jlMCceqGV0ae9p0Fd0juHtnQAcC5Tnz9XIdoBT0f5Pb7epTQt8x3vW6TQJzk7YaRzuqy46dkwMXrFVO2Kt9LI84hKxKr4YZGcPbhgRS06HwgSOy+UAIJxMuNIR0GpoU6x+WdtWECiTS/yWDkYUwpzRGQm0QyhpzJvduiS23ovszQC0Bf3tQfiobsB/XBi3AAAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.png 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.png 1024w" sizes="100vw">
</picture>
</li>
<li>Ajoutez de nouveau Staticman en tant que <a href="https://mademistakes.com/articles/jekyll-static-comments/#setting-up-staticman" target="_blank" rel="noopener noreferrer">collaborateur</a>.</li>
<li>Faites un appel sur ce endpoint de la version 2 de l’API <code>https://api.staticman.net/v2/connect/{votre nom d’utilisateur GitHub}/{nom de votre dépôt}</code> pour accepter l’invitation de collaboration.</li>
</ol>
<h3 id="mettre-a-jour-l-appel-post-du-formulaire-de-commentaires">Mettre à jour l’appel POST du formulaire de commentaires</h3>
<p>Pour faire une requête <code>POST</code> correcte à Staticman, l’attribut <code>action</code> de mon formulaire de commentaire avait besoin d’une petite mise à jour. Remplacer <code>v1</code> par <code>v2</code> dans <a href="https://github.com/mmistakes/made-mistakes-jekyll/blob/f0074b7b9e64b6d4b63dd13a371cedc576dae49d/src/_includes/page__comments.html#L34" target="_blank" rel="noopener noreferrer"><strong>_includes/page__comments.html</strong></a>, puis suffixer avec <code>/comments</code><sup id="fnref1:property"><a href="#fn:property" class="footnote-ref">2</a></sup> et le tour était joué pour moi.</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">form</span>
  <span class="hljs-attr">id</span>=<span class="hljs-string">"comment-form"</span>
  <span class="hljs-attr">class</span>=<span class="hljs-string">"page__form js-form form"</span>
  <span class="hljs-attr">method</span>=<span class="hljs-string">"post"</span>
  <span class="hljs-attr">action</span>=<span class="hljs-string">"https://api.staticman.net/v2/entry/{{ site.repository }}/{{ site.staticman.branch }}/comments"</span>
&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span></code></pre>
<h2 id="ajout-du-support-des-fils-de-commentaires">Ajout du support des fils de commentaires</h2>
<p>Réussir à faire marcher les commentaires imbriqués s'est révélé assez pénible. Plusieurs erreurs Liquid, plusieurs tentatives avant d’arriver à faire marcher des boucles <code>for</code> à l’intérieur d’autres boucles <code>for</code>, des filtres de tableau qui pétaient des trucs et tout un tas de galères font que j'ai mis un moment avant de m'en sortir.</p>
<h3 id="ajout-d-un-identifiant-au-parent">Ajout d’un identifiant au parent</h3>
<p>Pour imbriquer correctement les réponses, j'avais besoin de pouvoir déterminer leur hiérarchie. La <code>v2</code> de Staticman possède un nouveau champ nommé <code>options[parent]</code><sup id="fnref1:parent-field"><a href="#fn:parent-field" class="footnote-ref">3</a></sup> qui peut être utilisé pour aider à établir cette relation. Avant d’aller plus loin, ajoutons déjà cet identifiant à mon formulaire dans un champ caché.</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"hidden"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"comment-parent"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"options[parent]"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">""</span> /&gt;</span></code></pre>
<h3 id="mise-a-jour-des-boucles-liquid">Mise à jour des boucles Liquid</h3>
<p>Afin d’éviter d’afficher des doublons, j'avais besoin d’exclure les réponses et de ne montrer que les commentaires parents dans la boucle principale. C'était le moment idéal pour utiliser le filtre <code>where_exp</code> de Jekyll.</p>
<aside class="note note-tip"><h4 id="le-filtre-d-expression-where-de-jekyll">Le filtre d’expression where de Jekyll</h4>
<p>Sélectionne tous les objets d’un tableau pour lesquels la condition est vraie, depuis la version 3.2.0 de Jekyll.
<strong>Exemple:</strong> <code>site.members | where_exp:"item","item.graduation_year == 2014"</code></p></aside>
<p>Si le champ caché <code>options[parent]</code> que j'ai ajouté au formulaire fonctionne correctement, je devrais obtenir des fichiers de données de commentaires similaires à ceux-ci :</p>
<h4 id="exemple-de-commentaire-parent">Exemple de commentaire parent</h4>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">message:</span> <span class="hljs-string">Ceci</span> <span class="hljs-string">est</span> <span class="hljs-string">le</span> <span class="hljs-string">message</span> <span class="hljs-string">du</span> <span class="hljs-string">commentaire</span> <span class="hljs-string">parent</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">Prénom</span> <span class="hljs-string">Nom</span>
<span class="hljs-attr">email:</span> <span class="hljs-string">md5g1bb3r15h</span>
<span class="hljs-attr">date:</span> <span class="hljs-number">2016</span><span class="hljs-number">-11</span><span class="hljs-string">-30T22:03:15.286Z</span></code></pre>
<h4 id="exemple-de-commentaire-enfant">Exemple de commentaire enfant</h4>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">_parent:</span> <span class="hljs-number">7</span>
<span class="hljs-attr">message:</span> <span class="hljs-string">Ceci</span> <span class="hljs-string">est</span> <span class="hljs-string">un</span> <span class="hljs-string">message</span> <span class="hljs-string">de</span> <span class="hljs-string">commentaire</span> <span class="hljs-string">enfant</span>
<span class="hljs-attr">name:</span> <span class="hljs-string">Prénom</span> <span class="hljs-string">Nom</span>
<span class="hljs-attr">email:</span> <span class="hljs-string">md5g1bb3r15h</span>
<span class="hljs-attr">date:</span> <span class="hljs-number">2016</span><span class="hljs-number">-11</span><span class="hljs-string">-02T05:08:43.280Z</span></code></pre>
<p>Comme vous pouvez le voir ci-dessus, le commentaire "enfant" a une donnée <code>_parent</code> renseignée à partir du champ caché <code>options[parent]</code> du formulaire.<br>
Sachant cela, j'ai tenté d’utiliser <code>where_exp:"item","item._parent == nil"</code> pour créer un tableau ne contenant que les commentaires "parents".</p>
<p>Malheureusement, le code suivant n'a pas marché :</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> comments = site.data.comments[page.slug] | where_exp:"item","item._parent == nil" %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">for</span></span> comment in comments %}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> avatar = comment[1].avatar %}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> email = comment[1].email %}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> name = comment[1].name %}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> url = comment[1].url %}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> <span class="hljs-name">date</span> = comment[1].<span class="hljs-name">date</span> %}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> message = comment[1].message %}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">include</span></span> comment.html index=forloop.index avatar=avatar email=email name=name url=url <span class="hljs-name">date</span>=<span class="hljs-name">date</span> message=message %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> %}</span></code></pre>
<p>À la place, j'ai eu tout un tas de commentaires vides avec le balisage suivant :</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">article</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"comment-1"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"js-comment comment"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"comment__avatar"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">""</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">""</span> /&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"comment__author-name"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"comment__timestamp"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#comment-1"</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"Permalien vers ce commentaire"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">time</span> <span class="hljs-attr">datetime</span>=<span class="hljs-string">""</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">time</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"comment__content"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span></code></pre>
<p>Hmm… j'imagine qu'il était temps d’ajouter des filtres <code>inspect</code> à mes tableaux pour voir ce qui se passait.</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-variable">{{ site.data.comments[page.slug] | inspect }}</span></code></pre>
<h4 id="exemple-de-tableau-avant-filtrage-avec-where-exp">Exemple de tableau avant filtrage avec <code>where_exp</code></h4>
<pre><code class="language-yaml hljs yaml"><span class="hljs-string">{</span>
  <span class="hljs-string">"comment-1471818805944"</span> <span class="hljs-string">=&gt;</span> <span class="hljs-string">{</span>
    <span class="hljs-string">"message"</span> <span class="hljs-string">=&gt;</span> <span class="hljs-string">"Ceci est un message de commentaire parent."</span><span class="hljs-string">,</span>
    <span class="hljs-string">"name"</span>    <span class="hljs-string">=&gt;</span> <span class="hljs-string">"Prénom Nom"</span><span class="hljs-string">,</span>
    <span class="hljs-string">"email"</span>   <span class="hljs-string">=&gt;</span> <span class="hljs-string">"md5g1bb3r15h"</span><span class="hljs-string">,</span>
    <span class="hljs-string">"url"</span>     <span class="hljs-string">=&gt;</span> <span class="hljs-string">""</span><span class="hljs-string">,</span>
    <span class="hljs-string">"hidden"</span>  <span class="hljs-string">=&gt;</span> <span class="hljs-string">""</span><span class="hljs-string">,</span>
    <span class="hljs-string">"date"</span>    <span class="hljs-string">=&gt;</span> <span class="hljs-string">"2016-08-21T22:33:25.272Z"</span>
  <span class="hljs-string">},</span>
  <span class="hljs-string">"comment-1471904599908"</span> <span class="hljs-string">=&gt;</span> <span class="hljs-string">{</span>
    <span class="hljs-string">"message"</span> <span class="hljs-string">=&gt;</span> <span class="hljs-string">"Ceci est un autre message de commentaire parent."</span><span class="hljs-string">,</span>
    <span class="hljs-string">"name"</span>    <span class="hljs-string">=&gt;</span> <span class="hljs-string">"Prénom Nom"</span><span class="hljs-string">,</span>
    <span class="hljs-string">"email"</span>   <span class="hljs-string">=&gt;</span> <span class="hljs-string">"md5g1bb3r15h"</span><span class="hljs-string">,</span>
    <span class="hljs-string">"url"</span>     <span class="hljs-string">=&gt;</span> <span class="hljs-string">""</span><span class="hljs-string">,</span>
    <span class="hljs-string">"hidden"</span>  <span class="hljs-string">=&gt;</span> <span class="hljs-string">""</span><span class="hljs-string">,</span>
    <span class="hljs-string">"date"</span>    <span class="hljs-string">=&gt;</span> <span class="hljs-string">"2016-08-22T21:42:48.075Z"</span>
  <span class="hljs-string">}</span>
<span class="hljs-string">}</span></code></pre>
<h4 id="exemple-de-tableau-apres-filtrage-avec-where-exp">Exemple de tableau après filtrage avec <code>where_exp</code></h4>
<pre><code class="language-json hljs json">[
  {
    <span class="hljs-attr">"message"</span> =&gt; <span class="hljs-attr">"Ceci est un message de commentaire parent."</span>,
    <span class="hljs-attr">"name"</span>    =&gt; <span class="hljs-attr">"Prénom Nom"</span>,
    <span class="hljs-attr">"email"</span>   =&gt; <span class="hljs-attr">"md5g1bb3r15h"</span>,
    <span class="hljs-attr">"url"</span>     =&gt; <span class="hljs-attr">""</span>,
    <span class="hljs-attr">"hidden"</span>  =&gt; <span class="hljs-attr">""</span>,
    <span class="hljs-attr">"date"</span>    =&gt; <span class="hljs-attr">"2016-08-21T22:33:25.272Z"</span>
  },
  {
    <span class="hljs-attr">"message"</span> =&gt; <span class="hljs-attr">"Ceci est un autre message de commentaire parent."</span>,
    <span class="hljs-attr">"name"</span>    =&gt; <span class="hljs-attr">"Prénom Nom"</span>,
    <span class="hljs-attr">"email"</span>   =&gt; <span class="hljs-attr">"md5g1bb3r15h"</span>,
    <span class="hljs-attr">"url"</span>     =&gt; <span class="hljs-attr">""</span>,
    <span class="hljs-attr">"hidden"</span>  =&gt; <span class="hljs-attr">""</span>,
    <span class="hljs-attr">"date"</span>    =&gt; <span class="hljs-attr">"2016-08-22T21:42:48.075Z"</span>
  }
]</code></pre>
<p>Apparemment l’utilisation du filtre <code>where_exp</code> aplatit quelque peu les choses en supprimant les objets <code>comment-xxxxxxxxxxxxx</code>. Cela fait que mes tags <code>assign</code> retournent des valeurs nulles parce que <code>comment[1]</code> n'existe plus.</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> avatar  = comment[1].avatar %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> email   = comment[1].email %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> name    = comment[1].name %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> url     = comment[1].url %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> <span class="hljs-name">date</span>    = comment[1].<span class="hljs-name">date</span> %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> message = comment[1].message %}</span></code></pre>
<p>Une fois cela découvert, la solution était simple --- supprimer <code>[1]</code> pour chacun des noms des propriétés.</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> avatar  = comment.avatar %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> email   = comment.email %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> name    = comment.name %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> url     = comment.url %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> <span class="hljs-name">date</span>    = comment.<span class="hljs-name">date</span> %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> message = comment.message %}</span></code></pre>
<figure>
<picture title="Ça marche, nous avons des commentaires parents.">
<source type="image/webp" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.webp 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.webp 1024w" width="1024" height="512" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.avif 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.avif 1024w" width="1024" height="512" sizes="100vw">
<img src="/images/no-image.158b054bca15d8e77f8ac75c945a532c.png" alt="Seulement des commentaires parents" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="512" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAD7ElEQVR4nO2bUZLbIAyGf3n61NP0VL3/CVAfECAJYTvebcN29c9kwDaObX0IQSLTr5+/Gf+VvvbjHO++gZRVAtlMCWQzJZDN9OPxmfSJd/GvtXHcfw0IxSUFbQgA+Nmz04dgE/xVGXYXs9reDM41EG18UsYiOaQBeEA8G+PWtfTmJZyzBmxrXGEQaskMoLxwf/9A50A0hFUJX5LpfdUQfOuZ155BF8dHG7+r7WVmKeVTpESt7wJlDaQZ/aiGaKUHQgSQPDkpo7RhofdMKU+v5zdfAGDaU7uTjgMAgZkFCIMJKAKCdbM3656HOCi9JAEhBiDpwq3nQXph2wdw356uo6tBUFqzGeMkuVJ/X+sgRbyjgEEM4ACIEd/XG3TqIaQ/hwYhEA4piQYgqGGBGFQA5nqcGb3U1/H1yPiBP8zneBg0DrS4cZQaNogBOqSDtHYbQImBLLpj9YIICjoUYPS4Ugh8AFRYYNRhI4wFJ9fUtXAoIxdjNJgeR8TyxGqoDS72ZigxkNZr/G6ej5GeTiKoq4CuA6tRYAg/PyCp1ZHPeQiz+QrzdezupaiA7u9jWw8B+hQRMuYCqOv6NlU8ABRGIQL1XkfqXB5DV+HzGKItGe0XHP3w1AuoMnJD4vBEuW67p8L9nvoMawMYwFVQF+PzUcu+70Cf0LMawvy5ZlbjZl1Gc0gwTgBw4EQ6eHCvsoJgJlmAAdKCe+gpb9Sph/SiiOGlB7ayBv4RE4jakNSCu+2Zl72QbBPNpJl8xJD5yzxEs6XXIQrE1/MQwMxEGhhAeYYYqI9YrkfafSfX86FhimO8nv66A2x6lL2H7q0NxCYwgDs/nShDNgO1YaoO37xcxHFfkOC1h6Z182lVQnHLcGgErKduBKLptR8XAzjAqA8PuZp63dDCFdRc65k2hKD1/Od3b2i/4Pvog29uuL+l50C8vqkBP1v5j+FmSiCbKYFspgSymRLIZkogmymBbKYEspkSyGZKIJspgWymBLKZEshmSiCbKYFsps/7P+Q7a87GeKwE8op8upJPg9V/bT9MwksgV3L5Aj6X2LTjRdYkj+NXYBLISibFCSr3bBw3baXaXgjSdZMkCJxCSSCBVi8o9WO9oUuOkZw1k2Osc790Wu4CSgLx0hAO5xkRFDmH9PmAMbgZxnx2jlMCceqGV0ae9p0Fd0juHtnQAcC5Tnz9XIdoBT0f5Pb7epTQt8x3vW6TQJzk7YaRzuqy46dkwMXrFVO2Kt9LI84hKxKr4YZGcPbhgRS06HwgSOy+UAIJxMuNIR0GpoU6x+WdtWECiTS/yWDkYUwpzRGQm0QyhpzJvduiS23ovszQC0Bf3tQfiobsB/XBi3AAAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.png 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.png 1024w" sizes="100vw">
</picture>
<figcaption>Ça marche, nous avons des commentaires parents.</figcaption>
</figure>
<aside class="note note-note"><p><strong>Note : <code>sort</code> et les filtres <code>where</code> ne font pas bon ménage</strong>
Je suis tombé sur des comportements étranges et des erreurs dus à l’utilisation du filtre de tri <code>sort</code> avec les filtres de recherche <code>where</code> et <code>where_exp</code>. J'en suis arrivé à la conclusion que ce n'était pas nécessaire, car les éléments étaient déjà classés par ordre alphabétique en fonction de leurs noms de fichier et j'ai donc supprimé les filtres.
J'utilise le format suivant : <code>filename: \"comment-{@timestamp}\"</code>. Tout dépend donc de comment vous nommez vos fichiers de commentaires.</p></aside>
<h4 id="afficher-les-commentaires-imbriques">Afficher les commentaires imbriqués</h4>
<p>Voici ce que je cherchais à accomplir… avant que le mal de tête ne commence
😧 🔫</p>
<ul>
<li>Déclarer une boucle et, à chaque itération, créer un nouveau tableau nommé <code>replies</code> ne contenant que les réponses aux commentaires.</li>
<li>Évaluer la valeur de <code>_parent</code> pour ces réponses.</li>
<li>Si <code>_parent</code> est égal à l’index de la boucle parente alors il doit être traité comme un commentaire "enfant".</li>
<li>Sinon, on passe à l’entrée suivante du tableau.</li>
<li>Et ainsi de suite.</li>
</ul>
<p>J'ai déterminé que la manière la plus simple d’assigner un identifiant unique à chaque commentaire parent était de le faire à l’aide d’une séquence.<br>
Heureusement Liquid nous permet de faire cela à l’aide de <code>forloop.index</code>.</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> index = forloop.index %}</span></code></pre>
<p>Ensuite j'ai imbriqué une copie modifiée de la boucle <em>parent</em> précédente à l’intérieur d’elle-même --- pour faire fonction de boucle "enfant" ou <code>replies</code>.</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> replies = site.data.comments[page.slug] | where_exp:"item","item._parent == <span class="hljs-name">include</span>.index" %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">for</span></span> reply in replies %}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> <span class="hljs-name">parent</span>  = reply._parent %}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> avatar  = reply.avatar %}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> email   = reply.email %}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> name    = reply.name %}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> url     = reply.url %}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> <span class="hljs-name">date</span>    = reply.<span class="hljs-name">date</span> %}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> message = reply.message %}</span><span class="xml">
  </span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">include</span></span> comment.html <span class="hljs-name">parent</span>=<span class="hljs-name">parent</span> avatar=avatar email=email name=name url=url <span class="hljs-name">date</span>=<span class="hljs-name">date</span> message=message %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name"><span class="hljs-keyword">endfor</span></span> %}</span></code></pre>
<p>Malheureusement le filtre <code>where_exp</code> s'est révélé problématique une fois de plus, obligeant Jekyll à générer l’erreur suivante :<br>
<code>Liquid Exception: Liquid error (line 47): Nesting too deep in /_layouts/page.html</code>.</p>
<p>Après avoir brièvement songé un moment au film <strong>Inception</strong>, j'ai appliqué un filtre <code>inspect</code> pour m'aider à m'en sortir avec la boucle <code>replies</code>. J'en ai conclu que la condition <code>where_exp</code> échouait<sup id="fnref1:integer-string"><a href="#fn:integer-string" class="footnote-ref">4</a></sup> parce que je tentais de comparer un entier avec une chaîne de caractères 😳.</p>
<p>Pour résoudre cela, j'ai placé une balise <code>capture</code> autour de la variable d’index pour la convertir en chaîne de caractères. Puis j'ai modifié la condition du filtre <code>where_exp</code> afin de comparer <code>_parent</code> avec cette nouvelle variable <code>{{ i }}</code> --- pour corriger le problème et me permettre de passer à la suite.</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name">capture</span> i %}</span><span class="hljs-template-variable">{{ <span class="hljs-name">include</span>.index }}</span><span class="hljs-template-tag">{% <span class="hljs-name">endcapture</span> %}</span><span class="xml">
</span><span class="hljs-template-tag">{% <span class="hljs-name">assign</span> replies = site.data.comments[page.slug] | where_exp:"item","item._parent == i" %}</span></code></pre>
<h4 id="includes-page-comments-html"><code>_includes/page__comments.html</code></h4>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">section</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"page__reactions"</span>&gt;</span>
  {% if site.repository and site.staticman.branch %}
    {% if site.data.comments[page.slug] %}
      <span class="hljs-comment">&lt;!-- Start static comments --&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"comments"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"js-comments"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"page__section-label"</span>&gt;</span>
          {% if site.data.comments[page.slug].size &gt; 1 %}
            {{ site.data.comments[page.slug] | size }}
          {% endif %}
          Comments
        <span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
        {% assign comments = site.data.comments[page.slug] | sort | where_exp: 'comment', 'comment[1].replying_to == blank' %}
        {% for comment in comments %}
          {% assign index       = forloop.index %}
          {% assign replying_to = comment[1].replying_to | to_integer %}
          {% assign avatar      = comment[1].avatar %}
          {% assign email       = comment[1].email %}
          {% assign name        = comment[1].name %}
          {% assign url         = comment[1].url %}
          {% assign date        = comment[1].date %}
          {% assign message     = comment[1].message %}
          {% include comment.html index=index replying_to=replying_to avatar=avatar email=email name=name url=url date=date message=message %}
        {% endfor %}
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-comment">&lt;!-- End static comments --&gt;</span>
    {% endif %}

    {% unless page.comments_locked == true %}
      <span class="hljs-comment">&lt;!-- Start new comment form --&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"respond"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h2</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"page__section-label"</span>&gt;</span>Leave a Comment <span class="hljs-tag">&lt;<span class="hljs-name">small</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"nofollow"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"cancel-comment-reply-link"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ page.url | absolute_url }}#respond"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"display:none;"</span>&gt;</span>Cancel reply<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">small</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">form</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"comment-form"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"page__form js-form form"</span> <span class="hljs-attr">method</span>=<span class="hljs-string">"post"</span> <span class="hljs-attr">action</span>=<span class="hljs-string">"https://api.staticman.net/v2/entry/{{ site.repository }}/{{ site.staticman.branch }}/comments"</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">fieldset</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"comment-form-message"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>Comment<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">small</span>&gt;</span>(<span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"https://kramdown.gettalong.org/quickref.html"</span>&gt;</span>Markdown<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span> is allowed)<span class="hljs-tag">&lt;/<span class="hljs-name">small</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">textarea</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">rows</span>=<span class="hljs-string">"6"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"comment-form-message"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"fields[message]"</span> <span class="hljs-attr">required</span> <span class="hljs-attr">spellcheck</span>=<span class="hljs-string">"true"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">textarea</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">fieldset</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">fieldset</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"comment-form-name"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>Name<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"comment-form-name"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"fields[name]"</span> <span class="hljs-attr">required</span> <span class="hljs-attr">spellcheck</span>=<span class="hljs-string">"false"</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">fieldset</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">fieldset</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"comment-form-email"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>Email<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">small</span>&gt;</span>(used for <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"http://gravatar.com"</span>&gt;</span>Gravatar<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span> image and reply notifications)<span class="hljs-tag">&lt;/<span class="hljs-name">small</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"comment-form-email"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"fields[email]"</span> <span class="hljs-attr">required</span> <span class="hljs-attr">spellcheck</span>=<span class="hljs-string">"false"</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">fieldset</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">fieldset</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"comment-form-url"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">strong</span>&gt;</span>Website<span class="hljs-tag">&lt;/<span class="hljs-name">strong</span>&gt;</span> <span class="hljs-tag">&lt;<span class="hljs-name">small</span>&gt;</span>(optional)<span class="hljs-tag">&lt;/<span class="hljs-name">small</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"url"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"comment-form-url"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"fields[url]"</span>/&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">fieldset</span>&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">fieldset</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hidden"</span> <span class="hljs-attr">style</span>=<span class="hljs-string">"display: none;"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"hidden"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"options[origin]"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"{{ page.url | absolute_url }}"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"hidden"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"options[parent]"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"{{ page.url | absolute_url }}"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"hidden"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"comment-replying-to"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"fields[replying_to]"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">""</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"hidden"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"comment-post-id"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"options[slug]"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"{{ page.slug }}"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"comment-form-location"</span>&gt;</span>Leave blank if you are a human<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"comment-form-location"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"fields[hidden]"</span> <span class="hljs-attr">autocomplete</span>=<span class="hljs-string">"off"</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">fieldset</span>&gt;</span>
          <span class="hljs-comment">&lt;!-- Start comment form alert messaging --&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">p</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"hidden js-notice"</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"js-notice-text"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
          <span class="hljs-comment">&lt;!-- End comment form alert messaging --&gt;</span>
          <span class="hljs-tag">&lt;<span class="hljs-name">fieldset</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"comment-form-reply"</span>&gt;</span>
              <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"checkbox"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"comment-form-reply"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"options[subscribe]"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"email"</span>&gt;</span>
              Notify me of replies by email.
            <span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"submit"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"comment-form-submit"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn btn--large"</span>&gt;</span>Submit Comment<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
          <span class="hljs-tag">&lt;/<span class="hljs-name">fieldset</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">form</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-comment">&lt;!-- End new comment form --&gt;</span>
    {% else %}
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">em</span>&gt;</span>Comments are closed. If you have a question concerning the content of this page, please feel free to <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"/contact/"</span>&gt;</span>contact me<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>.<span class="hljs-tag">&lt;/<span class="hljs-name">em</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
    {% endunless %}
  {% endif %}
<span class="hljs-tag">&lt;/<span class="hljs-name">section</span>&gt;</span></code></pre>
<h4 id="includes-comment-html"><code>_includes/comment.html</code></h4>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">article</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"comment{% unless include.r %}{{ index | prepend: '-' }}{% else %}{{ include.index | prepend: '-' }}{% endunless %}"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"js-comment comment {% if include.name == site.author.name %}admin{% endif %} {% unless include.replying_to == 0 %}child{% endunless %}"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"comment__avatar"</span>&gt;</span>
    {% if include.avatar %}
      <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"{{ include.avatar }}"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"{{ include.name | escape }}"</span>&gt;</span>
    {% elsif include.email %}
      <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"https://www.gravatar.com/avatar/{{ include.email }}?d=mm&amp;s=60"</span> <span class="hljs-attr">srcset</span>=<span class="hljs-string">"https://www.gravatar.com/avatar/{{ include.email }}?d=mm&amp;s=120 2x"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"{{ include.name | escape }}"</span>&gt;</span>
    {% else %}
      <span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"/assets/images/avatar-60.png"</span> <span class="hljs-attr">srcset</span>=<span class="hljs-string">"/assets/images/avatar-120.png 2x"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"{{ include.name | escape }}"</span>&gt;</span>
    {% endif %}
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">h3</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"comment__author-name"</span>&gt;</span>
    {% unless include.url == blank %}
      <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"external nofollow"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"{{ include.url }}"</span>&gt;</span>
        {% if include.name == site.author.name %}<span class="hljs-tag">&lt;<span class="hljs-name">svg</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"20px"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"20px"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">use</span> <span class="hljs-attr">xlink:href</span>=<span class="hljs-string">"#icon-mistake"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">use</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span> {% endif %}{{ include.name }}
      <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
    {% else %}
      {% if include.name == site.author.name %}<span class="hljs-tag">&lt;<span class="hljs-name">svg</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"icon"</span> <span class="hljs-attr">width</span>=<span class="hljs-string">"20px"</span> <span class="hljs-attr">height</span>=<span class="hljs-string">"20px"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">use</span> <span class="hljs-attr">xlink:href</span>=<span class="hljs-string">"#icon-mistake"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">use</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">svg</span>&gt;</span> {% endif %}{{ include.name }}
    {% endunless %}
  <span class="hljs-tag">&lt;/<span class="hljs-name">h3</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"comment__timestamp"</span>&gt;</span>
    {% if include.date %}
      {% if include.index %}<span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#comment{% if r %}{{ index | prepend: '-' }}{% else %}{{ include.index | prepend: '-' }}{% endif %}"</span> <span class="hljs-attr">title</span>=<span class="hljs-string">"path to this comment"</span>&gt;</span>{% endif %}
      <span class="hljs-tag">&lt;<span class="hljs-name">time</span> <span class="hljs-attr">datetime</span>=<span class="hljs-string">"{{ include.date | date_to_xmlschema }}"</span>&gt;</span>{{ include.date | date: '%B %d, %Y' }}<span class="hljs-tag">&lt;/<span class="hljs-name">time</span>&gt;</span>
      {% if include.index %}<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>{% endif %}
    {% endif %}
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"comment__content"</span>&gt;</span>
    {{ include.message | markdownify }}
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  {% unless include.replying_to != 0 or page.comments_locked == true %}
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"comment__reply"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"nofollow"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#comment-{{ include.index }}"</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"return addComment.moveForm('comment-{{ include.index }}', '{{ include.index }}', 'respond', '{{ page.slug }}')"</span>&gt;</span>Reply to {{ include.name }}<span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
  {% endunless %}
<span class="hljs-tag">&lt;/<span class="hljs-name">article</span>&gt;</span>

{% capture i %}{{ include.index }}{% endcapture %}
{% assign replies = site.data.comments[page.slug] | sort | where_exp: 'comment', 'comment[1].replying_to == i' %}
{% for reply in replies %}
  {% assign index       = forloop.index | prepend: '-' | prepend: include.index %}
  {% assign replying_to = reply[1].replying_to | to_integer %}
  {% assign avatar      = reply[1].avatar %}
  {% assign email       = reply[1].email %}
  {% assign name        = reply[1].name %}
  {% assign url         = reply[1].url %}
  {% assign date        = reply[1].date %}
  {% assign message     = reply[1].message %}
  {% include comment.html index=index replying_to=replying_to avatar=avatar email=email name=name url=url date=date message=message %}
{% endfor %}</code></pre>
<h3 id="html-et-javascript-pour-la-reponse-a-un-commentaire">HTML et JavaScript pour la réponse à un commentaire</h3>
<p>L'étape suivante a consisté à ajouter quelques touches finales pour que le tout fonctionne.</p>
<p>Habitué à la manière dont <a href="https://wordpress.org/" target="_blank" rel="noopener noreferrer"><strong>WordPress</strong></a> gère les formulaires de réponse, j'y ai pioché de l’inspiration. En mettant le nez dans le code JavaScript qui se trouve dans <a href="https://core.svn.wordpress.org/trunk/wp-includes/js/comment-reply.js" target="_blank" rel="noopener noreferrer"><code>wp-includes/js/comment-reply.js</code></a> j'ai trouvé tout ce dont j'avais besoin :</p>
<ul>
<li>une fonction <code>respond</code> pour déplacer le formulaire dans la vue,</li>
<li>une fonction <code>cancel</code> pour supprimer un formulaire de réponse et le repositionner à son état d’origine,</li>
<li>passer l’identifiant unique du parent à <code>options[parent]</code> lors de la soumission du formulaire.</li>
</ul>
<p>J'ai commencé par utiliser une condition <code>unless</code> pour n'afficher que les liens "répondre" sur les commentaires <em>parents</em>. J'avais seulement envisagé un seul niveau de profondeur pour les réponses, donc cela m'a semblé être un bon moyen pour m'en tenir à ça.</p>
<pre><code class="language-html hljs xml">{% unless r %}
  <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"comment__reply"</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">a</span> <span class="hljs-attr">rel</span>=<span class="hljs-string">"nofollow"</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"btn"</span> <span class="hljs-attr">href</span>=<span class="hljs-string">"#comment-{{ include.index }}"</span>&gt;</span>
      Reply to {{ include.name }}
    <span class="hljs-tag">&lt;/<span class="hljs-name">a</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
{% endunless %}</code></pre>
<figure>
<picture title="Commentaires imbriqués sur un seul niveau de profondeur.">
<source type="image/webp" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.webp 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.webp 1024w" width="1024" height="512" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.avif 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.avif 1024w" width="1024" height="512" sizes="100vw">
<img src="/images/no-image.158b054bca15d8e77f8ac75c945a532c.png" alt="Commentaires imbriqués" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="512" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAD7ElEQVR4nO2bUZLbIAyGf3n61NP0VL3/CVAfECAJYTvebcN29c9kwDaObX0IQSLTr5+/Gf+VvvbjHO++gZRVAtlMCWQzJZDN9OPxmfSJd/GvtXHcfw0IxSUFbQgA+Nmz04dgE/xVGXYXs9reDM41EG18UsYiOaQBeEA8G+PWtfTmJZyzBmxrXGEQaskMoLxwf/9A50A0hFUJX5LpfdUQfOuZ155BF8dHG7+r7WVmKeVTpESt7wJlDaQZ/aiGaKUHQgSQPDkpo7RhofdMKU+v5zdfAGDaU7uTjgMAgZkFCIMJKAKCdbM3656HOCi9JAEhBiDpwq3nQXph2wdw356uo6tBUFqzGeMkuVJ/X+sgRbyjgEEM4ACIEd/XG3TqIaQ/hwYhEA4piQYgqGGBGFQA5nqcGb3U1/H1yPiBP8zneBg0DrS4cZQaNogBOqSDtHYbQImBLLpj9YIICjoUYPS4Ugh8AFRYYNRhI4wFJ9fUtXAoIxdjNJgeR8TyxGqoDS72ZigxkNZr/G6ej5GeTiKoq4CuA6tRYAg/PyCp1ZHPeQiz+QrzdezupaiA7u9jWw8B+hQRMuYCqOv6NlU8ABRGIQL1XkfqXB5DV+HzGKItGe0XHP3w1AuoMnJD4vBEuW67p8L9nvoMawMYwFVQF+PzUcu+70Cf0LMawvy5ZlbjZl1Gc0gwTgBw4EQ6eHCvsoJgJlmAAdKCe+gpb9Sph/SiiOGlB7ayBv4RE4jakNSCu+2Zl72QbBPNpJl8xJD5yzxEs6XXIQrE1/MQwMxEGhhAeYYYqI9YrkfafSfX86FhimO8nv66A2x6lL2H7q0NxCYwgDs/nShDNgO1YaoO37xcxHFfkOC1h6Z182lVQnHLcGgErKduBKLptR8XAzjAqA8PuZp63dDCFdRc65k2hKD1/Od3b2i/4Pvog29uuL+l50C8vqkBP1v5j+FmSiCbKYFspgSymRLIZkogmymBbKYEspkSyGZKIJspgWymBLKZEshmSiCbKYFsps/7P+Q7a87GeKwE8op8upJPg9V/bT9MwksgV3L5Aj6X2LTjRdYkj+NXYBLISibFCSr3bBw3baXaXgjSdZMkCJxCSSCBVi8o9WO9oUuOkZw1k2Osc790Wu4CSgLx0hAO5xkRFDmH9PmAMbgZxnx2jlMCceqGV0ae9p0Fd0juHtnQAcC5Tnz9XIdoBT0f5Pb7epTQt8x3vW6TQJzk7YaRzuqy46dkwMXrFVO2Kt9LI84hKxKr4YZGcPbhgRS06HwgSOy+UAIJxMuNIR0GpoU6x+WdtWECiTS/yWDkYUwpzRGQm0QyhpzJvduiS23ovszQC0Bf3tQfiobsB/XBi3AAAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.png 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.png 1024w" sizes="100vw">
</picture>
<figcaption>Commentaires imbriqués sur un seul niveau de profondeur.</figcaption>
</figure>
<p>Pour donner vie au <strong>lien répondre</strong> j'ai lui ai ajouté l’attribut <code>onclick</code> suivant et du <a href="https://github.com/mmistakes/made-mistakes-jekyll/blob/49632d19977e341b51c91dad8e71bf6ef88e79c3/src/assets/javascripts/main.js#L84-L181" target="_blank" rel="noopener noreferrer">JavaScript</a>.</p>
<pre><code class="language-javascript hljs javascript">onclick = <span class="hljs-string">"return addComment.moveForm('comment-{{ include.index }}', '{{ include.index }}', 'respond’, '{{ page.slug }}')"</span>;</code></pre>
<p>J'ai juste eu à modifier quelques noms de variables dans le script <code>comment-reply.js</code> de WordPress pour que tout marche bien avec le balisage de mon formulaire.</p>
<h2 id="ajout-du-support-des-notifications-par-mail">Ajout du support des notifications par mail</h2>
<p>Comparées aux réponses de commentaires imbriqués, les notifications par mail
furent très simples à mettre en place.</p>
<h3 id="mise-a-jour-de-la-configuration-staticman-yml">Mise à jour de la configuration <code>staticman.yml</code></h3>
<p>Pour s'assurer que les liens dans les mails de notifications sont sûrs et ne
proviennent que de domaines de confiance, définissez <code>allowedOrigins</code> en
fonction.</p>
<p><strong>Exemple :</strong></p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">allowedOrigins:</span> <span class="hljs-string">["mademistakes.com"]</span></code></pre>
<p>Le(s) domaine(s) autorisé()s doi(ven)t correspondre à ceux passés par le champ <code>options.origin</code> que nous allons ajouter à la prochaine étape. Seuls les domaines correspondants déclencheront les notifications à envoyer, faute de quoi l’opération échouera.</p>
<aside class="note note-tip"><h4 id="protip-utilisez-votre-propre-compte-mailgun">ProTip : Utilisez votre propre compte Mailgun</h4>
<p>L'instance publique de Static man utilise un compte <a href="https://www.mailgun.com/" target="_blank" rel="noopener noreferrer"><strong>Mailgun</strong></a> limité à 10 000 emails par mois. Je
vous encourage à créer un compte et à ajouter votre propre <a href="https://staticman.net/docs/configuration#notifications.enabled" target="_blank" rel="noopener noreferrer">API et domaine Mailgun</a> dans le fichier <code>staticman.yml</code>. Assurez-vous de bien chiffrer les deux en utilisant le chemin suivant : <code>https://api.staticman.net/v2/encrypt/{TEXTE À CHIFFRER}</code>.</p></aside>
<h3 id="mise-a-jour-du-formulaire-de-commentaire">Mise à jour du formulaire de commentaire</h3>
<p>Pour terminer, ajoutons deux champs au formulaire de commentaire.</p>
<p><strong>Champ 1 :</strong> Un champ caché qui passe la valeur d’<code>origin</code><sup id="fnref1:origin"><a href="#fn:origin" class="footnote-ref">5</a></sup> défini dans le fichier <code>staticman.yml</code>:</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"hidden"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"options[origin]"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"{{ page.url | absolute_url }}"</span>&gt;</span></code></pre>
<p><strong>Champ 2 :</strong> Un <code>input</code> de type case à cocher pour s'inscrire aux notifications par mail.</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">for</span>=<span class="hljs-string">"comment-form-reply"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"checkbox"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"comment-form-reply"</span> <span class="hljs-attr">name</span>=<span class="hljs-string">"options[subscribe]"</span> <span class="hljs-attr">value</span>=<span class="hljs-string">"email"</span>&gt;</span>
  Me prévenir par mail des nouveaux commentaires.
<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span></code></pre>
<p>Rien de bien surprenant ici, <code>name=options[subscribe]</code> and <code>value="email"</code> sont ajoutés au champ pour associer les données d’abonnement avec l’adresse mail.</p>
<p>Si tout est correctement configuré, l’utilisateur devrait recevoir un mail dès qu'un nouveau commentaire est posté sur le billet ou la page auxquels il s'est abonné.</p>
<figure>
<picture title="Exemple d’un mail de notification « Nouvelle réponse » de Staticman.">
<source type="image/webp" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.webp 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.webp 1024w" width="1024" height="512" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.avif 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.avif 1024w" width="1024" height="512" sizes="100vw">
<img src="/images/no-image.158b054bca15d8e77f8ac75c945a532c.png" alt="Staticman reply email notification" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="512" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAD7ElEQVR4nO2bUZLbIAyGf3n61NP0VL3/CVAfECAJYTvebcN29c9kwDaObX0IQSLTr5+/Gf+VvvbjHO++gZRVAtlMCWQzJZDN9OPxmfSJd/GvtXHcfw0IxSUFbQgA+Nmz04dgE/xVGXYXs9reDM41EG18UsYiOaQBeEA8G+PWtfTmJZyzBmxrXGEQaskMoLxwf/9A50A0hFUJX5LpfdUQfOuZ155BF8dHG7+r7WVmKeVTpESt7wJlDaQZ/aiGaKUHQgSQPDkpo7RhofdMKU+v5zdfAGDaU7uTjgMAgZkFCIMJKAKCdbM3656HOCi9JAEhBiDpwq3nQXph2wdw356uo6tBUFqzGeMkuVJ/X+sgRbyjgEEM4ACIEd/XG3TqIaQ/hwYhEA4piQYgqGGBGFQA5nqcGb3U1/H1yPiBP8zneBg0DrS4cZQaNogBOqSDtHYbQImBLLpj9YIICjoUYPS4Ugh8AFRYYNRhI4wFJ9fUtXAoIxdjNJgeR8TyxGqoDS72ZigxkNZr/G6ej5GeTiKoq4CuA6tRYAg/PyCp1ZHPeQiz+QrzdezupaiA7u9jWw8B+hQRMuYCqOv6NlU8ABRGIQL1XkfqXB5DV+HzGKItGe0XHP3w1AuoMnJD4vBEuW67p8L9nvoMawMYwFVQF+PzUcu+70Cf0LMawvy5ZlbjZl1Gc0gwTgBw4EQ6eHCvsoJgJlmAAdKCe+gpb9Sph/SiiOGlB7ayBv4RE4jakNSCu+2Zl72QbBPNpJl8xJD5yzxEs6XXIQrE1/MQwMxEGhhAeYYYqI9YrkfafSfX86FhimO8nv66A2x6lL2H7q0NxCYwgDs/nShDNgO1YaoO37xcxHFfkOC1h6Z182lVQnHLcGgErKduBKLptR8XAzjAqA8PuZp63dDCFdRc65k2hKD1/Od3b2i/4Pvog29uuL+l50C8vqkBP1v5j+FmSiCbKYFspgSymRLIZkogmymBbKYEspkSyGZKIJspgWymBLKZEshmSiCbKYFsps/7P+Q7a87GeKwE8op8upJPg9V/bT9MwksgV3L5Aj6X2LTjRdYkj+NXYBLISibFCSr3bBw3baXaXgjSdZMkCJxCSSCBVi8o9WO9oUuOkZw1k2Osc790Wu4CSgLx0hAO5xkRFDmH9PmAMbgZxnx2jlMCceqGV0ae9p0Fd0juHtnQAcC5Tnz9XIdoBT0f5Pb7epTQt8x3vW6TQJzk7YaRzuqy46dkwMXrFVO2Kt9LI84hKxKr4YZGcPbhgRS06HwgSOy+UAIJxMuNIR0GpoU6x+WdtWECiTS/yWDkYUwpzRGQm0QyhpzJvduiS23ovszQC0Bf3tQfiobsB/XBi3AAAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/images/no-image.158b054bca15d8e77f8ac75c945a532c.png 768w, /thumbnails/1024x/images/no-image.158b054bca15d8e77f8ac75c945a532c.png 1024w" sizes="100vw">
</picture>
<figcaption>Exemple d’un mail de notification « Nouvelle réponse » de Staticman.</figcaption>
</figure>
<p>Voilà, vous avez mis en place un système de commentaires basé sur des fichiers statiques dans Jekyll et qui gère les commentaires imbriqués et les notifications de réponse. Maintenant j'aimerais gagner une minute de temps de génération pour pouvoir ajouter les nouveaux commentaires encore plus vite 😦.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn:staticman-yml">
<p>Un des avantages du nouveau fichier de configuration c'est qu'on peut utiliser Staticman avec d’autres générateurs de site statique. La <code>v2</code> ne vous oblige plus à utiliser un fichier <code>_config.yml</code> spécifique à Jekyll.&#160;<a href="#fnref1:staticman-yml" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
<li id="fn:property">
<p>Les propriétés de site sont optionnelles. Se reporter à la documentation de Staticman pour plus de détails sur comment <a href="https://staticman.net/docs/#step-3-hook-up-your-forms" target="_blank" rel="noopener noreferrer">connecter vos formulaires</a>.&#160;<a href="#fnref1:property" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
<li id="fn:parent-field">
<p>Staticman nomme ce champ <code>_parent</code> dans les entrées.&#160;<a href="#fnref1:parent-field" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
<li id="fn:integer-string">
<p><code>15</code> n'est pas la même chose que <code>'15'</code>. Ces guillemets simples font toute la différence…&#160;<a href="#fnref1:integer-string" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
<li id="fn:origin">
<p>Cette URL sera ajoutée dans la notification par mail envoyée aux abonnés pour leur permettre d’ouvrir directement la page.&#160;<a href="#fnref1:origin" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
</ol>
</div>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2016/11/10/designer-un-portfolio-avec-jekyll/</id>
    <title>Process de design d’un portfolio</title>
    <published>2016-11-10T00:00:00+00:00</published>
    <link href="https://jamstatic.fr/2016/11/10/designer-un-portfolio-avec-jekyll/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p><a href="https://medium.com/@katfukui/the-design-portfolio-workflow-a94030d0b39e#.uut2a0ulw" target="_blank" rel="noopener noreferrer">The Design Portfolio Workflow</a> de @katmeister, s'adresse aux webdesigners curieux de se frotter un peu à la ligne de commande et à Jekyll. Nous espérons qu'il vous permettra de faire vos premiers pas avec Jekyll, d’apprécier la liberté et la souplesse qu'il procure et qui sait de pouvoir ensuite <a href="/2016/10/29/creer-un-theme-pour-jekyll/">développer des thèmes pour Jekyll</a> 😊</p></aside>
<h2 id="un-guide-complet-pour-coder-et-deployer-un-site-tout-en-ligne-de-commande">Un guide complet pour coder et déployer un site, tout en ligne de commande</h2>
<p>Donc j'étais en train de mettre à jour mon <a href="http://www.katfukui.com/" target="_blank" rel="noopener noreferrer">portfolio</a>, j'éditais quelques liens dans le pied de page sur toutes les pages et j'ai pensé… putain mais que suis-je en train de faire de ma vie ? N'y aurait-il pas un moyen plus intelligent de faire ? J'étais dans l’expectative quand <a href="https://medium.com/u/57b87df79e32" target="_blank" rel="noopener noreferrer">James</a> m'a suggéré de tester <a href="https://jekyllrb.com/" target="_blank" rel="noopener noreferrer">Jekyll</a>. Mon épopée commence donc ici.</p>
<p>Je partage ce guide pas-à-pas, car je pense que ce workflow pour un portfolio peut avoir de la valeur aux yeux de ceux qui souhaitent bénéficier de méthodes d’ingénierie robustes et éprouvées sans renoncer à leur amour du détail dans le design. Les designers aiment les systèmes clairs et les choses propres, donc pourquoi ne pas appliquer aussi cela à notre code ?</p>
<p>J'espère aussi que cet article montre aux designers curieux du code que ce n'est pas la peine d’avoir peur des outils modernes de développement web. Ça vous branche ? Allez, c'est parti !</p>
<figure>
<picture title="Ce type, OK ?">
<source type="image/webp" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1HaArgKPM5NEVxucSnAVkpQ.5e522bccaf5b5f05a9924cafa3af98b6.webp" width="620" height="414">
<source type="image/avif" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1HaArgKPM5NEVxucSnAVkpQ.5e522bccaf5b5f05a9924cafa3af98b6.avif" width="620" height="414">
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1HaArgKPM5NEVxucSnAVkpQ.5e522bccaf5b5f05a9924cafa3af98b6.jpeg" alt="Ce type, OK ?" loading="lazy" decoding="async" class="dark:brightness-90" width="620" height="414" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A5S9HyGuVu1xIa37m6DLjNZEsfmtxXLextGLsZoUmlNuzDpWxbaaZCMitu20TIGVpOokXyOxw7WMh7VWkt3j6ivSZNFRV6Vganp6pnApxr62MZRsciilnArpdLtiQOKzYbTM/Sut0y2CoOKzxVXSxCQ4W52VTntm54roRDkdKjktQR0rzo1bMdjkpYGB6Vr6OpVhU09lz0qexg8thxXZCqpISR0UR/diioUYhRRTsaXOCdXJq1aWpcjIrQWyBPStSyshkcVdaXKjqo2kxllZ7ccVsxoEXpViCzAUcU+SLbXnOrdnXOnZFGVNwrA1S1JUnFdUkYY81Q1aBREeKcatpHm1EcHFCFuOR3rqNPjGwVz0xCXP41vabNlRWmIu1cxNZYqf5OR0pY2yKsoM15zbGZ8lrntUaW+09K1mQYqAoM124a7AgC4FFTbaK77AZax89K0LUbSKiEdWIlxWlaN0aUZ2ZrQuNtMmINRRNxUxUsK8ea5WexfmiVw201laxcDyjzWnOpRSa5LW7kqrDNOlHmkeXW0Zzl3cAXHXvWzpdwDjmuOurgtMT71e0/UChHNetVw7cDjvqejQyjA5q7HKPWuPt9U+Uc1ei1ME9a8mWHdy7nUbwRTTg1lwXu/HNX433CuyhFRAftopworqAq1LHRRWlTYdPctxVbTpRRXi19z2qXwFa8/1ZrgvEHRqKK0wfxnmYjc4eb/WGlg+/RRXvv4TjNqAnaOavwnkUUV509yjcsegrdt+goorOO40WxRRRXQM//9k=);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Ce type, OK ?</figcaption>
</figure>
<h2 id="c-est-quoi-jekyll">C’est quoi Jekyll ?</h2>
<p>Jekyll est un "générateur de site, simple et paré pour le blog", ce qui signifie que vous créez des contenus dynamiques et des modèles sur votre ordinateur en local, puis Jekyll recrache le tout en fichiers statiques HTML et CSS que vous pouvez déposer sur votre site hébergé. Chaque entrée de portfolio est traitée comme un article de blog et est extrêmement simple à créer et à éditer. Jekyll inclus également un serveur web de test et supporte Sass.</p>
<figure>
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1pUi-rGl2BFZ2HuVMj_HsiA.ef6ace0cc4aa09d9c7f18bbcb29c4773.gif" alt="Liz, j&#039;ai été là-bas et c&#039;est incroyable" title="Liz, j&#039;ai été là-bas et c&#039;est incroyable." loading="lazy" decoding="async" class="dark:brightness-90" width="500" height="277" style="">
<figcaption>Liz, j'ai été là-bas et c'est incroyable.</figcaption>
</figure>
<p>Rapide ✔ Propre ✔ Simple ✔ Complet ✔</p>
<p>OK, tu m’intéresses.</p>
<h2 id="d茅veloppons-un-portfolio聽">Développons un portfolio !</h2>
<p>C’est assez fascinant pour vous ? Développons et déployons un site ensemble pour faire une démonstration du processus de travail. Premièrement, nous allons lancer le terminal et travailler avec la ligne de commande.</p>
<figure>
<picture title="Le terminal">
<source type="image/webp" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1-novrtLzHXbmEmpM10a7Qg.b6a661c92d9286126eb076d1da8289b3.webp" width="700" height="300">
<source type="image/avif" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1-novrtLzHXbmEmpM10a7Qg.b6a661c92d9286126eb076d1da8289b3.avif" width="700" height="300">
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1-novrtLzHXbmEmpM10a7Qg.b6a661c92d9286126eb076d1da8289b3.png" alt="Le terminal" loading="lazy" decoding="async" class="dark:brightness-90" width="700" height="300" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAChElEQVR4nO2b27KrIAyG/zh5gTX7Yr//izbrAtGIgIdVIWq+GaW2tEL+BORQ+v/zTwBARBCIaZ401yotfr3+u/agLnfl9I2yQQsQAAEIApkvnZOsBJnZMuvag6IYLsp5ioLsNaiWRYvhopxjmExKSXoCKqT68z4t831gIkBAIBEIjanKkPPyksFzEUKZ79V+++0wQMFQBGAUBcBorbLJjnh6LlIiLsoSnuwv0UyC+dUyzYlQ6ys8Oo4zaFPp4NBHZGnE8GkYv5wzr/cpawZtzMm2iZFzwuyRYCvPeSmfC88DwTkmtiKk1kzlRvD+CLwfDs0OENOtJqj01FWaSknzbeV5O/z5zOYRKUeHJvd+zcjpey5EGY4REc9HvHdrvFLL7+Thj+o3gHNGc0N/D9ZR4fRncDFsMfQugLPEBTGGC2IMF8QYLogxXBBjDD79bQumccUwDg99XNIXpsySrYvSDx4orlnQNA0/n23wpuXfaZOD0FhNQ7Ut7W6JGCrqVyDEbUASKx3OV083HvH40rr7k8TQjsfLmoWp+HRlr0WB0ntRcpT4dhlbN49p/Xh6upLxQLvNB29fa9dixHSAQHXmbc2zZz9Xmk8vpD1PTBoXqFR/3qKi6RZTZK7TvEeXi89S2xR4PRK2AfUYFOZEqeVNr1s4zdXktlWxlRH6HnGeun1IO+dtJhefLIZOs3/Y2fLWFo+CLe5jkcpf2hzgWFP6DW7TZL2FKUJae8LdqE3ffPMR+XZNVstplB7wmQ7cVxkDV9jhNhGyJzKe4CjeqRvjUIRY9cDW5bryfqabrKd34Dn4uQvWVuO5zmaEdK9WwWG6lwu4pBDeqRuDVzN5JlyvgvXy/ZFf9enwdDipP10AAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Le terminal</figcaption>
</figure>
<p>Même si vous n'avez pas beaucoup l’habitude de taper des commandes, vous vous en sortirez en connaissant les bases, comme se déplacer dans les dossiers de votre répertoire de travail. Vous pouvez apprendre les bases <a href="http://klare.io/terminal-commands.html" target="_blank" rel="noopener noreferrer">ici</a> ou <a href="http://www.iamtomnewton.com/blog/designers-guide-command-line/" target="_blank" rel="noopener noreferrer">ici</a>. Sinon, le copier-coller n'est pas une si mauvaise chose pour commencer à apprendre.</p>
<p>Installons Jekyll<sup id="fnref1:1"><a href="#fn:1" class="footnote-ref">1</a></sup> ! Dans le terminal, j'ai tapé :</p>
<pre><code class="language-sh hljs bash">gem install jekyll bundler</code></pre>
<p>Par défaut, le terminal s'ouvre dans votre dossier utilisateur. Vous pouvez voir les fichiers et les dossiers du répertoire courant en entrant <code>ls</code>. Ça me va très bien de créer mon nouveau portfolio à cet endroit, donc je vais taper :</p>
<pre><code class="language-sh hljs bash">jekyll new mon-site
<span class="hljs-built_in">cd</span> mon-site
jekyll serve</code></pre>
<p>Excellent ! J'ai donc maintenant un dossier sur mon ordinateur nommé <code>mon-site</code>.<br>
La commande <code>jekyll serve</code> lance un serveur web local pour prévisualiser votre site à l’adresse <a href="http://localhost:4000" target="_blank" rel="noopener noreferrer">http://localhost:4000</a>. Je vais garder cet onglet ouvert dans mon navigateur.</p>
<figure>
<picture title="Le thème par défaut de Jekyll.">
<source type="image/webp" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1BrFV6ggy3qaZsfUQnojCVQ.c102d8424af18341ce8aaf4ee5436bd1.webp" width="700" height="400">
<source type="image/avif" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1BrFV6ggy3qaZsfUQnojCVQ.c102d8424af18341ce8aaf4ee5436bd1.avif" width="700" height="400">
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1BrFV6ggy3qaZsfUQnojCVQ.c102d8424af18341ce8aaf4ee5436bd1.png" alt="Le thème par défaut de Jekyll" loading="lazy" decoding="async" class="dark:brightness-90" width="700" height="400" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAC10lEQVR4nO2ba5KDIBCEe7a8/z1zhaSo2R8GIwgIODy1q1azeShO8zEgSK/Xi5kZAODdA859KZFvTySyj9FpTBL2zAylFJRS+Hw+29/7/Tb2Sin8RZfwURU9hnSmJEPigc+X3UzdSQRgif0i0C533EEEAETpTRahLil3kDYDOCFk3yvRvYbSso1I6RkNq901ZueQG4SpiZyEuMggELhgFjkz+xakIIIQcoSnVGjuEfKwDEJicgahfG9LW+OCYnZSvEn90EQ5nJAyhxyvSqhWxwRYK07O+Rb9Y63DQei7sd6XJ8WZOdbtQKTocuWafyDElby34NPRmDKSDXZNMvbKoWTxOkpfc04O+JCySmrMthES060tl9B9Qb0W7JZk5J7/mNQDUa97eWOQYp/3aiVYQmTUr2H5pLSiwVZu70rrR0gMGX1cM0KkVCuB4+QSlSLq9nudntXhpPCToadHqxWmmv7S8qZ8BGafA0nNbXGEVNJvMYP9iaZlXjK0OjCEfpNeFCJkHBdkknqU6jYoRGYe4f56F5fkas6arjoxp4PNwvmb3jHMCFESyisdNFlusZE2fBc3hjlA/GK9SEPKNFU6nHlHH8cMl3zGpBFSIYW4TGJzE/hVH7KDHaLDfq+LlYuMKyHtywygai+rrEJjc/15z6T4CGDm6BzSBSEyak9KvXtZJ6qz8MGl9iZIK4oQTdns951ai4jkcshjzjXp3BIkpJf56tm1j/NESX0Oedf2xvYYHoZk9RDSmQ5re3smY1tECeiVe5it6zskIbRtjBdTaFvbOwIZWvydZdyeoue2pEj2RoclhLAOWKnxMFV6aBA9MGw2JCH3PwyAmFc49Bsli3ESACljurrbmyLdSG1+CLdaKQGWpOTUkD5G6+frZ9c8I1/WK8bILLa+uVpXQP8jbV2QYcr73GNjMiT1ELJTD5Xw+EhbB4WyVZMMqePmzh4OOQ4ZRTnG/gPC2gVh8QJqegAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Le thème par défaut de Jekyll.</figcaption>
</figure>
<h2 id="plongeons-dans-les-fichiers">Plongeons dans les fichiers</h2>
<p>Regardons à quoi ressemble l’arborescence de notre site Jekyll en ouvrant le dossier <code>mon-site</code> dans notre éditeur de texte préféré. J'ai utilisé Sublime Text pendant des années mais récemment je suis passé à <a href="https://atom.io/" target="_blank" rel="noopener noreferrer">Atom</a>.<br>
C’est bien documenté et les paquets de la communauté sont assez mortels - <a href="https://atom.io/packages/pigments" target="_blank" rel="noopener noreferrer">pigments</a>, <a href="https://atom.io/packages/emmet" target="_blank" rel="noopener noreferrer">Emmet</a> et <a href="https://atom.io/packages/bezier-curve-editor" target="_blank" rel="noopener noreferrer">bezier-curve-editor</a> pour n'en citer que quelques-uns.</p>
<figure>
<picture title="Le dossier mon-site ouvert dans Atom.">
<source type="image/webp" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/118hVdarzmkx6KkSvty5Uaw.4e6674b500efe8a8b180f7b6aa0c40d8.webp" width="700" height="395">
<source type="image/avif" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/118hVdarzmkx6KkSvty5Uaw.4e6674b500efe8a8b180f7b6aa0c40d8.avif" width="700" height="395">
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/118hVdarzmkx6KkSvty5Uaw.4e6674b500efe8a8b180f7b6aa0c40d8.png" alt="Le dossier mon-site ouvert dans Atom" loading="lazy" decoding="async" class="dark:brightness-90" width="700" height="395" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAABsklEQVR4nO2aS3KDMBBEZ1I5SBZZ5f73UxZmEv0RDta0on4bDEWV8Ty3RoD04/MryPKo6LF9fDj2tLE9zsu3j3OmXnjBm+/XkxwKAYNCwKAQMCgEDAoB4937AjDIZ/5+c18mBIzNE4KTDIMJAWPThLSTEapH58GEgLFZQurJQOokTAgYmyQEPxnGJkIu4NzV8YWolH/lYdZJhoEpJK+Q7b/y3ab3fPcAS4jKWDFOxayXDANHiEoqJE9FkLKCdyQGJBmGi5Did6tIyIVUTzxjhQz0gUlI0rt7Up5ISjUEYMkwXIXYip0kHfmQJfIrIZaRHFs/GYarkKCvLV0RggW8uQgJEi1Ii5Lx1C0HYFH/AkYPMQvaGOPj/VA5dhVgiRhCIkKvaQ/KAK73Kf7T3rg597gjIQuYmi4kHpI0njGNcMdwBY5vQs6KWkvOFSkLJCJnqpBWfX7q3ivwBukQcWzqvfu+0Cr4PxZh+PWQETYQkDP1nXqQLWt8Cbcha+S+b0emC8mfL1FEituzrNpn4rwuizJKuFAODAoBg0LAoBAwKAQMCgGDQsCgEDAoBAwKAYNCwPgG1otVHYGMh0kAAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Le dossier mon-site ouvert dans Atom.</figcaption>
</figure>
<p>Pour ce guide, je vais utiliser <a href="https://atom.io/" target="_blank" rel="noopener noreferrer">Atom</a>. Vous remarquerez le panneau avec l’arborescence de fichier sur la gauche. Laissez-moi vous la détailler :</p>
<pre><code class="language-sh hljs bash">mon-site/
|
|-- _config.yml    <span class="hljs-comment"># Configuration de votre site</span>
|-- _drafts/       <span class="hljs-comment"># Articles non publiés</span>
|-- _includes/     <span class="hljs-comment"># Composants HTML réutilisables</span>
|-- _layouts/      <span class="hljs-comment"># Modèles</span>
|-- _posts/        <span class="hljs-comment"># Articles (ou entrées de portfolio !)</span>
|-- _sass/         <span class="hljs-comment"># Fichiers Sass</span>
|-- _site/         <span class="hljs-comment"># Votre site généré</span>
|-- css/           <span class="hljs-comment"># fichier CSS principal</span>
|-- about.md       <span class="hljs-comment"># Page à propos</span>
|-- index.html     <span class="hljs-comment"># index du site</span></code></pre>
<p>C’est l’arborescence par défaut. Tout dossier dont le nom commence par un tiret bas <code>_</code> ne sera pas généré tel quel. Par exemple quand Jekyll va générer votre site, il ne va pas créer un dossier <code>layouts</code>, par contre il générera le fichier <code>index.html</code> puisqu'il n'y a pas de tiret bas devant.</p>
<p>Vous pouvez ajouter autant de dossiers que vous voulez pour organiser vos icônes, vos vignettes, vos fichiers JavaScript, etc. Ils seront copiés dans le site généré tels quels. Organisez-vous comme bon vous semble, voici quelques exemples de dossiers :</p>
<pre><code class="language-sh hljs bash">assets/        <span class="hljs-comment"># images du projet</span>
images/        <span class="hljs-comment"># fichiers SVG, images diverses</span>
js/            <span class="hljs-comment"># fichiers Javascript, les vôtres et ceux des différentes bibliothèques utilisées.</span></code></pre>
<h2 id="modifier-les-parametres-de-votre-site">Modifier les paramètres de votre site</h2>
<p>Jekyll inclus ce super fichier nommé <code>_config.yml</code> dans le répertoire racine. Vous pouvez définir <a href="http://jekyllrb.com/docs/configuration/" target="_blank" rel="noopener noreferrer">n'importe quel paramètre</a> global de votre portfolio dedans. Ouvrons-le et personnalisons tout ça !</p>
<figure>
<picture title="Remplissez vos infos !">
<source type="image/webp" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1fF_CWur2wd6DS7uQDGX6ew.19bb1bd00704932f41902264307ba1cb.webp" width="700" height="492">
<source type="image/avif" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1fF_CWur2wd6DS7uQDGX6ew.19bb1bd00704932f41902264307ba1cb.avif" width="700" height="492">
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1fF_CWur2wd6DS7uQDGX6ew.19bb1bd00704932f41902264307ba1cb.png" alt="Remplissez vos infos !" loading="lazy" decoding="async" class="dark:brightness-90" width="700" height="492" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAA9klEQVR4nO3bQQrCMBQA0UQ8i+DS+x+ubhRsrZAE00zz523qMmT8qUWab/fHks4m59dlfZ3BZfQCtGYQGIPAGATGIDAGgTEIjEFgDAJjEBiDwBgExiAwBoExCIxBYAwCYxAYg8AYBMYgMAaBMQiMQWAMAmMQGIPAGATGIDAGgbmOXsCXed4saFI9IcH3q7viCck7n7u+6RO0fNWR9blHSzooTDBFQbBfVuzC2hXdQ5yA41QdWdswhvq/4iDvzc/JED1VP4e0xpjwuO/CJ3WYIU/qTstvTgiMQWAMAmMQGIPA8P4P2RXnd9kJJiROjJTwE7KNMX+cJ3E+CwadF+G1AAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Remplissez vos infos !</figcaption>
</figure>
<p>J'ai ajouté un paramètre <em>permalink</em> pour définir comment je voulais construire les URLs du site (sinon par défaut la date de l’article est présente). J'ai aussi ajouté une variable <code>dribbble_username</code>.</p>
<p>À chaque fois que nous allons modifier <code>_config.yml</code>, il nous faudra relancer le serveur de Jekyll. Donc une fois les changements effectués, arrêtez le serveur en ligne de commande avec le raccourci <code>ctrl-c</code>. Entez à nouveau <code>jekyll serve</code> et jetez un coup d’œil à localhost !</p>
<figure>
<picture title="Les variables globales sont appliquées !">
<source type="image/webp" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1uDszfYceRXqPFeI7Xm6m0w.353d90e08aba5feed4060a0b6d113788.webp" width="700" height="398">
<source type="image/avif" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1uDszfYceRXqPFeI7Xm6m0w.353d90e08aba5feed4060a0b6d113788.avif" width="700" height="398">
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1uDszfYceRXqPFeI7Xm6m0w.353d90e08aba5feed4060a0b6d113788.png" alt="Les variables globales sont appliquées !" loading="lazy" decoding="async" class="dark:brightness-90" width="700" height="398" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAEtUlEQVR4nO1cTW/sKgy1r+b//8RqKnXRRRddVOqiH36LYDBggyFkppqXI80lQ4IxPhy+mrn4/PxMAABEBDPpXiDiISkj+kkABOl6FFx2JD6/v7/w8/MD39/f8PX1BZ+fn/Dx8QHv7+/w9vYGr6+v8PLyAtfrFa7XKzw9PeG/VQ0fBy4P/kDV6TMJj0+IGD9eXPYGdlwpCFzl/TpD5k6CoymxbgplrRTmRpEmIYgIRFSlJXwVW0Rs+b2eZt1zAwGwIQkiSuT4mtO0B5QrxKuUC0C7gXyPiLLAy2uLKOH5NkIoRAA7qxBW+rBUGaWXwjYpjDSDrxqEjRSo29Jqh6qQzLkQaA46T1YSunowJlvAYQs+SEKEOgIpTB4Xlu7NEDJahtvC1/0KdLIIgx1MJEgyLL8uPee13l86HJ9BBCBiL0WAk2MAuZMgh62MEHa8H5OVWKVCbbjyKL07qcseIyuTKZOBREB8D7jXQxiWQm4m2TRUZcMYJFJuTQhjVim0ra+7ZFgEXWrLRT2KU9Ukz2SA6N1yCCrmjGwiR5ZEGqvwTuqQkO2eVU2LjLIehkshsnD8HoYnDCnFPEixjeOrcIqvWTEhV37Lh+T7qGR241sS2VppabHvKkSpEtK6UFzHIUsOVxhXUSmwRfC11Us0f0eJRBfGFwVcruzM8toixlRI1UN4wkYAIO7JYpAiAuIwC4UURjJFxFxi+2AVhFspZe+RUGunri15y2HMNYdsPhYrrfhvOLsBDJEN+WJpmwWTU8dOt6ztlpidN7yEWvuRSiGaQQyBomyMl8PW9h0J86yYyokl3dObjMZ1yjtCKasPS62tQg+mQkzD1VUihgqFZKNPJBEBUZWBA8crZe8+RM4h2lzh3ql719p2pqKQ4olqemi2/TZKWaWMFjxHU4xKIe1zqXrukHcyhWzG8u8ysLuCuV4pK3boo5vJ4WVv02hlTCgEYEAJvft9pUzxc4AyWp3ZWl2VuMgHLGOkXNTmpEKy7JRgkVlcjmN/r151duWZxK05ZOjoJNRm34vGCoUAqJ3b3Ga08g1j1nKjDe1ofR9WzkOX3pxRVF3lyFWXqhDxEFbXYr+urwecmCil9eBBE0csCCaOTgwg1ApRnomJGPpLMnqjiDiQsCtplOo9OYsVBC0jBCGdqLge5svATl8hnr2Lx4HGM06VrtxEdpe9k6anSkQSjECMzbdV6ZAev8/woHXqK7GGkB3az3coqKxiZ3b1I0OZbaJa2N9gE7mTkElliI/LXMj3UXP88cqR2EfIorbXC9rC8OzRl2ZrpvgNlMGYJOS4Xqg1Hc0vj4c5QnYEpT4BOworLN9+QTBIyDHha7xiZ+NBlTJGiGPfN7PnPza2UyePy73wwklIu1EjRGgo//b4f4aLkOz9BqjT7NlBB3YtoB4QHUJWHZOf8KJJCCvDg5OvNfh3bwdO5FAVMvI3klMZa3Eq5I+h+n3IqYz74lTIH0P8jeGpjP0Yez9Bx6mQPwb3WdapDBuWMjzv8pY4FXIA9rx85/h9yLTtP4OjmrBizihxKmQx9r6a2v19yCNhVYs8v92fxamQgzDbodXfhzw6ZlvYUgZAWx3eFdepkMXw/hcaFv4DKgWex3oJdMAAAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Les variables globales sont appliquées !</figcaption>
</figure>
<p>Cette flexibilité c'est ce qui rend Jekyll si fun et simple à mettre en œuvre.<br>
Voyons comment ce concept est aussi valable pour nos articles.</p>
<h2 id="ajoutons-notre-premier-article聽">Ajoutons notre premier article !</h2>
<p>À quoi sert un portfolio sans démonstration de notre travail ? Écrivons un article à propos de mon <em>side-projet</em> d’application de livraison de nourriture pour chat, <em>Food Right Meow</em>. Dans le dossier <code>_posts</code>, je vais créer un nouveau fichier Markdown en utilisant la convention <code>ANNEE-MOIS-JOUR-titre</code><sup id="fnref1:2"><a href="#fn:2" class="footnote-ref">2</a></sup>. Le fichier de mon article est donc nommé <code>2016-11-10-livraison-nourriture-chats.markdown</code>.</p>
<figure>
<picture title="YAML front matter.">
<source type="image/webp" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1kkyEEvzkXYwHlvstT7Tytg.dccf8f264a327cfe165af568dfd8e954.webp" width="672" height="123">
<source type="image/avif" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1kkyEEvzkXYwHlvstT7Tytg.dccf8f264a327cfe165af568dfd8e954.avif" width="672" height="123">
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1kkyEEvzkXYwHlvstT7Tytg.dccf8f264a327cfe165af568dfd8e954.png" alt="YAML front matter" loading="lazy" decoding="async" class="dark:brightness-90" width="672" height="123" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAA8UlEQVR4nO3aSw6CMBgA4bbhMC5cev+r1QUqRf0bEh6dxXwJgVVDHNoSYr7dHzUJo4y+Aa0ZBMYgMAaBMQiMQWAMAmMQGIPAGATGIDAGgTEIjEFgDAJjEBiDwBgExiAwBoExCExJefQtqDXPkPw6NFxZxTDKcMse0kYxzDDxDDkqSv461DV9rnJKqTbnvXrLoH9eDZ37lhWN7UwJlUueVgNstmzqNTjvEY3lkhWaTgnxHuffvmSMrt8gR6rBtULzknX2j2WMza7Z1LWZX3thDAJjEBiDwBgExiAwBoExCIxBYAwCYxAYg8AYBMYgMAaBeQKYPRn0CcvDJwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>YAML front matter.</figcaption>
</figure>
<p>Vous remarquerez une portion de contenu en haut de l’introduction de l’article sur Jekyll. Copions-la dans notre nouvel article et voyons ensemble ses pouvoirs extraordinaires.</p>
<h2 id="yaml-front-matter">YAML front matter</h2>
<p>Front matter est un puissant outil qui permet de définir des variables spécifiques à une page. Ces variables sont accessibles de partout grâce aux balises Liquid, que nous allons voir très bientôt</p>
<p>Éditons notre front matter en haut de l’article entre les triples tirets :</p>
<figure>
<picture title="C’est ici que nous définissons le titre, la date et la catégorie.">
<source type="image/webp" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1CJkHCXOIOLYrssnP4zgtiw.7f3680c766404e8e605df441fd3e24bd.webp" width="672" height="125">
<source type="image/avif" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1CJkHCXOIOLYrssnP4zgtiw.7f3680c766404e8e605df441fd3e24bd.avif" width="672" height="125">
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1CJkHCXOIOLYrssnP4zgtiw.7f3680c766404e8e605df441fd3e24bd.png" alt="C’est ici que nous définissons le titre, la date et la catégorie" loading="lazy" decoding="async" class="dark:brightness-90" width="672" height="125" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAA4klEQVR4nO3aMQqEMBQA0Z+Qw2yx5d7/atlCZEmI2JhkFuY1IlgEx28QTK/3p4Yw8u4FqGUQGIPAGATGIDAGgTEIjEFgDAJjEBiDwBgExiAwBoExCIxBYAwCYxAYg8AYBCZHioi0exk6/SbEKAhOCEy7hxhmOzd1mNKczfyHsZ8+/5ccKtNvTOqOEUeMFEYZOF5ZNdZMxyiOGnnJU1q7oy7N39T7GLOn8c+V+0se0AcwyKU1QU6GuOV3CIxBYAwCYxAYg8AYBMYgMAaBMQiMQWAMAmMQGIPAGATGIDAGgTEIzBe1uxLvZL5huwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>C’est ici que nous définissons le titre, la date et la catégorie.</figcaption>
</figure>
<p>Jekyll possède quelques variables front matter prédéfinies, mais c'est en créant vos propres variables dans vos modèles que vous en tirerez le plus parti ! Regardons comment notre premier article utilise les variables front matter :</p>
<figure>
<picture title="Ça marche !">
<source type="image/webp" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1-QBd-5OFh48-eCVM1sJMhA.c5d198a839e6d351665e52fb8495b947.webp" width="700" height="237">
<source type="image/avif" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1-QBd-5OFh48-eCVM1sJMhA.c5d198a839e6d351665e52fb8495b947.avif" width="700" height="237">
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1-QBd-5OFh48-eCVM1sJMhA.c5d198a839e6d351665e52fb8495b947.png" alt="Ça marche !" loading="lazy" decoding="async" class="dark:brightness-90" width="700" height="237" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAE2UlEQVR4nO2aa5ajIBCFb/Vx/1vr/fQjNT+ksIACeUlMpu+ZaRAFTX1cQBL6/PxkLBQRXZLWngMAMIN9llWxy/si1tkl+lhzmz/V6g/IzbQUyIrh6tW1rbjJKhA1+eTZcEwPPq8LF+tSIKtBVAFhBhOBTvMAQCBZACyCdAkQHaArV0s9zhnSAihTgYyA6OnxS4CIawDlHFwGZgqQUoCumBeWAtFycJgZuAjMMJAg+Humf44AYf9HxwfuaKeUr/xQMGu4+cS3d4FjNvNhaxt3VX0bLTBgpBGMK1ZfQ9IOUcczwdgOaXh+KxgzlABrGAZL+fwNM67Qih3ii9mBAZgUkQ44oUNG6DIAcg9HFKYgMKKUGMSVqRGqEpg4H1Vs6W9mXb3/pe/lP6t3kIpNpTa90dalUmWBhIbgawiEAIZA0P+lXKdxPqsWOCpOVttxR9TlLT1gS+J5Qsfc/fRBR+gSByKGAk7njCT1Qf/Ah9rgiYNhgblEI85q0AZnL5ODUWgCkV1rAsjn2b1HHYHmnYQfskwwRNEQxe4RCTreJafcSa3PtJkBhvsmoBaIKpNtBvJ5dnkXcD7A+AfmMA2dsQMhsV9Gz4YRz5u92pLK/juaDiDqnA1GucStE2VVonu6f4IOEM8Ak5vke7Qvew0IQ0DUNSyvFYxjso7yet75cBPGGYh31XYGg8M/XqdgAhAwQfg1u166R6uUXq1wykxniDaSThqthnznPf7sDyE3118ilCRgZM7Qb+KZLZL/WfbWiRYZh9WjCQXZHBjAwci8Z9TKuv4KyFc4Q3QOxFJSheziuEICJSyk4P0jD6b0QvjqsoEEAQtzdcoAOhmqci6xwJQgXOWUK50h6nNIoMwbrLICReWhM1yxwEDeJb1D2SvpAKJ777BUK8kcVHAH6lxSC2aWU1Y4Q7TN26OhxAnmGf2GfpK2zCfvoglDVkYZ0DUgJD0AhGnrsNXrlJXOEE38kQPpaSN3tq9d0qk6UznJv5LS7fcGhSHg44VeByo868H4L6qMN/NwiErvKm5pfl7VWMsG4ErYfvu9RxLiaJ6GbjP+ytMDYAdl8MO+25wy5BAAILfvFbQTOUR6owRLvkWLh7Hyiiq6b0fcR7bFV2nIIYBsrUdDVOQQmeADMN2buXbFd3HKsEMAAPKrCyinaEjud7PA4ZjALZUyhz51rK/pvcezNewQEedcQuR2jfnIj94rCnYO1Kyt/JWa4xAgdEnkjpoBv9yL9zlEu+CVen2LpjkEOECQcseklpXhUhglJ5TO3dE98xyC9mHJuu6s5z8eD7/yshwTA7MA3nluSX/k0CnC7pCzIWp/6XbnBaDRi0vPFV9f88Z+1Zb8bG38eIy3UvnBNDTzV0SOpWYqwY/3uCQfPkbf0vdOYLbHJCBUkfqhxlejZLlau+kY3r4exJ2Cb2kOEFEBBJAG086nm4m1w9NbOOTn52daY629O5hPUBf4mrngpR0yE8iutGcn6X6gaiALpua49bqzes/U9vX1NbVBqzdbIM6gxG1Zx7mys+e6s7bv7+9Lb2AC2g+O8sbjmnu1PttdNN0hlhJnxMeZc1b94n1uGOBWrQMCHKsvl0+usYakTHly3RvAACYDCUJScoQ6Duo2BPVdAMTafn9/pzV2FvwcDH/uzSboHv0Def4b2YJhUb4AAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Ça marche !</figcaption>
</figure>
<h2 id="liquid">Liquid</h2>
<p>Alors comment ça marche tout ça ? Ces variables front matter sont référencées dans le HTML et le Markdown grâce à <a href="http://shopify.github.io/liquid/" target="_blank" rel="noopener noreferrer">Liquid</a>, un langage de modélisation très facile à prendre en main. Liquid vous permet d’ajouter de la logique, comme des conditions <code>if/else</code> et des boucles <code>for</code>, d’assigner des chaines de caractères à des variables. Les trucs entre <code>{{ }}</code> ou <code>{% %}</code> c'est pour faire bosser Liquid !</p>
<p>Si vous ouvrez le fichier <code>post.html</code> dans le dossier <code>_layouts</code>, nous pouvons le voir en action.</p>
<picture>
<source type="image/webp" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1FYILLFGGLdTohX7sJZjSIw.d19ab2494b214fa07fbd311e52f401d2.webp" width="700" height="403">
<source type="image/avif" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1FYILLFGGLdTohX7sJZjSIw.d19ab2494b214fa07fbd311e52f401d2.avif" width="700" height="403">
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1FYILLFGGLdTohX7sJZjSIw.d19ab2494b214fa07fbd311e52f401d2.png" alt="" loading="lazy" decoding="async" class="dark:brightness-90" width="700" height="403" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAADR0lEQVR4nO2bzbKkIAyFz+nqp5nNbOf9XyyzUOgQExTbH+ziVHkBBYR8JqDVl3/+/hOcphO7vlw8pIcXAb6AFwmSIIHpD0AQ76/vAuC3DH+NBBMgAcC5IBALZBj2bGUAkBkKIRRACAJ4DwjXaeYwpdSFWcRRIWuoRQkMBBBKXp1EBpDLpb2E8yECcIpjA8gdyh6CCQaY1pUB5FaJyiSPed03nCErwQDSnQaQzvRYIGmH8mt63KJOky4Lk/RO5kl6DBALgrpg84GeAKl7IBGIBRCv0QZJZ4C6BbIGgrZirZOVKvZ94E7tAhLNU9YqrczWhTBnQiBbBrZy0/T5Ir0xFx8BL1YzEL27yR/FTAXRFz2pBhEEmoIFQX7ebsOBrp/KQ9FzoU4vBtMEhPCBEICYk6Jnr2dtYLj9sUwXYMzpJs/gsrhqcCmSU7UZCJ0jX8DniRUz4ewt5rs/xQFCP9VGpGP8haGikOY0zF6m3SKo697rYO32kBxCbIjiJ0mDFxMHKFO7DCUAURifRTL1m8bQMgk1rtxWyr5kHp/IrqXwK20C4nkH50zKCz82T4eWjdVFgSUY6LxncAMnNFBAis7T7oWuFIrztQtiVlPISukiZNFMyLGUfdg12AJGtE4sCv7pwmYVA0YPjFtxPsL7HKjNHqLTDMEaqDLiwkNq7cz5Iqw7VtjqHYt61kuc8mZoB2oViLPRKT1EqzLiyEN00wQ4Q9BGalgo1sKL50ULMPIpd7vtbdY8kxzO6EDQZRUW7G6NLd5RNNwGIGfFyZs6ty3qNW8QIP/AKz1F9sgVgz6KKnYdimJ2pSPXUNbgpuCGrABEeI8D9d61ZZTPDiQP3rFeNAm95c9riwkN2quWA2gYa3TOAyUlEJ29KmxtClnVHUiK+8pL4KRes6hf6ylhpY2qesZcuBtEUrOHAGrwKVypxXjrRKJ3gBqosIOd1QsIxkOvBpG0eVGvuj++e6KunHx0r7s8wqppl1Xblx81kTsM0gsMYMe2twe3/mW9m7/MzRowztFjfwb0q9rtIUPnaHhIZxpAOtMA0pkGkM40gHSmAaQzDSCdaQDpTANIZxpAOtMA0pm6/f+Qy7XjJ6lnaHhIZxoe0olnJP0HrVgWbDUz8EoAAAAASUVORK5CYII=);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<p>En préfixant nos variables avec <code>page</code>, Liquid va rechercher dans votre page les entrées front matter correspondantes entre les triples tirets. Si elles existent, nous pouvons écrire le texte stocké dans ces variables dans notre HTML. Cool, non ?<br>
<a href="https://jekyllrb.com/docs/variables/" target="_blank" rel="noopener noreferrer">Plus de détails sur les variables par ici</a>.</p>
<p>Et si nous ajoutons encore quelques variables à nous dans le front matter pour épicer un peu nos articles :</p>
<picture>
<source type="image/webp" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1j0EnKALwOPpCRrhBPaM7Nw.427eb232b76fa4d3c06df4121e6b014c.webp" width="666" height="206">
<source type="image/avif" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1j0EnKALwOPpCRrhBPaM7Nw.427eb232b76fa4d3c06df4121e6b014c.avif" width="666" height="206">
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1j0EnKALwOPpCRrhBPaM7Nw.427eb232b76fa4d3c06df4121e6b014c.png" alt="img" loading="lazy" decoding="async" class="dark:brightness-90" width="666" height="206" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAABNklEQVR4nO3azY6CMBSG4UPTi3Hh0vu/NVyQSinlp1A83+J9EjMTMwvTd06xyvB6f0aDjOD9ArBEEDEEEUMQMQQRQxAxBBFDEDEEEUMQMQQRQxAxBBFDEDHBBjMbvF8GkrgZg29JXMxbVjkpTI2LectKAQjhiou6mFDdppgWN+sJYQtzNQUpI5gRxcn2hMDF+hySnz+GynN4VH1C4GZ9DkkI42I5IXmEMXvgb+Lp6wSfef1FNLP6op5Z6HKicFtcLGS5TdUWee9aQ5Tb4u+3MkTLFkaIbuJqGsqJSY7edXFm6aIehHdXbraDtCJiF/tbVm60aVvKf6K72OU/m+noJjQt5NXzCk6Lx39SUUYgSjftQdLicx15xPWbHIjxCO46EUMQMQQRQxAxBBFDEDEEEUMQMQQR8wXa+Dn6ei15YwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<p>Maintenant que nous disposons de toutes ces super variables, comment pouvons-nous les utiliser ? Modifions notre modèle de mise en page <code>post.html</code> en utilisant les variables <code>page.type</code> et <code>page.intro</code> :</p>
<figure>
<picture title="Regardez ce qui est en violet.">
<source type="image/webp" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1kndQjfPKY_2GF10ZE2Vobg.f125b881274e606a776be433f03bf58f.webp" width="700" height="325">
<source type="image/avif" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1kndQjfPKY_2GF10ZE2Vobg.f125b881274e606a776be433f03bf58f.avif" width="700" height="325">
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1kndQjfPKY_2GF10ZE2Vobg.f125b881274e606a776be433f03bf58f.png" alt="Regardez ce qui est en violet" loading="lazy" decoding="async" class="dark:brightness-90" width="700" height="325" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Regardez ce qui est en violet.</figcaption>
</figure>
<p>Cool ! Vous pouvez bien entendu utiliser CSS comme à votre habitude pour mettre en forme tous les rendus de vos chouettes balises. Essayons d’autres trucs. Et si nous ajoutions des vignettes pour chaque article sur la page <code>index.html</code> ?<br>
Et leur légende aussi peut-être.</p>
<figure>
<picture title="Front matter c&#039;est de la bombe !">
<source type="image/webp" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1h4YbEuULViTNp3ixMtpxKg.c292423ad062f8959a89bd62bd5c570c.webp" width="700" height="325">
<source type="image/avif" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1h4YbEuULViTNp3ixMtpxKg.c292423ad062f8959a89bd62bd5c570c.avif" width="700" height="325">
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1h4YbEuULViTNp3ixMtpxKg.c292423ad062f8959a89bd62bd5c570c.png" alt="Front matter c&#039;est de la bombe !" loading="lazy" decoding="async" class="dark:brightness-90" width="700" height="325" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Front matter c'est de la bombe !</figcaption>
</figure>
<p>Hé, c'est pas trop mal. Je suis sûr que vous pouvez déjà voir comment Jekyll va automatiser votre site en utilisant Liquid et YAML.</p>
<h2 id="ecrire-un-article-pour-de-vrai">Écrire un article pour de vrai</h2>
<p>Vous aurez noté l’extension <code>.md</code> ou <code>.markdown</code> pour vos articles. C’est l’abréviation pour Markdown, un langage léger qui convertit sans heurt du texte brut en HTML. Je me suis rendu-compte qu'écrire à l’aide de la syntaxe Markdown me permet de mieux me concentrer sur mon contenu, plutôt que de penser quelles balises fermer.</p>
<picture>
<source type="image/webp" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1h1nmOO9BWVno52HIGqkyyQ.b58995197cdab3580537438ad6a34eee.webp" width="700" height="510">
<source type="image/avif" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1h1nmOO9BWVno52HIGqkyyQ.b58995197cdab3580537438ad6a34eee.avif" width="700" height="510">
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1h1nmOO9BWVno52HIGqkyyQ.b58995197cdab3580537438ad6a34eee.png" alt="" loading="lazy" decoding="async" class="dark:brightness-90" width="700" height="510" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAsUlEQVR4nO3RSwqDMABAwaZ4GBdd9v5XqxsRCwUtCL7FzCYfEgh5Y369P4/VWMdt44yxm/91kV+m/cJ/3m86PnJAxUs9734A3wSJESRGkBhBYgSJESRGkBhBYgSJESRGkBhBYgSJESRGkBhBYgSJESRGkBhBYgSJESRGkBhBYgSJESRGkBhBYgSJESRGkBhBYgSJESRGkBhBYgSJESRGkBhBYgSJESRGkBhBYgSJESRmAcnFBe1108xNAAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<p>La beauté de Markdown c'est que vous pouvez toujours utiliser HTML si vous en avez besoin. Pour forcer le rendu de Markdown à l’intérieur de balises HTML, ajoutez <code>markdown=1</code> et le tour est joué ! Le meilleur des deux mondes. Voici un extrait de l’article une fois généré :</p>
<figure>
<picture title="Ne voudrions-nous pas vivre de Purring Cat ?">
<source type="image/webp" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1X7jkghcRXQKIH2BSCIxvVQ.a90c9b45829412be3297f1ffebe15be7.webp" width="700" height="548">
<source type="image/avif" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1X7jkghcRXQKIH2BSCIxvVQ.a90c9b45829412be3297f1ffebe15be7.avif" width="700" height="548">
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1X7jkghcRXQKIH2BSCIxvVQ.a90c9b45829412be3297f1ffebe15be7.png" alt="Ne voudrions-nous pas vivre de Purring Cat ?" loading="lazy" decoding="async" class="dark:brightness-90" width="700" height="548" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Ne voudrions-nous pas vivre de Purring Cat ?</figcaption>
</figure>
<h2 id="un-autre-exemple-d-article-en-md">Un autre exemple d’article en .md</h2>
<p>Lorsque j'ai migré les vieux projets de mon portfolio dans Jekyll, j'ai trouvé que mélanger le HTML et Markdown c'était trop bizarre et que ça allait à l’encontre de l’objectif de clarté de Markdown. j’ai créé <a href="http://katfukui.com/clean-posts-jekyll/" target="_blank" rel="noopener noreferrer">une démo</a> qui montre ce que j'ai fait pour parvenir à des articles plus propres tout en gardant les styles désirés !<br>
Ce n'est en aucun cas une obligation ou la bonne manière de faire — juste une technique pour satisfaire mon côté hyper-maniaque. 😊 Vous êtes libres de télécharger et de vous amuser avec les <a href="https://github.com/katmeister/clean-posts-jekyll" target="_blank" rel="noopener noreferrer">fichiers sur Github</a>.</p>
<h2 id="ajoutons-un-peu-de-css-a-tout-ca">Ajoutons un peu de CSS à tout ça</h2>
<p>Maintenant que nous nous sommes familiarisés avec les possibilités de Jekyll et que nous avons un peu de contenu avec lequel travailler, ajoutons un peu de style ! Vous pouvez recopier le CSS ou le Sass de votre portfolio existant ou repartir de zéro.</p>
<picture>
<source type="image/webp" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/19nHxZAUcaEIoPxYPB9UwQg.013adf1162ebe62bcb526aa5ed62c655.webp" width="700" height="325">
<source type="image/avif" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/19nHxZAUcaEIoPxYPB9UwQg.013adf1162ebe62bcb526aa5ed62c655.avif" width="700" height="325">
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/19nHxZAUcaEIoPxYPB9UwQg.013adf1162ebe62bcb526aa5ed62c655.jpeg" alt="img" loading="lazy" decoding="async" class="dark:brightness-90" width="700" height="325" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A4/y3/umjy3/umu6/4R8f3aP+EfH939K5tDS5wvlv/dNL5T/3TXcjw+D/AA08eHR/cp2QrnH2Vm7yDIrqrKxbYOK0rbQhGR8tbMFgEUcVpFCbMRbA+lSDTm9K3xbgdqeIR6VVgOfGnH0pw04+ldCIB6U4W49KLCOd/s4+lH9mn0ro/IHpThAPSnYDm/7NPpRXS/Zx6UUWApOq+lR4HpTmYUzcM9a8ShUnPc6JRSJEUZ6VbjiVu1VI2Ga0ICOK2p1Jc9iWlYcLdfSl8oDtVlQMUhWvUjsYlUx+1ASp9tLtp2C5CEp4SpglPCUAQeXSiOrISl2UAV9lFWdlFAHnzaumPvVEdWXP3q4L+0pfU0n9oyeprgp0VE0crnoceroP4qvQa2g/iFeXjUpR3NPXVZQepqlTSdwbPXoNYR8fMK0I7oOOteS6frD+YAWNdjYamWQZNdKkRY63zAe9OVx61hjUBjrThqI9armFY3hIKeJRWANRHrSjUvejmA6ASj1pwlHrXPf2l70v9pe9HMFjofNX1ornf7T96KOYDxCiiiuYsKKKKYFuy/1orstP/wBWKKKpAzSBOKUE0UVRIuT60uTRRQMcCaMmiigQ3NFFFAH/2Q==);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<h2 id="sass">Sass</h2>
<p>Vous avez probablement remarqué que les projets Jekyll sont livrés avec des fichiers Sass (avec une extension <code>.scss</code>). Bien que vous n'ayez pas besoin de connaître <a href="https://sass-lang.com/" target="_blank" rel="noopener noreferrer">Sass</a> pour pouvoir utiliser Jekyll, c'est un outil apprécié et recommandé dans le processus de développement CSS. Sérieusement, votre CSS sera bien mieux organisé et cohérent une fois que vous l’aurez adopté.<br>
Ces <a href="http://thesassway.com/beginner" target="_blank" rel="noopener noreferrer">guides pour débutant</a> m'ont beaucoup aidé quand j'ai commencé. Jekyll intègre par défaut le support de Sass, vous n'avez donc pas d’excuse pour l’adopter. 😉</p>
<h2 id="versionnement-avec-git">Versionnement avec Git</h2>
<p>Maintenant que vous vous êtes accommodés du terminal, je vous recommande vivement d’utiliser Git pour versionner votre portfolio. Git prend des "clichés" de votre <strong>dépôt</strong> — le répertoire du projet — à chaque fois que vous faites un <strong>commit</strong>, de façon à ce que vous puissiez revenir à des versions antérieures de votre travail quand c'est nécessaire. Si vous travaillez avec une autre personne, vous pouvez travailler chacun sur votre propre <strong>branche</strong> du projet et <strong>fusionner</strong> ensuite tout ça dans la branche <strong>master</strong>, visible de tous les intervenants du projet.</p>
<h2 id="configurer-git">Configurer Git</h2>
<p><a href="https://git-scm.com/downloads" target="_blank" rel="noopener noreferrer">Téléchargez et installez Git</a>. De retour dans votre terminal, entrez ces commandes (et ajoutez vos propres infos entre les guillemets) :</p>
<pre><code class="language-sh hljs bash">git config --global user.name “Votre Nom”
git config --global user.email <span class="hljs-string">"[adresse mail]"</span></code></pre>
<p>Super, nous sommes parés pour Git ! Mais notre projet ne pourra pas utiliser Git tant que nous ne l’aurons pas initialisé. Dans le répertoire <code>mon-site</code>, je vais donc taper :</p>
<pre><code class="language-sh hljs bash">git init</code></pre>
<p>Facile ! Notre nouveau dépôt Git est vide, ajoutons-y donc nos fichiers.</p>
<pre><code class="language-sh hljs bash">git add .</code></pre>
<p>Super, notre projet est ajouté au dépôt local et est prêt à être enregistré dans un commit. Prenons une photo de notre dépôt en faisant notre premier commit !<br>
Utilisez l’option <code>-m</code> suivi d’un message significatif entre guillemets.</p>
<pre><code class="language-sh hljs bash">git commit -m <span class="hljs-string">"Première entrée du portfolio"</span></code></pre>
<p>Notre projet possède officiellement un historique ! Git fonctionne en local, mais GitHub est un service de stockage distant pour <strong>pousser</strong> nos dépôts sur le web et les partager avec le reste du monde. Connectons notre dépôt local à un dépôt distant sur GitHub et poussons notre premier commit.</p>
<h2 id="creer-un-depot-sur-github">Créer un dépôt sur GitHub</h2>
<p>Maintenant que nous avons initialisé Git pour notre portfolio, configurons un dépôt distant sur GitHub.</p>
<ol>
<li><a href="https://github.com/join" target="_blank" rel="noopener noreferrer">Créez-vous un compte</a>, si vous n'en possédez pas encore.</li>
<li>Créez un <a href="https://github.com/new" target="_blank" rel="noopener noreferrer">nouveau dépôt</a>.  <figure>
<picture title="Dans le coin en haut à droite.">
<source type="image/webp" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1kFGsk6bcyTe6-_c2-G0GzQ-1666824266270-50.ba153d02fc9da808f7aa5980f8a49ed3.webp" width="421" height="209">
<source type="image/avif" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1kFGsk6bcyTe6-_c2-G0GzQ-1666824266270-50.ba153d02fc9da808f7aa5980f8a49ed3.avif" width="421" height="209">
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1kFGsk6bcyTe6-_c2-G0GzQ-1666824266270-50.ba153d02fc9da808f7aa5980f8a49ed3.png" alt="Dans le coin en haut à droite" loading="lazy" decoding="async" class="dark:brightness-90" width="421" height="209" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Dans le coin en haut à droite.</figcaption>
</figure>
</li>
<li>Donnez un nom et ajoutez une description à votre dépôt. Créez le dépôt !  <picture>
<source type="image/webp" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1_kV4Itznh62DLnpdKBCGSA.5a7532bd736bcbd44e0d47660cf45c29.webp" width="700" height="464">
<source type="image/avif" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1_kV4Itznh62DLnpdKBCGSA.5a7532bd736bcbd44e0d47660cf45c29.avif" width="700" height="464">
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1_kV4Itznh62DLnpdKBCGSA.5a7532bd736bcbd44e0d47660cf45c29.png" alt="img" loading="lazy" decoding="async" class="dark:brightness-90" width="700" height="464" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAEaUlEQVR4nOVbWXbkIAws9Zv7n7TPEM0HCMRmBF7ASeU52G2zWEUhNtP3+2UAYGYchj8/YObjA9eDiMIh1/pe7XlLCLmW/+TPiUAxsXC/klU13/pNIKSU5Z88BODfcTYRLMeDZACxQgDlSx/d6ycsEWdLliU3WhbOCuADMyH2DCygaUPIiw8TIPFjCW4DMys7Z5XoIB5hgpCWUnqZ5ankBe3GyGrgNDGMe9no5l9XhuAyhbBJIXPKYOZAgg7fhJZ18reYJiRXylGmOpYo43GjrlaGsbF8SCF1ZVijdHtMG8OqDMF1Tv0QsXqmyrAUNxq/SgSVjnMvjEnTRkirKg/1rIzRMjHlIoj9+N2JmMOYQsSSM0QwK+uOCVmrIz6ZM/U7CLITkpMxqY7hRtUCN8Re77cvwEM+xKNJYtuMLkpnzFF0YN5Ly2d1AUah59jc38iAdH88q5BJsP5HFPwRdbv271PK5gqpzANxFv4ybE6I4L6avg+prvHdn5BkXPK+JmgUGxBCtjDpYZ0nZh9lpFjs1DNjywqdCvV9Ksiy44l1kCuwiJCSCBccL7depYydSVlACKVE5KpAn4iRKZK3KEPwMCEUD4IjQc/i+v+Rh7+jDMEAIYT4WjocRODkA/rk6qjt7qiZkZp3BG9ThmBQIWfI0OpIVp+cStRzJI8ncefwFmUIjIS0CLASoxb0yZGi91u11aHiHvymrwplvIwRu0KC7SdVQqKQctEppK8uzqrkWh64MlHd3zw3g4GBoW5qRgZplJ76+OSNXsaukXEMVkfyo7mM+8CmkEQM/oIA8EiTpQ8gr8MULmUBTDVzUL93kKVqitNOrLYj8x5lCOa6vUQZGdrItefjQcT+1MfxU+kMBnkSOCFmbNyRFMd+YxuM97KIUx4KYmqkqN/YNXsMgGSdPay3s3pcK2SmR5eemalo7lO+VxmCCaeur7WDB/IGI0EiJg6Pai4cDzGtyIXREnV3VfVTu2JMIbUO1lBnSymgNs5Uj6SWtGbQMH2VlKxczQWvZ5QhODf9nne2pBdW8+GA7D31K35pKJ80xAaj2ncyFSkUxZ/sq4cSw069FAmpi37v3/kO70pUWKiuiNUrVedOQoz2aXsoQ2BWSNEm14YkI1BC8MI5Da2McmPdOzCnEAKIKanlAPT2EBOenNWwfS4BrFKGYMqpB4UIGXqcuKgu5kZ84zckwCAhzt5UtLtE0TesWa1mMBOISs29RRmCUwtU4lfSWZVVb+XIkK2nb/3SapIQPcWhhhBRJkvgCAA4GbD2e2g7cTZFSPMVlyrEIZKiZ2XuLNO1aU9OLhYn7ooBXqgQgfUD1OeV0c/wtA/RkO7wLsiJeYNSTiok+2GxD2lBf069h89oF2CKkGZywaHuh+SLuqvRqqATOLcvq9Jm0YYKuRvz5i9j3qCQmRTvhjRTNzdX1bTHMrxm56Kay1rfPmtoAtLze3I7j0t7WcCaiZM6aqpwJ49Umsk8rt3bS1t2shKkZNzHzGzK/wE576x8FPAqnQAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
</li>
<li>Maintenant que vous avez créé le dépôt, vous allez voir cette page s'afficher sur GitHub :  <picture>
<source type="image/webp" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/173bT5OREQUaVsdTpqfbA-w.d702ff21fe9bbc89eb6d365d906d730b.webp" width="700" height="425">
<source type="image/avif" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/173bT5OREQUaVsdTpqfbA-w.d702ff21fe9bbc89eb6d365d906d730b.avif" width="700" height="425">
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/173bT5OREQUaVsdTpqfbA-w.d702ff21fe9bbc89eb6d365d906d730b.jpeg" alt="img" loading="lazy" decoding="async" class="dark:brightness-90" width="700" height="425" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A9azRmmk0maAJQaeGqANTg1AifdRmoQ1KGoGSg07NRhqUGmIkpRTRThSGgooooGFFFFAFFuKbmnsajoELmgGkpRQA6nAGkAqVRTECg08A04AU7igBBTwKQU4UgDFLilFOxQAzFFSYooAyjTKkYUzFShiU4UmKUUwHipBUYp61Nxkopwpi08UJgOFPApgqQdKoQop46UwU8dKYBRRRTEZjVHRRWaKCiiimA8U8UUVLAkWniiimgHCpB0ooqhCinjpRRTAKKKKAP//Z);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<p>C’est la partie "publier un dépôt existant en ligne de commande" qui nous intéresse. Je vais copier-coller ces commandes dans le terminal.<br>
<code>git remote add origin</code> connecte les deux dépôts pour permettre le déploiement.<br>
<code>git push</code> pousse les commits locaux dans votre dépôt distant !</p>
</li>
<li>Actualisez la page de votre dépôt distant et félicitez-vous ! ON A RÉUSSI.<br>
Maintenant le monde entier peut admirer notre super portfolio !</li>
</ol>
<picture>
<source type="image/webp" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/147y0QjcanT-aWr1CscE6sA.38d0c9ce4781f0b592b7cebdcb42088e.webp" width="700" height="542">
<source type="image/avif" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/147y0QjcanT-aWr1CscE6sA.38d0c9ce4781f0b592b7cebdcb42088e.avif" width="700" height="542">
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/147y0QjcanT-aWr1CscE6sA.38d0c9ce4781f0b592b7cebdcb42088e.png" alt="" loading="lazy" decoding="async" class="dark:brightness-90" width="700" height="542" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<p>Voici un <a href="http://readwrite.com/2013/09/30/understanding-github-a-journey-for-beginners-part-1" target="_blank" rel="noopener noreferrer">guide pour débutant très sympa</a> pour des explications plus détaillées.</p>
<h2 id="github-pages">GitHub Pages</h2>
<p>À partir de là, nous pouvons aller encore plus loin et configurer notre dépôt pour <a href="https://pages.github.com/" target="_blank" rel="noopener noreferrer">GitHub Pages</a> — un hébergement gratuit sans FTP. Yep, ça veut dire que vous pouvez enregistrer et publier vos changements et voir immédiatement ces modifications en ligne !</p>
<p>C’est un écosystème complet, accessible depuis votre terminal de confiance.</p>
<h2 id="mes-conseils-supplementaires">Mes conseils supplémentaires</h2>
<p>Parce que ce guide n'est pas déjà assez long.</p>
<h3 id="tester-sur-mobile">Tester sur mobile</h3>
<p>Le test sur mobile est inclus ! Autorisons Jekyll à accéder à notre mobile en lançant le serveur de cette manière dans le terminal :</p>
<pre><code class="language-sh hljs bash">jekyll serve --host 0.0.0.0</code></pre>
<p>Ensuite, récupérez votre adresse IP sur le réseau Wi-Fi local et faites là pointer vers le port 4000. En gros, vous allez taper un truc comme <strong>192.168.x.x:4000</strong> dans votre navigateur mobile. Si vous voulez savoir comment ça marche, <a href="http://zarino.co.uk/post/jekyll-local-network/" target="_blank" rel="noopener noreferrer">lisez cet article</a>.</p>
<h2 id="systeme-de-grille">Système de grille</h2>
<picture>
<source type="image/webp" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/19nraqBzaZDQU9de3zH5Klw.f221f0a717ef0cf5d813c9c99931d02c.webp" width="700" height="267">
<source type="image/avif" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/19nraqBzaZDQU9de3zH5Klw.f221f0a717ef0cf5d813c9c99931d02c.avif" width="700" height="267">
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/19nraqBzaZDQU9de3zH5Klw.f221f0a717ef0cf5d813c9c99931d02c.png" alt="img" loading="lazy" decoding="async" class="dark:brightness-90" width="700" height="267" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAAA0ElEQVR4nO3aMQ6EMAwF0W/E/a9sKgoqKCI8oHk1RbQjs7JCdXfEsU0fQFcGgakkvrOSdHclSVWN/h5OCIxBYAwCYxAYg8AYBKbc1FmcEBgXQxgnBMYgMAaBMQiMQWAMAuNiCOOEwBgE5tVN/bwmTeavSu9MndUJgTEIjEFgDAJjEBiDwLipwzghML+4wqV8l7uCEwJjEBiDwBgExiAwBoFxMYRxQmAMAjO+qf9py17BCYExCMw+fYBvqPtHFjHII+/9vfnKgjEIjJs6jBMCcwB6bSxHAgGbXgAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<p>Pour un portfolio, un système de grille léger est facile à implémenter et il ne se met pas en travers de votre CSS. J'utilise <a href="http://jeet.gs/" target="_blank" rel="noopener noreferrer">Jeet</a> parce que j'aime sa syntaxe et sa souplesse. Ceci dit, il y en a des tonnes de super chouettes, comme <a href="http://neat.bourbon.io/" target="_blank" rel="noopener noreferrer">Neat</a> ou <a href="http://daneden.github.io/Toast/" target="_blank" rel="noopener noreferrer">Toast</a>.</p>
<h2 id="rythme-vertical">Rythme vertical</h2>
<figure>
<picture title="From Typecast.">
<source type="image/webp" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1h8vVPQhl-HT5AqIlTRoA8g.7918f25313931d3c3d48ff046f5bf43b.webp" width="450" height="300">
<source type="image/avif" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1h8vVPQhl-HT5AqIlTRoA8g.7918f25313931d3c3d48ff046f5bf43b.avif" width="450" height="300">
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1h8vVPQhl-HT5AqIlTRoA8g.7918f25313931d3c3d48ff046f5bf43b.png" alt="From Typecast" loading="lazy" decoding="async" class="dark:brightness-90" width="450" height="300" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>From Typecast.</figcaption>
</figure>
<p>Le rythme vertical c'est l’espacement constant et la mise à l’échelle des paragraphes, des marges externes et internes, des tailles de police et des hauteurs de ligne. Trouver le bon rythme améliore la lisibilité et l’harmonie d’un site. J'utilise <a href="https://github.com/modularscale/modularscale-sass" target="_blank" rel="noopener noreferrer">modular-scale</a> sur mon propre portfolio. Apprenez en davantage sur le rythme vertical
<a href="http://webdesign.tutsplus.com/articles/improving-layout-with-vertical-rhythm--webdesign-14070" target="_blank" rel="noopener noreferrer">ici</a> ou <a href="http://typecast.com/blog/4-simple-steps-to-vertical-rhythm" target="_blank" rel="noopener noreferrer">là</a>.</p>
<h2 id="fin">Fin</h2>
<p>Et voilà comment on crée et on déploie un joli modèle de portfolio, entièrement à partir du terminal. Vous n'avez plus qu'à créer un simple article au format texte pour les nouveaux projets et à taper quelques commandes pour le publier sur GitHub Pages.</p>
<p>J'espère que vous avez aimé construire un portfolio pour <em>Food Right Meow</em> avec moi. Créer et redesigner votre portfolio devrait être fun et enrichissant, alors à vos claviers et à vos lignes de commande !</p>
<h3 id="telechargez-ou-forkez-le-depot-de-demo-https-github-com-katmeister-my-site"><a href="https://github.com/katmeister/my-site" target="_blank" rel="noopener noreferrer">Téléchargez ou forkez le dépôt de démo</a></h3>
<h3 id="voir-la-demo-en-action-http-katfukui-com-my-site"><a href="http://katfukui.com/my-site/" target="_blank" rel="noopener noreferrer">Voir la démo en action</a></h3>
<picture>
<source type="image/webp" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1qKtPxbGF11Ekq_B9hFRmhg.8803e586215c0aac803d38645d0f3b01.webp" width="120" height="120">
<source type="image/avif" srcset="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1qKtPxbGF11Ekq_B9hFRmhg.8803e586215c0aac803d38645d0f3b01.avif" width="120" height="120">
<img src="/images/post/2016-11-10_designer-un-portfolio-avec-jekyll/1qKtPxbGF11Ekq_B9hFRmhg.8803e586215c0aac803d38645d0f3b01.png" alt="\o/" loading="lazy" decoding="async" class="dark:brightness-90" width="120" height="120" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<div class="footnotes">
<hr>
<ol>
<li id="fn:1">
<p><a href="https://gist.github.com/DirtyF/5d2bde5c682101b7b5d90708ad333bf3" target="_blank" rel="noopener noreferrer">Installez Ruby et Jekyll à l’aide d’Homebrew sous Mac</a>.&#160;<a href="#fnref1:1" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
<li id="fn:2">
<p>Le plugin <a href="https://github.com/Arcath/jekyll-atom/" target="_blank" rel="noopener noreferrer">jekyll-atom</a> facilite la création de posts en respectant cette convention.&#160;<a href="#fnref1:2" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
</ol>
</div>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2016/10/29/creer-un-theme-pour-jekyll/</id>
    <title>Créer un thème pour Jekyll</title>
    <published>2016-10-29T13:18:59+00:00</published>
    <link href="https://jamstatic.fr/2016/10/29/creer-un-theme-pour-jekyll/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Depuis la version 3.2, les webdesigners ont la possibilité de créer des thèmes pour Jekyll. Le support des thèmes sous forme de gem est encore récent mais les premiers thèmes commencent à arriver. Nous allons voir dans cet article que l’opération est assez triviale si vous êtes déjà familiarisé avec Jekyll et Git. Packager un thème se fait en quelques minutes grâce à l’utilisation de <code>bundler</code>. Voyons ensemble à quoi ressemble le workflow de création de thème pour Jekyll.</p></aside>
<h2 id="prerequis">Prérequis</h2>
<p>Nous allons supposer que vous connaissez déjà Jekyll, que vous savez créer des modèles de pages, si ce n'est pas le cas, commencez par lire <a href="https://jekyllrb.com/docs/templates/" target="_blank" rel="noopener noreferrer">la documentation officielle,</a> vous verrez c'est très accessible, c'est du HTML auquel on va ajouter un peu de <a href="https://shopify.github.io/liquid/" target="_blank" rel="noopener noreferrer">Liquid</a>, le langage de templating conçu pour les concepteurs de thèmes Shopify, pour accéder à nos données.</p>
<p>Nous ne nous étendrons donc pas sur cette partie, qui consiste à préparer vos différents modèles de pages, ce sont les conventions par défaut de Jekyll qui s'appliquent : les feuilles de styles sont stockées dans le dossier <code>_sass</code>, les modèles de pages dans le dossier <code>_layouts</code>, les composants réutilisables dans <code>_includes</code>. Enfin tous les assets (CSS, JS, images, fonts) sont regroupés dans un dossier <code>assets</code> (et non <code>_assets</code> pour éviter les conflits avec le plugin <code>jekyll-assets</code>).</p>
<p>On notera que pour le moment par défaut, seuls ces dossiers seront fournis par le thème, nous verrons comment y ajouter des contenus et des exemples un peu plus bas.</p>
<p>Les thèmes seront téléchargés sous forme de gem, vous devez donc avoir installé <a href="https://bundler.io/" target="_blank" rel="noopener noreferrer">bundler</a> avec la commande <code>gem install bundler</code>, ce qui est généralement le cas puisque Jekyll encourage son utilisation pour la gestion des dépendances. Enfin, il vous faut <a href="https://rubygems.org/sign_up" target="_blank" rel="noopener noreferrer">créer un compte sur Rubygems</a> pour publier votre thème, ça ne vous prendra que quelques secondes si vous avez déjà un compte GitHub.</p>
<h2 id="preparer-son-theme">Préparer son thème</h2>
<p>Vous avez donc un site statique sous Jekyll et vous souhaitez le partager avec la communauté sous forme de thème. Il y a deux façons de faire, selon vos préférences. L'une d’elle consiste à utiliser la commande <code>new-theme</code> pour générer une structure de base dans laquelle vous allez pouvoir ajouter vos fichiers :</p>
<pre><code class="language-sh hljs bash">bundle <span class="hljs-built_in">exec</span> jekyll new-theme mon-super-theme</code></pre>
<p>Cette commande va créer un dossier dans le répertoire courant, qui portera le même nom que celui que vous avez fourni en argument, étonnant non ?</p>
<p>Ce dossier comprend tous les dossiers évoqués plus haut : <code>_includes</code>, <code>_layouts</code>, <code>_sass</code> et <code>assets</code> ainsi qu'un fichier <code>Gemfile</code> et un fichier <code>mon-super-theme.gempsec</code> qui contient les informations sur votre thème.</p>
<p>Vous pouvez maintenant recopier les fichiers de votre thème dans cette structure d’exemple.</p>
<p>L'autre manière de faire, c'est de partir de votre site fonctionnel et d’adapter sa structure de manière à vous retrouver avec quelque chose qui ressemble à ça :</p>
<pre><code class="language-sh hljs bash">├── .gitignore
├── Gemfile
├── LICENSE.md
├── README.md
├── _includes
│   └── head.html
│   └── …
├── _layouts
│   ├── default.html
│   ├── home.html
│   ├── page.html
│   └── post.html
│   └── …
├── _sass
│   ├── _base.scss
│   ├── _variables.scss
│   ├── …
├── assets
│   ├── favicons
│   ├── fonts
│   └── images
│   └── js
│   └── css
├── demo
│   ├── 404.html
│   ├── Gemfile
│   ├── _config.yml
│   ├── _posts
│   │   ├── 2016-10-20-presentation-de-jekyll.md
│   │   ├── 2016-01-02-exemple-de-contenu.md
│   │   └── 2016-01-03-introduction.md
│   │   └── …
├── mon-super-theme.gemspec
└── screenshot.png</code></pre>
<p>Parmi les différences avec un site Jekyll classique, on remarque la présence d’un fichier <code>Gemfile</code> un peu particulier, d’un fichier <code>gemspec</code> pour la spécification de la gem, d’un fichier LICENSE (MIT par défaut), d’un fichier <code>README.md</code>, d’une capture d’écran et d’un dossier <code>demo</code> (ou <code>example</code>, c'est selon).</p>
<p>Si on regarde le contenu du fichier <code>Gemfile</code> d’une gem, il est différent de ceux que vous avez l’habitude d’utiliser. Il fait simplement référence au fichier de spécification de la gem :</p>
<pre><code class="language-ruby hljs ruby">source <span class="hljs-string">"https://rubygems.org"</span>
gemspec</code></pre>
<p>En effet, c'est le fichier <code>gemspec</code> qui va contenir toutes les infos sur notre thème : le numéro de version, sa description, ses dépendances, etc. Pour savoir tout ce que peut contenir ce type de fichier, je vous invite à consulter la <a href="https://guides.rubygems.org/specification-reference/" target="_blank" rel="noopener noreferrer">documentation de référence des spécifications d’une gem</a>.</p>
<p>Lorsque vous utilisez la commande <code>new-theme</code> de Jekyll, par défaut, le fichier de spécification de votre gem ressemble à ça :</p>
<pre><code class="language-ruby hljs ruby"><span class="hljs-comment"># frozen-string-literal: true</span>
Gem::Specification.new <span class="hljs-keyword">do</span> <span class="hljs-params">|spec|</span>
  spec.name     = <span class="hljs-string">"mon-super-theme"</span>
  spec.version  = <span class="hljs-string">"0.1.0"</span>
  spec.authors  = [<span class="hljs-string">"Frank Taillandier"</span>]
  spec.email    = [<span class="hljs-string">"frank@jekyllrb.com"</span>]
  spec.summary  = <span class="hljs-string">%q{TODO : Une courte description de votre thème, requise par Rubygems.}</span>
  spec.homepage = <span class="hljs-string">"TODO : Indiquez ici l’adresse du site de votre gem ou l’URL publique de son dépôt"</span>
  spec.license  = <span class="hljs-string">"MIT"</span>
  spec.files    = <span class="hljs-string">`git ls-files -z`</span>.split(<span class="hljs-string">"\x0"</span>).select { <span class="hljs-params">|f|</span> f.match(<span class="hljs-regexp">%r{^(assets|_layouts|_includes|_sass|LICENSE|README)}i</span>) }
  spec.add_development_dependency <span class="hljs-string">"jekyll"</span>, <span class="hljs-string">"~&gt; 3.5"</span>
  spec.add_development_dependency <span class="hljs-string">"bundler"</span>, <span class="hljs-string">"~&gt; 1.15"</span>
  spec.add_development_dependency <span class="hljs-string">"rake"</span>, <span class="hljs-string">"~&gt; 12.0"</span>
<span class="hljs-keyword">end</span></code></pre>
<p>Outre les méta-données à renseigner, il est intéressant de noter qu'une expression régulière basée sur une commande Git liste les fichiers à inclure dans la gem, cela implique donc que vos fichiers soient versionnés avec Git. Le minimum étant d’avoir initialisé votre dépôt, d’avoir ajouté tous les fichiers qui vont bien et d’avoir enregistré le tout : <code>git init &amp;&amp; git add . &amp;&amp; git commit -m "Initial commit"</code>.</p>
<p>On peut voir aussi à la fin du fichier que la version 3.3 ou supérieure de Jekyll est requise, en effet avant cette version, le dossier <code>assets</code> n'était pas géré et le support initial des thèmes n'est arrivé qu'avec la version 3.2.</p>
<p>On constate aussi que <code>bundler</code> est déclaré en tant que dépendance pour le développement, ainsi que <code>rake</code>, souvent utilisé pour ajouter des tests ou des tâches automatisées.</p>
<p>Vous pouvez très bien vouloir ajouter d’autres dépendances lors de l’exécution, si vous souhaitez utiliser des plugins dans votre thème. Dans notre exemple, nous voulons ajouter la gestion d’un flux RSS, la génération d’un sitemap et des méta-données pour le SEO.</p>
<p>Nous ajoutons donc les dépendances ainsi que les versions minimales requises, comme nous le ferions dans un fichier <code>Gemfile</code> à l’aide de <a href="https://guides.rubygems.org/specification-reference/#add_runtime_dependency" target="_blank" rel="noopener noreferrer"><code>spec.add_runtime_dependency</code></a> :</p>
<pre><code class="language-ruby hljs ruby">spec.add_runtime_dependency <span class="hljs-string">"jekyll-feed"</span>, <span class="hljs-string">"~&gt; 0.8"</span>
spec.add_runtime_dependency <span class="hljs-string">"jekyll-sitemap"</span>, <span class="hljs-string">"~&gt; 0.12"</span>
spec.add_runtime_dependency <span class="hljs-string">"jekyll-seo-tag"</span>, <span class="hljs-string">"~&gt; 2.0"</span></code></pre>
<p>Une fois que votre fichier de spécification est complété, vous allez pouvoir tester votre thème.</p>
<h2 id="tester-son-theme">Tester son thème</h2>
<p>Commencez par lancer <code>bundle install</code> pour installer les dépendances mentionnées dans le fichier <code>gemspec</code>.</p>
<h3 id="fournir-des-exemples-de-contenu">Fournir des exemples de contenu</h3>
<p>Comme nous l’avons dit plus haut, pour le moment les thèmes sont livrés sans contenu et ne contiennent que les modèles et les assets. <strong>Lors d’une mise à jour de thème, tous ces fichiers seront écrasés</strong>, c'est le principal intérêt d’utiliser une gem pour le designer ou l’utilisateur, mais à aucun moment vous ne souhaitez écraser les contenus existants.</p>
<p>Cependant, même si la gem ne contient pas de contenus, rien ne vous empêche d’en ajouter dans le dépôt de votre thème pour fournir un exemple de thème entièrement fonctionnel à vos utilisateurs.</p>
<p>Afin de ne pas mélanger les fichiers d’exemple avec ceux de votre thème, il est donc conseillé de créer un dossier <code>demo</code> (ou <code>example</code> ou ce que vous voulez) et d’y stocker des contenus destinés à présenter aux utilisateurs le rendu de votre thème.</p>
<p>En plus des fichiers générés par la commande <code>new-theme</code>, nous ajouterons dans ce dossier <code>demo</code> tout ce qu'il faut pour faire tourner un site sous Jekyll : un fichier <code>_config.yml</code>, un fichier <code>Gemfile</code> ainsi que des pages et des posts bien entendu. Vous pouvez également ajouter des exemples de données dans le dossier <code>_data</code> voire des collections, comme vous le feriez dans n'importe quel site.</p>
<p>Le fichier <code>demo/Gemfile</code> ressemble à quelques détails près à celui que vous utiliseriez pour n'importe quel site :</p>
<pre><code class="language-ruby hljs ruby">source <span class="hljs-string">"https://rubygems.org"</span>
gem <span class="hljs-string">"jekyll"</span>, <span class="hljs-string">"~&gt; 3.3"</span>
gem <span class="hljs-string">"mon-super-theme"</span>, <span class="hljs-symbol">path:</span> <span class="hljs-string">"../"</span>
group <span class="hljs-symbol">:jekyll_plugins</span> <span class="hljs-keyword">do</span>
  gem <span class="hljs-string">"jekyll-sitemap"</span>
  gem <span class="hljs-string">"jekyll-seo-tag"</span>
  gem <span class="hljs-string">"jekyll-feed"</span>
<span class="hljs-keyword">end</span></code></pre>
<p>Nous voyons qu'il fait référence à la gem de notre thème, mais comme pour le moment nous n'avons pas encore publié notre thème sous forme de gem, nous nous contentons de faire référence au dossier parent, qui contient les fichiers de notre thème. Cette notation est bien pratique pour tester votre thème en local. Une fois votre gem publiée, vous pourrez choisir de supprimer paramètre path de votre gem, pour que bundler aille télécharger la dernière version sur Rubygems. Nous listons également les plugins utilisés dans notre thème.</p>
<p>Maintenant, pour que Jekyll utilise notre thème, nous allons devoir le lui indiquer dans le fichier <code>demo/_config.yml</code> en ajoutant la ligne :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">theme:</span> <span class="hljs-string">mon-super-theme</span></code></pre>
<p>Là encore, le nom utilisé doit être le même que celui de votre fichier <code>gemspec</code>. Nous indiquons également dans ce fichier de configuration que nous utilisons les plugins suivants :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">gems:</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">jekyll-feed</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">jekyll-sitemap</span>
  <span class="hljs-bullet">-</span> <span class="hljs-string">jekyll-seo-tag</span></code></pre>
<p>Nous pouvons maintenant tester notre thème en exécutant la commande <code>bundle exec jekyll serve --source demo</code> depuis le répertoire racine ou <code>cd demo &amp;&amp; bundle exec jekyll serve</code>, c'est du pareil au même. N'oubliez pas d’ajouter le répertoire de destination - <code>_site</code> par défaut - à votre fichier <code>.gitignore</code>.</p>
<p>Vérifiez le rendu sur différents navigateurs et appareils, aidez-vous d’<a href="https://github.com/gjtorikian/html-proofer" target="_blank" rel="noopener noreferrer">html-proofer</a> si vous souhaitez vous assurer que tous les liens internes fonctionnent :</p>
<pre><code class="language-sh hljs bash">bundle <span class="hljs-built_in">exec</span> htmlproofer ./demo/_site --<span class="hljs-built_in">disable</span>-external</code></pre>
<p>Vous êtes satisfait de la première version de votre thème ? Parfait ! Veillez maintenant à bien renseigner le fichier <code>README.md</code> pour expliquer comment installer et utiliser votre thème, c'est toujours agréable d’avoir une bonne documentation à sa disposition.</p>
<p>Afin de permettre aux utilisateurs d’avoir un aperçu de votre thème, il est également recommandé d’ajouter une capture d’écran nommée <code>screenshot.png</code> à la racine de votre dépôt et de l’insérer dans votre fichier <code>README.md</code>.</p>
<h3 id="packager-son-theme">Packager son thème</h3>
<p>Une fois que votre thème est fonctionnel, documenté, vous êtes parés pour la publication. Cette étape est déjà <a href="https://jekyllrb.com/docs/themes/#publishing-your-theme" target="_blank" rel="noopener noreferrer">documentée sur le site de Jekyll</a>, nous nous contenterons simplement ici de rappeler qu'elle se fait en deux temps.</p>
<p>La première commande va créer la gem à proprement parlé à partir du fichier de spécification :</p>
<pre><code class="language-sh hljs bash">gem build mon-super-theme.gemspec
  Successfully built RubyGem
  Name: mon-super-theme
  Version: 0.0.1
  File: mon-super-theme-0.0.1.gem</code></pre>
<p>Comme il est inutile de versionner la gem générée (mais cela ne vous dispense pas d’utiliser les tags git pour vous rappeler du moment où vous l’avez générée), pensez donc à ajouter la ligne suivante dans votre fichier <code>.gitignore</code> :</p>
<pre><code class="language-ruby hljs ruby">*.gem</code></pre>
<p>Vous pouvez en profiter pour vérifier que l’installation de votre gem se déroule
sans encombres :</p>
<pre><code class="language-sh hljs bash">gem install mon-super-theme-0.0.1.gem
Successfully installed mon-super-theme-0.0.1
1 gem installed</code></pre>
<p>Si tout est OK, il ne vous reste maintenant plus qu'à publier votre thème sur <a href="https://rubygems.org/" target="_blank" rel="noopener noreferrer">Rubygems</a> :</p>
<pre><code class="language-sh hljs bash">gem push mon-super-theme-0.0.1.gem
Pushing gem to https://rubygems.org…</code></pre>
<p>Et voilà ! Votre thème peut maintenant être utilisé avec Jekyll. Il n'existe pas encore de site officiel qui répertorie tous les thèmes installables via des gems pour Jekyll, c'est donc à vous de communiquer sur la disponibilité de votre thème, par exemple sur <a href="https://talk.jekyllrb.com/t/gem-based-themes/3089/" target="_blank" rel="noopener noreferrer">le forum de Jekyll</a>, sur Twitter avec le hashtag <code>#jekyllrb</code> ou en commentaire de ce billet 😄.</p>
<p>Si vous cherchez des références, vous pouvez toujours prendre exemple sur des structures de thèmes accessibles sur Github, notamment :</p>
<ul>
<li><a href="https://github.com/jekyll/minima" target="_blank" rel="noopener noreferrer">Minima</a>, le thème par défaut de Jekyll, idéal pour se familiariser avec la structure que nous venons de voir,</li>
<li><a href="https://github.com/daviddarnes/alembic/" target="_blank" rel="noopener noreferrer">Alembic</a> un bon point de départ un plus complet proposé par David Darnes</li>
<li><a href="https://github.com/mmistakes/minimal-mistakes/" target="_blank" rel="noopener noreferrer">Minimal mistakes</a>, le thème très complet de Michael Rose, qui utilise des collections et tout un tas d’autres fonctionnalités plus avancées de Jekyll.</li>
</ul>
<p>Voilà, maintenant c'est à vous de jouer, faites de beaux thèmes pour Jekyll !</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2016/10/07/jekyll-3-3-est-dispo/</id>
    <title>Les nouveautés de Jekyll 3.3</title>
    <published>2016-10-07T00:00:00+00:00</published>
    <link href="https://jamstatic.fr/2016/10/07/jekyll-3-3-est-dispo/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>Plein de nouveautés pour vous simplifier la vie dans la version
3.3 de Jekyll. On retiendra trois fonctionnalités à tester en priorité.</p></aside>
<h2 id="les-themes-peuvent-desormais-fournir-des-assets-statiques-et-dynamiques-dans-le-dossier-assets">Les thèmes peuvent désormais fournir des assets statiques et dynamiques dans le dossier <code>/assets</code></h2>
<p>Depuis Jekyll 3.2, il est possible de packager un thème sous forme de
<a href="http://guides.rubygems.org/" target="_blank" rel="noopener noreferrer">gem</a>, il était déjà possible d’embarquer des
includes, des layouts et des fichiers Sass. Avec la version 3.3, il est enfin
possible d’ajouter aussi des assets à son thème. Les développeurs de thèmes vont
donc pouvoir fournir des thèmes complets et les utilisateurs pourront les tester
et les mettre à jour plus simplement. Cette fonctionnalité n'est pas encore très
mature et le support de fichier de configuration pour les thèmes ou l’ajout d’un
dossier <code>data</code> sont en cours de discussion.</p>
<p>Pour faciliter davantage la gestion de thème, tout fichier présent dans le
dossier <code>/assets</code> de votre thème sera considéré comme faisant partie du site de
l’utilisateur du thème. Vous pouvez donc ajouter du Sass, du JavaScript, du
CoffeeScript, des images, des fontes et tout ce qui sera utile à la présentation
et au comportement de votre thème. Les règles sont les mêmes que dans Jekyll :
si un fichier comporte des entêtes YAML Front Matter, il sera converti et traité
par le moteur de rendu. Si le fichier ne comporte aucun en-tête YFM, il sera
simplement copié comme un asset statique.</p>
<p>Notez bien que les fichiers de l’utilisateur avec le même chemin prennent
toujours le pas sur ceux de votre thème. Par exemple si un fichier
<code>/assets/main.scss</code> est présent dans le dossier du site de l’utilisateur, c'est
lui qui sera traité en lieu et place du fichier <code>/assets/main.scss</code> présent dans
la gem de votre thème.</p>
<p>Nous vous invitons à vous reporter à la
<a href="https://jekyllrb.com/docs/themes/#assets" target="_blank" rel="noopener noreferrer">documentation officielle sur la gestion des assets dans les thèmes</a>
pour plus d’informations.</p>
<h2 id="les-filtres-relative-url-et-absolute-url">Les filtres <code>relative_url</code> et <code>absolute_url</code></h2>
<p>Deux nouveaux filtres font leur apparition pour simplifier la gestion des URLs
dans vos templates. Fini de vous emmêler les pinceaux avec <code>baseurl</code> et <code>url</code>.
Lorsque vous développez en local, si vous définissez la valeur de <code>baseurl</code> afin
qu'elle corresponde à votre environnement de développement, mettons par exemple
<code>baseurl: "/mondossier"</code>, le filtre <code>relative_url</code> se chargera de préfixer cette
valeur pour toutes les URLs que vous appellerez :</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-variable">{{ "/docs/assets/" | relative_url }}</span><span class="xml"> =&gt; /mondossier/docs/assets</span></code></pre>
<p>Par défaut, <code>baseurl</code> est défini à <code>""</code> et sera remplacé tel quel (ne définissez
jamais cette valeur à <code>"/"</code>):</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-variable">{{ "/docs/assets/" | relative_url }}</span><span class="xml"> =&gt; /docs/assets</span></code></pre>
<p>Le résultat d’un appel à <code>relative_url</code> produira toujours une URL relative au
domaine racine. Le même principe s'applique au filtre <code>absolute_url</code>, il ajoute
les valeurs définies dans <code>baseurl</code> et <code>url</code> et facilite ainsi la création
d’URLs absolues :</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-variable">{{ "/docs/assets/" | absolute_url }}</span><span class="xml"> =&gt; http://jamstatic.fr/mondossier/docs/assets</span></code></pre>
<h2 id="site-url-est-maintenant-defini-pour-le-serveur-de-developpement"><code>site.url</code> est maintenant défini pour le serveur de développement</h2>
<p>Quand vous lancez la commande <code>jekyll serve</code> en local, elle va démarrer un
serveur web, généralement à l’adresse <code>http://localhost:4000</code>, sur lequel vous
allez pouvoir prévisualiser votre site durant la phase de développement. Si vous
utilisez le filtre <code>absolute_url</code> ou <code>site.url</code> dans votre code, vous avez
probablement dû créer un fichier de configuration pour le développement, qui va
réinitialiser la valeur d’<code>url</code> à <code>http://localhost:4000</code>.</p>
<p>C’est maintenant inutile ! Quand vous lancez la commande <code>jekyll serve</code>, Jekyll
va générer votre site avec les valeurs de <code>host</code>, <code>port</code> et des options
relatives à SSL. Par défaut ce sera donc <code>url: http://localhost:4000</code>. Quand
vous développez en local, la valeur de <code>site.url</code> sera donc remplacée par
<code>http://localhost:4000</code>.</p>
<p>C’est le comportement par défaut lorsque vous exécutez Jekyll en local. Ce ne
sera pas le cas si vous exécutez <code>jekyll serve</code> si vous précisez un
environnement de production avec <code>JEKYLL_ENV=production</code>. Si la variable
d’environnement <code>JEKYLL_ENV</code> possède une autre valeur que <code>development</code> (sa
valeur par défaut), Jekyll n'écrasera pas la valeur du paramètre <code>url</code> définie
dans votre fichier de configuration. Attention, cela ne s'applique qu'à la
commande <code>serve</code>, pas à la commande build`.</p>
<h2 id="et-bien-plus-encore">Et bien plus encore</h2>
<p>Pour avoir le détail de tous les correctifs et les améliorations mineures
apportées, vous pouvez
<a href="https://jekyllrb.com/docs/history/#v3-3-0" target="_blank" rel="noopener noreferrer">consulter le CHANGELOG complet</a>.</p>
<p>Jekyllez bien !</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2016/09/18/utiliser-des-plugins-jekyll-avec-github-pages/</id>
    <title>Utiliser des plugins Jekyll sur GitHub Pages</title>
    <published>2016-09-18T11:51:13+00:00</published>
    <updated>2010-09-18T11:51:13+00:00</updated>
    <link href="https://jamstatic.fr/2016/09/18/utiliser-des-plugins-jekyll-avec-github-pages/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p>La popularité de Jekyll est en partie due à son support natif par GitHub Pages. Si cette solution gratuite est bien pratique, elle n’en reste pas moins limitée en termes de support de plugins Jekyll et ce pour des raisons de sécurité. Si vous voulez utiliser des plugins comme <a href="/2016/08/31/gestion-images-responsive-avec-jekyll-cloudinary/">jekyll-cloudinary</a> ou <a href="https://github.com/jekyll/jekyll-assets" target="_blank" rel="noopener noreferrer">jekyll-assets</a>, il va falloir utiliser GitHub Actions ou générer votre site localement avant de le pousser en ligne.</p></aside>
<aside class="note note-info"><p><strong>Mise à jour</strong> : GitHub Actions permet maintenant de <a href="https://jekyllrb.com/docs/continuous-integration/github-actions/" target="_blank" rel="noopener noreferrer">publier un site Jekyll quels que soient les plugins utilisés</a>.</p></aside>
<p>Nous allons voir que cette opération est facilement automatisable à l’aide d’un fichier <code>Rakefile</code>, la manière la plus courante en Ruby de créer des tâches.</p>
<h2 id="prerequis">Prérequis</h2>
<p>Nous partons du principe que vous avez déjà un site qui tourne avec Jekyll sur GitHub, si ce n’est pas le cas, reportez-vous à la <a href="https://help.github.com/articles/using-jekyll-as-a-static-site-generator-with-github-pages/" target="_blank" rel="noopener noreferrer">documentation officielle</a>.</p>
<p>Comme nous allons utiliser <code>rake</code> pour écrire une tâche automatisée, il vous faut ajoutez la dépendance à votre fichier <code>Gemfile</code>, si elle n'est pas déjà présente :</p>
<pre><code class="language-ruby hljs ruby">  gem <span class="hljs-string">"rake"</span></code></pre>
<p>Une fois que c'est fait, lancez <code>bundle install</code> pour installer <code>rake</code>.</p>
<p>Maintenant que vous êtes parés sous allons voir les deux cas de figures possibles dans Github : les pages utilisateurs ou organisation et les pages projets.</p>
<h2 id="pages-utilisateur-et-organisation">Pages utilisateur et organisation</h2>
<p>Pour activer la génération automatique par GitHub Pages d’un dépôt de compte utilisateur ou organisation, il suffit de respecter la nomenclature <code>username/username.github.io</code>.</p>
<p>GitHub va utiliser la branche <code>master</code> de ces dépôts et publier les pages. Cela fait que nous aurons une branche <code>master</code> qui contient le site généré et une branche <code>source</code> avec les sources de notre site.</p>
<h3 id="configuration-du-depot">Configuration du dépôt</h3>
<p>La préparation du dépôt se résume à créer la branche <code>source</code> en ligne de commande :</p>
<pre><code class="language-sh hljs bash">git checkout -b <span class="hljs-built_in">source</span> master
git push -u origin <span class="hljs-built_in">source</span></code></pre>
<p>Maintenant que vous avez créé la branche <code>source</code>, vous pouvez en faire la branche par <em>défaut</em> dans GitHub :</p>
<figure>
<picture title="Paramétrage des branches dans GitHub.">
<source type="image/webp" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346483/default-branch-github.8ee8d1cdeb70ebadee73f5d552e80ac0.webp" width="752" height="160">
<source type="image/avif" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346483/default-branch-github.8ee8d1cdeb70ebadee73f5d552e80ac0.avif" width="752" height="160">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346483/default-branch-github.8ee8d1cdeb70ebadee73f5d552e80ac0.png" alt="Paramétrage des branches dans GitHub" loading="lazy" decoding="async" class="dark:brightness-90" width="752" height="160" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAAAyCAYAAACqNX6+AAAACXBIWXMAAA7EAAAOxAGVKw4bAAACKElEQVR4nO2aba7CIBBF75juf6kuobwflMq32EIZ5s1JjE1TK8z1AKL0fr8NFDZsxmgenHjNboASooYwQw1hhhrCDDWEGf/CECLCKv38F4asEgYg3BAiSs5x7+82uwEj4V78HCINyZkRw7XfIg3hWuwWRBoC1C3h3GeRhgC8i15j2/d9dhsUDzFD1vr9IBAJMmREIKNDDue5I5CVP1l+28UEsrIhIgMx+w7ujsRLWFcoSYHYDdDDEA0kz8hAPjvQ1gxjDIhIA6kxJhAXAEAE79gGtO0LTOq5wqwUSjg0AcYQiKwdzozTkOl6fNsH9At+nkpD4LxadMV2na3tfS6xdeKX+ikzeuLCsEMUeefS5yUCOakMXRwJiy0xECKQMTBAMO7yCyVXfP9cOZw5gVA6dRRLShRca6fC+BKKhrDbLWyiPBeUiv85LhrS8ONaa/OaL7vzlq7hn4kyPq5Pmr25G0oSyOvV848nDZUo2PFrDUuT+tOjVzX8w246vnC44/gBMAzkV5YIBGkY9ji1RAP5kavDYGDAF0uASYFceFXCU4Hcmo+8BUkaTH6CZxHIFW4FcmFh0fLXopbX+8/xOQDYqGsgz+F3YvSubIebJKa41Vb8Hp0NmcOlPG4uv8/b0HGzCw3IZb11+QRMZnQXeteodj8Rhkhirb2sUUTbMzMRMWRJQg1hZAeghrBDDXEwMUUNYYYaEjPZFDWEGWpIiUmmqCHMUEO+8bApfyt3HWm05SVeAAAAAElFTkSuQmCC);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>Paramétrage des branches dans GitHub.</figcaption>
</figure>
<h3 id="publication-automatique">Publication automatique</h3>
<p>Maintenant que le dépôt est configuré, vous pouvez générer votre site et pousser les fichiers générés sur la branche <code>master</code>. Mais plutôt que de s'embêter à faire ça manuellement, créons un simple tâche <code>rake</code>. Créez (si vous n'en avez pas déjà un) un fichier <code>Rakefile</code> à la racine de votre site et ajoutez le contenu suivant <sup id="fnref1:1"><a href="#fn:1" class="footnote-ref">1</a></sup> :</p>
<p><script src="https://gist.github.com/DirtyF/24cb9c96b64173ecd85578f38bcc940d.js"></p>
<p>Maintenant vous pouvez simplement lancer la commande <code>rake publish</code> pour générer et publier votre site sur GitHub Pages.</p>
<p>Si vous utilisez un nom de domaine personnalisé, vérifiez bien que le fichier CNAME est bien présent dans la branche générée.</p>
<h2 id="pages-projet">Pages projet</h2>
<p>Les pages projet sont presque pareilles que les pages utilisateur et organisation, à une différence près : la branche <code>gh-pages</code> est utilisée à la place de la branche <code>master</code> pour générer et publier les pages.</p>
<p>Il n'y a aucune configuration supplémentaire à faire, il faut simplement apporter quelques petites modifications au fichier <code>Rakefile</code> :</p>
<p><script src="https://gist.github.com/DirtyF/2eacfb7ecec18b3b738af1c3c8d1fe5e.js"></p>
<p>Vous pouvez maintenant lancer <code>rake site:publish</code> pour générer votre site et le publier sur GitHub. Jetez également un coup d’œil au <a href="https://github.com/jekyll/jekyll/blob/master/rake/site.rake#L55" target="_blank" rel="noopener noreferrer">fichier Rakefile de Jekyll</a> pour une implémentation alternative de la tâche <code>rake site:publish</code>.</p>
<figure>
<picture title="OctoJekyll.">
<source type="image/webp" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346531/octojekyll.b00628737e6e4aed46779c1e681a5b39.webp" width="660" height="552">
<source type="image/avif" srcset="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346531/octojekyll.b00628737e6e4aed46779c1e681a5b39.avif" width="660" height="552">
<img src="/res.cloudinary.com/jamstatic/image/upload/f_auto-q_auto/v1523346531/octojekyll.b00628737e6e4aed46779c1e681a5b39.png" alt="OctoJekyll" loading="lazy" decoding="async" class="dark:brightness-90" width="660" height="552" style=";max-width:100%;height:auto;background-image:url(data:image/png;base64,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);background-repeat:no-repeat;background-position:center;background-size:cover;">
</picture>
<figcaption>OctoJekyll.</figcaption>
</figure>
<p>Enfin, sachez qu'il existe d’autres solutions d’hébergement comme <a href="https://pages.gitlab.io/" target="_blank" rel="noopener noreferrer">GitLab Pages</a>, <a href="https://www.netlify.com" target="_blank" rel="noopener noreferrer">Netlify</a>, <a href="https://cloudcannon.com" target="_blank" rel="noopener noreferrer">Cloudcannon</a>, <a href="https://www.siteleaf.com/" target="_blank" rel="noopener noreferrer">Siteleaf</a> ou <a href="https://forestry.io/" target="_blank" rel="noopener noreferrer">Forestry.io</a> qui vous permettent d’utiliser les plugins de votre choix, sans avoir recours à ce genre de hack.</p>
<div class="footnotes">
<hr>
<ol>
<li id="fn:1">
<p>Les tâches utilisées dans ce billet ont été écrites par <a href="http://ixti.net/software/2013/01/28/using-jekyll-plugins-on-github-pages.html" target="_blank" rel="noopener noreferrer">Ixti</a>, le créateur du plugin <code>jekyll-assets</code>.&#160;<a href="#fnref1:1" rev="footnote" class="footnote-backref">&#8617;</a></p>
</li>
</ol>
</div>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2016/08/31/gestion-images-responsive-avec-jekyll-cloudinary/</id>
    <title>Gérer les images responsive dans Jekyll avec le plugin Cloudinary</title>
    <published>2016-08-31T00:00:00+00:00</published>
    <link href="https://jamstatic.fr/2016/08/31/gestion-images-responsive-avec-jekyll-cloudinary/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<p>J'ai récemment mis à jour <a href="https://nicolas-hoizey.com" target="_blank" rel="noopener noreferrer">mon site perso</a> avec <a href="https://jekyllrb.com/news/2015/10/26/jekyll-3-0-released/" target="_blank" rel="noopener noreferrer">la version 3.0 de Jekyll</a> et j'en ai profité pour changer quelques outils.</p>
<p>Les plugins que j'utilisais ne répondaient pas à mes exigences pour les images responsive, j'ai donc décidé de trouver d’autres moyens de satisfaire ces besoins.</p>
<p>Pour générer le code HTML des images responsive (dois-je vous vraiment vous rappeler qu'utiliser les <a href="http://responsiveimages.org/" target="_blank" rel="noopener noreferrer">images responsive natives</a> devrait être un réflexe de nos jours ?), j'ai testé le plugin <a href="https://github.com/wildlyinaccurate/jekyll-responsive-image" target="_blank" rel="noopener noreferrer">Jekyll Responsive Image</a>.<br>
Il est vraiment sympa, il vous laisse définir vos propres gabarits de balisage d’image, vous pouvez donc utiliser <code>srcset</code> ou <code>&lt;picture&gt;</code> selon votre envie.<br>
Mais il ne répondait à tous mes besoins :</p>
<ul>
<li>Lors de la première génération d’un site statique Jekyll avec ce plugin vous devez générer toutes les variantes à partir des images originales. J'ai actuellement environ 750 images sur mon blog et cela entraîne des temps de compilation extrêmement longs,</li>
<li>Envoyer toutes ces variantes au serveur prend également du temps, car je n'ai pas un accès très rapide chez moi,</li>
<li>Et bien entendu toutes ces images sont servies sur le même serveur que les pages, dans mon cas sur un hébergement mutualisé sympa et bon marché.</li>
</ul>
<p>Je voulais revenir à un workflow plus simple et plus rapide et qui génère moins de charge côté serveur.</p>
<p>La plupart des sites web responsive que ma société développe pour ses clients utilisent des solutions ad hoc pour les images responsive, mais j'avais connaissance de quelques solutions SaaS d’images responsive. J'ai donc décidé de voir si l’une d’entre elles pouvait répondre à mes besoins.</p>
<p><a href="http://cloudinary.com/" target="_blank" rel="noopener noreferrer">Cloudinary</a> est une des solutions disponibles qui offre le plus de fonctionnalités <strong>et</strong> qui peut être utilisée gratuitement si vous avez des besoins légers. Difficile pour les autres solutions de rivaliser avec cette offre…</p>
<p>Avec un compte gratuit, j'ai pu tester ce que je voulais, essayer différentes fonctionnalités et décider si je continuais ou si j'allais voir ailleurs.</p>
<p>Les fonctions principales que je cherchais et que fournit Cloudinary sont :</p>
<ul>
<li><strong><a href="http://cloudinary.com/documentation/upload_images#auto_fetching_remote_images" target="_blank" rel="noopener noreferrer">La possibilité d’utiliser le service comme un proxy</a> :</strong> les images originales sont stockées sur mon serveur, mais toutes les images servies à mes visiteurs le sont depuis Cloudinary, générées à la volée à partir des originales. Encore mieux, je n'ai pas besoin d’uploader les images originales - Cloudinary les récupère automatiquement à partir de mes versions publiées en local. Entre d’autres termes, le seul "client" pour mes images d’origine c'est Cloudinary. Du coup, je consomme très peu de bande passante pour mes images chez mon hébergeur.</li>
<li><strong>Recadrage et options de redimensionnement des images :</strong> actuellement, je ne fais que retailler mes images à partir des originaux en large résolution pour les adapter aux mises en page responsive. Je me penche sérieusement sur la possibilité de faire de la direction artistique avancée à l’aide des <a href="http://cloudinary.com/blog/introducing_smart_cropping_intelligent_quality_selection_and_automated_responsive_images" target="_blank" rel="noopener noreferrer">fonctionnalités de recadrage automagiques de Cloudinary</a>.</li>
<li><strong><a href="http://cloudinary.com/documentation/image_transformations#automatic_format_selection" target="_blank" rel="noopener noreferrer">Optimisation du format des images</a> :</strong> Si je publie des images JPEG dans mes billets, Cloudinary peut envoyer des images au format WebP aux visiteurs s'il est supporté par leur navigateur. Le mois dernier, deux tiers des images servies par Cloudinary à mes visiteurs étaient au format WebP, que Cloudinary génère et sert pour moi automatiquement. C’est un gain énorme à la fois pour la performance et les forfaits de données de mes visiteurs et également pour mon quota de bande passante chez Cloudinary.</li>
<li><strong><a href="http://cloudinary.com/documentation/image_transformations#automatic_quality_and_encoding_settings" target="_blank" rel="noopener noreferrer">Optimisation de la compression d’image</a> :</strong> Cloudinary est capable de calculer le meilleur niveau de compression pour réduire le poids de chaque image, sans pour autant dégrader la qualité du visuel.</li>
</ul>
<p>Persuadé que Cloudinary répondait à toutes mes attentes, il me fallait encore développer un plugin Jekyll qui puisse utiliser ces fonctionnalités.</p>
<p>Après réflexion, j'ai décidé de partir avec une <a href="https://github.com/Shopify/liquid/wiki/Liquid-for-Designers" target="_blank" rel="noopener noreferrer">balise Liquid</a> <code>{% cloudinary %}</code> qui simplifierait la publication d’image avec Cloudinary et qui était relativement simple à développer. Je me suis inspiré d’autres plugins, j'ai trouvé de l’aide sur StackOverflow quand j'en avais besoin et j'ai fini par publier la première version du <a href="https://nhoizey.github.io/jekyll-cloudinary/" target="_blank" rel="noopener noreferrer">plugin Jekyll Cloudinary</a> en juillet 2016.</p>
<p>La syntaxe est assez intuitive :</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name">cloudinary</span> [preset] path/to/img.jpg alt="alt text" caption="image caption" %}</span></code></pre>
<p>À partir de cette entrée, le plugin génère le HTML de l’image responsive, en utilisant les attributs <code>srcset</code> et <code>sizes</code> pour la balise <code>&lt;img&gt;</code> tag (voir <a href="https://jakearchibald.com/2015/anatomy-of-responsive-images/#varying-size-and-density" target="_blank" rel="noopener noreferrer">la section “varier la taille et la densité” de ce billet</a> pour comprendre comment fonctionnent ces attributs et <a href="https://cloudfour.com/thinks/dont-use-picture-most-of-the-time/" target="_blank" rel="noopener noreferrer">ce billet qui explique pourquoi vous devriez les utiliser plutôt que <code>&lt;picture&gt;</code>, la plupart du temps</a>).<br>
L'attribut <code>srcset</code> et son fallback <code>src</code> contiennent les URLs Cloudinary qui récupèrent les images originales du billet à la volée et les retaillent en plusieurs tailles alternatives.</p>
<p>Par exemple, comme indiqué dans <a href="https://nhoizey.github.io/jekyll-cloudinary/#live-example" target="_blank" rel="noopener noreferrer">la documentation</a>, ce code dans un fichier Markdown :</p>
<pre><code class="language-twig hljs twig"><span class="hljs-template-tag">{% <span class="hljs-name">cloudinary</span> logo /assets/logos/cloudinary.png alt="Cloudinary logo" %}</span></code></pre>
<p>va générer le code HTML suivant :</p>
<pre><code class="language-html hljs xml"><span class="hljs-tag">&lt;<span class="hljs-name">img</span>
  <span class="hljs-attr">src</span>=<span class="hljs-string">"https://res.cloudinary.com/&lt;cloud_name&gt;/image/fetch/c_limit,w_480,q_auto,f_auto/https://&lt;domain&gt;/assets/logos/cloudinary.png"</span>
  <span class="hljs-attr">srcset</span>=<span class="hljs-string">"
    https://res.cloudinary.com/&lt;cloud_name&gt;/image/fetch/c_limit,w_80,q_auto,f_auto/https://&lt;domain&gt;/assets/logos/cloudinary.png   80w,
    https://res.cloudinary.com/&lt;cloud_name&gt;/image/fetch/c_limit,w_240,q_auto,f_auto/https://&lt;domain&gt;/assets/logos/cloudinary.png 240w,
    https://res.cloudinary.com/&lt;cloud_name&gt;/image/fetch/c_limit,w_400,q_auto,f_auto/https://&lt;domain&gt;/assets/logos/cloudinary.png 400w
  "</span>
  <span class="hljs-attr">sizes</span>=<span class="hljs-string">"
    (min-width: 50rem) 13rem,
    (min-width: 40rem) 25vw,
    45vw"</span>
  <span class="hljs-attr">class</span>=<span class="hljs-string">"logo"</span>
  <span class="hljs-attr">alt</span>=<span class="hljs-string">"logo Cloudinary"</span>
  <span class="hljs-attr">width</span>=<span class="hljs-string">"480"</span>
  <span class="hljs-attr">height</span>=<span class="hljs-string">"350"</span>
/&gt;</span></code></pre>
<p>Vous avez entièrement la main sur le nombre d’images générées, leurs résolutions et les attributs <code>sizes</code> (qui aident le navigateur à décider quelle image télécharger). Cela se fait à partir des options de configuration à votre disposition dans votre fichier <code>_config.yml</code>. Voici l’extrait de mon fichier de configuration où je définis les règles pour les logos :</p>
<pre><code class="language-yaml hljs yaml"><span class="hljs-attr">cloudinary:</span>
  <span class="hljs-attr">cloud_name:</span> <span class="hljs-string">…</span>
  <span class="hljs-attr">presets:</span>
    <span class="hljs-attr">logo:</span>
      <span class="hljs-attr">min_width:</span> <span class="hljs-number">80</span>
      <span class="hljs-attr">max_width:</span> <span class="hljs-number">400</span>
      <span class="hljs-attr">fallback_max_width:</span> <span class="hljs-number">200</span>
      <span class="hljs-attr">steps:</span> <span class="hljs-number">3</span>
      <span class="hljs-attr">sizes:</span> <span class="hljs-string">"(min-width: 50rem) 13rem, (min-width: 40rem) 25vw, 45vw"</span>
      <span class="hljs-attr">figure:</span> <span class="hljs-string">never</span>
      <span class="hljs-attr">attributes:</span>
        <span class="hljs-attr">class:</span> <span class="hljs-string">logo</span></code></pre>
<ul>
<li><code>cloud_name: …</code> votre ID personnel Cloudinary</li>
<li><code>presets:</code> englobe la liste des préréglages que vous définissez pour vote site</li>
<li><code>logo:</code> est le nom d’un des préréglages, que j'utilise dans le tag Liquid avant le nom du fichier image</li>
<li><code>min_width: 80</code> définit la largeur minimum d’image générée</li>
<li><code>max_width: 400</code> définit la largeur maximale d’image générée</li>
<li><code>fallback_max_width: 200</code> définit la largeur de l’image de la solution de repli (<code>src</code>)</li>
<li><code>steps: 3</code> définit le nombre d’images à générer</li>
<li><code>sizes: '(min-width: 50rem) 13rem, (min-width: 40rem) 25vw, 45vw'</code> définit l’attribut <code>sizes</code> de l’image responsive, qui dépend du design et des breakpoints</li>
<li><code>figure: never</code> empêche la génération d’un bloc <code>&lt;figure&gt;</code>/<code>&lt;img&gt;</code>/<code>&lt;figcaption&gt;</code> (Je n'en veux généralement pas sur les logos)</li>
<li><code>attributes:</code> englobe la liste d’attributs à toujours ajouter aux éléments <code>&lt;figure&gt;</code> et/ou <code>&lt;img&gt;</code></li>
<li><code>class: logo</code> ajoute l’attribut <code>class</code> ayant pour valeur <code>logo</code>, que j'utilise dans mon CSS pour m'assurer que le logo ne prenne pas plus d’un quart de la largeur de son conteneur et le fait flotter à droite.</li>
</ul>
<p>Vous pouvez définir toutes ces règles pour autant de préréglages dont vous aurez
besoin.</p>
<p>Avec ce plugin et mon compte Cloudinary, <strong>le temps de génération de mon site a été réduit de 90% et la capacité de stockage utilisée sur mon serveur de 60%</strong> et je n'ai plus à me soucier du tout de l’optimisation de mes images. C’est un gain énorme.</p>
<p>Et après ? Au début, je voulais permettre aux auteurs d’utiliser simplement <a href="http://kramdown.gettalong.org/syntax.html#images" target="_blank" rel="noopener noreferrer">la syntaxe Markdown pour les images</a>, mais je n'y suis pas encore parvenu, malgré <a href="http://stackoverflow.com/questions/35614552/with-jekyll-3-can-i-transform-a-posts-markdown-before-actual-markdown-parsing" target="_blank" rel="noopener noreferrer">quelques</a> <a href="https://github.com/jekyll/jekyll/issues/5099" target="_blank" rel="noopener noreferrer">réponses</a> <a href="http://stackoverflow.com/questions/38126629/how-is-the-priority-flag-in-jekyll-plugins-supposed-to-work" target="_blank" rel="noopener noreferrer">valables</a> à mes questions du principal mainteneur de Jekyll <a href="https://github.com/parkr" target="_blank" rel="noopener noreferrer">Parker Moore</a> lui-même. Il faudra que je creuse les hooks Jekyll à l’avenir.</p>
<p>Au final, cela a était un bon moyen d’apprendre un peu de Ruby, de comprendre les rouages internes de Jekyll, comment fonctionnent les plugins et comment publier une gem… J'ai tellement appris en peu de temps grâce à ce petit projet si utile et important à mes yeux.</p>
<p>Bien entendu, toute aide est la bienvenue pour aider à améliorer le plugin. Il y a déjà <a href="https://github.com/nhoizey/jekyll-cloudinary/issues" target="_blank" rel="noopener noreferrer">quelques anomalies et demandes de fonctionnalités ouvertes</a>. N'hésitez pas à me signaler tout problème ou à partager vos idées, voire à contribuer via des <a href="https://github.com/nhoizey/jekyll-cloudinary/pulls" target="_blank" rel="noopener noreferrer">pull requests</a> !</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2016/04/19/entretien-avec-parker-moore/</id>
    <title>Entretien avec Parker Moore de Jekyll</title>
    <published>2016-04-19T00:00:00+00:00</published>
    <link href="https://jamstatic.fr/2016/04/19/entretien-avec-parker-moore/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<figure>
<picture title="Parker Moore.">
<source type="image/webp" srcset="/thumbnails/768x/cdn.netlify.com/a3dc6515430891d6df896d718dd7e54f6941d647/99084/uploads/parker-moore-jekyll.5651eff9b1a53e3ce8c2fbd1d7b42451.webp 768w, /thumbnails/1024x/cdn.netlify.com/a3dc6515430891d6df896d718dd7e54f6941d647/99084/uploads/parker-moore-jekyll.5651eff9b1a53e3ce8c2fbd1d7b42451.webp 1024w" width="1024" height="576" sizes="100vw">
<source type="image/avif" srcset="/thumbnails/768x/cdn.netlify.com/a3dc6515430891d6df896d718dd7e54f6941d647/99084/uploads/parker-moore-jekyll.5651eff9b1a53e3ce8c2fbd1d7b42451.avif 768w, /thumbnails/1024x/cdn.netlify.com/a3dc6515430891d6df896d718dd7e54f6941d647/99084/uploads/parker-moore-jekyll.5651eff9b1a53e3ce8c2fbd1d7b42451.avif 1024w" width="1024" height="576" sizes="100vw">
<img src="/cdn.netlify.com/a3dc6515430891d6df896d718dd7e54f6941d647/99084/uploads/parker-moore-jekyll.5651eff9b1a53e3ce8c2fbd1d7b42451.jpg" alt="Parker Moore" loading="lazy" decoding="async" class="dark:brightness-90" width="1024" height="576" style=";max-width:100%;height:auto;background-image:url(data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD//gA7Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gNzUK/9sAQwAIBgYHBgUIBwcHCQkICgwUDQwLCwwZEhMPFB0aHx4dGhwcICQuJyAiLCMcHCg3KSwwMTQ0NB8nOT04MjwuMzQy/9sAQwEJCQkMCwwYDQ0YMiEcITIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIy/8AAEQgAMgBkAwEiAAIRAQMRAf/EAB8AAAEFAQEBAQEBAAAAAAAAAAABAgMEBQYHCAkKC//EALUQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+v/EAB8BAAMBAQEBAQEBAQEAAAAAAAABAgMEBQYHCAkKC//EALURAAIBAgQEAwQHBQQEAAECdwABAgMRBAUhMQYSQVEHYXETIjKBCBRCkaGxwQkjM1LwFWJy0QoWJDThJfEXGBkaJicoKSo1Njc4OTpDREVGR0hJSlNUVVZXWFlaY2RlZmdoaWpzdHV2d3h5eoKDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uLj5OXm5+jp6vLz9PX29/j5+v/aAAwDAQACEQMRAD8A5FAR2p0uStaosCe1SDTGPUUAclcW7vnArPfT5Sc4Nd+ulL3Wnto6EcLQByOk27RSjIr0XSbkRxqM1hHThCc4qRbnyeM0AdvHdK461DendETXO2eoksBmtkzh4Dz2oA4jXh8zVy0oya63W8Fmrl3X5zQBRdKhaImtFkFNEOaAM7yTRWl5AooA9dhsVJ6VcOnKI84rOt9UjLDkVffVY/LxkUAUZoAjdKYqio571XbrSRzA96AIr1QEOBWA8bPJiukuAGQmsdiiSUAOtbYqQa0jIUix7Uy3kjZR0pbnHlnFAHMavLljWAxy1a2rH5jWIW5oAlxT1wKgD07fQBNxRUO+igDTg1R0PLVabW2xjdXJy3WwdaqNqBJxmgDtI9X3Ny1atrqakDLV5zHekHrVyPVGQfeoA9AudXRYiN1c3d6yBJw1c/PqzuMbqzXuWZsk0Adzaa1jHzVonVw8eN1edRXjKetaEF8xHWgDZv7kSE81lE80rSlqTrQAZNLuNJilAoAXJop4WigDGufums0dTRRQBOlS9qKKAImplFFACjrV227UUUAaC9KkFFFADqctFFAElFFFAH//2Q==);background-repeat:no-repeat;background-position:center;background-size:cover;" srcset="/thumbnails/768x/cdn.netlify.com/a3dc6515430891d6df896d718dd7e54f6941d647/99084/uploads/parker-moore-jekyll.5651eff9b1a53e3ce8c2fbd1d7b42451.jpg 768w, /thumbnails/1024x/cdn.netlify.com/a3dc6515430891d6df896d718dd7e54f6941d647/99084/uploads/parker-moore-jekyll.5651eff9b1a53e3ce8c2fbd1d7b42451.jpg 1024w" sizes="100vw">
</picture>
<figcaption>Parker Moore.</figcaption>
</figure>
<p>Alors que nous constatons une augmentation constante du nombre d’outils
permettant de générer des sites statiques à destination des développeurs,
professionnels ou amateurs, aucun d’entre eux n'attire autant l’attention que
<a href="https://jekyllrb.com/" target="_blank" rel="noopener noreferrer">Jekyll</a>.</p>
<p>Sa popularité n'est pas vraiment surprenante quand on sait que Jekyll a été créé
par un des cofondateurs de GitHub et que c'est le moteur qui fait tourner GitHub
pages.</p>
<p>Il y a quelque temps, nous avons pu assister au lancement de Jekyll 3.0, avec
la
<a href="https://youtu.be/sPZK8w55cBQ?t=37m58s" target="_blank" rel="noopener noreferrer">publication en direct du commit final</a>
pendant une présentation de Parker Moore, actuellement en charge de la
maintenance de Jekyll, lors du Meetup Static Web à San Francisco. Après sa
présentation, Moore a pris quelques minutes pour parler avec nous de Jekyll, de
son développement et du futur des sites web statiques.</p>
<h2 id="qu-est-ce-que-vous-faites-quand-vous-ne-travaillez-pas-sur-jekyll">Qu'est-ce que vous faites quand vous ne travaillez pas sur Jekyll ?</h2>
<p>Je suis ingénieur logiciel chez Github, entreprise dans laquelle j'aide au
développement du service Github Pages. Mon travail consiste aussi à maintenir
Jekyll et à faire grandir la communauté autour du projet.</p>
<h2 id="peux-tu-nous-dire-comment-tu-as-ete-amene-a-t-impliquer-dans-jekyll-et-ce-qui-s-est-passe-depuis-pour-aboutir-a-la-mise-en-ligne-de-la-version-3-0-de-jekyll">Peux-tu nous dire comment tu as été amené à t'impliquer dans Jekyll et ce qui s'est passé depuis pour aboutir à la mise en ligne de la version 3.0 de Jekyll ?</h2>
<p>Le parcours était intéressant. J'ai été amené à utiliser Jekyll pour un job
d’été à l’université de Cornell. Il s'agissait de la refonte du site
cals.cornell.edu, nous voulions réaliser un nouveau site, et ce de manière
rapide. Nous voulions garder l’existant et le migrer sur un nouveau système.
Nous avions de nouvelles maquettes et de nouveaux contenus. J'avais déjà entendu
parler de Jekyll ; je l’avais déjà utilisé. J'ai réussi à convaincre mon
supérieur, je ne sais pas comment j'ai fait, mais il a accepté. Pour moi, Jekyll
correspondait exactement à la demande, je devais juste servir du contenu. Au
final, nous avons même utilisé Jekyll pour servir des pages PHP avec un serveur
LAMP qui permet d’envoyer des mails ou de générer des PDF. J’ai donc utilisé un
peu Jekyll à cette période et ça m'a beaucoup plu.</p>
<p>J'ai regardé les bugs majeurs, il y en avait tellement : plus de 800 sur le
dépôt, ce qui est énorme. Beaucoup trop pour un projet Open Source majeur, selon
moi. J'ai perçu un besoin, j'ai pensé que j'avais suffisamment de temps libre -
même si ce n'était pas le cas - pour aider la communauté sur ces corrections.
Peu avant, j'étais parti travailler en Allemagne pour une société du nom de 6
Wunderkinder – l’éditeur de Wunderlist — j'ai bombardé Tom — NdT: Prester-Werner
le créateur de Jekyll — de mails, sans cesse. Au final, j'ai publié une lettre
ouverte sur mon blog, ce qui avec le recul était assez fou et immature, mais qui
a eu les effets escomptés. Peu de temps après, Tom m'a répondu : "Il faut qu'on
parle, tu as l’air vraiment intéressé, qu'en dis-tu ?"</p>
<p>Je venais juste d’avoir un échange avec Brandon Mathis, qui travaille sur
Octopress, et il m'a ajouté comme contributeur au projet. Et ainsi j'ai pu dire
à Tom : "regarde, je suis aussi contributeur d’Octopress, je connais les sites
statiques". Brandon a dû approuver ma candidature j'imagine. Quelques semaines
plus tard alors que j'étais en visite chez ma sœur à San Francisco, j'ai
rencontré Tom au siège de GitHub pour parler de Jekyll pendant une heure. Il m'a
donné les accès, j'ai corrigé quelques bugs et j'ai commencé à travailler sur
Jekyll 1.</p>
<h2 id="tu-as-debute-avec-la-0-12-1">Tu as débuté avec la 0.12.1 ?</h2>
<p>Effectivement, je l’utilisais, et il y avait tant de choses qui ne
fonctionnaient pas. Je savais que je pouvais corriger tout ça ; je connaissais
Ruby, j'avais fait un peu de Ruby on Rails avant, et j'avais beaucoup appris sur
le développement web à cette occasion.</p>
<h2 id="quelle-fut-ta-premiere-experience-de-programmation">Quelle fut ta première expérience de programmation ?</h2>
<p>À la Rochester Institute of Technology, je m'étais rendu avec des camarades
d’écoles à des journées de codes pour élèves de collège. J'avais 13 ans. Il y a
eu un cours de 30 minutes sur HTML. J'ai adoré. Je n'arrêtais pas d’en écrire,
encore et encore. Je changeais la couleur de fond avec un attribut… je faisais
tous ces trucs bêtes et ordinaires.</p>
<h2 id="quelle-fut-ta-premiere-rencontre-avec-les-generateurs-de-site-statique-modernes">Quelle fut ta première rencontre avec les générateurs de site statique modernes ?</h2>
<p>Je ne me souviens pas plus loin que de mon expérience avec Jekyll à Cornell. Je
savais que les générateurs de site statique marcheraient, car j'avais codé du
HTML, du CSS et du JavaScript et je sais ce qu'ils font. J'ai donc décidé de
partir de ça, j'ai regardé plusieurs outils et Jekyll était celui qui avait le
plus d’étoiles sur Github et avait l’air d’être le plus populaire. Les gens
écrivaient dessus et l’utilisaient. GitHub Pages existait déjà. Du coup j'ai
emprunté cette direction.</p>
<h2 id="c-etait-donc-une-question-de-masse-critique-il-y-avait-pas-mal-de-gens-donc-tu-savais-que-c-etait-vivant-et-actif">C'était donc une question de masse critique ? Il y avait pas mal de gens, donc tu savais que c'était vivant et actif</h2>
<p>Tu sors dans les endroits branchés en général non ? Je me suis dit qu'il y
aurait des contributeurs. Un projet open source a besoin de gens plus que de
tout autre chose, parce qu'il y a besoin de code.</p>
<p>La pire chose à faire sur un forum est de poser une question et de retourner
voir tous les jours s'il y a une réponse, mais toujours rien.</p>
<p>Personne n'a répondu !</p>
<h2 id="que-souhaites-tu-aux-generateurs-de-site-statique">Que souhaites-tu aux générateurs de site statique ?</h2>
<p>Qu'ils soient mieux compris. Je les comprends et tu les comprends, mais je rêve
d’un monde où ce serait le cas pour tout le monde… et j'imagine que cela
arrivera, car l’informatique s'apprend de plus en plus tôt, la création de sites
web est un bon moyen pour commencer à apprendre le code. Mon plus grand souhait
c'est qu'ils soient mieux compris de tous et perçus comme une vraie solution,
honnête et prête pour la production, pour les sociétés intéressées dans la
réalisation de sites web.</p>
<h2 id="comment-est-il-possible-de-faire-cela">Comment est-il possible de faire cela ?</h2>
<p>Un truc dont j'ai parlé avec Christian et Matt, les fondateurs de Netlify (NdT :
Service d’hébergement et de déploiement de sites statiques) ; c'est que les
sites statiques, les générateurs de sites statiques, les générateurs statiques,
le site statique, tout ça a l’air plutôt ennuyeux, ça sonne très technique…</p>
<h2 id="le-dynamique-a-l-air-bien-plus-interessant">Le dynamique a l’air bien plus intéressant !</h2>
<p>C’est ça ! Drupal et WordPress sont basés sur le principe de "l’installation en
1 clic". Sur wordpress.com, il est possible d’avoir un site gratuitement avec un
thème en place. C’est toutes ces petites barrières qui ne sont pas grand-chose
mais qui font la différence. Donc que je pense que l’accessibilité c'est
important. Des entreprises comme Netlify ou CloudCannon y travaillent. Plusieurs
sociétés font en sorte qu'il soit plus simple d’apprendre à construire des sites
statiques. Je pense qu'il faut faire évoluer le dialogue et notre vocabulaire,
pour ne pas intimider ceux qui sont en train d’apprendre ce que sont les
générateurs de sites statiques. Qu'est-ce que ça veut dire un site statique ?
C’est un terme qui parle de lui-même site statique. Mais vous avez à apprendre
la différence entre le statique et le dynamique… qui n'est pas un terme que les
gens emploient tous les jours. Quelle est la dernière fois où vous avez utilisé
le mot statique en dehors du cadre professionnel ?</p>
<h2 id="oui-statique-ca-fait-un-peu-pejoratif-dans-ce-contexte">Oui statique ça fait un peu péjoratif dans ce contexte</h2>
<p>Oui, il faut véhiculer une vision claire, accessible et s'outiller d’un meilleur
lexique. Quelque chose qui explique tout ça très bien, sans utiliser de mots qui
font peur.</p>
<h2 id="en-tant-que-mainteneur-de-jekyll-qu-aimerais-tu-dire-communiquer-a-ceux-qui-s-interessent-aux-technologies-autour-du-web-statique-moderne-la-tribune-est-a-toi">En tant que mainteneur de Jekyll, qu'aimerais-tu dire, communiquer à ceux qui s'intéressent aux technologies autour du web statique moderne ? La tribune est à toi</h2>
<p>Si j'avais une tribune, je dirais "N'abandonnez pas". Prenez-vous la tête dessus
un petit moment. Si cela ne marche pas au bout d’une heure, d’un jour, allez
demander de l’aide. Je dirai que ne pas abandonner et se prendre la tête sur
quelque chose est la meilleure façon d’apprendre, d’affronter les problèmes. De
manière générale, ma génération n'est vraiment pas douée quand il s'agit de
persévérer… que ce soit travailler quelque part plus d’un an ou affronter une
grande difficulté pendant plus de 15 minutes. C’est comme si tout était éphémère
de nos jours, contrairement à ce que j'ai pu apprendre à travers les livres
d’histoires ou les histoires que mes parents et mes grand-parents m'ont raconté.
Ne pas abandonner, c'est avoir confiance dans la technologie du web statique,
c'est avoir confiance dans les personnes qui développent tout ça, qui font des
sites web et qui apprennent de ça, c'est avoir confiance dans notre futur.</p>
<p><strong>Et lorsqu'on n'abandonne pas, on a plus de chance de faire partie de la solution, car on s'est investi dedans</strong>.</p>
<p>Oui, c'est exactement ça.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2016/04/14/jekyllconf-2016/</id>
    <title>JekyllConf 2016</title>
    <published>2016-04-14T00:00:00+00:00</published>
    <link href="https://jamstatic.fr/2016/04/14/jekyllconf-2016/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<aside class="note note-intro"><p><a href="https://jekyllconf.com/" target="_blank" rel="noopener noreferrer">JekyllConf</a> est la première conférence
entièrement consacrée à Jekyll. C’est une conférence en ligne, gratuite et
ouverte à tous, preuve de la volonté de partage qui anime la communauté. Elle
aura lieu le samedi 7 mai 2016 à partir de 19h heure française, les vidéos
seront mises en ligne peu après.</p></aside>
<p>La première édition avait rassemblé Tom Preston-Werner, Parker Moore, Brandon
Mathis ou Ben Balter, respectivement créateurs et mainteneurs de Jekyll,
OctoPress et GitHub Pages ; outre une vision des futures améliorations possibles
pour les générateurs de sites statiques, il y avait eu quelques retours
d’expérience plus pratiques comme la création de <em>styleguide</em>. Nous ne pouvons
que vous inviter à aller regarder les
<a href="https://jekyllconf.com/2015/" target="_blank" rel="noopener noreferrer">vidéos de l’édition de 2015</a> si vous vous
intéressez à Jekyll et aux générateurs de site statique.</p>
<p>Le <a href="https://jekyllconf.com/" target="_blank" rel="noopener noreferrer">programme</a> de cette deuxième édition s’annonce tout
aussi intéressant et met encore plus l’accent sur des retours d’expérience ainsi
que différents cas d’utilisation avancée de Jekyll.</p>
<p>Cette édition permettra de constater comment Jekyll peut répondre à différentes
problématiques, que ce soit générer de la documentation, tester ses CSS,
prototyper ses sites web, travailler sa stratégie de contenu voire faire des
choses beaucoup moins conventionnelles comme c’est le cas chez Mapbox.</p>
<p>Les témoignages de Stack Overflow et quelques-unes des meilleures agences seront
surement riches en enseignements. CloudCannon nous montrera comment les
utilisateurs finaux peuvent éditer des sites sous Jekyll.</p>
<p>La conférence compte aussi plusieurs interventions sur des aspects plus
techniques comme les différentes manières de déployer Jekyll : sur Amazon,
déploiement continu avec Docker… d’implémenter une solution de recherche comme
Algolia ou Elasticsearch ou comment gérer les images responsive.</p>
<p><strong>Mise à jour</strong> : Toutes les conférences sont disponibles sur
<a href="https://jekyllrb.com/community/" target="_blank" rel="noopener noreferrer">https://jekyllrb.com/community/</a>.</p>]]>
    </content>
  </entry>
  <entry xml:lang="fr">
    <id>https://jamstatic.fr/2016/04/01/jamstatic-fr/</id>
    <title>Lancement de Jamstatic France</title>
    <published>2016-04-01T00:00:00+00:00</published>
    <link href="https://jamstatic.fr/2016/04/01/jamstatic-fr/" rel="alternate" type="text/html" />
    <content type="html">
      <![CDATA[<h2 id="a-propos-de-jamstatic">À propos de Jamstatic</h2>
<p>Nous observons actuellement un vrai engouement pour l’utilisation des générateurs de site statique. Du fait de son utilisation par <a href="https://pages.github.com/" target="_blank" rel="noopener noreferrer">Github Pages</a>, <a href="http://jekyllrb.com/" target="_blank" rel="noopener noreferrer">Jekyll</a> est encore très populaire. Beaucoup d’autres générateurs comme <a href="http://gohugo.io/" target="_blank" rel="noopener noreferrer">Hugo</a> ou <a href="https://11ty.dev/" target="_blank" rel="noopener noreferrer">Eleventy</a> connaissent une forte progression. Sans parler de l'engouement autour de frameworks comme <a href="https://nextjs.org/" target="_blank" rel="noopener noreferrer">Next.js</a> ou <a href="https://nuxtjs.org/" target="_blank" rel="noopener noreferrer">Nuxt.js</a>.</p>
<p>On observe un bouillonnement aussi bien de la communauté qui a lancé de nombreux chantiers en parallèle que de <a href="/2017/01/23/produire-des-livres-avec-le-statique/">nouveaux acteurs du monde de l’édition</a> qui utilisent des <abbr lang="en" aria-label="Static Site Generators">SSG</abbr> pour leurs publications.</p>
<p>Nous relayons et agrégeons en français ce qui se passe à divers endroits de la toile autour des générateurs de site statique et de <a href="/2017/03/16/5-raisons-de-tester-la-jamstack/">la stack JavaScript, APIs &amp; Markup</a>.</p>
<h2 id="actualites">Actualités</h2>
<p>Les news, les conférences et appels à orateurs… tout ce qui peut toucher de près ou de loin la communauté. <a href="https://jamstatic.fr/slack">Rejoignez nos canaux Slack</a> où nous relayons et commentons l'actualité autour de la Jamstack.</p>
<h2 id="references">Références</h2>
<p>Il existe des sites ou des articles qui font références pour l’installation, la mise en production, l’optimisation du code, la gestion du contenu… il s'agit de lister les liens que nous estimons essentiels.</p>
<h2 id="exemples-de-code">Exemples de code</h2>
<p>À travers des traductions d’articles de référence, des retours d’expérience, des cas pratiques, nous voulons vous aider à apprendre à maîtriser ses outils.</p>
<p>Nous espérons ainsi pouvoir fédérer une communauté d’utilisateurs, qui partagent leurs problématiques et leurs solutions, afin que l’utilisation des générateurs de site statique soit plus simple pour tous.</p>
<p>Toute contribution est la bienvenue, nous sommes <a href="https://github.com/jamstatic/" target="_blank" rel="noopener noreferrer">Jamstatic sur Github</a>, <a href="https://twitter.com/jamstatic_fr" target="_blank" rel="noopener noreferrer">jamstatic_fr sur Twitter</a> et vous pouvez <a href="https://jamstatic.fr/slack">vous connecter sur notre canal de discussion Slack</a>, si vous souhaitez interagir avec les membres de notre communauté.</p>]]>
    </content>
  </entry>
</feed>
